Files
sam-react-prod/src/components/orders/ItemAddDialog.tsx
유병철 a1f4c82cec fix: 프로젝트 전체 TypeScript 타입에러 408개 수정 (tsc --noEmit 0 errors)
- 공통 템플릿 타입 수정 (IntegratedDetailTemplate, UniversalListPage)
- 페이지(app/[locale]) 타입 호환성 수정 (80개)
- 재고/자재 모듈 타입 수정 (StockStatus, ReceivingManagement)
- 생산 모듈 타입 수정 (WorkOrders, WorkerScreen, WorkResults)
- 주문/출고 모듈 타입 수정 (ShipmentManagement, Orders)
- 견적/단가 모듈 타입 수정 (Quotes, Pricing)
- 건설 모듈 타입 수정 (49개, 17개 하위 모듈)
- HR 모듈 타입 수정 (CardManagement, VacationManagement 등)
- 설정 모듈 타입 수정 (PermissionManagement, AccountManagement 등)
- 게시판 모듈 타입 수정 (BoardManagement, BoardList 등)
- 회계 모듈 타입 수정 (VendorManagement, BadDebtCollection 등)
- 기타 모듈 타입 수정 (CEODashboard, clients, vehicle 등)
- 유틸/훅/API 타입 수정 (hooks, contexts, lib)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 10:07:58 +09:00

317 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 { NumberInput } from "@/components/ui/number-input";
import { CurrencyInput } from "@/components/ui/currency-input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Package } from "lucide-react";
// 품목 타입 - actions.ts의 OrderItem과 호환
export interface OrderItem {
id: string;
itemId?: number;
itemCode?: string; // 품목코드
itemName: string; // 품명
specification?: string;
type?: string; // 층
symbol?: string; // 부호
spec?: string; // 규격
width?: number; // 가로 (mm)
height?: number; // 세로 (mm)
quantity: number; // 수량
unit?: string; // 단위
unitPrice: number; // 단가
supplyAmount?: number;
taxAmount?: number;
totalAmount?: number;
amount?: number; // 금액
sortOrder?: 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>
<NumberInput
id="width"
placeholder="예: 7260"
value={parseFloat(form.width) || 0}
onChange={(value) => setForm({ ...form, width: String(value ?? 0) })}
/>
{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>
<NumberInput
id="height"
placeholder="예: 2600"
value={parseFloat(form.height) || 0}
onChange={(value) => setForm({ ...form, height: String(value ?? 0) })}
/>
{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>
<CurrencyInput
id="unitPrice"
placeholder="예: 8000000"
value={parseFloat(form.unitPrice) || 0}
onChange={(value) => setForm({ ...form, unitPrice: String(value ?? 0) })}
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => handleOpenChange(false)}>
</Button>
<Button onClick={handleAdd}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}