feat(WEB): 생산 대시보드 최근 완료 작업 카드 추가

- 4컬럼 레이아웃으로 변경 (긴급/지연/완료/작업자)
- recentCompletedOrders 필터 추가 (최신 5건)
- WorkOrderCard에 showCompleted 옵션 추가
This commit is contained in:
2026-01-26 15:29:59 +09:00
parent f79ee8be87
commit ada24eef66

View File

@@ -106,7 +106,7 @@ export default function ProductionDashboard() {
// ===== 필터링된 데이터 (탭에서 이미 필터링됨) =====
const filteredOrders = workOrders;
// ===== 긴급/지연 작업 필터링 =====
// ===== 긴급/지연/완료 작업 필터링 =====
const urgentOrders = useMemo(
() => filteredOrders.filter((o) => o.isUrgent).slice(0, 5),
[filteredOrders]
@@ -115,6 +115,14 @@ export default function ProductionDashboard() {
() => filteredOrders.filter((o) => o.isDelayed).slice(0, 5),
[filteredOrders]
);
// 최근 완료 작업 (createdAt 기준 정렬, 최신 5건)
const recentCompletedOrders = useMemo(
() => filteredOrders
.filter((o) => o.status === 'completed')
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
.slice(0, 5),
[filteredOrders]
);
// ===== 핸들러 =====
const handleOrderClick = (id: string) => {
@@ -212,8 +220,8 @@ export default function ProductionDashboard() {
/>
</div>
{/* 3컬럼 레이아웃 */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* 4컬럼 레이아웃 */}
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
{/* 긴급 작업 */}
<Card>
<CardHeader className="pb-3">
@@ -271,6 +279,35 @@ export default function ProductionDashboard() {
</CardContent>
</Card>
{/* 최근 완료 작업 */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-base">
<CheckCircle2 className="h-4 w-4 text-green-500" />
<Badge className="ml-auto bg-green-100 text-green-800 hover:bg-green-100">
{stats.completed}
</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{recentCompletedOrders.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-4">
.
</p>
) : (
recentCompletedOrders.map((order) => (
<WorkOrderCard
key={order.id}
order={order}
onClick={() => handleOrderClick(order.id)}
showCompleted
/>
))
)}
</CardContent>
</Card>
{/* 작업자별 현황 */}
<Card>
<CardHeader className="pb-3">
@@ -328,15 +365,23 @@ interface WorkOrderCardProps {
order: WorkOrder;
onClick: () => void;
showDelay?: boolean;
showCompleted?: boolean;
}
function WorkOrderCard({ order, onClick, showDelay }: WorkOrderCardProps) {
function WorkOrderCard({ order, onClick, showDelay, showCompleted }: WorkOrderCardProps) {
const statusColors = {
waiting: 'bg-gray-100 text-gray-800',
inProgress: 'bg-blue-100 text-blue-800',
completed: 'bg-green-100 text-green-800',
};
// 완료일 포맷팅 (createdAt을 완료일로 사용 - 실제로는 완료 시점의 timestamp 필요)
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getMonth() + 1}/${date.getDate()}`;
};
return (
<div
onClick={onClick}
@@ -348,9 +393,11 @@ function WorkOrderCard({ order, onClick, showDelay }: WorkOrderCardProps) {
<span className="text-xs font-medium text-muted-foreground">
{order.orderNo}
</span>
<Badge className={`text-xs ${statusColors[order.status]}`}>
{STATUS_LABELS[order.status]}
</Badge>
{!showCompleted && (
<Badge className={`text-xs ${statusColors[order.status]}`}>
{STATUS_LABELS[order.status]}
</Badge>
)}
</div>
<p className="text-sm font-medium mt-1 truncate">{order.productName}</p>
<p className="text-xs text-muted-foreground truncate">{order.client}</p>
@@ -365,12 +412,15 @@ function WorkOrderCard({ order, onClick, showDelay }: WorkOrderCardProps) {
{showDelay && order.delayDays && (
<p className="text-xs text-orange-600 mt-1">+{order.delayDays} </p>
)}
{order.isUrgent && !showDelay && (
{showCompleted && (
<p className="text-xs text-green-600 mt-1">{formatDate(order.createdAt)}</p>
)}
{order.isUrgent && !showDelay && !showCompleted && (
<p className="text-xs text-muted-foreground mt-1"> {order.priority}</p>
)}
</div>
</div>
{order.instruction && (
{order.instruction && !showCompleted && (
<p className="text-xs text-muted-foreground mt-2 truncate">
{order.instruction}
</p>