312 lines
9.2 KiB
TypeScript
312 lines
9.2 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 품목 추가 팝업
|
|||
|
|
*
|
|||
|
|
* 수주 등록 시 품목을 수동으로 추가하는 다이얼로그
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { useState } from "react";
|
|||
|
|
import {
|
|||
|
|
Dialog,
|
|||
|
|
DialogContent,
|
|||
|
|
DialogHeader,
|
|||
|
|
DialogTitle,
|
|||
|
|
DialogFooter,
|
|||
|
|
} from "@/components/ui/dialog";
|
|||
|
|
import { Input } from "@/components/ui/input";
|
|||
|
|
import { Label } from "@/components/ui/label";
|
|||
|
|
import { Button } from "@/components/ui/button";
|
|||
|
|
import {
|
|||
|
|
Select,
|
|||
|
|
SelectContent,
|
|||
|
|
SelectItem,
|
|||
|
|
SelectTrigger,
|
|||
|
|
SelectValue,
|
|||
|
|
} from "@/components/ui/select";
|
|||
|
|
import { Package } from "lucide-react";
|
|||
|
|
|
|||
|
|
// 품목 타입
|
|||
|
|
export interface OrderItem {
|
|||
|
|
id: string;
|
|||
|
|
itemCode: string; // 품목코드
|
|||
|
|
itemName: string; // 품명
|
|||
|
|
type: string; // 층
|
|||
|
|
symbol: string; // 부호
|
|||
|
|
spec: string; // 규격
|
|||
|
|
width: number; // 가로 (mm)
|
|||
|
|
height: number; // 세로 (mm)
|
|||
|
|
quantity: number; // 수량
|
|||
|
|
unit: string; // 단위
|
|||
|
|
unitPrice: number; // 단가
|
|||
|
|
amount: number; // 금액
|
|||
|
|
guideRailType?: string; // 가이드레일 타입
|
|||
|
|
finish?: string; // 마감
|
|||
|
|
floor?: string; // 층
|
|||
|
|
isFromQuotation?: boolean; // 견적에서 가져온 품목 여부
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface ItemAddDialogProps {
|
|||
|
|
open: boolean;
|
|||
|
|
onOpenChange: (open: boolean) => void;
|
|||
|
|
onAdd: (item: OrderItem) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 가이드레일 타입 옵션
|
|||
|
|
const GUIDE_RAIL_TYPES = [
|
|||
|
|
{ value: "back-120-70", label: "백면형 (120-70)" },
|
|||
|
|
{ value: "back-150-70", label: "백면형 (150-70)" },
|
|||
|
|
{ value: "side-120-70", label: "측면형 (120-70)" },
|
|||
|
|
{ value: "side-150-70", label: "측면형 (150-70)" },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 마감 옵션
|
|||
|
|
const FINISH_OPTIONS = [
|
|||
|
|
{ value: "sus", label: "SUS마감" },
|
|||
|
|
{ value: "powder", label: "분체도장" },
|
|||
|
|
{ value: "paint", label: "일반도장" },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 초기 폼 데이터
|
|||
|
|
const INITIAL_FORM = {
|
|||
|
|
floor: "",
|
|||
|
|
symbol: "",
|
|||
|
|
itemName: "",
|
|||
|
|
width: "",
|
|||
|
|
height: "",
|
|||
|
|
guideRailType: "",
|
|||
|
|
finish: "",
|
|||
|
|
unitPrice: "",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export function ItemAddDialog({
|
|||
|
|
open,
|
|||
|
|
onOpenChange,
|
|||
|
|
onAdd,
|
|||
|
|
}: ItemAddDialogProps) {
|
|||
|
|
const [form, setForm] = useState(INITIAL_FORM);
|
|||
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|||
|
|
|
|||
|
|
// 폼 리셋
|
|||
|
|
const resetForm = () => {
|
|||
|
|
setForm(INITIAL_FORM);
|
|||
|
|
setErrors({});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 다이얼로그 닫기 시 리셋
|
|||
|
|
const handleOpenChange = (open: boolean) => {
|
|||
|
|
if (!open) {
|
|||
|
|
resetForm();
|
|||
|
|
}
|
|||
|
|
onOpenChange(open);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 유효성 검사
|
|||
|
|
const validate = (): boolean => {
|
|||
|
|
const newErrors: Record<string, string> = {};
|
|||
|
|
|
|||
|
|
if (!form.floor.trim()) {
|
|||
|
|
newErrors.floor = "층을 입력해주세요";
|
|||
|
|
}
|
|||
|
|
if (!form.symbol.trim()) {
|
|||
|
|
newErrors.symbol = "도면부호를 입력해주세요";
|
|||
|
|
}
|
|||
|
|
if (!form.width || Number(form.width) <= 0) {
|
|||
|
|
newErrors.width = "가로 치수를 입력해주세요";
|
|||
|
|
}
|
|||
|
|
if (!form.height || Number(form.height) <= 0) {
|
|||
|
|
newErrors.height = "세로 치수를 입력해주세요";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setErrors(newErrors);
|
|||
|
|
return Object.keys(newErrors).length === 0;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 추가 핸들러
|
|||
|
|
const handleAdd = () => {
|
|||
|
|
if (!validate()) return;
|
|||
|
|
|
|||
|
|
const width = Number(form.width);
|
|||
|
|
const height = Number(form.height);
|
|||
|
|
const unitPrice = Number(form.unitPrice) || 0;
|
|||
|
|
|
|||
|
|
const newItem: OrderItem = {
|
|||
|
|
id: `item-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|||
|
|
itemCode: `PRD-${Date.now().toString().slice(-4)}`,
|
|||
|
|
itemName: form.itemName || "국민방화스크린세터",
|
|||
|
|
type: "B1",
|
|||
|
|
symbol: form.symbol,
|
|||
|
|
spec: `${width}×${height}`,
|
|||
|
|
width,
|
|||
|
|
height,
|
|||
|
|
quantity: 1,
|
|||
|
|
unit: "EA",
|
|||
|
|
unitPrice,
|
|||
|
|
amount: unitPrice,
|
|||
|
|
guideRailType: form.guideRailType,
|
|||
|
|
finish: form.finish,
|
|||
|
|
floor: form.floor,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
onAdd(newItem);
|
|||
|
|
handleOpenChange(false);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|||
|
|
<DialogContent className="max-w-lg">
|
|||
|
|
<DialogHeader>
|
|||
|
|
<DialogTitle className="flex items-center gap-2">
|
|||
|
|
<Package className="h-5 w-5" />
|
|||
|
|
품목 추가
|
|||
|
|
</DialogTitle>
|
|||
|
|
</DialogHeader>
|
|||
|
|
|
|||
|
|
<div className="space-y-4 py-4">
|
|||
|
|
{/* 층 / 도면부호 */}
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="floor">
|
|||
|
|
층 <span className="text-red-500">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
id="floor"
|
|||
|
|
placeholder="예: 4층"
|
|||
|
|
value={form.floor}
|
|||
|
|
onChange={(e) => setForm({ ...form, floor: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
{errors.floor && (
|
|||
|
|
<p className="text-xs text-red-500">{errors.floor}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="symbol">
|
|||
|
|
도면부호 <span className="text-red-500">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
id="symbol"
|
|||
|
|
placeholder="예: FSS1"
|
|||
|
|
value={form.symbol}
|
|||
|
|
onChange={(e) => setForm({ ...form, symbol: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
{errors.symbol && (
|
|||
|
|
<p className="text-xs text-red-500">{errors.symbol}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 품목명 */}
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="itemName">품목명</Label>
|
|||
|
|
<Input
|
|||
|
|
id="itemName"
|
|||
|
|
placeholder="예: 국민방화스크린세터"
|
|||
|
|
value={form.itemName}
|
|||
|
|
onChange={(e) => setForm({ ...form, itemName: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 오픈사이즈 (고객 제공 치수) */}
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-muted-foreground text-sm">
|
|||
|
|
오픈사이즈 (고객 제공 치수)
|
|||
|
|
</Label>
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="width">
|
|||
|
|
가로 (mm) <span className="text-red-500">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
id="width"
|
|||
|
|
type="number"
|
|||
|
|
placeholder="예: 7260"
|
|||
|
|
value={form.width}
|
|||
|
|
onChange={(e) => setForm({ ...form, width: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
{errors.width && (
|
|||
|
|
<p className="text-xs text-red-500">{errors.width}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="height">
|
|||
|
|
세로 (mm) <span className="text-red-500">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
id="height"
|
|||
|
|
type="number"
|
|||
|
|
placeholder="예: 2600"
|
|||
|
|
value={form.height}
|
|||
|
|
onChange={(e) => setForm({ ...form, height: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
{errors.height && (
|
|||
|
|
<p className="text-xs text-red-500">{errors.height}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 가이드레일 타입 / 마감 */}
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label>가이드레일 타입</Label>
|
|||
|
|
<Select
|
|||
|
|
value={form.guideRailType}
|
|||
|
|
onValueChange={(value) =>
|
|||
|
|
setForm({ ...form, guideRailType: value })
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
<SelectTrigger>
|
|||
|
|
<SelectValue placeholder="선택" />
|
|||
|
|
</SelectTrigger>
|
|||
|
|
<SelectContent>
|
|||
|
|
{GUIDE_RAIL_TYPES.map((type) => (
|
|||
|
|
<SelectItem key={type.value} value={type.value}>
|
|||
|
|
{type.label}
|
|||
|
|
</SelectItem>
|
|||
|
|
))}
|
|||
|
|
</SelectContent>
|
|||
|
|
</Select>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label>마감</Label>
|
|||
|
|
<Select
|
|||
|
|
value={form.finish}
|
|||
|
|
onValueChange={(value) => setForm({ ...form, finish: value })}
|
|||
|
|
>
|
|||
|
|
<SelectTrigger>
|
|||
|
|
<SelectValue placeholder="선택" />
|
|||
|
|
</SelectTrigger>
|
|||
|
|
<SelectContent>
|
|||
|
|
{FINISH_OPTIONS.map((opt) => (
|
|||
|
|
<SelectItem key={opt.value} value={opt.value}>
|
|||
|
|
{opt.label}
|
|||
|
|
</SelectItem>
|
|||
|
|
))}
|
|||
|
|
</SelectContent>
|
|||
|
|
</Select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 단가 */}
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="unitPrice">단가 (원)</Label>
|
|||
|
|
<Input
|
|||
|
|
id="unitPrice"
|
|||
|
|
type="number"
|
|||
|
|
placeholder="예: 8000000"
|
|||
|
|
value={form.unitPrice}
|
|||
|
|
onChange={(e) => setForm({ ...form, unitPrice: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<DialogFooter>
|
|||
|
|
<Button variant="outline" onClick={() => handleOpenChange(false)}>
|
|||
|
|
취소
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={handleAdd}>추가</Button>
|
|||
|
|
</DialogFooter>
|
|||
|
|
</DialogContent>
|
|||
|
|
</Dialog>
|
|||
|
|
);
|
|||
|
|
}
|