- 생산관리: 대시보드, 작업지시, 작업실적, 작업자화면 - 품질관리: 검사관리 (리스트/등록/상세) - 자재관리: 입고관리, 재고현황 - 출고관리: 출하관리 (리스트/등록/상세/수정) - 주문관리: 수주관리, 생산의뢰 - 기존 컴포넌트 개선: CardTransactionInquiry, VendorDetail, QuoteRegistration - IntegratedListTemplateV2 개선 - 공통 컴포넌트 분석 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
|
||
);
|
||
} |