227 lines
7.4 KiB
TypeScript
227 lines
7.4 KiB
TypeScript
|
|
'use client';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 입고처리 다이얼로그
|
|||
|
|
* - 발주 정보 표시
|
|||
|
|
* - 입고LOT*, 공급업체LOT, 입고수량*, 입고위치* 입력
|
|||
|
|
* - 비고 입력
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { useState, useCallback } from 'react';
|
|||
|
|
import { Download } from 'lucide-react';
|
|||
|
|
import { Button } from '@/components/ui/button';
|
|||
|
|
import { Input } from '@/components/ui/input';
|
|||
|
|
import { Label } from '@/components/ui/label';
|
|||
|
|
import { Textarea } from '@/components/ui/textarea';
|
|||
|
|
import {
|
|||
|
|
Dialog,
|
|||
|
|
DialogContent,
|
|||
|
|
DialogHeader,
|
|||
|
|
DialogTitle,
|
|||
|
|
} from '@/components/ui/dialog';
|
|||
|
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|||
|
|
import { generateLotNo } from './mockData';
|
|||
|
|
import type { ReceivingDetail } from './types';
|
|||
|
|
|
|||
|
|
interface Props {
|
|||
|
|
open: boolean;
|
|||
|
|
onOpenChange: (open: boolean) => void;
|
|||
|
|
detail: ReceivingDetail;
|
|||
|
|
onComplete: (lotNo: string) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ReceivingProcessDialog({ open, onOpenChange, detail, onComplete }: Props) {
|
|||
|
|
// 폼 데이터
|
|||
|
|
const [receivingLot, setReceivingLot] = useState(() => generateLotNo());
|
|||
|
|
const [supplierLot, setSupplierLot] = useState('');
|
|||
|
|
const [receivingQty, setReceivingQty] = useState<string>(detail.orderQty.toString());
|
|||
|
|
const [receivingLocation, setReceivingLocation] = useState('');
|
|||
|
|
const [remark, setRemark] = useState('');
|
|||
|
|
|
|||
|
|
// 유효성 검사 에러
|
|||
|
|
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
|||
|
|
|
|||
|
|
// 유효성 검사
|
|||
|
|
const validateForm = useCallback((): boolean => {
|
|||
|
|
const errors: string[] = [];
|
|||
|
|
|
|||
|
|
if (!receivingLot.trim()) {
|
|||
|
|
errors.push('입고LOT는 필수 입력 항목입니다.');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!receivingQty.trim() || isNaN(Number(receivingQty)) || Number(receivingQty) <= 0) {
|
|||
|
|
errors.push('입고수량은 필수 입력 항목입니다. 유효한 숫자를 입력해주세요.');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!receivingLocation.trim()) {
|
|||
|
|
errors.push('입고위치는 필수 입력 항목입니다.');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setValidationErrors(errors);
|
|||
|
|
return errors.length === 0;
|
|||
|
|
}, [receivingLot, receivingQty, receivingLocation]);
|
|||
|
|
|
|||
|
|
// 입고 처리
|
|||
|
|
const handleSubmit = useCallback(() => {
|
|||
|
|
if (!validateForm()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TODO: API 호출
|
|||
|
|
console.log('입고 처리:', {
|
|||
|
|
detailId: detail.id,
|
|||
|
|
receivingLot,
|
|||
|
|
supplierLot,
|
|||
|
|
receivingQty: Number(receivingQty),
|
|||
|
|
receivingLocation,
|
|||
|
|
remark,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
onComplete(receivingLot);
|
|||
|
|
}, [validateForm, detail.id, receivingLot, supplierLot, receivingQty, receivingLocation, remark, onComplete]);
|
|||
|
|
|
|||
|
|
// 취소
|
|||
|
|
const handleCancel = useCallback(() => {
|
|||
|
|
onOpenChange(false);
|
|||
|
|
}, [onOpenChange]);
|
|||
|
|
|
|||
|
|
// 다이얼로그 닫힐 때 상태 초기화
|
|||
|
|
const handleOpenChange = useCallback(
|
|||
|
|
(newOpen: boolean) => {
|
|||
|
|
if (!newOpen) {
|
|||
|
|
setValidationErrors([]);
|
|||
|
|
}
|
|||
|
|
onOpenChange(newOpen);
|
|||
|
|
},
|
|||
|
|
[onOpenChange]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|||
|
|
<DialogContent className="max-w-md">
|
|||
|
|
<DialogHeader>
|
|||
|
|
<DialogTitle>입고 처리</DialogTitle>
|
|||
|
|
</DialogHeader>
|
|||
|
|
|
|||
|
|
<div className="space-y-6 mt-4">
|
|||
|
|
{/* 발주 정보 요약 */}
|
|||
|
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
|||
|
|
<div>
|
|||
|
|
<span className="text-muted-foreground">발주번호:</span>{' '}
|
|||
|
|
<span className="font-medium">{detail.orderNo}</span>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-muted-foreground">공급업체:</span>{' '}
|
|||
|
|
<span className="font-medium">{detail.supplier}</span>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-muted-foreground">품목:</span>{' '}
|
|||
|
|
<span className="font-medium">{detail.itemName}</span>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-muted-foreground">발주수량:</span>{' '}
|
|||
|
|
<span className="font-medium">{detail.orderQty} {detail.orderUnit}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Validation 에러 표시 */}
|
|||
|
|
{validationErrors.length > 0 && (
|
|||
|
|
<Alert className="bg-red-50 border-red-200">
|
|||
|
|
<AlertDescription className="text-red-900">
|
|||
|
|
<div className="flex items-start gap-2">
|
|||
|
|
<span className="text-lg">⚠️</span>
|
|||
|
|
<div className="flex-1">
|
|||
|
|
<strong className="block mb-2">
|
|||
|
|
입력 내용을 확인해주세요 ({validationErrors.length}개 오류)
|
|||
|
|
</strong>
|
|||
|
|
<ul className="space-y-1 text-sm">
|
|||
|
|
{validationErrors.map((error, index) => (
|
|||
|
|
<li key={index} className="flex items-start gap-1">
|
|||
|
|
<span>•</span>
|
|||
|
|
<span>{error}</span>
|
|||
|
|
</li>
|
|||
|
|
))}
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</AlertDescription>
|
|||
|
|
</Alert>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 입력 필드 */}
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-sm">
|
|||
|
|
입고LOT <span className="text-red-500">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
value={receivingLot}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
setReceivingLot(e.target.value);
|
|||
|
|
setValidationErrors([]);
|
|||
|
|
}}
|
|||
|
|
placeholder="예: 251223-41"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-sm text-muted-foreground">공급업체LOT</Label>
|
|||
|
|
<Input
|
|||
|
|
value={supplierLot}
|
|||
|
|
onChange={(e) => setSupplierLot(e.target.value)}
|
|||
|
|
placeholder="예: 2402944"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-sm">
|
|||
|
|
입고수량 <span className="text-red-500">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
type="number"
|
|||
|
|
value={receivingQty}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
setReceivingQty(e.target.value);
|
|||
|
|
setValidationErrors([]);
|
|||
|
|
}}
|
|||
|
|
placeholder="수량 입력"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-sm">
|
|||
|
|
입고위치 <span className="text-red-500">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
value={receivingLocation}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
setReceivingLocation(e.target.value);
|
|||
|
|
setValidationErrors([]);
|
|||
|
|
}}
|
|||
|
|
placeholder="예: A-01"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 비고 */}
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-sm text-muted-foreground">비고</Label>
|
|||
|
|
<Textarea
|
|||
|
|
value={remark}
|
|||
|
|
onChange={(e) => setRemark(e.target.value)}
|
|||
|
|
placeholder="특이사항 입력"
|
|||
|
|
rows={3}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 버튼 영역 */}
|
|||
|
|
<div className="flex justify-end gap-2 mt-6 pt-4 border-t">
|
|||
|
|
<Button variant="outline" onClick={handleCancel}>
|
|||
|
|
취소
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={handleSubmit}>
|
|||
|
|
입고 처리
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</DialogContent>
|
|||
|
|
</Dialog>
|
|||
|
|
);
|
|||
|
|
}
|