refactor: WorkerScreen 컴포넌트 기획 디자인 적용

- WorkCard: 헤더 박스(품목명+수량), 뱃지 영역, 담당자 정보, 버튼 레이아웃 개선
- ProcessDetailSection: 자재 투입 섹션, 공정 단계 뱃지, 검사 요청 AlertDialog 추가
- MaterialInputModal: FIFO 순위 설명, 테이블 형태 자재 목록, 중복 닫기 버튼 제거

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-23 22:23:40 +09:00
parent f0e8e51d06
commit e0b2ab63e7
3 changed files with 531 additions and 378 deletions

View File

@@ -3,18 +3,16 @@
/**
* 자재투입 모달
*
* - FIFO 순위 표시
* - 자재 테이블 (BOM 기준)
* - 투입 등록 기능
* 기획 화면에 맞춘 레이아웃:
* - FIFO 순위 설명 (1 최우선, 2 차선, 3+ 대기)
* - ① 자재 선택 (BOM 기준) 테이블
* - 취소 / 투입 등록 버튼 (전체 너비)
*/
import { useState } from 'react';
import { Package } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
@@ -64,13 +62,9 @@ interface MaterialInputModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
order: WorkOrder | null;
/** 전량완료 흐름에서 사용 - 투입 등록/취소 후 완료 처리 */
onComplete?: () => void;
/** 전량완료 흐름 여부 (취소 시에도 완료 처리) */
isCompletionFlow?: boolean;
/** 자재 투입 저장 콜백 */
onSaveMaterials?: (orderId: string, materials: MaterialInput[]) => void;
/** 이미 투입된 자재 목록 */
savedMaterials?: MaterialInput[];
}
@@ -81,14 +75,10 @@ export function MaterialInputModal({
onComplete,
isCompletionFlow = false,
onSaveMaterials,
savedMaterials = [],
}: MaterialInputModalProps) {
const [selectedMaterials, setSelectedMaterials] = useState<Set<string>>(new Set());
const [materials] = useState<MaterialInput[]>(MOCK_MATERIALS);
// 이미 투입된 자재가 있으면 선택 상태로 초기화
const hasSavedMaterials = savedMaterials.length > 0;
const handleToggleMaterial = (materialId: string) => {
setSelectedMaterials((prev) => {
const next = new Set(prev);
@@ -101,144 +91,153 @@ export function MaterialInputModal({
});
};
// 자재 투입 등록
// 투입 등록
const handleSubmit = () => {
if (!order) return;
// 선택된 자재 정보 추출
const selectedMaterialList = materials.filter((m) => selectedMaterials.has(m.id));
console.log('[자재투입] 저장:', order.id, selectedMaterialList);
// 자재 저장 콜백
if (onSaveMaterials) {
onSaveMaterials(order.id, selectedMaterialList);
}
setSelectedMaterials(new Set());
onOpenChange(false);
resetAndClose();
// 전량완료 흐름이면 완료 처리
if (isCompletionFlow && onComplete) {
onComplete();
}
};
// 건너뛰기 (자재 없이 완료) - 전량완료 흐름에서만 사용
const handleSkip = () => {
setSelectedMaterials(new Set());
onOpenChange(false);
// 전량완료 흐름이면 완료 처리
if (onComplete) {
onComplete();
}
};
// 취소 (모달만 닫기)
// 취소
const handleCancel = () => {
setSelectedMaterials(new Set());
onOpenChange(false);
resetAndClose();
};
const getFifoRankBadge = (rank: number) => {
const colors = {
1: 'bg-red-100 text-red-800',
2: 'bg-orange-100 text-orange-800',
3: 'bg-gray-100 text-gray-800',
};
const labels = {
1: '최우선',
2: '차선',
3: '대기',
};
return (
<Badge className={colors[rank as 1 | 2 | 3] || colors[3]}>
{rank} ({labels[rank as 1 | 2 | 3] || labels[3]})
</Badge>
);
const resetAndClose = () => {
setSelectedMaterials(new Set());
onOpenChange(false);
};
if (!order) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Package className="h-5 w-5" />
</DialogTitle>
<DialogDescription>
{order.orderNo} .
</DialogDescription>
<DialogContent className="max-w-2xl p-0 gap-0">
{/* 헤더 */}
<DialogHeader className="p-6 pb-4">
<DialogTitle className="text-xl font-semibold"> </DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* FIFO 순위 안내 */}
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<span>FIFO :</span>
<span className="flex items-center gap-1">
<Badge className="bg-red-100 text-red-800">1</Badge>
</span>
<span className="flex items-center gap-1">
<Badge className="bg-orange-100 text-orange-800">2</Badge>
</span>
<span className="flex items-center gap-1">
<Badge className="bg-gray-100 text-gray-800">3+</Badge>
</span>
<div className="px-6 pb-6 space-y-6">
{/* FIFO 순위 설명 */}
<div className="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
<span className="text-sm font-medium text-gray-700">FIFO :</span>
<div className="flex items-center gap-4">
<span className="flex items-center gap-1.5">
<Badge className="bg-gray-900 hover:bg-gray-900 text-white rounded-full w-6 h-6 flex items-center justify-center p-0 text-xs">
1
</Badge>
<span className="text-sm text-gray-600"></span>
</span>
<span className="flex items-center gap-1.5">
<Badge className="bg-gray-900 hover:bg-gray-900 text-white rounded-full w-6 h-6 flex items-center justify-center p-0 text-xs">
2
</Badge>
<span className="text-sm text-gray-600"></span>
</span>
<span className="flex items-center gap-1.5">
<Badge className="bg-gray-900 hover:bg-gray-900 text-white rounded-full w-6 h-6 flex items-center justify-center p-0 text-xs">
3+
</Badge>
<span className="text-sm text-gray-600"></span>
</span>
</div>
</div>
{/* 자재 테이블 */}
{materials.length === 0 ? (
<div className="py-8 text-center text-muted-foreground border rounded-lg">
.
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12"></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead>FIFO</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{materials.map((material) => (
<TableRow key={material.id}>
<TableCell>
<Checkbox
checked={selectedMaterials.has(material.id)}
onCheckedChange={() => handleToggleMaterial(material.id)}
/>
</TableCell>
<TableCell className="font-medium">{material.materialCode}</TableCell>
<TableCell>{material.materialName}</TableCell>
<TableCell>{material.unit}</TableCell>
<TableCell className="text-right">{material.currentStock.toLocaleString()}</TableCell>
<TableCell>{getFifoRankBadge(material.fifoRank)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</div>
{/* 자재 선택 섹션 */}
<div>
<h3 className="text-sm font-medium text-gray-900 mb-3">
(BOM )
</h3>
<DialogFooter className="flex-col sm:flex-row gap-2">
<Button variant="outline" onClick={handleCancel}>
</Button>
{isCompletionFlow && (
<Button variant="secondary" onClick={handleSkip}>
{materials.length === 0 ? (
<div className="border rounded-lg">
<Table>
<TableHeader>
<TableRow className="bg-gray-50">
<TableHead className="text-center"></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-center"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell colSpan={5} className="text-center py-12 text-gray-500">
.
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
) : (
<div className="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-gray-50">
<TableHead className="text-center font-medium"></TableHead>
<TableHead className="text-center font-medium"></TableHead>
<TableHead className="text-center font-medium"></TableHead>
<TableHead className="text-center font-medium"></TableHead>
<TableHead className="text-center font-medium"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{materials.map((material) => (
<TableRow key={material.id}>
<TableCell className="text-center font-medium">
{material.materialCode}
</TableCell>
<TableCell className="text-center">{material.materialName}</TableCell>
<TableCell className="text-center">{material.unit}</TableCell>
<TableCell className="text-center">
{material.currentStock.toLocaleString()}
</TableCell>
<TableCell className="text-center">
<Checkbox
checked={selectedMaterials.has(material.id)}
onCheckedChange={() => handleToggleMaterial(material.id)}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</div>
{/* 버튼 영역 */}
<div className="flex gap-3">
<Button
variant="outline"
onClick={handleCancel}
className="flex-1 py-6 text-base font-medium"
>
</Button>
)}
<Button onClick={handleSubmit} disabled={selectedMaterials.size === 0}>
</Button>
</DialogFooter>
<Button
onClick={handleSubmit}
disabled={selectedMaterials.size === 0}
className="flex-1 py-6 text-base font-medium bg-gray-400 hover:bg-gray-500 disabled:bg-gray-300"
>
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}
}