style(pop-work-detail): ISA-101 디자인 토큰 도입 및 크기 수정

- DESIGN 상수 도입 (button, input, stat, section, sidebar, nav, infoBar, defectRow)
- COLORS 상수 도입 (good, defect, complete, warning, info)
- 버튼 높이: 44px → 56px (ISA-101 장갑 터치 기준)
- 핵심 숫자(수량/실적): 18px → 36px (2-3m 가독성)
- 섹션 제목/라벨: 14px → 16px (1m 가독성)
- 입력 필드 높이: 44px → 56px
- 불량 유형 행 높이: 40px → 56px
- 사이드바 너비: 208px → 220px, 항목 패딩 확대
- 기능 변경 없음 (디자인만 수정)
This commit is contained in:
SeongHyun Kim
2026-03-25 10:57:29 +09:00
parent b677840952
commit 525237d42d
@@ -133,6 +133,29 @@ const DEFAULT_CFG: PopWorkDetailConfig = {
],
};
// ========================================
// ISA-101 디자인 토큰 (산업 터치 기준)
// ========================================
const DESIGN = {
button: { height: 56, minWidth: 100 },
input: { height: 56 },
stat: { valueSize: 36, labelSize: 14, weight: 700 },
section: { titleSize: 16, gap: 20 },
sidebar: { width: 220, itemPadding: '16px 20px' },
nav: { height: 56 },
infoBar: { labelSize: 14, valueSize: 16 },
defectRow: { height: 56 },
} as const;
const COLORS = {
good: 'text-green-600',
defect: 'text-red-600',
complete: 'text-blue-600',
warning: 'text-amber-600',
info: 'text-violet-600',
} as const;
// ========================================
// Props
// ========================================
@@ -710,7 +733,7 @@ export function PopWorkDetailComponent({
{/* 본문: 좌측 사이드바 + 우측 콘텐츠 */}
<div className="flex flex-1 overflow-hidden">
{/* 좌측 사이드바 */}
<div className="w-52 shrink-0 overflow-y-auto border-r bg-muted/30">
<div className="shrink-0 overflow-y-auto border-r bg-muted/30" style={{ width: `${DESIGN.sidebar.width}px` }}>
{(["PRE", "IN", "POST"] as WorkPhase[]).map((phase) => {
const phaseGroups = groupsByPhase[phase];
if (!phaseGroups || phaseGroups.length === 0) return null;
@@ -723,11 +746,12 @@ export function PopWorkDetailComponent({
<button
key={g.itemId}
className={cn(
"flex w-full items-center gap-2.5 px-4 py-3 text-left transition-colors",
"flex w-full items-center gap-2.5 text-left transition-colors",
selectedGroupId === g.itemId
? "bg-primary/10 text-primary border-l-2 border-primary"
: "hover:bg-muted/60"
)}
style={{ padding: DESIGN.sidebar.itemPadding }}
onClick={() => {
setSelectedGroupId(g.itemId);
setResultTabActive(false);
@@ -736,7 +760,7 @@ export function PopWorkDetailComponent({
>
<StepStatusIcon status={g.stepStatus} />
<div className="flex-1 min-w-0">
<span className="block truncate text-sm font-medium leading-tight">
<span className="block truncate font-medium leading-tight" style={{ fontSize: `${DESIGN.section.titleSize}px` }}>
{g.title}
</span>
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
@@ -745,7 +769,7 @@ export function PopWorkDetailComponent({
<span className="text-primary"></span>
)}
{g.timer.completedAt && (
<span className="text-green-600"></span>
<span className={COLORS.good}></span>
)}
</div>
</div>
@@ -761,11 +785,12 @@ export function PopWorkDetailComponent({
<div className="mx-3 my-2 border-t" />
<button
className={cn(
"flex w-full items-center gap-2.5 px-4 py-3 text-left transition-colors",
"flex w-full items-center gap-2.5 text-left transition-colors",
resultTabActive
? "bg-primary/10 text-primary border-l-2 border-primary"
: "hover:bg-muted/60"
)}
style={{ padding: DESIGN.sidebar.itemPadding }}
onClick={() => {
setResultTabActive(true);
setSelectedGroupId(null);
@@ -774,7 +799,7 @@ export function PopWorkDetailComponent({
>
<ClipboardList className="h-4 w-4 shrink-0" />
<div className="flex-1 min-w-0">
<span className="block truncate text-sm font-medium leading-tight">
<span className="block truncate font-medium leading-tight" style={{ fontSize: `${DESIGN.section.titleSize}px` }}>
</span>
<span className="text-xs text-muted-foreground">
@@ -818,15 +843,15 @@ export function PopWorkDetailComponent({
{cfg.showQuantityInput && !isProcessCompleted && !hasResultSections && (
<div className="w-full max-w-sm space-y-4 rounded-lg border p-5">
<p className="text-sm font-semibold"> </p>
<p className="font-semibold" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> </p>
<div className="space-y-3">
<div className="flex items-center gap-3">
<span className="w-12 text-sm text-muted-foreground"></span>
<Input type="number" className="h-11 text-base" value={goodQty} onChange={(e) => setGoodQty(e.target.value)} placeholder="0" />
<span className="w-12 text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></span>
<Input type="number" className="text-base" style={{ height: `${DESIGN.input.height}px` }} value={goodQty} onChange={(e) => setGoodQty(e.target.value)} placeholder="0" />
</div>
<div className="flex items-center gap-3">
<span className="w-12 text-sm text-muted-foreground"></span>
<Input type="number" className="h-11 text-base" value={defectQty} onChange={(e) => setDefectQty(e.target.value)} placeholder="0" />
<span className="w-12 text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></span>
<Input type="number" className="text-base" style={{ height: `${DESIGN.input.height}px` }} value={defectQty} onChange={(e) => setDefectQty(e.target.value)} placeholder="0" />
</div>
{(parseInt(goodQty, 10) || 0) + (parseInt(defectQty, 10) || 0) > 0 && (
<p className="text-right text-sm text-muted-foreground">
@@ -839,7 +864,8 @@ export function PopWorkDetailComponent({
{!isProcessCompleted && cfg.navigation.showCompleteButton && (
<Button
className="h-12 gap-2 px-8 text-base"
className="gap-2 px-8"
style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
onClick={handleProcessComplete}
>
<CheckCircle2 className="h-5 w-5" />
@@ -912,7 +938,8 @@ export function PopWorkDetailComponent({
<Button
size="sm"
variant="outline"
className="h-11 gap-1.5 px-5 text-sm"
className="gap-1.5 px-5"
style={{ height: `${DESIGN.nav.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
disabled={currentItemIdx <= 0 && selectedIndex <= 0}
onClick={() => {
if (currentItemIdx > 0) {
@@ -937,7 +964,8 @@ export function PopWorkDetailComponent({
<Button
size="sm"
variant={currentItemIdx >= currentItems.length - 1 && selectedIndex >= groups.length - 1 ? "default" : "outline"}
className="h-11 gap-1.5 px-5 text-sm"
className="gap-1.5 px-5"
style={{ height: `${DESIGN.nav.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
onClick={() => {
if (currentItemIdx < currentItems.length - 1) {
setCurrentItemIdx(currentItemIdx + 1);
@@ -999,14 +1027,14 @@ export function PopWorkDetailComponent({
<div className="flex items-center gap-3 px-4 py-2.5">
<Package className="h-4.5 w-4.5 shrink-0 text-muted-foreground" />
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground"></span>
<Input type="number" className="h-10 w-24 text-sm" value={goodQty} onChange={(e) => setGoodQty(e.target.value)} disabled={isProcessCompleted} />
<span className="text-muted-foreground" style={{ fontSize: `${DESIGN.section.titleSize}px` }}></span>
<Input type="number" className="w-24" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={goodQty} onChange={(e) => setGoodQty(e.target.value)} disabled={isProcessCompleted} />
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground"></span>
<Input type="number" className="h-10 w-24 text-sm" value={defectQty} onChange={(e) => setDefectQty(e.target.value)} disabled={isProcessCompleted} />
<span className="text-muted-foreground" style={{ fontSize: `${DESIGN.section.titleSize}px` }}></span>
<Input type="number" className="w-24" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={defectQty} onChange={(e) => setDefectQty(e.target.value)} disabled={isProcessCompleted} />
</div>
<Button size="sm" variant="outline" className="h-10 px-4 text-sm" onClick={handleQuantityRegister} disabled={isProcessCompleted}>
<Button size="sm" variant="outline" className="px-4" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} onClick={handleQuantityRegister} disabled={isProcessCompleted}>
</Button>
</div>
@@ -1017,7 +1045,8 @@ export function PopWorkDetailComponent({
<Button
size="sm"
variant="outline"
className="h-11 gap-1.5 px-5 text-sm"
className="gap-1.5 px-5"
style={{ height: `${DESIGN.nav.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
disabled={selectedIndex <= 0}
onClick={() => navigateStep(-1)}
>
@@ -1032,7 +1061,8 @@ export function PopWorkDetailComponent({
<Button
size="sm"
variant="outline"
className="h-11 gap-1.5 px-5 text-sm"
className="gap-1.5 px-5"
style={{ height: `${DESIGN.nav.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
onClick={() => navigateStep(1)}
>
<ChevronRight className="h-4 w-4" />
@@ -1041,7 +1071,8 @@ export function PopWorkDetailComponent({
<Button
size="sm"
variant="default"
className="h-11 gap-1.5 px-5 text-sm"
className="gap-1.5 px-5"
style={{ height: `${DESIGN.nav.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
onClick={handleProcessComplete}
>
<CheckCircle2 className="h-4 w-4" />
@@ -1292,33 +1323,33 @@ function ResultPanel({
{/* 확정 상태 배너 */}
{isConfirmed && (
<div className="flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-2.5">
<FileCheck className="h-4 w-4 text-green-600" />
<span className="text-sm font-medium text-green-700"> </span>
<FileCheck className={cn("h-4 w-4", COLORS.good)} />
<span className="font-medium text-green-700" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> </span>
</div>
)}
{/* 공정 현황: 접수량 / 작업완료 / 잔여 + 앞공정 완료량 */}
<div className="rounded-lg border bg-muted/20 px-4 py-3">
<div className="mb-2 text-xs font-semibold uppercase text-muted-foreground"> </div>
<div className="mb-2 font-semibold uppercase text-muted-foreground" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> </div>
<div className="flex items-center gap-5">
<div className="text-center">
<div className="text-lg font-bold">{inputQty}</div>
<div className="text-xs text-muted-foreground"></div>
<div className="font-bold" style={{ fontSize: `${DESIGN.stat.valueSize}px`, fontWeight: DESIGN.stat.weight }}>{inputQty}</div>
<div className="text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-blue-600">{accumulatedTotal}</div>
<div className="text-xs text-muted-foreground"></div>
<div className={COLORS.complete} style={{ fontSize: `${DESIGN.stat.valueSize}px`, fontWeight: DESIGN.stat.weight }}>{accumulatedTotal}</div>
<div className="text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></div>
</div>
<div className="text-center">
<div className={`text-lg font-bold ${remainingQty > 0 ? "text-amber-600" : "text-green-600"}`}>
<div className={remainingQty > 0 ? COLORS.warning : COLORS.good} style={{ fontSize: `${DESIGN.stat.valueSize}px`, fontWeight: DESIGN.stat.weight }}>
{remainingQty}
</div>
<div className="text-xs text-muted-foreground"></div>
<div className="text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></div>
</div>
{availableInfo && availableInfo.availableQty > 0 && (
<div className="text-center">
<div className="text-lg font-bold text-violet-600">{availableInfo.availableQty}</div>
<div className="text-xs text-muted-foreground"></div>
<div className={COLORS.info} style={{ fontSize: `${DESIGN.stat.valueSize}px`, fontWeight: DESIGN.stat.weight }}>{availableInfo.availableQty}</div>
<div className="text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></div>
</div>
)}
</div>
@@ -1340,23 +1371,23 @@ function ResultPanel({
{/* 누적 실적 현황 */}
<div className="rounded-lg border bg-muted/20 px-4 py-3">
<div className="mb-2 text-xs font-semibold uppercase text-muted-foreground"> </div>
<div className="mb-2 font-semibold uppercase text-muted-foreground" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> </div>
<div className="flex items-center gap-5">
<div className="text-center">
<div className="text-lg font-bold">{accumulatedTotal}</div>
<div className="text-xs text-muted-foreground"></div>
<div className="font-bold" style={{ fontSize: `${DESIGN.stat.valueSize}px`, fontWeight: DESIGN.stat.weight }}>{accumulatedTotal}</div>
<div className="text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-green-600">{accumulatedGood}</div>
<div className="text-xs text-muted-foreground"></div>
<div className={COLORS.good} style={{ fontSize: `${DESIGN.stat.valueSize}px`, fontWeight: DESIGN.stat.weight }}>{accumulatedGood}</div>
<div className="text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-red-600">{accumulatedDefect}</div>
<div className="text-xs text-muted-foreground"></div>
<div className={COLORS.defect} style={{ fontSize: `${DESIGN.stat.valueSize}px`, fontWeight: DESIGN.stat.weight }}>{accumulatedDefect}</div>
<div className="text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></div>
</div>
<div className="text-center">
<div className="text-lg font-bold">{history.length}</div>
<div className="text-xs text-muted-foreground"></div>
<div className="font-bold" style={{ fontSize: `${DESIGN.stat.valueSize}px`, fontWeight: DESIGN.stat.weight }}>{history.length}</div>
<div className="text-muted-foreground" style={{ fontSize: `${DESIGN.stat.labelSize}px` }}></div>
</div>
</div>
{accumulatedTotal > 0 && (
@@ -1372,21 +1403,22 @@ function ResultPanel({
{/* 이번 차수 실적 입력 */}
{!isConfirmed && (
<div className="space-y-4">
<div className="text-sm font-semibold"> </div>
<div className="font-semibold" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> </div>
{/* 생산수량 */}
{enabledSections.some((s) => s.type === "total-qty") && (
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<label className="font-medium" style={{ fontSize: `${DESIGN.section.titleSize}px` }}></label>
<div className="flex items-center gap-3">
<Input
type="number"
className="h-11 w-40 text-base"
className="w-40 text-base"
style={{ height: `${DESIGN.input.height}px` }}
value={batchQty}
onChange={(e) => setBatchQty(e.target.value)}
placeholder="0"
/>
<span className="text-sm text-muted-foreground">EA</span>
<span className="text-muted-foreground" style={{ fontSize: `${DESIGN.section.titleSize}px` }}>EA</span>
</div>
</div>
)}
@@ -1394,23 +1426,25 @@ function ResultPanel({
{/* 양품/불량 */}
{enabledSections.some((s) => s.type === "good-defect") && (
<div className="space-y-2">
<label className="text-sm font-medium"> / </label>
<label className="font-medium" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> / </label>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground"></span>
<span className="text-muted-foreground" style={{ fontSize: `${DESIGN.section.titleSize}px` }}></span>
<Input
type="number"
className="h-11 w-28 bg-muted/50 text-base"
className="w-28 bg-muted/50 text-base"
style={{ height: `${DESIGN.input.height}px` }}
value={batchGood > 0 ? String(batchGood) : ""}
readOnly
placeholder="자동"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground"></span>
<span className="text-muted-foreground" style={{ fontSize: `${DESIGN.section.titleSize}px` }}></span>
<Input
type="number"
className="h-11 w-28 text-base"
className="w-28 text-base"
style={{ height: `${DESIGN.input.height}px` }}
value={batchDefect}
onChange={(e) => setBatchDefect(e.target.value)}
placeholder="0"
@@ -1429,19 +1463,20 @@ function ResultPanel({
{enabledSections.some((s) => s.type === "defect-types") && (
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-sm font-medium"> </label>
<Button size="sm" variant="outline" className="h-8 gap-1 text-xs" onClick={addDefectEntry}>
<label className="font-medium" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> </label>
<Button size="sm" variant="outline" className="gap-1" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.stat.labelSize}px` }} onClick={addDefectEntry}>
<Plus className="h-3.5 w-3.5" />
</Button>
</div>
{defectEntries.length === 0 ? (
<p className="text-sm text-muted-foreground"> .</p>
<p className="text-muted-foreground" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> .</p>
) : (
<div className="space-y-2">
{defectEntries.map((entry, idx) => (
<div key={idx} className="flex items-center gap-2 rounded-lg border p-2.5">
<select
className="h-10 rounded-md border px-2 text-sm"
className="rounded-md border px-2"
style={{ height: `${DESIGN.defectRow.height}px`, fontSize: `${DESIGN.stat.labelSize}px` }}
value={entry.defect_code}
onChange={(e) => updateDefectEntry(idx, "defect_code", e.target.value)}
>
@@ -1454,13 +1489,15 @@ function ResultPanel({
</select>
<Input
type="number"
className="h-10 w-20 text-sm"
className="w-20"
style={{ height: `${DESIGN.defectRow.height}px`, fontSize: `${DESIGN.stat.labelSize}px` }}
value={entry.qty}
onChange={(e) => updateDefectEntry(idx, "qty", e.target.value)}
placeholder="수량"
/>
<select
className="h-10 rounded-md border px-2 text-sm"
className="rounded-md border px-2"
style={{ height: `${DESIGN.defectRow.height}px`, fontSize: `${DESIGN.stat.labelSize}px` }}
value={entry.disposition}
onChange={(e) => updateDefectEntry(idx, "disposition", e.target.value)}
>
@@ -1468,8 +1505,8 @@ function ResultPanel({
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<Button size="icon" variant="ghost" className="h-8 w-8 shrink-0" onClick={() => removeDefectEntry(idx)}>
<Trash2 className="h-3.5 w-3.5 text-destructive" />
<Button size="icon" variant="ghost" className="shrink-0" style={{ height: `${DESIGN.defectRow.height}px`, width: `${DESIGN.defectRow.height}px` }} onClick={() => removeDefectEntry(idx)}>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
))}
@@ -1481,7 +1518,7 @@ function ResultPanel({
{/* 비고 */}
{enabledSections.some((s) => s.type === "note") && (
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<label className="font-medium" style={{ fontSize: `${DESIGN.section.titleSize}px` }}></label>
<Textarea
className="min-h-[60px] text-sm"
value={resultNote}
@@ -1507,7 +1544,8 @@ function ResultPanel({
{/* 등록 버튼 */}
<div className="flex items-center gap-3">
<Button
className="h-11 gap-2 px-6 text-sm"
className="gap-2 px-6"
style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
onClick={handleSubmitBatch}
disabled={saving || !batchQty}
>
@@ -1520,7 +1558,7 @@ function ResultPanel({
{/* 이전 실적 이력 */}
<div className="space-y-2">
<div className="text-sm font-semibold"> </div>
<div className="font-semibold" style={{ fontSize: `${DESIGN.section.titleSize}px` }}> </div>
{historyLoading ? (
<div className="flex items-center gap-2 py-4 text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" /> ...
@@ -1534,9 +1572,9 @@ function ResultPanel({
<div className="flex items-center gap-3">
<Badge variant="secondary" className="text-xs px-2">#{h.seq}</Badge>
<span className="text-sm font-medium">+{h.batch_qty}</span>
<span className="text-xs text-green-600"> +{h.batch_good}</span>
<span className={cn("text-xs", COLORS.good)}> +{h.batch_good}</span>
{h.batch_defect > 0 && (
<span className="text-xs text-red-600"> +{h.batch_defect}</span>
<span className={cn("text-xs", COLORS.defect)}> +{h.batch_defect}</span>
)}
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
@@ -1599,8 +1637,8 @@ function InfoBar({ fields, parentRow, processName }: InfoBarProps) {
: parentRow[f.column];
return (
<div key={f.column} className="flex items-center gap-1.5">
<span className="text-xs text-muted-foreground">{f.label}</span>
<span className="text-sm font-medium">{val != null ? String(val) : "-"}</span>
<span className="text-muted-foreground" style={{ fontSize: `${DESIGN.infoBar.labelSize}px` }}>{f.label}</span>
<span className="font-medium" style={{ fontSize: `${DESIGN.infoBar.valueSize}px` }}>{val != null ? String(val) : "-"}</span>
</div>
);
})}
@@ -1640,13 +1678,13 @@ function GroupTimerHeader({
{/* 그룹 제목 + 진행 카운트 */}
<div className="flex items-center justify-between px-4 pt-3 pb-1.5">
<div className="flex items-center gap-2.5">
<span className="text-sm font-semibold">{group.title}</span>
<span className="font-semibold" style={{ fontSize: `${DESIGN.section.titleSize}px` }}>{group.title}</span>
<Badge variant="secondary" className="text-xs px-2 py-0.5">
{group.completed}/{group.total}
</Badge>
</div>
{isGroupCompleted && (
<Badge variant="outline" className="text-xs text-green-600 px-2 py-0.5"></Badge>
<Badge variant="outline" className={cn("text-xs px-2 py-0.5", COLORS.good)}></Badge>
)}
</div>
@@ -1656,7 +1694,7 @@ function GroupTimerHeader({
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Timer className="h-4 w-4 text-muted-foreground" />
<span className="font-mono text-sm font-medium tabular-nums">{groupTimerFormatted}</span>
<span className="font-mono font-medium tabular-nums" style={{ fontSize: `${DESIGN.section.titleSize}px` }}>{groupTimerFormatted}</span>
<span className="text-xs text-muted-foreground"></span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
@@ -1667,26 +1705,26 @@ function GroupTimerHeader({
{!isProcessCompleted && !isGroupCompleted && (
<div className="flex items-center gap-1.5">
{!isGroupStarted && (
<Button size="sm" variant="outline" className="h-10 px-4 text-sm" onClick={() => onTimerAction("start")}>
<Button size="sm" variant="outline" className="px-4" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} onClick={() => onTimerAction("start")}>
<Play className="mr-1.5 h-4 w-4" />
</Button>
)}
{isGroupStarted && !isGroupPaused && (
<>
<Button size="sm" variant="outline" className="h-10 px-3 text-sm" onClick={() => onTimerAction("pause")}>
<Button size="sm" variant="outline" className="px-3" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} onClick={() => onTimerAction("pause")}>
<Pause className="mr-1.5 h-4 w-4" />
</Button>
<Button size="sm" variant="default" className="h-10 px-3 text-sm" onClick={() => onTimerAction("complete")}>
<Button size="sm" variant="default" className="px-3" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} onClick={() => onTimerAction("complete")}>
<CheckCircle2 className="mr-1.5 h-4 w-4" />
</Button>
</>
)}
{isGroupStarted && isGroupPaused && (
<>
<Button size="sm" variant="outline" className="h-10 px-3 text-sm" onClick={() => onTimerAction("resume")}>
<Button size="sm" variant="outline" className="px-3" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} onClick={() => onTimerAction("resume")}>
<Play className="mr-1.5 h-4 w-4" />
</Button>
<Button size="sm" variant="default" className="h-10 px-3 text-sm" onClick={() => onTimerAction("complete")}>
<Button size="sm" variant="default" className="px-3" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} onClick={() => onTimerAction("complete")}>
<CheckCircle2 className="mr-1.5 h-4 w-4" />
</Button>
</>
@@ -1845,7 +1883,7 @@ function InspectNumeric({ item, disabled, saving, onSave }: { item: WorkResultRo
<div className="mb-2 text-xs text-muted-foreground">{item.inspection_method}</div>
)}
<div className="flex items-center gap-2.5">
<Input type="number" className="h-10 w-36 text-sm" value={inputVal} onChange={(e) => setInputVal(e.target.value)} onBlur={handleBlur} disabled={disabled} placeholder="측정값 입력" />
<Input type="number" className="w-36" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={inputVal} onChange={(e) => setInputVal(e.target.value)} onBlur={handleBlur} disabled={disabled} placeholder="측정값 입력" />
{item.unit && <span className="text-xs text-muted-foreground">{item.unit}</span>}
{saving && <Loader2 className="h-4 w-4 animate-spin" />}
{item.is_passed === "Y" && !saving && <Badge variant="outline" className="text-xs text-green-600"></Badge>}
@@ -1877,7 +1915,8 @@ function InspectOX({ item, disabled, saving, onSave }: { item: WorkResultRow; di
<Button
size="sm"
variant={currentValue === "OK" ? "default" : "outline"}
className={cn("h-11 gap-1.5 px-6 text-sm font-medium", currentValue === "OK" && "bg-green-600 hover:bg-green-700")}
className={cn("gap-1.5 px-6 font-medium", currentValue === "OK" && "bg-green-600 hover:bg-green-700")}
style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
disabled={disabled}
onClick={() => handleSelect("OK")}
>
@@ -1886,7 +1925,8 @@ function InspectOX({ item, disabled, saving, onSave }: { item: WorkResultRow; di
<Button
size="sm"
variant={currentValue === "NG" ? "default" : "outline"}
className={cn("h-11 gap-1.5 px-6 text-sm font-medium", currentValue === "NG" && "bg-red-600 hover:bg-red-700")}
className={cn("gap-1.5 px-6 font-medium", currentValue === "NG" && "bg-red-600 hover:bg-red-700")}
style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
disabled={disabled}
onClick={() => handleSelect("NG")}
>
@@ -1933,7 +1973,8 @@ function InspectSelect({ item, disabled, saving, onSave }: { item: WorkResultRow
key={opt}
size="sm"
variant={currentValue === opt ? "default" : "outline"}
className="h-10 px-4 text-sm"
className="px-4"
style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
disabled={disabled}
onClick={() => handleSelect(opt)}
>
@@ -1974,11 +2015,12 @@ function InspectText({ item, disabled, saving, onSave }: { item: WorkResultRow;
{item.is_required === "Y" && <span className="text-xs font-semibold text-destructive"></span>}
</div>
<div className="flex items-center gap-2.5">
<Input className="h-10 flex-1 text-sm" value={inputVal} onChange={(e) => setInputVal(e.target.value)} onBlur={handleBlur} disabled={disabled} placeholder="내용 입력" />
<Input className="flex-1" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={inputVal} onChange={(e) => setInputVal(e.target.value)} onBlur={handleBlur} disabled={disabled} placeholder="내용 입력" />
<Button
size="sm"
variant={judged === "Y" ? "default" : "outline"}
className={cn("h-10 px-4 text-sm", judged === "Y" && "bg-green-600 hover:bg-green-700")}
className={cn("px-4", judged === "Y" && "bg-green-600 hover:bg-green-700")}
style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
disabled={disabled}
onClick={() => handleJudge("Y")}
>
@@ -1987,7 +2029,8 @@ function InspectText({ item, disabled, saving, onSave }: { item: WorkResultRow;
<Button
size="sm"
variant={judged === "N" ? "default" : "outline"}
className={cn("h-10 px-4 text-sm", judged === "N" && "bg-red-600 hover:bg-red-700")}
className={cn("px-4", judged === "N" && "bg-red-600 hover:bg-red-700")}
style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
disabled={disabled}
onClick={() => handleJudge("N")}
>
@@ -2016,7 +2059,7 @@ function InputItem({ item, disabled, saving, onSave }: { item: WorkResultRow; di
<div className={cn("rounded-lg border px-4 py-3", item.status === "completed" && "border-green-200 bg-green-50")}>
<div className="mb-2 text-sm font-medium">{item.detail_label || item.detail_content}</div>
<div className="flex items-center gap-2.5">
<Input type={inputType} className="h-10 flex-1 text-sm" value={inputVal} onChange={(e) => setInputVal(e.target.value)} onBlur={handleBlur} disabled={disabled} placeholder="값 입력" />
<Input type={inputType} className="flex-1" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={inputVal} onChange={(e) => setInputVal(e.target.value)} onBlur={handleBlur} disabled={disabled} placeholder="값 입력" />
{saving && <Loader2 className="h-4 w-4 animate-spin" />}
</div>
</div>
@@ -2084,14 +2127,15 @@ function MaterialItem({ item, disabled, saving, onSave }: { item: WorkResultRow;
<div className="flex items-center gap-2.5">
<Input
ref={inputRef}
className="h-10 flex-1 text-sm"
className="flex-1"
style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
value={inputVal}
onChange={(e) => setInputVal(e.target.value)}
onKeyDown={handleKeyDown}
disabled={disabled}
placeholder="LOT/바코드 스캔 또는 입력"
/>
<Button size="sm" variant="outline" className="h-10 px-4 text-sm" onClick={handleSubmit} disabled={disabled || !inputVal}>
<Button size="sm" variant="outline" className="px-4" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} onClick={handleSubmit} disabled={disabled || !inputVal}>
</Button>
{saving && <Loader2 className="h-4 w-4 animate-spin" />}
@@ -2133,11 +2177,11 @@ function ResultInputItem({ item, disabled, saving, onSave }: { item: WorkResultR
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground"></span>
<Input type="number" className="h-10 w-24 text-sm" value={good} onChange={(e) => setGood(e.target.value)} disabled={disabled} placeholder="0" />
<Input type="number" className="w-24" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={good} onChange={(e) => setGood(e.target.value)} disabled={disabled} placeholder="0" />
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground"></span>
<Input type="number" className="h-10 w-24 text-sm" value={defect} onChange={(e) => setDefect(e.target.value)} disabled={disabled} placeholder="0" />
<Input type="number" className="w-24" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={defect} onChange={(e) => setDefect(e.target.value)} disabled={disabled} placeholder="0" />
</div>
<span className="text-xs text-muted-foreground">: {total}</span>
</div>
@@ -2145,17 +2189,17 @@ function ResultInputItem({ item, disabled, saving, onSave }: { item: WorkResultR
{parseInt(defect, 10) > 0 && (
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground"></span>
<Input className="h-10 flex-1 text-sm" value={defectType} onChange={(e) => setDefectType(e.target.value)} disabled={disabled} placeholder="스크래치, 치수불량 등" />
<Input className="flex-1" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={defectType} onChange={(e) => setDefectType(e.target.value)} disabled={disabled} placeholder="스크래치, 치수불량 등" />
</div>
)}
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground"></span>
<Input className="h-10 flex-1 text-sm" value={note} onChange={(e) => setNote(e.target.value)} disabled={disabled} placeholder="비고 (선택)" />
<Input className="flex-1" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={note} onChange={(e) => setNote(e.target.value)} disabled={disabled} placeholder="비고 (선택)" />
</div>
<div className="flex items-center gap-2.5">
<Button size="sm" variant="outline" className="h-10 px-4 text-sm" onClick={handleSave} disabled={disabled}>
<Button size="sm" variant="outline" className="px-4" style={{ height: `${DESIGN.button.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} onClick={handleSave} disabled={disabled}>
</Button>
{saving && <Loader2 className="h-4 w-4 animate-spin" />}