ERP-node/frontend/components/pop/PopApp.tsx

463 lines
15 KiB
TypeScript

"use client";
import React, { useState, useEffect, useCallback } from "react";
import "./styles.css";
import {
AppState,
ModalState,
PanelState,
StatusType,
ProductionType,
WorkOrder,
WorkStep,
Equipment,
Process,
} from "./types";
import { WORK_ORDERS, EQUIPMENTS, PROCESSES, WORK_STEP_TEMPLATES, STATUS_TEXT } from "./data";
import { PopHeader } from "./PopHeader";
import { PopStatusTabs } from "./PopStatusTabs";
import { PopWorkCard } from "./PopWorkCard";
import { PopBottomNav } from "./PopBottomNav";
import { PopEquipmentModal } from "./PopEquipmentModal";
import { PopProcessModal } from "./PopProcessModal";
import { PopAcceptModal } from "./PopAcceptModal";
import { PopSettingsModal } from "./PopSettingsModal";
import { PopProductionPanel } from "./PopProductionPanel";
export function PopApp() {
// 앱 상태
const [appState, setAppState] = useState<AppState>({
currentStatus: "waiting",
selectedEquipment: null,
selectedProcess: null,
selectedWorkOrder: null,
showMyWorkOnly: false,
currentWorkSteps: [],
currentStepIndex: 0,
currentProductionType: "work-order",
selectionMode: "single",
completionAction: "close",
acceptTargetWorkOrder: null,
acceptQuantity: 0,
theme: "dark",
});
// 모달 상태
const [modalState, setModalState] = useState<ModalState>({
equipment: false,
process: false,
accept: false,
settings: false,
});
// 패널 상태
const [panelState, setPanelState] = useState<PanelState>({
production: false,
});
// 현재 시간 (hydration 에러 방지를 위해 초기값 null)
const [currentDateTime, setCurrentDateTime] = useState<Date | null>(null);
const [isClient, setIsClient] = useState(false);
// 작업지시 목록 (상태 변경을 위해 로컬 상태로 관리)
const [workOrders, setWorkOrders] = useState<WorkOrder[]>(WORK_ORDERS);
// 클라이언트 마운트 확인 및 시계 업데이트
useEffect(() => {
setIsClient(true);
setCurrentDateTime(new Date());
const timer = setInterval(() => {
setCurrentDateTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
// 로컬 스토리지에서 설정 로드
useEffect(() => {
const savedSelectionMode = localStorage.getItem("selectionMode") as "single" | "multi" | null;
const savedCompletionAction = localStorage.getItem("completionAction") as "close" | "stay" | null;
const savedTheme = localStorage.getItem("popTheme") as "dark" | "light" | null;
setAppState((prev) => ({
...prev,
selectionMode: savedSelectionMode || "single",
completionAction: savedCompletionAction || "close",
theme: savedTheme || "dark",
}));
}, []);
// 상태별 카운트 계산
const getStatusCounts = useCallback(() => {
const myProcessId = appState.selectedProcess?.id;
let waitingCount = 0;
let pendingAcceptCount = 0;
let inProgressCount = 0;
let completedCount = 0;
workOrders.forEach((wo) => {
if (!wo.processFlow) return;
const myProcessIndex = myProcessId
? wo.processFlow.findIndex((step) => step.id === myProcessId)
: -1;
if (wo.status === "completed") {
completedCount++;
} else if (wo.status === "in-progress" && wo.accepted) {
inProgressCount++;
} else if (myProcessIndex >= 0) {
const currentProcessIndex = wo.currentProcessIndex || 0;
const myStep = wo.processFlow[myProcessIndex];
if (currentProcessIndex < myProcessIndex) {
waitingCount++;
} else if (currentProcessIndex === myProcessIndex && myStep.status !== "completed") {
pendingAcceptCount++;
} else if (myStep.status === "completed") {
completedCount++;
}
} else {
if (wo.status === "waiting") waitingCount++;
else if (wo.status === "in-progress") inProgressCount++;
}
});
return { waitingCount, pendingAcceptCount, inProgressCount, completedCount };
}, [workOrders, appState.selectedProcess]);
// 필터링된 작업 목록
const getFilteredWorkOrders = useCallback(() => {
const myProcessId = appState.selectedProcess?.id;
let filtered: WorkOrder[] = [];
workOrders.forEach((wo) => {
if (!wo.processFlow) return;
const myProcessIndex = myProcessId
? wo.processFlow.findIndex((step) => step.id === myProcessId)
: -1;
const currentProcessIndex = wo.currentProcessIndex || 0;
const myStep = myProcessIndex >= 0 ? wo.processFlow[myProcessIndex] : null;
switch (appState.currentStatus) {
case "waiting":
if (myProcessIndex >= 0 && currentProcessIndex < myProcessIndex) {
filtered.push(wo);
} else if (!myProcessId && wo.status === "waiting") {
filtered.push(wo);
}
break;
case "pending-accept":
if (
myProcessIndex >= 0 &&
currentProcessIndex === myProcessIndex &&
myStep &&
myStep.status !== "completed" &&
!wo.accepted
) {
filtered.push(wo);
}
break;
case "in-progress":
if (wo.accepted && wo.status === "in-progress") {
filtered.push(wo);
} else if (!myProcessId && wo.status === "in-progress") {
filtered.push(wo);
}
break;
case "completed":
if (wo.status === "completed") {
filtered.push(wo);
} else if (myStep && myStep.status === "completed") {
filtered.push(wo);
}
break;
}
});
// 내 작업만 보기 필터
if (appState.showMyWorkOnly && myProcessId) {
filtered = filtered.filter((wo) => {
const mySteps = wo.processFlow.filter((step) => step.id === myProcessId);
if (mySteps.length === 0) return false;
return !mySteps.every((step) => step.status === "completed");
});
}
return filtered;
}, [workOrders, appState.currentStatus, appState.selectedProcess, appState.showMyWorkOnly]);
// 상태 탭 변경
const handleStatusChange = (status: StatusType) => {
setAppState((prev) => ({ ...prev, currentStatus: status }));
};
// 생산 유형 변경
const handleProductionTypeChange = (type: ProductionType) => {
setAppState((prev) => ({ ...prev, currentProductionType: type }));
};
// 내 작업만 보기 토글
const handleMyWorkToggle = () => {
setAppState((prev) => ({ ...prev, showMyWorkOnly: !prev.showMyWorkOnly }));
};
// 테마 토글
const handleThemeToggle = () => {
const newTheme = appState.theme === "dark" ? "light" : "dark";
setAppState((prev) => ({ ...prev, theme: newTheme }));
localStorage.setItem("popTheme", newTheme);
};
// 모달 열기/닫기
const openModal = (type: keyof ModalState) => {
setModalState((prev) => ({ ...prev, [type]: true }));
};
const closeModal = (type: keyof ModalState) => {
setModalState((prev) => ({ ...prev, [type]: false }));
};
// 설비 선택
const handleEquipmentSelect = (equipment: Equipment) => {
setAppState((prev) => ({
...prev,
selectedEquipment: equipment,
// 공정이 1개면 자동 선택
selectedProcess:
equipment.processIds.length === 1
? PROCESSES.find((p) => p.id === equipment.processIds[0]) || null
: null,
}));
};
// 공정 선택
const handleProcessSelect = (process: Process) => {
setAppState((prev) => ({ ...prev, selectedProcess: process }));
};
// 작업 접수 모달 열기
const handleOpenAcceptModal = (workOrder: WorkOrder) => {
const acceptedQty = workOrder.acceptedQuantity || 0;
const remainingQty = workOrder.orderQuantity - acceptedQty;
setAppState((prev) => ({
...prev,
acceptTargetWorkOrder: workOrder,
acceptQuantity: remainingQty,
}));
openModal("accept");
};
// 접수 확인
const handleConfirmAccept = (quantity: number) => {
if (!appState.acceptTargetWorkOrder) return;
setWorkOrders((prev) =>
prev.map((wo) => {
if (wo.id === appState.acceptTargetWorkOrder!.id) {
const previousAccepted = wo.acceptedQuantity || 0;
const newAccepted = previousAccepted + quantity;
return {
...wo,
acceptedQuantity: newAccepted,
remainingQuantity: wo.orderQuantity - newAccepted,
accepted: true,
status: "in-progress" as const,
isPartialAccept: newAccepted < wo.orderQuantity,
};
}
return wo;
})
);
closeModal("accept");
setAppState((prev) => ({
...prev,
acceptTargetWorkOrder: null,
acceptQuantity: 0,
}));
};
// 접수 취소
const handleCancelAccept = (workOrderId: string) => {
setWorkOrders((prev) =>
prev.map((wo) => {
if (wo.id === workOrderId) {
return {
...wo,
accepted: false,
acceptedQuantity: 0,
remainingQuantity: wo.orderQuantity,
isPartialAccept: false,
status: "waiting" as const,
};
}
return wo;
})
);
};
// 생산진행 패널 열기
const handleOpenProductionPanel = (workOrder: WorkOrder) => {
const template = WORK_STEP_TEMPLATES[workOrder.process] || WORK_STEP_TEMPLATES["default"];
const workSteps: WorkStep[] = template.map((step) => ({
...step,
status: "pending" as const,
startTime: null,
endTime: null,
data: {},
}));
setAppState((prev) => ({
...prev,
selectedWorkOrder: workOrder,
currentWorkSteps: workSteps,
currentStepIndex: 0,
}));
setPanelState((prev) => ({ ...prev, production: true }));
};
// 생산진행 패널 닫기
const handleCloseProductionPanel = () => {
setPanelState((prev) => ({ ...prev, production: false }));
setAppState((prev) => ({
...prev,
selectedWorkOrder: null,
currentWorkSteps: [],
currentStepIndex: 0,
}));
};
// 설정 저장
const handleSaveSettings = (selectionMode: "single" | "multi", completionAction: "close" | "stay") => {
setAppState((prev) => ({ ...prev, selectionMode, completionAction }));
localStorage.setItem("selectionMode", selectionMode);
localStorage.setItem("completionAction", completionAction);
closeModal("settings");
};
const statusCounts = getStatusCounts();
const filteredWorkOrders = getFilteredWorkOrders();
return (
<div className={`pop-container ${appState.theme === "light" ? "light" : ""}`}>
<div className="pop-app">
{/* 헤더 */}
<PopHeader
currentDateTime={currentDateTime || new Date()}
productionType={appState.currentProductionType}
selectedEquipment={appState.selectedEquipment}
selectedProcess={appState.selectedProcess}
showMyWorkOnly={appState.showMyWorkOnly}
theme={appState.theme}
onProductionTypeChange={handleProductionTypeChange}
onEquipmentClick={() => openModal("equipment")}
onProcessClick={() => openModal("process")}
onMyWorkToggle={handleMyWorkToggle}
onSearchClick={() => {
/* 조회 */
}}
onSettingsClick={() => openModal("settings")}
onThemeToggle={handleThemeToggle}
/>
{/* 상태 탭 */}
<PopStatusTabs
currentStatus={appState.currentStatus}
counts={statusCounts}
onStatusChange={handleStatusChange}
/>
{/* 메인 콘텐츠 */}
<div className="pop-main-content">
{filteredWorkOrders.length === 0 ? (
<div className="pop-empty-state">
<div className="pop-empty-state-text"> </div>
<div className="pop-empty-state-desc">
{appState.currentStatus === "waiting" && "대기 중인 작업이 없습니다"}
{appState.currentStatus === "pending-accept" && "접수 대기 작업이 없습니다"}
{appState.currentStatus === "in-progress" && "진행 중인 작업이 없습니다"}
{appState.currentStatus === "completed" && "완료된 작업이 없습니다"}
</div>
</div>
) : (
<div className="pop-work-list">
{filteredWorkOrders.map((workOrder) => (
<PopWorkCard
key={workOrder.id}
workOrder={workOrder}
currentStatus={appState.currentStatus}
selectedProcess={appState.selectedProcess}
onAccept={() => handleOpenAcceptModal(workOrder)}
onCancelAccept={() => handleCancelAccept(workOrder.id)}
onStartProduction={() => handleOpenProductionPanel(workOrder)}
onClick={() => handleOpenProductionPanel(workOrder)}
/>
))}
</div>
)}
</div>
{/* 하단 네비게이션 */}
<PopBottomNav />
</div>
{/* 모달들 */}
<PopEquipmentModal
isOpen={modalState.equipment}
equipments={EQUIPMENTS}
selectedEquipment={appState.selectedEquipment}
onSelect={handleEquipmentSelect}
onClose={() => closeModal("equipment")}
/>
<PopProcessModal
isOpen={modalState.process}
selectedEquipment={appState.selectedEquipment}
selectedProcess={appState.selectedProcess}
processes={PROCESSES}
onSelect={handleProcessSelect}
onClose={() => closeModal("process")}
/>
<PopAcceptModal
isOpen={modalState.accept}
workOrder={appState.acceptTargetWorkOrder}
quantity={appState.acceptQuantity}
onQuantityChange={(qty) => setAppState((prev) => ({ ...prev, acceptQuantity: qty }))}
onConfirm={handleConfirmAccept}
onClose={() => closeModal("accept")}
/>
<PopSettingsModal
isOpen={modalState.settings}
selectionMode={appState.selectionMode}
completionAction={appState.completionAction}
onSave={handleSaveSettings}
onClose={() => closeModal("settings")}
/>
{/* 생산진행 패널 */}
<PopProductionPanel
isOpen={panelState.production}
workOrder={appState.selectedWorkOrder}
workSteps={appState.currentWorkSteps}
currentStepIndex={appState.currentStepIndex}
currentDateTime={currentDateTime || new Date()}
onStepChange={(index) => setAppState((prev) => ({ ...prev, currentStepIndex: index }))}
onStepsUpdate={(steps) => setAppState((prev) => ({ ...prev, currentWorkSteps: steps }))}
onClose={handleCloseProductionPanel}
/>
</div>
);
}