Files
vexplor/frontend/components/pop/hardcoded/production/AcceptProcessModal.tsx
SeongHyun Kim 9b7b88ff7c feat: POP 하드코딩 화면 추가 (PC 코드 무변경 재병합)
- POP 전용 39개 파일 추가 (홈/입고/출고/생산)
- 백엔드 INSERT에 id gen_random_uuid 추가 (5개 파일)
- POP 전용 API 7개 추가 (창고/위치/입고/동기화)
- PC 코드 구조/순서/로직 변경 없음 (AppLayout, UserDropdown 미수정)
2026-04-02 17:39:42 +09:00

185 lines
5.8 KiB
TypeScript

"use client";
import React, { useState, useEffect, useCallback } from "react";
/* ------------------------------------------------------------------ */
/* Types */
/* ------------------------------------------------------------------ */
interface AcceptProcessModalProps {
open: boolean;
onClose: () => void;
onConfirm: (qty: number) => void;
maxQty: number;
processName: string;
seqNo: string;
loading?: boolean;
}
/* ------------------------------------------------------------------ */
/* Numpad Keys */
/* ------------------------------------------------------------------ */
const KEYS = [
{ label: "7", action: "7" },
{ label: "8", action: "8" },
{ label: "9", action: "9" },
{ label: "\u2190", action: "backspace" },
{ label: "4", action: "4" },
{ label: "5", action: "5" },
{ label: "6", action: "6" },
{ label: "C", action: "clear" },
{ label: "1", action: "1" },
{ label: "2", action: "2" },
{ label: "3", action: "3" },
{ label: "MAX", action: "max" },
];
/* ------------------------------------------------------------------ */
/* Component */
/* ------------------------------------------------------------------ */
export function AcceptProcessModal({
open,
onClose,
onConfirm,
maxQty,
processName,
seqNo,
loading = false,
}: AcceptProcessModalProps) {
const [qty, setQty] = useState("0");
useEffect(() => {
if (open) {
setQty("0");
}
}, [open]);
const qtyNum = parseInt(qty, 10) || 0;
const isOverMax = qtyNum > maxQty;
const handleKey = useCallback(
(key: string) => {
setQty((prev) => {
switch (key) {
case "backspace":
return prev.length <= 1 ? "0" : prev.slice(0, -1);
case "clear":
return "0";
case "max":
return String(maxQty);
default: {
const next = prev === "0" ? key : prev + key;
const num = parseInt(next, 10);
if (isNaN(num)) return prev;
return next;
}
}
});
},
[maxQty]
);
const handleConfirm = () => {
const finalQty = Math.min(qtyNum, maxQty);
if (finalQty <= 0) return;
onConfirm(finalQty);
};
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Overlay */}
<div className="absolute inset-0 bg-black/40" onClick={onClose} />
{/* Panel */}
<div className="relative bg-white w-[90%] max-w-[360px] rounded-2xl shadow-2xl z-10 overflow-hidden">
{/* Header */}
<div
className="px-4 py-3"
style={{ background: "linear-gradient(135deg, #f59e0b 0%, #d97706 100%)" }}
>
<div className="flex items-center justify-between">
<span className="text-white font-bold text-base"> </span>
<span className="text-white/80 text-xs bg-white/20 px-2.5 py-1 rounded-full">
{maxQty.toLocaleString()} EA
</span>
</div>
<p className="text-white/80 text-xs mt-1">
{seqNo}: {processName}
</p>
</div>
{/* Body */}
<div className="p-4">
<p className="text-center text-sm font-semibold text-gray-700 mb-1">
</p>
<p className="text-center text-xs text-gray-400 mb-3">
(EA)
</p>
{/* Display */}
<input
type="text"
readOnly
value={qtyNum.toLocaleString()}
className={`w-full px-4 py-3 text-right text-3xl font-bold border-2 rounded-xl bg-gray-50 mb-3 ${
isOverMax ? "border-red-300 text-red-500" : "border-gray-200 text-gray-900"
}`}
style={{ fontVariantNumeric: "tabular-nums" }}
/>
{isOverMax && (
<p className="text-xs text-red-500 text-center mb-2 font-medium">
({maxQty.toLocaleString()})
</p>
)}
{/* Numpad */}
<div className="grid grid-cols-4 gap-2.5">
{KEYS.map((key) => (
<button
key={key.action}
onClick={() => handleKey(key.action)}
className={`h-14 rounded-xl text-lg font-semibold active:scale-95 transition-all ${
key.action === "backspace" || key.action === "clear"
? "bg-amber-100 text-amber-700 hover:bg-amber-200"
: key.action === "max"
? "bg-blue-100 text-blue-700 hover:bg-blue-200 text-sm"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
}`}
>
{key.label}
</button>
))}
{/* Bottom row */}
<button
onClick={() => handleKey("0")}
className="col-span-2 h-14 rounded-xl text-lg font-semibold bg-gray-100 text-gray-900 hover:bg-gray-200 active:scale-95 transition-all"
>
0
</button>
<button
onClick={handleConfirm}
disabled={qtyNum <= 0 || loading}
className="col-span-2 h-14 rounded-xl text-lg font-bold text-white active:scale-95 transition-all disabled:opacity-40"
style={{
background:
qtyNum <= 0 || loading
? "#9ca3af"
: "linear-gradient(135deg, #f59e0b 0%, #d97706 100%)",
}}
>
{loading ? "처리중..." : "접수"}
</button>
</div>
</div>
</div>
</div>
);
}