feat(WEB): 글로벌 검색, 토큰 갱신 개선, 템플릿 기능 확장

- CommandMenuSearch 컴포넌트 추가 (Cmd+K 글로벌 메뉴 검색)
- AuthenticatedLayout: 검색 통합, 모바일/데스크톱 스켈레톤 분리
- middleware: 토큰 갱신 후 리다이렉트 방식으로 변경 (race condition 방지)
- IntegratedDetailTemplate: stickyButtons 옵션 추가 (하단 고정 버튼)
- UniversalListPage: 컬럼 정렬 기능 추가 (sortBy, sortOrder)
- Sidebar: 축소 모드 패딩/간격 최적화
- 각종 컴포넌트 버그 수정 및 경로 정규화

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-26 15:07:10 +09:00
parent cd060ec562
commit a15132d75d
38 changed files with 927 additions and 443 deletions

View File

@@ -207,8 +207,10 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
<CardTitle className="text-base"> </CardTitle>
</CardHeader>
<CardContent className="pt-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
{/* 반응형 6열 그리드: PC 6열, 태블릿 4열, 작은태블릿 2열, 모바일 1열 */}
{/* 4개 필드 → 2+1+2+1 = 6열 채움 */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-6">
<div className="space-y-2 lg:col-span-2">
<Label htmlFor="processName"> *</Label>
<Input
id="processName"
@@ -235,7 +237,7 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<div className="space-y-2 lg:col-span-2">
<Label> *</Label>
<Select value={department} onValueChange={setDepartment} disabled={isDepartmentsLoading}>
<SelectTrigger>
@@ -243,7 +245,7 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
</SelectTrigger>
<SelectContent>
{departmentOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
<SelectItem key={opt.id} value={opt.value}>
{opt.label}
</SelectItem>
))}
@@ -383,31 +385,33 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
<CardHeader className="bg-muted/50">
<CardTitle className="text-base"> </CardTitle>
</CardHeader>
<CardContent className="pt-6 space-y-6">
<div className="space-y-2">
<Label></Label>
<QuantityInput
value={requiredWorkers}
onChange={(value) => setRequiredWorkers(value ?? 1)}
min={1}
className="w-32"
/>
</div>
<div className="space-y-2">
<Label></Label>
<Input
value={equipmentInfo}
onChange={(e) => setEquipmentInfo(e.target.value)}
placeholder="예: 미싱기 3대, 절단기 1대"
/>
</div>
<div className="space-y-2">
<Label> ( )</Label>
<Input
value={workSteps}
onChange={(e) => setWorkSteps(e.target.value)}
placeholder="예: 원단절단, 미싱, 핸드작업, 중간검사, 포장"
/>
<CardContent className="pt-6">
{/* 반응형 6열 그리드: PC 6열, 태블릿 4열, 작은태블릿 2열, 모바일 1열 */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-6">
<div className="space-y-2">
<Label></Label>
<QuantityInput
value={requiredWorkers}
onChange={(value) => setRequiredWorkers(value ?? 1)}
min={1}
/>
</div>
<div className="space-y-2 lg:col-span-2">
<Label></Label>
<Input
value={equipmentInfo}
onChange={(e) => setEquipmentInfo(e.target.value)}
placeholder="예: 미싱기 3대, 절단기 1대"
/>
</div>
<div className="space-y-2 lg:col-span-3">
<Label> ( )</Label>
<Input
value={workSteps}
onChange={(e) => setWorkSteps(e.target.value)}
placeholder="예: 원단절단, 미싱, 핸드작업, 중간검사, 포장"
/>
</div>
</div>
</CardContent>
</Card>