ERP-node/frontend/lib/registry/pop-components/pop-card-list/NumberInputModal.tsx

210 lines
7.4 KiB
TypeScript
Raw Normal View History

"use client";
import React, { useState, useEffect } from "react";
import { Delete } from "lucide-react";
import {
Dialog,
DialogPortal,
DialogOverlay,
} from "@/components/ui/dialog";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import {
PackageUnitModal,
PACKAGE_UNITS,
type PackageUnit,
} from "./PackageUnitModal";
interface NumberInputModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
unit?: string;
initialValue?: number;
initialPackageUnit?: string;
min?: number;
maxValue?: number;
onConfirm: (value: number, packageUnit?: string) => void;
}
export function NumberInputModal({
open,
onOpenChange,
unit = "EA",
initialValue = 0,
initialPackageUnit,
min = 0,
maxValue = 999999,
onConfirm,
}: NumberInputModalProps) {
const [displayValue, setDisplayValue] = useState("");
const [packageUnit, setPackageUnit] = useState<string | undefined>(undefined);
const [isPackageModalOpen, setIsPackageModalOpen] = useState(false);
useEffect(() => {
if (open) {
setDisplayValue(initialValue > 0 ? String(initialValue) : "");
setPackageUnit(initialPackageUnit);
}
}, [open, initialValue, initialPackageUnit]);
const handleNumberClick = (num: string) => {
const newStr = displayValue + num;
const numericValue = parseInt(newStr, 10);
setDisplayValue(numericValue > maxValue ? String(maxValue) : newStr);
};
const handleBackspace = () =>
setDisplayValue((prev) => prev.slice(0, -1));
const handleClear = () => setDisplayValue("");
const handleMax = () => setDisplayValue(String(maxValue));
const handleConfirm = () => {
const numericValue = parseInt(displayValue, 10) || 0;
const finalValue = Math.max(min, Math.min(maxValue, numericValue));
onConfirm(finalValue, packageUnit);
onOpenChange(false);
};
const handlePackageUnitSelect = (selected: PackageUnit) => {
setPackageUnit(selected);
};
const matchedUnit = packageUnit
? PACKAGE_UNITS.find((u) => u.value === packageUnit)
: null;
const packageUnitLabel = matchedUnit?.label ?? null;
const packageUnitEmoji = matchedUnit?.emoji ?? "📦";
const displayText = displayValue
? parseInt(displayValue, 10).toLocaleString()
: "";
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
className="bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-[1000] w-full max-w-[95vw] translate-x-[-50%] translate-y-[-50%] overflow-hidden border shadow-lg duration-200 sm:max-w-[360px] sm:rounded-lg"
>
{/* 파란 헤더 */}
<div className="flex items-center justify-between bg-blue-500 px-4 py-3">
<span className="rounded-full bg-blue-400/50 px-3 py-1 text-sm font-medium text-white">
{maxValue.toLocaleString()} {unit}
</span>
<button
type="button"
onClick={() => setIsPackageModalOpen(true)}
className="flex items-center gap-1 rounded-full bg-white/20 px-3 py-1.5 text-sm font-medium text-white hover:bg-white/30 active:bg-white/40"
>
{packageUnitEmoji} {packageUnitLabel ? `${packageUnitLabel}` : "포장등록"}
</button>
</div>
<div className="space-y-3 p-4">
{/* 숫자 표시 영역 */}
<div className="flex min-h-[72px] items-center justify-end rounded-xl border-2 border-gray-200 bg-gray-50 px-4 py-4">
{displayText ? (
<span className="text-4xl font-bold tracking-tight text-gray-900">
{displayText}
</span>
) : (
<span className="text-2xl text-gray-300">0</span>
)}
</div>
{/* 안내 텍스트 */}
<p className="text-muted-foreground text-center text-sm">
</p>
{/* 키패드 4x4 */}
<div className="grid grid-cols-4 gap-2">
{/* 1행: 7 8 9 ← (주황) */}
{["7", "8", "9"].map((n) => (
<button
key={n}
type="button"
className="h-14 rounded-2xl border border-gray-200 bg-gray-50 text-xl font-semibold text-gray-800 active:bg-gray-200"
onClick={() => handleNumberClick(n)}
>
{n}
</button>
))}
<button
type="button"
className="flex h-14 items-center justify-center rounded-2xl bg-amber-100 text-amber-600 active:bg-amber-200"
onClick={handleBackspace}
>
<Delete className="h-5 w-5" />
</button>
{/* 2행: 4 5 6 C (주황) */}
{["4", "5", "6"].map((n) => (
<button
key={n}
type="button"
className="h-14 rounded-2xl border border-gray-200 bg-gray-50 text-xl font-semibold text-gray-800 active:bg-gray-200"
onClick={() => handleNumberClick(n)}
>
{n}
</button>
))}
<button
type="button"
className="h-14 rounded-2xl bg-amber-100 text-base font-bold text-amber-600 active:bg-amber-200"
onClick={handleClear}
>
C
</button>
{/* 3행: 1 2 3 MAX (파란) */}
{["1", "2", "3"].map((n) => (
<button
key={n}
type="button"
className="h-14 rounded-2xl border border-gray-200 bg-gray-50 text-xl font-semibold text-gray-800 active:bg-gray-200"
onClick={() => handleNumberClick(n)}
>
{n}
</button>
))}
<button
type="button"
className="h-14 rounded-2xl bg-blue-100 text-sm font-bold text-blue-600 active:bg-blue-200"
onClick={handleMax}
>
MAX
</button>
{/* 4행: 0 / 확인 (초록, 3칸) */}
<button
type="button"
className="h-14 rounded-2xl border border-gray-200 bg-gray-50 text-xl font-semibold text-gray-800 active:bg-gray-200"
onClick={() => handleNumberClick("0")}
>
0
</button>
<button
type="button"
className="col-span-3 h-14 rounded-2xl bg-green-500 text-base font-bold text-white active:bg-green-600"
onClick={handleConfirm}
>
</button>
</div>
</div>
</DialogPrimitive.Content>
</DialogPortal>
</Dialog>
{/* 포장 단위 선택 모달 */}
<PackageUnitModal
open={isPackageModalOpen}
onOpenChange={setIsPackageModalOpen}
onSelect={handlePackageUnitSelect}
/>
</>
);
}