feat: MES 카드 산업용 태블릿 UI 리디자인 (10인치 터치 최적화)
산업현장 10인치 태블릿에서의 가독성과 터치 조작성을 대폭 개선한다. HTML 시안(pop-design-v2)을 기반으로 기존 기능을 모두 보존하면서 디자인만 변경. [카드 헤더 재구성] - 품목명(item_name) 20px bold 강조 (기존: 미표시) - WO번호+공정명 14px 보조 정보로 전환 (기존: WO번호 13px 주인공) - 상태 배지 14px rounded-full pill (기존: 10px) - 재작업 배지 12px (기존: 9px) - 좌측 컬러바 4px (기존: 3px) [메트릭 3단 통일 구조] - 접수가능: 컨텍스트 + 파랑 박스(접수가능 N EA) - 진행중: 컨텍스트 + 주황 박스(생산 N / N EA) + 칩(양품/불량/잔여) - 완료: 컨텍스트+수율pill + 초록 박스(최종양품 N EA) + 칩 - 재작업: 컨텍스트 + 주황 박스(재작업 수량 N EA) - 핵심 수량 36px extrabold, 컨텍스트 14px (기존: 18px/10px) [카드 높이 일관성] - 메트릭 영역 flex-1 적용: 같은 행의 카드들이 동일 높이로 정렬 - 공정 흐름/버튼이 항상 카드 하단 고정 [공정 흐름 노드 기반 디자인] - 텍스트 칩 -> 40x40px 노드 박스 + 라벨 (기존: 10px 칩) - 커넥터 라인으로 공정 연결 시각화 [액션 버튼 대형화] - h-14(56px) full-width rounded-xl 17px bold (기존: h-7 28px 11px) [MES 카드 래퍼 패딩] - MES 카드 전용 p-1 제거 (카드 내부 충분한 패딩 px-5 보유)
This commit is contained in:
parent
17fb815513
commit
73674385be
|
|
@ -1861,11 +1861,11 @@ function CardV2({
|
|||
)}
|
||||
|
||||
{/* CSS Grid 기반 셀 렌더링 */}
|
||||
<div className="flex-1 overflow-hidden p-1" style={gridStyle}>
|
||||
<div className={cn("flex-1 overflow-hidden", !isMesCard && "p-1")} style={gridStyle}>
|
||||
{cardGrid.cells.map((cell) => (
|
||||
<div
|
||||
key={cell.id}
|
||||
className="overflow-hidden p-1"
|
||||
className={cn("overflow-hidden", !isMesCard && "p-1")}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
|
|
|||
|
|
@ -829,12 +829,6 @@ function ProcessQtySummaryCell({ cell, row }: CellRendererProps) {
|
|||
style={{ width: `${progressPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className={cn(
|
||||
"text-[10px] font-bold tabular-nums",
|
||||
isBatchDone ? "text-violet-600" : "text-primary",
|
||||
)}>
|
||||
{progressPct}%
|
||||
</span>
|
||||
</div>
|
||||
{/* 수량 상세 */}
|
||||
<div className="flex items-center justify-between gap-0.5">
|
||||
|
|
@ -1027,6 +1021,7 @@ function MesProcessCardCell({ cell, row, onActionButtonClick, currentUserId }: C
|
|||
const processName = currentStep?.processName || String(row.__process_process_name ?? "");
|
||||
const woNo = String(row.work_instruction_no ?? "");
|
||||
const itemId = String(row.item_id ?? "");
|
||||
const itemName = String(row.item_name ?? "");
|
||||
|
||||
// MES 워크플로우 상태 기반 버튼 결정
|
||||
const acceptBtn = (cell.actionButtons || []).find((b) => b.showCondition?.type === "timeline-status");
|
||||
|
|
@ -1052,36 +1047,32 @@ function MesProcessCardCell({ cell, row, onActionButtonClick, currentUserId }: C
|
|||
<>
|
||||
<div
|
||||
className="flex h-full w-full flex-col overflow-hidden"
|
||||
style={{ borderLeft: `3px solid ${st.color}`, backgroundColor: st.bg }}
|
||||
style={{ borderLeft: `4px solid ${st.color}`, backgroundColor: st.bg }}
|
||||
>
|
||||
{/* ── 헤더 ── */}
|
||||
<div className="flex items-start justify-between px-3 pt-2.5 pb-1">
|
||||
<div className="flex items-start justify-between px-5 pt-5 pb-1">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="truncate text-[13px] font-bold leading-tight">{woNo}</span>
|
||||
{itemId && itemId !== "-" && (
|
||||
<span className="truncate text-[10px] text-muted-foreground">{itemId}</span>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-[14px] font-medium text-muted-foreground">{woNo}</span>
|
||||
{processName && (
|
||||
<span className="text-[14px] font-semibold" style={{ color: st.color }}>
|
||||
{processName}
|
||||
{processFlow && processFlow.length > 1 && ` (${currentIdx + 1}/${processFlow.length})`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{processName && (
|
||||
<div className="mt-0.5 flex items-center gap-1">
|
||||
<span className="text-[11px] font-semibold" style={{ color: st.color }}>{processName}</span>
|
||||
{processFlow && processFlow.length > 1 && (
|
||||
<span className="text-[9px] text-muted-foreground">
|
||||
({currentIdx + 1}/{processFlow.length}공정)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-1">
|
||||
<span className="text-[20px] font-bold leading-tight">{itemName || itemId || "-"}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex shrink-0 items-center gap-1">
|
||||
<div className="ml-3 flex shrink-0 items-center gap-2">
|
||||
{isRework && (
|
||||
<span className="rounded bg-amber-500 px-1.5 py-0.5 text-[9px] font-bold text-white">
|
||||
<span className="rounded-md bg-amber-500 px-2.5 py-1 text-[12px] font-bold text-white">
|
||||
재작업
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="rounded px-2 py-0.5 text-[10px] font-bold"
|
||||
className="rounded-full px-3.5 py-1.5 text-[14px] font-bold"
|
||||
style={{ backgroundColor: st.color, color: "#fff" }}
|
||||
>
|
||||
{st.label}
|
||||
|
|
@ -1090,7 +1081,7 @@ function MesProcessCardCell({ cell, row, onActionButtonClick, currentUserId }: C
|
|||
</div>
|
||||
|
||||
{/* ── 수량 메트릭 (상태별) ── */}
|
||||
<div className="px-3 py-1.5">
|
||||
<div className="flex-1 px-5 py-3">
|
||||
{(isClone || rawStatus === "acceptable" || rawStatus === "waiting") && (
|
||||
<MesAcceptableMetrics
|
||||
instrQty={instrQty}
|
||||
|
|
@ -1130,7 +1121,7 @@ function MesProcessCardCell({ cell, row, onActionButtonClick, currentUserId }: C
|
|||
{/* ── 공정 흐름 스트립 (클릭 시 모달) ── */}
|
||||
{processFlow && processFlow.length > 0 && (
|
||||
<div
|
||||
className="cursor-pointer border-t px-3 py-1.5 transition-colors hover:bg-black/2"
|
||||
className="cursor-pointer border-t px-5 py-3 transition-colors hover:bg-black/3"
|
||||
style={{ borderColor: `${st.color}20` }}
|
||||
onClick={(e) => { e.stopPropagation(); setFlowModalOpen(true); }}
|
||||
title="클릭하여 공정 상세 보기"
|
||||
|
|
@ -1139,49 +1130,58 @@ function MesProcessCardCell({ cell, row, onActionButtonClick, currentUserId }: C
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* ── 하단: 부가정보 + 액션 ── */}
|
||||
<div
|
||||
className="mt-auto flex items-center justify-between border-t px-3 py-1.5"
|
||||
style={{ borderColor: `${st.color}20` }}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[9px] text-muted-foreground">
|
||||
{row.end_date && <span>납기 {formatValue(row.end_date)}</span>}
|
||||
{row.equipment_id && <span>{String(row.equipment_id)}</span>}
|
||||
{row.work_team && <span>{String(row.work_team)}</span>}
|
||||
{/* ── 부가정보 ── */}
|
||||
{(row.end_date || row.equipment_id || row.work_team) && (
|
||||
<div
|
||||
className="border-t px-5 py-2"
|
||||
style={{ borderColor: `${st.color}20` }}
|
||||
>
|
||||
<div className="flex items-center gap-4 text-[14px] text-muted-foreground">
|
||||
{row.end_date && <span>납기 <b className="text-foreground">{formatValue(row.end_date)}</b></span>}
|
||||
{row.equipment_id && <span>{String(row.equipment_id)}</span>}
|
||||
{row.work_team && <span>{String(row.work_team)}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{activeBtn && (
|
||||
<Button
|
||||
variant={activeBtn.variant || "default"}
|
||||
size="sm"
|
||||
className="h-7 px-3 text-[11px] font-semibold"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const actions = activeBtn.clickActions?.length ? activeBtn.clickActions : [activeBtn.clickAction];
|
||||
const firstAction = actions[0];
|
||||
const config: Record<string, unknown> = { ...firstAction, __allActions: actions };
|
||||
if (processId !== undefined) config.__processId = processId;
|
||||
onActionButtonClick?.(activeBtn.label, row, config);
|
||||
}}
|
||||
>
|
||||
{activeBtn.label}
|
||||
</Button>
|
||||
)}
|
||||
{showManualComplete && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 px-3 text-[11px] font-semibold"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onActionButtonClick?.("__manualComplete", row, { __processId: processId });
|
||||
}}
|
||||
>
|
||||
수동 완료
|
||||
</Button>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* ── 액션 버튼 ── */}
|
||||
{(activeBtn || showManualComplete) && (
|
||||
<div
|
||||
className="mt-auto border-t px-5 py-3"
|
||||
style={{ borderColor: `${st.color}20` }}
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
{activeBtn && (
|
||||
<Button
|
||||
variant={activeBtn.variant || "default"}
|
||||
className="h-14 flex-1 rounded-xl text-[17px] font-bold"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const actions = activeBtn.clickActions?.length ? activeBtn.clickActions : [activeBtn.clickAction];
|
||||
const firstAction = actions[0];
|
||||
const config: Record<string, unknown> = { ...firstAction, __allActions: actions };
|
||||
if (processId !== undefined) config.__processId = processId;
|
||||
onActionButtonClick?.(activeBtn.label, row, config);
|
||||
}}
|
||||
>
|
||||
{activeBtn.label}
|
||||
</Button>
|
||||
)}
|
||||
{showManualComplete && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-14 flex-1 rounded-xl text-[17px] font-bold"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onActionButtonClick?.("__manualComplete", row, { __processId: processId });
|
||||
}}
|
||||
>
|
||||
수동 완료
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ── 공정 상세 모달 ── */}
|
||||
|
|
@ -1260,7 +1260,7 @@ function MesProcessCardCell({ cell, row, onActionButtonClick, currentUserId }: C
|
|||
);
|
||||
}
|
||||
|
||||
// ── 공정 흐름 스트립 (5슬롯: 지나온 + 이전 + 현재 + 다음 + 남은) ──
|
||||
// ── 공정 흐름 스트립 (노드 기반: 지나온 + 이전 + 현재 + 다음 + 남은) ──
|
||||
function ProcessFlowStrip({ steps, currentIdx, instrQty }: {
|
||||
steps: TimelineProcessStep[]; currentIdx: number; instrQty: number;
|
||||
}) {
|
||||
|
|
@ -1277,61 +1277,75 @@ function ProcessFlowStrip({ steps, currentIdx, instrQty }: {
|
|||
return sem === "done";
|
||||
});
|
||||
|
||||
const renderChip = (step: TimelineProcessStep, isCurrent: boolean) => {
|
||||
const renderNode = (step: TimelineProcessStep, isCurrent: boolean) => {
|
||||
const sem = step.semantic || LEGACY_STATUS_TO_SEMANTIC[step.status] || "pending";
|
||||
return (
|
||||
<span className={cn(
|
||||
"inline-flex shrink-0 items-center gap-0.5 rounded-full px-2 py-0.5 text-[10px] font-medium whitespace-nowrap",
|
||||
isCurrent
|
||||
? "bg-primary text-primary-foreground shadow-sm"
|
||||
: sem === "done"
|
||||
? "bg-emerald-50 text-emerald-700"
|
||||
: "bg-muted text-muted-foreground",
|
||||
)}>
|
||||
{sem === "done" && !isCurrent && <Check className="h-2.5 w-2.5" />}
|
||||
{step.seqNo} {step.processName}
|
||||
</span>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className={cn(
|
||||
"flex h-10 w-10 shrink-0 items-center justify-center rounded-[10px] border-2 text-[14px] font-bold",
|
||||
isCurrent
|
||||
? "border-primary bg-primary text-primary-foreground shadow-sm shadow-primary/20"
|
||||
: sem === "done"
|
||||
? "border-emerald-200 bg-emerald-50 text-emerald-600"
|
||||
: "border-border bg-muted text-muted-foreground",
|
||||
)}>
|
||||
{sem === "done" && !isCurrent ? <Check className="h-4 w-4" /> : step.seqNo}
|
||||
</div>
|
||||
<span className={cn(
|
||||
"max-w-[56px] truncate text-center text-[11px] font-medium",
|
||||
isCurrent ? "font-bold text-primary" : "text-muted-foreground",
|
||||
)}>
|
||||
{step.processName}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const connDone = "mt-[18px] h-[3px] w-5 shrink-0 bg-emerald-400";
|
||||
const connPending = "mt-[18px] h-[3px] w-5 shrink-0 bg-border";
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-start">
|
||||
{hiddenBefore > 0 && (
|
||||
<>
|
||||
<span className={cn(
|
||||
"inline-flex shrink-0 items-center rounded-full px-1.5 py-0.5 text-[10px] font-bold tabular-nums",
|
||||
allBeforeDone
|
||||
? "bg-emerald-100 text-emerald-600"
|
||||
: "bg-slate-100 text-slate-500",
|
||||
)}>
|
||||
+{hiddenBefore}
|
||||
</span>
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/40" />
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className={cn(
|
||||
"flex h-10 w-10 shrink-0 items-center justify-center rounded-[10px] border-2 text-[13px] font-bold tabular-nums",
|
||||
allBeforeDone
|
||||
? "border-emerald-200 bg-emerald-50 text-emerald-600"
|
||||
: "border-border bg-muted text-muted-foreground",
|
||||
)}>
|
||||
+{hiddenBefore}
|
||||
</div>
|
||||
</div>
|
||||
<div className={allBeforeDone ? connDone : connPending} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{prevStep && (
|
||||
<>
|
||||
{renderChip(prevStep, false)}
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/40" />
|
||||
{renderNode(prevStep, false)}
|
||||
<div className={connDone} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{currStep && renderChip(currStep, true)}
|
||||
{currStep && renderNode(currStep, true)}
|
||||
|
||||
{nextStep && (
|
||||
<>
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/40" />
|
||||
{renderChip(nextStep, false)}
|
||||
<div className={connPending} />
|
||||
{renderNode(nextStep, false)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{hiddenAfter > 0 && (
|
||||
<>
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/40" />
|
||||
<span className="inline-flex shrink-0 items-center rounded-full bg-amber-50 px-1.5 py-0.5 text-[10px] font-bold tabular-nums text-amber-600">
|
||||
+{hiddenAfter}
|
||||
</span>
|
||||
<div className={connPending} />
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-[10px] border-2 border-amber-200 bg-amber-50 text-[13px] font-bold tabular-nums text-amber-600">
|
||||
+{hiddenAfter}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1344,21 +1358,22 @@ function MesAcceptableMetrics({ instrQty, prevGoodQty, availableQty, inputQty, i
|
|||
}) {
|
||||
if (isRework) {
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-3 text-[10px]">
|
||||
<span className="text-amber-600 font-medium">불량 재작업 대상</span>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3 text-[14px]">
|
||||
<span className="font-medium text-amber-600">불량 재작업 대상</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center rounded-md py-2" style={{ backgroundColor: "rgba(245,158,11,0.08)" }}>
|
||||
<span className="text-[11px] text-muted-foreground">재작업 수량 </span>
|
||||
<span className="text-lg font-extrabold tabular-nums text-amber-600">{inputQty.toLocaleString()}</span>
|
||||
<div className="flex items-center justify-center gap-2.5 rounded-lg py-3.5" style={{ backgroundColor: "rgba(245,158,11,0.08)" }}>
|
||||
<span className="text-[16px] font-medium text-muted-foreground">재작업 수량</span>
|
||||
<span className="text-[36px] font-extrabold tabular-nums leading-none tracking-tight text-amber-600">{inputQty.toLocaleString()}</span>
|
||||
<span className="text-[16px] font-medium text-muted-foreground">EA</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const displayAvail = isClone ? availableQty : (availableQty || prevGoodQty);
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-3 text-[10px]">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-4 text-[14px]">
|
||||
<span className="text-muted-foreground">지시 <b className="text-foreground">{instrQty.toLocaleString()}</b></span>
|
||||
{!isFirstProcess && (
|
||||
<span className="text-muted-foreground">전공정양품 <b className="text-emerald-600">{prevGoodQty.toLocaleString()}</b></span>
|
||||
|
|
@ -1367,9 +1382,10 @@ function MesAcceptableMetrics({ instrQty, prevGoodQty, availableQty, inputQty, i
|
|||
<span className="text-muted-foreground">기접수 <b className="text-foreground">{inputQty.toLocaleString()}</b></span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-center rounded-md py-2" style={{ backgroundColor: "rgba(37,99,235,0.06)" }}>
|
||||
<span className="text-[11px] text-muted-foreground">접수가능 </span>
|
||||
<span className="text-lg font-extrabold tabular-nums text-primary">{displayAvail.toLocaleString()}</span>
|
||||
<div className="flex items-center justify-center gap-2.5 rounded-lg py-3.5" style={{ backgroundColor: "rgba(37,99,235,0.06)" }}>
|
||||
<span className="text-[16px] font-medium text-muted-foreground">접수가능</span>
|
||||
<span className="text-[36px] font-extrabold tabular-nums leading-none tracking-tight text-primary">{displayAvail.toLocaleString()}</span>
|
||||
<span className="text-[16px] font-medium text-muted-foreground">EA</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1380,28 +1396,42 @@ function MesInProgressMetrics({ inputQty, totalProd, goodQty, defectQty, concess
|
|||
inputQty: number; totalProd: number; goodQty: number; defectQty: number; concessionQty: number; remainingQty: number; progressPct: number; availableQty: number; isBatchDone: boolean; statusColor: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
{/* 메인 프로그레스 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[10px] text-muted-foreground">접수 <b className="text-foreground">{inputQty.toLocaleString()}</b></span>
|
||||
<div className="h-2.5 flex-1 overflow-hidden rounded-full bg-secondary">
|
||||
<div className="h-full rounded-full transition-all duration-300" style={{ width: `${progressPct}%`, backgroundColor: statusColor }} />
|
||||
</div>
|
||||
<span className="text-[11px] font-extrabold tabular-nums" style={{ color: statusColor }}>{progressPct}%</span>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-4 text-[14px]">
|
||||
<span className="text-muted-foreground">접수 <b className="text-foreground">{inputQty.toLocaleString()}</b></span>
|
||||
{availableQty > 0 && (
|
||||
<span className="text-muted-foreground">추가접수가능 <b className="text-violet-600">{availableQty.toLocaleString()}</b></span>
|
||||
)}
|
||||
</div>
|
||||
{/* 수량 메트릭 */}
|
||||
<div className={cn("grid gap-1", concessionQty > 0 ? "grid-cols-5" : "grid-cols-4")}>
|
||||
<MesMetricBox label="완료" value={totalProd} color="#3b82f6" />
|
||||
<MesMetricBox label="양품" value={goodQty} color="#10b981" />
|
||||
<MesMetricBox label="불량" value={defectQty} color="#ef4444" dimZero />
|
||||
{concessionQty > 0 && <MesMetricBox label="특채" value={concessionQty} color="#8b5cf6" />}
|
||||
<MesMetricBox label="잔여" value={remainingQty} color={remainingQty > 0 ? "#f59e0b" : "#10b981"} />
|
||||
<div className="flex items-center justify-center gap-2.5 rounded-lg py-3.5" style={{ backgroundColor: `${statusColor}0F` }}>
|
||||
<span className="text-[16px] font-medium text-muted-foreground">생산</span>
|
||||
<span className="text-[36px] font-extrabold tabular-nums leading-none tracking-tight" style={{ color: statusColor }}>
|
||||
{totalProd.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-[20px] font-normal text-muted-foreground">/ {inputQty.toLocaleString()}</span>
|
||||
<span className="text-[16px] font-medium text-muted-foreground">EA</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-50 px-3 py-1.5 text-[14px] font-semibold text-emerald-600">
|
||||
<span className="font-medium opacity-70">양품</span> {goodQty.toLocaleString()}
|
||||
</span>
|
||||
{defectQty > 0 && (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-red-50 px-3 py-1.5 text-[14px] font-semibold text-red-600">
|
||||
<span className="font-medium opacity-70">불량</span> {defectQty.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
{concessionQty > 0 && (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-violet-50 px-3 py-1.5 text-[14px] font-semibold text-violet-600">
|
||||
<span className="font-medium opacity-70">특채</span> {concessionQty.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[14px] font-semibold",
|
||||
remainingQty > 0 ? "bg-amber-50 text-amber-600" : "bg-emerald-50 text-emerald-600",
|
||||
)}>
|
||||
<span className="font-medium opacity-70">잔여</span> {remainingQty.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
{availableQty > 0 && (
|
||||
<div className="text-right text-[9px] text-muted-foreground">
|
||||
추가접수가능 <b className="text-violet-600">{availableQty.toLocaleString()}</b>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1411,17 +1441,37 @@ function MesCompletedMetrics({ instrQty, goodQty, defectQty, concessionQty, yiel
|
|||
instrQty: number; goodQty: number; defectQty: number; concessionQty: number; yieldRate: number;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-3 text-[10px]">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-4 text-[14px]">
|
||||
<span className="text-muted-foreground">지시 <b className="text-foreground">{instrQty.toLocaleString()}</b></span>
|
||||
<span className="text-muted-foreground">최종양품 <b className="text-emerald-600">{goodQty.toLocaleString()}</b></span>
|
||||
{concessionQty > 0 && (
|
||||
<span className="text-muted-foreground">특채 <b className="text-violet-600">{concessionQty.toLocaleString()}</b></span>
|
||||
)}
|
||||
<span className="ml-auto text-muted-foreground">수율 <b style={{ color: yieldRate >= 95 ? "#059669" : yieldRate >= 80 ? "#d97706" : "#ef4444" }}>{yieldRate}%</b></span>
|
||||
<span
|
||||
className="ml-auto rounded-full px-3.5 py-1 text-[14px] font-bold"
|
||||
style={{
|
||||
backgroundColor: yieldRate >= 95 ? "#f0fdf4" : yieldRate >= 80 ? "#fffbeb" : "#fef2f2",
|
||||
color: yieldRate >= 95 ? "#059669" : yieldRate >= 80 ? "#d97706" : "#ef4444",
|
||||
}}
|
||||
>
|
||||
수율 {yieldRate}%
|
||||
</span>
|
||||
</div>
|
||||
{defectQty > 0 && (
|
||||
<div className="text-[9px] text-muted-foreground">불량 <b className="text-destructive">{defectQty.toLocaleString()}</b></div>
|
||||
<div className="flex items-center justify-center gap-2.5 rounded-lg py-3.5" style={{ backgroundColor: "rgba(5,150,105,0.06)" }}>
|
||||
<span className="text-[16px] font-medium text-muted-foreground">최종양품</span>
|
||||
<span className="text-[36px] font-extrabold tabular-nums leading-none tracking-tight text-emerald-600">{goodQty.toLocaleString()}</span>
|
||||
<span className="text-[16px] font-medium text-muted-foreground">EA</span>
|
||||
</div>
|
||||
{(defectQty > 0 || concessionQty > 0) && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{defectQty > 0 && (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-red-50 px-3 py-1.5 text-[14px] font-semibold text-red-600">
|
||||
<span className="font-medium opacity-70">불량</span> {defectQty.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
{concessionQty > 0 && (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-violet-50 px-3 py-1.5 text-[14px] font-semibold text-violet-600">
|
||||
<span className="font-medium opacity-70">특채</span> {concessionQty.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue