Files
sam-react-prod/src/components/business/MasterData.tsx
byeongcheolryu a68a25b737 [feat]: 인증 및 UI/UX 개선 작업
주요 변경사항:
- 로그인/회원가입 페이지 인증 리다이렉트 로직 추가
- 로그인 상태에서 auth 페이지 접근 시 대시보드로 자동 리다이렉트
- router.replace() 사용으로 브라우저 히스토리에서 auth 페이지 제거
- 사이드바 메뉴 활성화 동기화 개선 (URL 직접 입력 및 뒤로가기 대응)
- usePathname 기반 자동 메뉴 활성화 로직 추가
- ESLint 설정 업데이트 (전역 변수 추가, business 폴더 제외)
- TypeScript 빌드 설정 조정 (ignoreBuildErrors 추가)
- 다국어 지원 및 테마 선택 기능 통합
- 대시보드 레이아웃 및 컴포넌트 구조 개선
- UI 컴포넌트 라이브러리 확장 (dialog, sheet, progress 등)

기술적 개선:
- HttpOnly 쿠키 기반 인증 시스템 유지
- 로딩 상태 UI 추가 (인증 체크 중)
- 경로 정규화 로직 (locale 제거)
- 재귀적 메뉴 탐색 및 자동 확장

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 18:55:16 +09:00

1559 lines
71 KiB
TypeScript

