Files
sam-react-prod/src/components/quality/InspectionManagement/InspectionCreate.tsx

320 lines
12 KiB
TypeScript
Raw Normal View History

'use client';
/**
*
*/
import { useState, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { ClipboardCheck, ImageIcon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { PageLayout } from '@/components/organisms/PageLayout';
import { inspectionItemsTemplate, judgeMeasurement } from './mockData';
import type { InspectionItem, QualityCheckItem, MeasurementItem } from './types';
export function InspectionCreate() {
const router = useRouter();
// 폼 상태
const [formData, setFormData] = useState({
lotNo: 'WO-251219-05', // 자동 (예시)
itemName: '조인트바', // 자동 (예시)
processName: '조립 공정', // 자동 (예시)
quantity: 50,
inspector: '',
remarks: '',
});
// 검사 항목 상태
const [inspectionItems, setInspectionItems] = useState<InspectionItem[]>(
inspectionItemsTemplate.map(item => ({ ...item }))
);
// validation 에러 상태
const [validationErrors, setValidationErrors] = useState<string[]>([]);
// 폼 입력 핸들러
const handleInputChange = (field: string, value: string | number) => {
setFormData(prev => ({ ...prev, [field]: value }));
// 입력 시 에러 클리어
if (validationErrors.length > 0) {
setValidationErrors([]);
}
};
// 품질 검사 항목 결과 변경 (양호/불량)
const handleQualityResultChange = useCallback((itemId: string, result: '양호' | '불량') => {
setInspectionItems(prev => prev.map(item => {
if (item.id === itemId && item.type === 'quality') {
return {
...item,
result,
judgment: result === '양호' ? '적합' : '부적합',
} as QualityCheckItem;
}
return item;
}));
// 입력 시 에러 클리어
setValidationErrors([]);
}, []);
// 측정 항목 값 변경
const handleMeasurementChange = useCallback((itemId: string, value: string) => {
setInspectionItems(prev => prev.map(item => {
if (item.id === itemId && item.type === 'measurement') {
const measuredValue = parseFloat(value) || 0;
const judgment = judgeMeasurement(item.spec, measuredValue);
return {
...item,
measuredValue,
judgment,
} as MeasurementItem;
}
return item;
}));
// 입력 시 에러 클리어
setValidationErrors([]);
}, []);
// 취소
const handleCancel = () => {
router.push('/quality/inspections');
};
// validation 체크
const validateForm = (): boolean => {
const errors: string[] = [];
// 필수 필드: 작업자
if (!formData.inspector.trim()) {
errors.push('작업자는 필수 입력 항목입니다.');
}
// 검사 항목 validation
inspectionItems.forEach((item, index) => {
if (item.type === 'quality') {
const qualityItem = item as QualityCheckItem;
if (!qualityItem.result) {
errors.push(`${index + 1}. ${item.name}: 결과를 선택해주세요.`);
}
} else if (item.type === 'measurement') {
const measurementItem = item as MeasurementItem;
if (measurementItem.measuredValue === undefined || measurementItem.measuredValue === null) {
errors.push(`${index + 1}. ${item.name}: 측정값을 입력해주세요.`);
}
}
});
setValidationErrors(errors);
return errors.length === 0;
};
// 검사완료
const handleSubmit = () => {
// validation 체크
if (!validateForm()) {
return;
}
// TODO: API 호출
console.log('Submit:', { ...formData, items: inspectionItems });
router.push('/quality/inspections');
};
return (
<PageLayout>
<div className="space-y-6">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<ClipboardCheck className="w-6 h-6" />
<h1 className="text-xl font-semibold"> </h1>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={handleCancel}>
</Button>
<Button onClick={handleSubmit}>
</Button>
</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>
)}
{/* 검사 개요 */}
<Card>
<CardHeader>
<CardTitle className="text-base"> </CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="space-y-2">
<Label className="text-muted-foreground">LOT NO ()</Label>
<Input
value={formData.lotNo}
disabled
className="bg-muted"
/>
</div>
<div className="space-y-2">
<Label className="text-muted-foreground"> ()</Label>
<Input
value={formData.itemName}
disabled
className="bg-muted"
/>
</div>
<div className="space-y-2">
<Label className="text-muted-foreground"> ()</Label>
<Input
value={formData.processName}
disabled
className="bg-muted"
/>
</div>
<div className="space-y-2">
<Label></Label>
<Input
type="number"
value={formData.quantity}
onChange={(e) => handleInputChange('quantity', parseInt(e.target.value) || 0)}
placeholder="수량 입력"
/>
</div>
<div className="space-y-2">
<Label> *</Label>
<Input
value={formData.inspector}
onChange={(e) => handleInputChange('inspector', e.target.value)}
placeholder="작업자 입력"
/>
</div>
<div className="space-y-2 md:col-span-3">
<Label></Label>
<Input
value={formData.remarks}
onChange={(e) => handleInputChange('remarks', e.target.value)}
placeholder="특이사항 입력"
/>
</div>
</div>
</CardContent>
</Card>
{/* 검사 기준 및 도해 */}
<Card>
<CardHeader>
<CardTitle className="text-base"> </CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center h-48 bg-muted rounded-lg border-2 border-dashed">
<div className="text-center text-muted-foreground">
<ImageIcon className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p>릿 </p>
</div>
</div>
</CardContent>
</Card>
{/* 검사 데이터 입력 */}
<Card>
<CardHeader>
<CardTitle className="text-base"> </CardTitle>
<p className="text-sm text-muted-foreground">
* .
</p>
</CardHeader>
<CardContent className="space-y-6">
{inspectionItems.map((item, index) => (
<div key={item.id} className="p-4 border rounded-lg">
<div className="flex items-center justify-between mb-4">
<h4 className="font-medium">
{index + 1}. {item.name}
{item.type === 'measurement' && ` (${(item as MeasurementItem).unit})`}
</h4>
<span className={`text-sm font-medium ${
item.judgment === '적합' ? 'text-green-600' :
item.judgment === '부적합' ? 'text-red-600' :
'text-muted-foreground'
}`}>
: {item.judgment || '-'}
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-muted-foreground">(Spec)</Label>
<Input
value={item.spec}
disabled
className="bg-muted"
/>
</div>
{item.type === 'quality' ? (
<div className="space-y-2">
<Label> *</Label>
<RadioGroup
value={(item as QualityCheckItem).result || ''}
onValueChange={(value) => handleQualityResultChange(item.id, value as '양호' | '불량')}
className="flex items-center gap-4 h-10"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="양호" id={`${item.id}-pass`} />
<Label htmlFor={`${item.id}-pass`} className="cursor-pointer"></Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="불량" id={`${item.id}-fail`} />
<Label htmlFor={`${item.id}-fail`} className="cursor-pointer"></Label>
</div>
</RadioGroup>
</div>
) : (
<div className="space-y-2">
<Label> ({(item as MeasurementItem).unit}) *</Label>
<Input
type="number"
step="0.1"
value={(item as MeasurementItem).measuredValue || ''}
onChange={(e) => handleMeasurementChange(item.id, e.target.value)}
placeholder={`측정값 입력 (${(item as MeasurementItem).unit})`}
/>
</div>
)}
</div>
</div>
))}
</CardContent>
</Card>
</div>
</PageLayout>
);
}