Files
sam-react-prod/src/components/business/CEODashboard/sections/DailyProductionSection.tsx
유병철 8f4a7ee842 refactor(WEB): CEO 대시보드 대규모 개선 및 문서/권한/스토어 리팩토링
- CEO 대시보드: 섹션별 API 연동 강화 (매출/매입/생산 실데이터 표시)
- DashboardSettingsDialog 드래그 정렬 및 설정 UX 개선
- dashboard transformers 모듈 분리 (파일 분할)
- DocumentTable/DocumentWrapper 공통 문서 컴포넌트 추출
- LineItemsTable organisms 컴포넌트 추가
- PurchaseOrderDocument/InspectionRequestDocument 문서 컴포넌트 리팩토링
- PermissionContext → permissionStore(Zustand) 전환
- useUIStore, stores/utils/userStorage 추가
- favoritesStore/useTableColumnStore 사용자별 저장 지원
- DepositDetail/WithdrawalDetail 삭제 (통합)
- PurchaseDetail/SalesDetail 간소화
- amount.ts/formatters.ts 유틸 확장

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:59:25 +09:00

309 lines
15 KiB
TypeScript

'use client';
import { useState } from 'react';
import {
Factory,
AlertTriangle,
Star,
Layers,
Users,
Truck,
Package,
ClipboardList,
ListTodo,
Play,
CheckCircle2,
} from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { formatKoreanAmount } from '@/lib/utils/amount';
import type { DailyProductionData } from '../types';
// 출고 현황 독립 섹션
interface ShipmentSectionProps {
data: DailyProductionData;
}
export function ShipmentSection({ data }: ShipmentSectionProps) {
return (
<div className="rounded-xl border overflow-hidden">
<div style={{ backgroundColor: '#1e293b' }} className="px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div style={{ backgroundColor: 'rgba(255,255,255,0.1)' }} className="p-2 rounded-lg">
<Truck style={{ color: '#ffffff' }} className="h-5 w-5" />
</div>
<div>
<h3 style={{ color: '#ffffff' }} className="text-lg font-semibold"> </h3>
<p style={{ color: '#cbd5e1' }} className="text-sm"> </p>
</div>
</div>
</div>
</div>
<div style={{ backgroundColor: '#ffffff' }} className="p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="rounded-xl border p-4" style={{ backgroundColor: '#eff6ff', borderColor: '#bfdbfe' }}>
<div className="flex items-center gap-2 mb-3">
<Package className="h-4 w-4 text-blue-500" />
<span className="text-sm font-medium text-gray-700"> (7 )</span>
</div>
<p className="text-xl font-bold text-gray-900 mb-1">{formatKoreanAmount(data.shipment.expectedAmount)}</p>
<p className="text-xs text-gray-500">{data.shipment.expectedCount}</p>
</div>
<div className="rounded-xl border p-4" style={{ backgroundColor: '#f0fdf4', borderColor: '#bbf7d0' }}>
<div className="flex items-center gap-2 mb-3">
<Truck className="h-4 w-4 text-green-500" />
<span className="text-sm font-medium text-gray-700"> (30 )</span>
</div>
<p className="text-xl font-bold text-gray-900 mb-1">{formatKoreanAmount(data.shipment.actualAmount)}</p>
<p className="text-xs text-gray-500">{data.shipment.actualCount}</p>
</div>
</div>
</div>
</div>
);
}
interface DailyProductionSectionProps {
data: DailyProductionData;
showShipment?: boolean;
}
const PROCESS_TAB_NAMES: Record<string, string> = {
'스크린': '스크린 공정',
'슬랫': '슬랫 공정',
'절곡': '절곡 공정',
};
export function DailyProductionSection({ data, showShipment = true }: DailyProductionSectionProps) {
const [activeTab, setActiveTab] = useState(data.processes[0]?.processName ?? '');
return (
<div className="space-y-6">
<div className="rounded-xl border overflow-hidden">
{/* 다크 헤더 */}
<div style={{ backgroundColor: '#1e293b' }} className="px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div style={{ backgroundColor: 'rgba(255,255,255,0.1)' }} className="p-2 rounded-lg">
<Factory style={{ color: '#ffffff' }} className="h-5 w-5" />
</div>
<div>
<h3 style={{ color: '#ffffff' }} className="text-lg font-semibold"> </h3>
<p style={{ color: '#cbd5e1' }} className="text-sm">{data.date}</p>
</div>
</div>
<Badge
style={{ backgroundColor: '#8b5cf6', color: '#ffffff', border: 'none' }}
className="hover:opacity-90"
>
</Badge>
</div>
</div>
<div style={{ backgroundColor: '#ffffff' }} className="p-6">
{/* 공정 탭 */}
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="mb-4">
{data.processes.map((process) => (
<TabsTrigger key={process.processName} value={process.processName}>
{PROCESS_TAB_NAMES[process.processName] ?? `${process.processName} 공정`}
</TabsTrigger>
))}
</TabsList>
{data.processes.map((process) => (
<TabsContent key={process.processName} value={process.processName}>
{/* 요약 카드: 전체 작업 / 할일 / 작업중 / 완료 */}
<div className="grid grid-cols-4 gap-3 mb-4">
<div className="flex items-center gap-2 rounded-lg border p-3">
<ClipboardList className="h-4 w-4 text-gray-500" />
<div>
<p className="text-[11px] text-gray-500"> </p>
<p className="text-sm font-bold text-gray-900">{process.totalWork}</p>
</div>
</div>
<div className="flex items-center gap-2 rounded-lg border p-3">
<ListTodo className="h-4 w-4 text-orange-500" />
<div>
<p className="text-[11px] text-gray-500"></p>
<p className="text-sm font-bold text-gray-900">{process.todo}</p>
</div>
</div>
<div className="flex items-center gap-2 rounded-lg border p-3">
<Play className="h-4 w-4 text-blue-500" />
<div>
<p className="text-[11px] text-gray-500"></p>
<p className="text-sm font-bold text-gray-900">{process.inProgress}</p>
</div>
</div>
<div className="flex items-center gap-2 rounded-lg border p-3">
<CheckCircle2 className="h-4 w-4 text-green-500" />
<div>
<p className="text-[11px] text-gray-500"></p>
<p className="text-sm font-bold text-gray-900">{process.completed}</p>
</div>
</div>
</div>
{/* 4열 카드 레이아웃: 긴급 / 우선 / 일반 / 작업자 현황 */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{/* 긴급 */}
<div className="border rounded-lg overflow-hidden">
<div className="p-3 border-b" style={{ backgroundColor: '#fef2f2' }}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<AlertTriangle className="h-3.5 w-3.5 text-red-500" />
<span className="text-xs font-semibold text-red-600"></span>
</div>
<span className="text-sm font-bold text-gray-900">{process.urgent}</span>
</div>
</div>
<div className="p-2 space-y-2">
{process.workItems
.filter((item) => item.status === '진행중')
.slice(0, process.urgent)
.map((item) => (
<div key={item.id} className="border rounded p-2 hover:bg-gray-50 cursor-pointer transition-colors">
<p className="text-xs font-medium text-gray-900 truncate">{item.client}</p>
<p className="text-[11px] text-gray-500">{item.product}</p>
<p className="text-[11px] text-gray-400">{item.quantity}</p>
</div>
))}
</div>
</div>
{/* 우선 */}
<div className="border rounded-lg overflow-hidden">
<div className="p-3 border-b" style={{ backgroundColor: '#fff7ed' }}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Star className="h-3.5 w-3.5 text-orange-500" />
<span className="text-xs font-semibold text-orange-600"></span>
</div>
<span className="text-sm font-bold text-gray-900">{process.subLine}</span>
</div>
</div>
<div className="p-2 space-y-2">
{process.workItems
.filter((item) => item.status === '대기')
.slice(0, process.subLine)
.map((item) => (
<div key={item.id} className="border rounded p-2 hover:bg-gray-50 cursor-pointer transition-colors">
<p className="text-xs font-medium text-gray-900 truncate">{item.client}</p>
<p className="text-[11px] text-gray-500">{item.product}</p>
<p className="text-[11px] text-gray-400">{item.quantity}</p>
</div>
))}
</div>
</div>
{/* 일반 */}
<div className="border rounded-lg overflow-hidden">
<div className="p-3 border-b" style={{ backgroundColor: '#eff6ff' }}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Layers className="h-3.5 w-3.5 text-blue-500" />
<span className="text-xs font-semibold text-blue-600"></span>
</div>
<span className="text-sm font-bold text-gray-900">{process.regular}</span>
</div>
</div>
<div className="p-2 space-y-2">
{process.workItems
.slice(0, process.regular)
.map((item) => (
<div key={item.id} className="border rounded p-2 hover:bg-gray-50 cursor-pointer transition-colors">
<p className="text-xs font-medium text-gray-900 truncate">{item.client}</p>
<p className="text-[11px] text-gray-500">{item.product}</p>
<p className="text-[11px] text-gray-400">{item.quantity}</p>
</div>
))}
</div>
</div>
{/* 작업자 현황 */}
<div className="border rounded-lg overflow-hidden">
<div className="p-3 border-b" style={{ backgroundColor: '#f0fdf4' }}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Users className="h-3.5 w-3.5 text-green-500" />
<span className="text-xs font-semibold text-green-600"> </span>
</div>
<span className="text-sm font-bold text-gray-900">{process.workerCount}</span>
</div>
</div>
<div className="p-2 space-y-2">
{process.workers.map((worker, idx) => (
<div key={idx} className="border rounded p-2">
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-medium text-gray-900">{worker.name}</span>
<span className="text-[11px] text-gray-500">{worker.completed}/{worker.assigned}</span>
</div>
<div className="flex items-center gap-1.5">
<div className="flex-1 h-1.5 bg-gray-200 rounded-full overflow-hidden">
<div
className="h-full rounded-full"
style={{
width: `${worker.rate}%`,
backgroundColor: worker.rate >= 80 ? '#22c55e' : worker.rate >= 50 ? '#f59e0b' : '#ef4444',
}}
/>
</div>
<span className="text-[10px] text-gray-500 min-w-[28px] text-right">{worker.rate}%</span>
</div>
</div>
))}
</div>
</div>
</div>
</TabsContent>
))}
</Tabs>
</div>
</div>
{/* 출고 현황 (별도 카드) */}
{showShipment && (
<div className="rounded-xl border overflow-hidden">
<div style={{ backgroundColor: '#1e293b' }} className="px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div style={{ backgroundColor: 'rgba(255,255,255,0.1)' }} className="p-2 rounded-lg">
<Truck style={{ color: '#ffffff' }} className="h-5 w-5" />
</div>
<div>
<h3 style={{ color: '#ffffff' }} className="text-lg font-semibold"> </h3>
<p style={{ color: '#cbd5e1' }} className="text-sm"> </p>
</div>
</div>
</div>
</div>
<div style={{ backgroundColor: '#ffffff' }} className="p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="rounded-xl border p-4" style={{ backgroundColor: '#eff6ff', borderColor: '#bfdbfe' }}>
<div className="flex items-center gap-2 mb-3">
<Package className="h-4 w-4 text-blue-500" />
<span className="text-sm font-medium text-gray-700"> (7 )</span>
</div>
<p className="text-xl font-bold text-gray-900 mb-1">{formatKoreanAmount(data.shipment.expectedAmount)}</p>
<p className="text-xs text-gray-500">{data.shipment.expectedCount}</p>
</div>
<div className="rounded-xl border p-4" style={{ backgroundColor: '#f0fdf4', borderColor: '#bbf7d0' }}>
<div className="flex items-center gap-2 mb-3">
<Truck className="h-4 w-4 text-green-500" />
<span className="text-sm font-medium text-gray-700"> (30 )</span>
</div>
<p className="text-xl font-bold text-gray-900 mb-1">{formatKoreanAmount(data.shipment.actualAmount)}</p>
<p className="text-xs text-gray-500">{data.shipment.actualCount}</p>
</div>
</div>
</div>
</div>
)}
</div>
);
}