Files
sam-react-prod/src/components/material/ReceivingManagement/ReceivingProcessDialog.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

238 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
/**
* 입고처리 다이얼로그
* - 발주 정보 표시
* - 입고LOT*, 공급업체LOT, 입고수량* 입력 (필수)
* - 입고위치, 비고 입력 (선택)
*/
import { useState, useCallback } from 'react';
import { Loader2 } 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 { QuantityInput } from '@/components/ui/quantity-input';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Alert, AlertDescription } from '@/components/ui/alert';
import type { ReceivingDetail, ReceivingProcessFormData } from './types';
// LOT 번호 생성 함수 (YYMMDD-NN 형식)
function generateLotNo(): string {
const now = new Date();
const yy = String(now.getFullYear()).slice(-2);
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const random = String(Math.floor(Math.random() * 100)).padStart(2, '0');
return `${yy}${mm}${dd}-${random}`;
}
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
detail: ReceivingDetail;
onComplete: (formData: ReceivingProcessFormData) => 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 ?? 0).toString());
const [receivingLocation, setReceivingLocation] = useState('');
const [remark, setRemark] = useState('');
// 유효성 검사 에러
const [validationErrors, setValidationErrors] = useState<string[]>([]);
const [isSubmitting, setIsSubmitting] = useState(false);
// 유효성 검사
const validateForm = useCallback((): boolean => {
const errors: string[] = [];
if (!receivingLot.trim()) {
errors.push('입고LOT는 필수 입력 항목입니다.');
}
if (!receivingQty.trim() || isNaN(Number(receivingQty)) || Number(receivingQty) <= 0) {
errors.push('입고수량은 필수 입력 항목입니다. 유효한 숫자를 입력해주세요.');
}
// 입고위치는 선택 항목 (필수 검사 제거)
setValidationErrors(errors);
return errors.length === 0;
}, [receivingLot, receivingQty]);
// 입고 처리
const handleSubmit = useCallback(async () => {
if (!validateForm()) {
return;
}
setIsSubmitting(true);
const formData: ReceivingProcessFormData = {
receivingQty: Number(receivingQty),
receivingLot,
supplierLot: supplierLot || undefined,
receivingLocation: receivingLocation || undefined,
remark: remark || undefined,
};
await onComplete(formData);
setIsSubmitting(false);
}, [validateForm, 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>
<QuantityInput
value={parseFloat(receivingQty) || 0}
onChange={(value) => {
setReceivingQty(String(value ?? 0));
setValidationErrors([]);
}}
placeholder="수량 입력"
/>
</div>
<div className="space-y-2">
<Label className="text-sm text-muted-foreground"></Label>
<Input
value={receivingLocation}
onChange={(e) => setReceivingLocation(e.target.value)}
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} disabled={isSubmitting}>
</Button>
<Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting ? (
<>
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" />
...
</>
) : (
'입고 처리'
)}
</Button>
</div>
</DialogContent>
</Dialog>
);
}