import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from "@/components/ui/dialog";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "sonner";
import { Search, Plus, Download, Filter, Eye, Edit, Trash2, Package, Zap, Users, Building, FileText } from "lucide-react";
interface Product {
id: string;
code: string;
name: string;
type: string;
unit: string;
standardTime: number;
materialCost: number;
laborCost: number;
description: string;
status: string;
}
interface BOM {
id: string;
productCode: string;
productName: string;
materialCode: string;
materialName: string;
quantity: number;
unit: string;
notes: string;
}
interface Process {
id: string;
code: string;
name: string;
workCenter: string;
standardTime: number;
setupTime: number;
description: string;
nextProcess?: string;
}
interface Customer {
id: string;
code: string;
name: string;
type: "고객" | "공급업체" | "양방향";
contact: string;
phone: string;
address: string;
status: string;
}
export function MasterData() {
const [products, setProducts] = useState<Product[]>([
{ id: "P001", code: "PRD-001", name: "스마트폰 케이스", type: "완제품", unit: "개", standardTime: 30, materialCost: 5000, laborCost: 2000, description: "iPhone 호환 케이스", status: "사용" },
{ id: "P002", code: "PRD-002", name: "태블릿 스탠드", type: "완제품", unit: "개", standardTime: 45, materialCost: 8000, laborCost: 3000, description: "각도 조절 가능", status: "사용" },
{ id: "P003", code: "PRD-003", name: "무선 충전기", type: "완제품", unit: "개", standardTime: 60, materialCost: 15000, laborCost: 5000, description: "고속 무선 충전", status: "사용" },
]);
const [bomData, setBomData] = useState<BOM[]>([
{ id: "BOM001", productCode: "PRD-001", productName: "스마트폰 케이스", materialCode: "MAT-001", materialName: "플라스틱 원료 A", quantity: 0.05, unit: "kg", notes: "주요 소재" },
{ id: "BOM002", productCode: "PRD-001", productName: "스마트폰 케이스", materialCode: "MAT-003", materialName: "실리콘 패드", quantity: 2, unit: "개", notes: "충격 흡수용" },
{ id: "BOM003", productCode: "PRD-002", productName: "태블릿 스탠드", materialCode: "MAT-002", materialName: "알루미늄 판재", quantity: 1, unit: "장", notes: "메인 프레임" },
{ id: "BOM004", productCode: "PRD-003", productName: "무선 충전기", materialCode: "MAT-005", materialName: "전자부품 모듈", quantity: 1, unit: "개", notes: "핵심 부품" },
]);
const [processes, setProcesses] = useState<Process[]>([
{ id: "PROC001", code: "PROC-001", name: "사출성형", workCenter: "성형라인 1", standardTime: 15, setupTime: 30, description: "플라스틱 사출성형 공정" },
{ id: "PROC002", code: "PROC-002", name: "조립", workCenter: "조립라인 1", standardTime: 10, setupTime: 15, description: "부품 조립 공정", nextProcess: "PROC-003" },
{ id: "PROC003", code: "PROC-003", name: "검사", workCenter: "품질검사실", standardTime: 5, setupTime: 5, description: "품질 검사 공정" },
{ id: "PROC004", code: "PROC-004", name: "포장", workCenter: "포장라인", standardTime: 3, setupTime: 5, description: "최종 포장 공정" },
]);
const [customers, setCustomers] = useState<Customer[]>([
{ id: "C001", code: "CUST-001", name: "삼성전자", type: "고객", contact: "김구매", phone: "02-1234-5678", address: "서울시 강남구", status: "활성" },
{ id: "C002", code: "SUPP-001", name: "ABC 원료", type: "공급업체", contact: "이공급", phone: "031-234-5678", address: "경기도 성남시", status: "활성" },
{ id: "C003", code: "CUST-002", name: "LG전자", type: "고객", contact: "박주문", phone: "02-3456-7890", address: "서울시 영등포구", status: "활성" },
{ id: "C004", code: "BOTH-001", name: "현대모비스", type: "양방향", contact: "최거래", phone: "031-456-7890", address: "경기도 용인시", status: "활성" },
]);
const [activeTab, setActiveTab] = useState("products");
const [isModalOpen, setIsModalOpen] = useState(false);
const [isViewModalOpen, setIsViewModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState<any>(null);
const [formData, setFormData] = useState<any>({});
const handleCreate = () => {
switch (activeTab) {
case "products":
const newProduct: Product = {
id: `P${String(products.length + 1).padStart(3, '0')}`,
code: formData.code || "",
name: formData.name || "",
type: formData.type || "",
unit: formData.unit || "",
standardTime: formData.standardTime || 0,
materialCost: formData.materialCost || 0,
laborCost: formData.laborCost || 0,
description: formData.description || "",
status: "사용",
};
setProducts([...products, newProduct]);
break;
case "bom":
const newBOM: BOM = {
id: `BOM${String(bomData.length + 1).padStart(3, '0')}`,
productCode: formData.productCode || "",
productName: products.find(p => p.code === formData.productCode)?.name || "",
materialCode: formData.materialCode || "",
materialName: formData.materialName || "",
quantity: formData.quantity || 0,
unit: formData.unit || "",
notes: formData.notes || "",
};
setBomData([...bomData, newBOM]);
break;
case "processes":
const newProcess: Process = {
id: `PROC${String(processes.length + 1).padStart(3, '0')}`,
code: formData.code || "",
name: formData.name || "",
workCenter: formData.workCenter || "",
standardTime: formData.standardTime || 0,
setupTime: formData.setupTime || 0,
description: formData.description || "",
nextProcess: formData.nextProcess || "",
};
setProcesses([...processes, newProcess]);
break;
case "customers":
const newCustomer: Customer = {
id: `C${String(customers.length + 1).padStart(3, '0')}`,
code: formData.code || "",
name: formData.name || "",
type: formData.type || "고객",
contact: formData.contact || "",
phone: formData.phone || "",
address: formData.address || "",
status: "활성",
};
setCustomers([...customers, newCustomer]);
break;
}
setFormData({});
setIsModalOpen(false);
toast.success("등록이 완료되었습니다.");
};
const handleUpdate = () => {
if (!selectedItem) return;
switch (activeTab) {
case "products":
const updatedProducts = products.map(item =>
item.id === selectedItem.id ? { ...item, ...formData } : item
);
setProducts(updatedProducts);
break;
case "bom":
const updatedBOM = bomData.map(item =>
item.id === selectedItem.id ? { ...item, ...formData } : item
);
setBomData(updatedBOM);
break;
case "processes":
const updatedProcesses = processes.map(item =>
item.id === selectedItem.id ? { ...item, ...formData } : item
);
setProcesses(updatedProcesses);
break;
case "customers":
const updatedCustomers = customers.map(item =>
item.id === selectedItem.id ? { ...item, ...formData } : item
);
setCustomers(updatedCustomers);
break;
}
setFormData({});
setSelectedItem(null);
setIsEditModalOpen(false);
toast.success("수정이 완료되었습니다.");
};
const handleDelete = (id: string) => {
switch (activeTab) {
case "products":
setProducts(products.filter(item => item.id !== id));
break;
case "bom":
setBomData(bomData.filter(item => item.id !== id));
break;
case "processes":
setProcesses(processes.filter(item => item.id !== id));
break;
case "customers":
setCustomers(customers.filter(item => item.id !== id));
break;
}
toast.success("삭제가 완료되었습니다.");
};
const getStatusColor = (status: string) => {
switch (status) {
case "사용":
case "활성": return "bg-green-500";
case "중단":
case "비활성": return "bg-red-500";
default: return "bg-gray-500";
}
};
const getTypeColor = (type: string) => {
switch (type) {
case "고객": return "text-blue-600";
case "공급업체": return "text-green-600";
case "양방향": return "text-purple-600";
default: return "text-gray-600";
}
};
return (
<div className="p-3 sm:p-4 lg:p-6 space-y-4 lg:space-y-6">
{/* 모바일 우선 헤더 */}
<div className="flex flex-col space-y-3 sm:flex-row sm:items-center sm:justify-between sm:space-y-0">
<div>
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold text-gray-900"> </h1>
<p className="text-sm sm:text-base text-gray-600 mt-1">, BOM, , </p>
</div>
<Button
className="samsung-button w-full sm:w-auto min-h-[48px] touch-manipulation"
onClick={() => setIsModalOpen(true)}
>
<Plus className="h-5 w-5 mr-3" />
</Button>
</div>
{/* 기준정보 대시보드 - 모바일 2열, 태블릿 2열, 데스크톱 4열 */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6 mb-6 sm:mb-8">
<Card className="samsung-card samsung-gradient-card border-0 hover:scale-105 hover:-translate-y-1 transition-all duration-300 group">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
<CardTitle className="text-xs sm:text-sm font-bold text-muted-foreground uppercase tracking-wide"> </CardTitle>
<div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center samsung-shadow group-hover:scale-110 transition-transform duration-300">
<Package className="h-5 w-5 text-white" />
</div>
</CardHeader>
<CardContent>
<div className="text-xl sm:text-2xl font-bold">{products.length}</div>
<p className="text-xs text-muted-foreground">
</p>
</CardContent>
</Card>
<Card className="samsung-card samsung-gradient-card border-0 hover:scale-105 hover:-translate-y-1 transition-all duration-300 group">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
<CardTitle className="text-xs sm:text-sm font-bold text-muted-foreground uppercase tracking-wide">BOM </CardTitle>
<div className="w-10 h-10 bg-gradient-to-br from-green-500 to-emerald-600 rounded-2xl flex items-center justify-center samsung-shadow group-hover:scale-110 transition-transform duration-300">
<FileText className="h-5 w-5 text-white" />
</div>
</CardHeader>
<CardContent>
<div className="text-xl sm:text-2xl font-bold">{bomData.length}</div>
<p className="text-xs text-muted-foreground">
BOM
</p>
</CardContent>
</Card>
<Card className="samsung-card samsung-gradient-card border-0 hover:scale-105 hover:-translate-y-1 transition-all duration-300 group">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
<CardTitle className="text-xs sm:text-sm font-bold text-muted-foreground uppercase tracking-wide"> </CardTitle>
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-violet-600 rounded-2xl flex items-center justify-center samsung-shadow group-hover:scale-110 transition-transform duration-300">
<Zap className="h-5 w-5 text-white" />
</div>
</CardHeader>
<CardContent>
<div className="text-xl sm:text-2xl font-bold">{processes.length}</div>
<p className="text-xs text-muted-foreground">
</p>
</CardContent>
</Card>
<Card className="samsung-card samsung-gradient-card border-0 hover:scale-105 hover:-translate-y-1 transition-all duration-300 group">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
<CardTitle className="text-xs sm:text-sm font-bold text-muted-foreground uppercase tracking-wide"></CardTitle>
<div className="w-10 h-10 bg-gradient-to-br from-orange-500 to-red-500 rounded-2xl flex items-center justify-center samsung-shadow group-hover:scale-110 transition-transform duration-300">
<Building className="h-5 w-5 text-white" />
</div>
</CardHeader>
<CardContent>
<div className="text-xl sm:text-2xl font-bold">{customers.length}</div>
<p className="text-xs text-muted-foreground">
</p>
</CardContent>
</Card>
</div>
{/* 기준정보 현황 - 모바일 1열, 데스크톱 2열 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6 mb-4 sm:mb-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2 text-sm sm:text-base">
<Package className="h-4 w-4 sm:h-5 sm:w-5" />
<span> BOM </span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{products.slice(0, 3).map((product) => {
const bomCount = bomData.filter(bom => bom.productCode === product.code).length;
return (
<div key={product.id} className="flex justify-between items-center p-3 border rounded-lg hover:bg-gray-50 transition-colors">
<div className="min-w-0 flex-1">
<h4 className="font-medium text-sm truncate">{product.name}</h4>
<p className="text-xs text-gray-600">{product.code}</p>
</div>
<div className="text-right ml-2 flex-shrink-0">
<p className="text-sm font-medium">{bomCount} </p>
<p className="text-xs text-gray-600">{product.type}</p>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2 text-sm sm:text-base">
<Building className="h-4 w-4 sm:h-5 sm:w-5" />
<span> </span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{["고객", "공급업체", "양방향"].map((type) => {
const count = customers.filter(c => c.type === type).length;
return (
<div key={type} className="flex justify-between items-center p-3 border rounded-lg hover:bg-gray-50 transition-colors">
<div>
<h4 className="font-medium text-sm">{type}</h4>
<p className="text-xs text-gray-600"> </p>
</div>
<div className="text-right">
<p className="text-sm font-medium">{count}</p>
<p className={`text-xs ${getTypeColor(type)}`}>{type}</p>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
</div>
{/* 탭 메뉴 - 모바일 스크롤 */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
<div className="overflow-x-auto">
<TabsList className="grid w-full grid-cols-4 min-w-[320px] h-auto">
<TabsTrigger
value="products"
className="flex flex-col sm:flex-row items-center space-y-1 sm:space-y-0 sm:space-x-2 p-2 sm:p-3 min-h-[44px] touch-manipulation"
>
<Package className="h-4 w-4" />
<span className="text-xs sm:text-sm"></span>
</TabsTrigger>
<TabsTrigger
value="bom"
className="flex flex-col sm:flex-row items-center space-y-1 sm:space-y-0 sm:space-x-2 p-2 sm:p-3 min-h-[44px] touch-manipulation"
>
<FileText className="h-4 w-4" />
<span className="text-xs sm:text-sm">BOM</span>
</TabsTrigger>
<TabsTrigger
value="processes"
className="flex flex-col sm:flex-row items-center space-y-1 sm:space-y-0 sm:space-x-2 p-2 sm:p-3 min-h-[44px] touch-manipulation"
>
<Zap className="h-4 w-4" />
<span className="text-xs sm:text-sm"></span>
</TabsTrigger>
<TabsTrigger
value="customers"
className="flex flex-col sm:flex-row items-center space-y-1 sm:space-y-0 sm:space-x-2 p-2 sm:p-3 min-h-[44px] touch-manipulation"
>
<Building className="h-4 w-4" />
<span className="text-xs sm:text-sm"></span>
</TabsTrigger>
</TabsList>
</div>
{/* 제품 마스터 탭 */}
<TabsContent value="products" className="space-y-4">
<Card>
<CardHeader>
<div className="flex flex-col space-y-3 sm:flex-row sm:items-center sm:justify-between sm:space-y-0">
<CardTitle className="text-base sm:text-lg"> </CardTitle>
<div className="flex flex-col space-y-2 sm:flex-row sm:items-center sm:space-y-0 sm:space-x-2">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="제품명 검색..."
className="pl-10 w-full sm:w-64 min-h-[44px] touch-manipulation"
/>
</div>
<Button variant="outline" size="sm" className="w-full sm:w-auto min-h-[44px] touch-manipulation">
<Filter className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
{/* 모바일: 카드 레이아웃, 데스크톱: 테이블 */}
<div className="block sm:hidden space-y-3">
{products.map((product) => (
<Card key={product.id} className="p-4 hover:shadow-md transition-shadow">
<div className="flex justify-between items-start mb-3">
<div className="min-w-0 flex-1">
<h3 className="font-medium text-sm truncate">{product.name}</h3>
<p className="text-xs text-gray-600">{product.code}</p>
</div>
<Badge className={`${getStatusColor(product.status)} text-white ml-2 flex-shrink-0`}>
{product.status}
</Badge>
</div>
<div className="grid grid-cols-2 gap-2 text-xs mb-3">
<div>
<span className="text-gray-600">:</span> {product.type}
</div>
<div>
<span className="text-gray-600">:</span> {product.unit}
</div>
<div>
<span className="text-gray-600">:</span> {product.standardTime}
</div>
<div>
<span className="text-gray-600">:</span> {product.materialCost.toLocaleString()}
</div>
</div>
<div className="flex space-x-2">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(product);
setIsViewModalOpen(true);
}}
className="flex-1 min-h-[40px] touch-manipulation"
>
<Eye className="h-3 w-3 mr-1" />
</Button>
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(product);
setFormData(product);
setIsEditModalOpen(true);
}}
className="flex-1 min-h-[40px] touch-manipulation"
>
<Edit className="h-3 w-3 mr-1" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
size="sm"
variant="outline"
className="min-h-[40px] px-3 touch-manipulation"
>
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="mx-4 max-w-lg">
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
{product.name} ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="min-h-[44px] touch-manipulation"></AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(product.id)}
className="min-h-[44px] touch-manipulation"
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</Card>
))}
</div>
{/* 데스크톱 테이블 */}
<div className="hidden sm:block overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="min-w-[100px]"></TableHead>
<TableHead className="min-w-[150px]"></TableHead>
<TableHead className="min-w-[80px]"></TableHead>
<TableHead className="min-w-[60px]"></TableHead>
<TableHead className="min-w-[100px]">()</TableHead>
<TableHead className="min-w-[100px]"></TableHead>
<TableHead className="min-w-[80px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{products.map((product) => (
<TableRow key={product.id}>
<TableCell className="font-medium">{product.code}</TableCell>
<TableCell>{product.name}</TableCell>
<TableCell>{product.type}</TableCell>
<TableCell>{product.unit}</TableCell>
<TableCell>{product.standardTime}</TableCell>
<TableCell>{product.materialCost.toLocaleString()}</TableCell>
<TableCell>
<Badge className={`${getStatusColor(product.status)} text-white`}>
{product.status}
</Badge>
</TableCell>
<TableCell>
<div className="flex items-center space-x-1">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(product);
setIsViewModalOpen(true);
}}
className="p-2"
>
<Eye className="h-3 w-3" />
</Button>
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(product);
setFormData(product);
setIsEditModalOpen(true);
}}
className="p-2"
>
<Edit className="h-3 w-3" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="outline" className="p-2">
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
{product.name} ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={() => handleDelete(product.id)}>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
{/* BOM 탭 */}
<TabsContent value="bom" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-base sm:text-lg">BOM (Bill of Materials)</CardTitle>
</CardHeader>
<CardContent>
{/* 모바일 카드 레이아웃 */}
<div className="block sm:hidden space-y-3">
{bomData.map((bom) => (
<Card key={bom.id} className="p-4 hover:shadow-md transition-shadow">
<div className="space-y-3">
<div>
<h3 className="font-medium text-sm">{bom.productName}</h3>
<p className="text-xs text-gray-600">{bom.productCode}</p>
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div>
<span className="text-gray-600">:</span> {bom.materialName}
</div>
<div>
<span className="text-gray-600">:</span> {bom.materialCode}
</div>
<div>
<span className="text-gray-600">:</span> {bom.quantity} {bom.unit}
</div>
<div>
<span className="text-gray-600">:</span> {bom.notes}
</div>
</div>
<div className="flex space-x-2">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(bom);
setFormData(bom);
setIsEditModalOpen(true);
}}
className="flex-1 min-h-[40px] touch-manipulation"
>
<Edit className="h-3 w-3 mr-1" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
size="sm"
variant="outline"
className="min-h-[40px] px-3 touch-manipulation"
>
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="mx-4 max-w-lg">
<AlertDialogHeader>
<AlertDialogTitle>BOM </AlertDialogTitle>
<AlertDialogDescription>
BOM ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="min-h-[44px] touch-manipulation"></AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(bom.id)}
className="min-h-[44px] touch-manipulation"
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
</Card>
))}
</div>
{/* 데스크톱 테이블 */}
<div className="hidden sm:block overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="min-w-[100px]"></TableHead>
<TableHead className="min-w-[150px]"></TableHead>
<TableHead className="min-w-[100px]"></TableHead>
<TableHead className="min-w-[150px]"></TableHead>
<TableHead className="min-w-[80px]"></TableHead>
<TableHead className="min-w-[60px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{bomData.map((bom) => (
<TableRow key={bom.id}>
<TableCell className="font-medium">{bom.productCode}</TableCell>
<TableCell>{bom.productName}</TableCell>
<TableCell>{bom.materialCode}</TableCell>
<TableCell>{bom.materialName}</TableCell>
<TableCell>{bom.quantity}</TableCell>
<TableCell>{bom.unit}</TableCell>
<TableCell>
<div className="flex items-center space-x-1">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(bom);
setFormData(bom);
setIsEditModalOpen(true);
}}
className="p-2"
>
<Edit className="h-3 w-3" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="outline" className="p-2">
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>BOM </AlertDialogTitle>
<AlertDialogDescription>
BOM ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={() => handleDelete(bom.id)}>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
{/* 공정 마스터 탭 */}
<TabsContent value="processes" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-base sm:text-lg"> </CardTitle>
</CardHeader>
<CardContent>
{/* 모바일 카드 레이아웃 */}
<div className="block sm:hidden space-y-3">
{processes.map((process) => (
<Card key={process.id} className="p-4 hover:shadow-md transition-shadow">
<div className="space-y-3">
<div>
<h3 className="font-medium text-sm">{process.name}</h3>
<p className="text-xs text-gray-600">{process.code}</p>
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div>
<span className="text-gray-600">:</span> {process.workCenter}
</div>
<div>
<span className="text-gray-600">:</span> {process.standardTime}
</div>
<div>
<span className="text-gray-600">:</span> {process.setupTime}
</div>
<div>
<span className="text-gray-600">:</span> {process.nextProcess || "-"}
</div>
</div>
<div className="flex space-x-2">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(process);
setFormData(process);
setIsEditModalOpen(true);
}}
className="flex-1 min-h-[40px] touch-manipulation"
>
<Edit className="h-3 w-3 mr-1" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
size="sm"
variant="outline"
className="min-h-[40px] px-3 touch-manipulation"
>
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="mx-4 max-w-lg">
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
{process.name} ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="min-h-[44px] touch-manipulation"></AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(process.id)}
className="min-h-[44px] touch-manipulation"
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
</Card>
))}
</div>
{/* 데스크톱 테이블 */}
<div className="hidden sm:block overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="min-w-[100px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
<TableHead className="min-w-[100px]">()</TableHead>
<TableHead className="min-w-[100px]">()</TableHead>
<TableHead className="min-w-[100px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{processes.map((process) => (
<TableRow key={process.id}>
<TableCell className="font-medium">{process.code}</TableCell>
<TableCell>{process.name}</TableCell>
<TableCell>{process.workCenter}</TableCell>
<TableCell>{process.standardTime}</TableCell>
<TableCell>{process.setupTime}</TableCell>
<TableCell>{process.nextProcess || "-"}</TableCell>
<TableCell>
<div className="flex items-center space-x-1">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(process);
setFormData(process);
setIsEditModalOpen(true);
}}
className="p-2"
>
<Edit className="h-3 w-3" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="outline" className="p-2">
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
{process.name} ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={() => handleDelete(process.id)}>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
{/* 거래처 탭 */}
<TabsContent value="customers" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-base sm:text-lg"> </CardTitle>
</CardHeader>
<CardContent>
{/* 모바일 카드 레이아웃 */}
<div className="block sm:hidden space-y-3">
{customers.map((customer) => (
<Card key={customer.id} className="p-4 hover:shadow-md transition-shadow">
<div className="flex justify-between items-start mb-3">
<div className="min-w-0 flex-1">
<h3 className="font-medium text-sm truncate">{customer.name}</h3>
<p className="text-xs text-gray-600">{customer.code}</p>
</div>
<div className="ml-2 flex-shrink-0">
<Badge className={`${getStatusColor(customer.status)} text-white`}>
{customer.status}
</Badge>
<p className={`text-xs ${getTypeColor(customer.type)} mt-1`}>{customer.type}</p>
</div>
</div>
<div className="space-y-2 text-xs mb-3">
<div>
<span className="text-gray-600">:</span> {customer.contact}
</div>
<div>
<span className="text-gray-600">:</span> {customer.phone}
</div>
<div>
<span className="text-gray-600">:</span> {customer.address}
</div>
</div>
<div className="flex space-x-2">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(customer);
setFormData(customer);
setIsEditModalOpen(true);
}}
className="flex-1 min-h-[40px] touch-manipulation"
>
<Edit className="h-3 w-3 mr-1" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
size="sm"
variant="outline"
className="min-h-[40px] px-3 touch-manipulation"
>
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="mx-4 max-w-lg">
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
{customer.name} ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="min-h-[44px] touch-manipulation"></AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(customer.id)}
className="min-h-[44px] touch-manipulation"
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</Card>
))}
</div>
{/* 데스크톱 테이블 */}
<div className="hidden sm:block overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="min-w-[100px]"></TableHead>
<TableHead className="min-w-[150px]"></TableHead>
<TableHead className="min-w-[80px]"></TableHead>
<TableHead className="min-w-[100px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
<TableHead className="min-w-[80px]"></TableHead>
<TableHead className="min-w-[120px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{customers.map((customer) => (
<TableRow key={customer.id}>
<TableCell className="font-medium">{customer.code}</TableCell>
<TableCell>{customer.name}</TableCell>
<TableCell>
<span className={getTypeColor(customer.type)}>{customer.type}</span>
</TableCell>
<TableCell>{customer.contact}</TableCell>
<TableCell>{customer.phone}</TableCell>
<TableCell>
<Badge className={`${getStatusColor(customer.status)} text-white`}>
{customer.status}
</Badge>
</TableCell>
<TableCell>
<div className="flex items-center space-x-1">
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedItem(customer);
setFormData(customer);
setIsEditModalOpen(true);
}}
className="p-2"
>
<Edit className="h-3 w-3" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="outline" className="p-2">
<Trash2 className="h-3 w-3" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
{customer.name} ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={() => handleDelete(customer.id)}>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* 신규 등록 모달 */}
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogContent className="mx-4 max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{activeTab === "products" && "제품 등록"}
{activeTab === "bom" && "BOM 등록"}
{activeTab === "processes" && "공정 등록"}
{activeTab === "customers" && "거래처 등록"}
</DialogTitle>
<DialogDescription>
{activeTab === "products" && "제품"}
{activeTab === "bom" && "BOM"}
{activeTab === "processes" && "공정"}
{activeTab === "customers" && "거래처"} .
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{activeTab === "products" && (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="code"></Label>
<Input
id="code"
value={formData.code || ""}
onChange={(e) => setFormData({...formData, code: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="name"></Label>
<Input
id="name"
value={formData.name || ""}
onChange={(e) => setFormData({...formData, name: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="type"></Label>
<Select
value={formData.type || ""}
onValueChange={(value) => setFormData({...formData, type: value})}
>
<SelectTrigger className="min-h-[44px] touch-manipulation">
<SelectValue placeholder="유형 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="완제품"></SelectItem>
<SelectItem value="반제품"></SelectItem>
<SelectItem value="원자재"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="unit"></Label>
<Input
id="unit"
value={formData.unit || ""}
onChange={(e) => setFormData({...formData, unit: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="standardTime">()</Label>
<Input
id="standardTime"
type="number"
value={formData.standardTime || ""}
onChange={(e) => setFormData({...formData, standardTime: Number(e.target.value)})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="materialCost"></Label>
<Input
id="materialCost"
type="number"
value={formData.materialCost || ""}
onChange={(e) => setFormData({...formData, materialCost: Number(e.target.value)})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div>
<Label htmlFor="description"></Label>
<Textarea
id="description"
value={formData.description || ""}
onChange={(e) => setFormData({...formData, description: e.target.value})}
className="min-h-[80px] touch-manipulation"
/>
</div>
</>
)}
{activeTab === "bom" && (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="productCode"></Label>
<Select
value={formData.productCode || ""}
onValueChange={(value) => setFormData({...formData, productCode: value})}
>
<SelectTrigger className="min-h-[44px] touch-manipulation">
<SelectValue placeholder="제품 선택" />
</SelectTrigger>
<SelectContent>
{products.map((product) => (
<SelectItem key={product.id} value={product.code}>
{product.code} - {product.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="materialCode"></Label>
<Input
id="materialCode"
value={formData.materialCode || ""}
onChange={(e) => setFormData({...formData, materialCode: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div>
<Label htmlFor="materialName"></Label>
<Input
id="materialName"
value={formData.materialName || ""}
onChange={(e) => setFormData({...formData, materialName: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="quantity"></Label>
<Input
id="quantity"
type="number"
step="0.01"
value={formData.quantity || ""}
onChange={(e) => setFormData({...formData, quantity: Number(e.target.value)})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="unit"></Label>
<Input
id="unit"
value={formData.unit || ""}
onChange={(e) => setFormData({...formData, unit: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div>
<Label htmlFor="notes"></Label>
<Textarea
id="notes"
value={formData.notes || ""}
onChange={(e) => setFormData({...formData, notes: e.target.value})}
className="min-h-[80px] touch-manipulation"
/>
</div>
</>
)}
{activeTab === "processes" && (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="code"></Label>
<Input
id="code"
value={formData.code || ""}
onChange={(e) => setFormData({...formData, code: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="name"></Label>
<Input
id="name"
value={formData.name || ""}
onChange={(e) => setFormData({...formData, name: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div>
<Label htmlFor="workCenter"></Label>
<Input
id="workCenter"
value={formData.workCenter || ""}
onChange={(e) => setFormData({...formData, workCenter: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="standardTime">()</Label>
<Input
id="standardTime"
type="number"
value={formData.standardTime || ""}
onChange={(e) => setFormData({...formData, standardTime: Number(e.target.value)})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="setupTime">()</Label>
<Input
id="setupTime"
type="number"
value={formData.setupTime || ""}
onChange={(e) => setFormData({...formData, setupTime: Number(e.target.value)})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div>
<Label htmlFor="description"></Label>
<Textarea
id="description"
value={formData.description || ""}
onChange={(e) => setFormData({...formData, description: e.target.value})}
className="min-h-[80px] touch-manipulation"
/>
</div>
</>
)}
{activeTab === "customers" && (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="code"></Label>
<Input
id="code"
value={formData.code || ""}
onChange={(e) => setFormData({...formData, code: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="name"></Label>
<Input
id="name"
value={formData.name || ""}
onChange={(e) => setFormData({...formData, name: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div>
<Label htmlFor="type"> </Label>
<Select
value={formData.type || ""}
onValueChange={(value) => setFormData({...formData, type: value})}
>
<SelectTrigger className="min-h-[44px] touch-manipulation">
<SelectValue placeholder="유형 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="고객"></SelectItem>
<SelectItem value="공급업체"></SelectItem>
<SelectItem value="양방향"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="contact"></Label>
<Input
id="contact"
value={formData.contact || ""}
onChange={(e) => setFormData({...formData, contact: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="phone"></Label>
<Input
id="phone"
value={formData.phone || ""}
onChange={(e) => setFormData({...formData, phone: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div>
<Label htmlFor="address"></Label>
<Input
id="address"
value={formData.address || ""}
onChange={(e) => setFormData({...formData, address: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</>
)}
</div>
<div className="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 pt-4">
<Button
variant="outline"
onClick={() => setIsModalOpen(false)}
className="w-full sm:flex-1 min-h-[44px] touch-manipulation"
>
</Button>
<Button
onClick={handleCreate}
className="w-full sm:flex-1 min-h-[44px] touch-manipulation bg-blue-600 hover:bg-blue-700"
>
</Button>
</div>
</DialogContent>
</Dialog>
{/* 수정 모달 */}
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
<DialogContent className="mx-4 max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{activeTab === "products" && "제품 수정"}
{activeTab === "bom" && "BOM 수정"}
{activeTab === "processes" && "공정 수정"}
{activeTab === "customers" && "거래처 수정"}
</DialogTitle>
<DialogDescription>
.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* 수정 폼은 등록 폼과 동일한 구조 */}
{activeTab === "products" && (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="edit-code"></Label>
<Input
id="edit-code"
value={formData.code || ""}
onChange={(e) => setFormData({...formData, code: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="edit-name"></Label>
<Input
id="edit-name"
value={formData.name || ""}
onChange={(e) => setFormData({...formData, name: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="edit-type"></Label>
<Select
value={formData.type || ""}
onValueChange={(value) => setFormData({...formData, type: value})}
>
<SelectTrigger className="min-h-[44px] touch-manipulation">
<SelectValue placeholder="유형 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="완제품"></SelectItem>
<SelectItem value="반제품"></SelectItem>
<SelectItem value="원자재"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="edit-unit"></Label>
<Input
id="edit-unit"
value={formData.unit || ""}
onChange={(e) => setFormData({...formData, unit: e.target.value})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="edit-standardTime">()</Label>
<Input
id="edit-standardTime"
type="number"
value={formData.standardTime || ""}
onChange={(e) => setFormData({...formData, standardTime: Number(e.target.value)})}
className="min-h-[44px] touch-manipulation"
/>
</div>
<div>
<Label htmlFor="edit-materialCost"></Label>
<Input
id="edit-materialCost"
type="number"
value={formData.materialCost || ""}
onChange={(e) => setFormData({...formData, materialCost: Number(e.target.value)})}
className="min-h-[44px] touch-manipulation"
/>
</div>
</div>
<div>
<Label htmlFor="edit-description"></Label>
<Textarea
id="edit-description"
value={formData.description || ""}
onChange={(e) => setFormData({...formData, description: e.target.value})}
className="min-h-[80px] touch-manipulation"
/>
</div>
</>
)}
{/* BOM, 공정, 거래처 수정 폼들도 유사하게 구성 */}
</div>
<div className="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 pt-4">
<Button
variant="outline"
onClick={() => setIsEditModalOpen(false)}
className="w-full sm:flex-1 min-h-[44px] touch-manipulation"
>
</Button>
<Button
onClick={handleUpdate}
className="w-full sm:flex-1 min-h-[44px] touch-manipulation bg-blue-600 hover:bg-blue-700"
>
</Button>
</div>
</DialogContent>
</Dialog>
{/* 상세보기 모달 */}
<Dialog open={isViewModalOpen} onOpenChange={setIsViewModalOpen}>
<DialogContent className="mx-4 max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle> </DialogTitle>
<DialogDescription>
.
</DialogDescription>
</DialogHeader>
{selectedItem && (
<div className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-600">:</span>
<p className="font-medium">{selectedItem.code}</p>
</div>
<div>
<span className="text-gray-600">:</span>
<p className="font-medium">{selectedItem.name}</p>
</div>
<div>
<span className="text-gray-600">:</span>
<p className="font-medium">{selectedItem.type}</p>
</div>
<div>
<span className="text-gray-600">:</span>
<p className="font-medium">{selectedItem.unit}</p>
</div>
<div>
<span className="text-gray-600">:</span>
<p className="font-medium">{selectedItem.standardTime}</p>
</div>
<div>
<span className="text-gray-600">:</span>
<p className="font-medium">{selectedItem.materialCost?.toLocaleString()}</p>
</div>
</div>
<div>
<span className="text-gray-600 text-sm">:</span>
<p className="font-medium mt-1">{selectedItem.description}</p>
</div>
</div>
)}
<div className="pt-4">
<Button
onClick={() => setIsViewModalOpen(false)}
className="w-full min-h-[44px] touch-manipulation"
>
</Button>
</div>
</DialogContent>
</Dialog>
</div>
);
}