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

275 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useRef, useEffect, useState } from "react";
import { WorkOrder, Process, StatusType } from "./types";
import { STATUS_TEXT } from "./data";
interface PopWorkCardProps {
workOrder: WorkOrder;
currentStatus: StatusType;
selectedProcess: Process | null;
onAccept: () => void;
onCancelAccept: () => void;
onStartProduction: () => void;
onClick: () => void;
}
export function PopWorkCard({
workOrder,
currentStatus,
selectedProcess,
onAccept,
onCancelAccept,
onStartProduction,
onClick,
}: PopWorkCardProps) {
const chipsRef = useRef<HTMLDivElement>(null);
const [showLeftBtn, setShowLeftBtn] = useState(false);
const [showRightBtn, setShowRightBtn] = useState(false);
const progress = ((workOrder.producedQuantity / workOrder.orderQuantity) * 100).toFixed(1);
const isReturnWork = workOrder.isReturn === true;
// 공정 스크롤 버튼 표시 여부 확인
const checkScrollButtons = () => {
const container = chipsRef.current;
if (!container) return;
const isScrollable = container.scrollWidth > container.clientWidth;
if (isScrollable) {
const scrollLeft = container.scrollLeft;
const maxScroll = container.scrollWidth - container.clientWidth;
setShowLeftBtn(scrollLeft > 5);
setShowRightBtn(scrollLeft < maxScroll - 5);
} else {
setShowLeftBtn(false);
setShowRightBtn(false);
}
};
// 현재 공정으로 스크롤
const scrollToCurrentProcess = () => {
const container = chipsRef.current;
if (!container || !workOrder.processFlow) return;
let targetIndex = -1;
// 내 공정 우선
if (selectedProcess) {
targetIndex = workOrder.processFlow.findIndex(
(step) =>
step.id === selectedProcess.id &&
(step.status === "current" || step.status === "pending")
);
}
// 없으면 현재 진행 중인 공정
if (targetIndex === -1) {
targetIndex = workOrder.processFlow.findIndex((step) => step.status === "current");
}
if (targetIndex === -1) return;
const chips = container.querySelectorAll(".pop-process-chip");
if (chips.length > targetIndex) {
const targetChip = chips[targetIndex] as HTMLElement;
const scrollPos =
targetChip.offsetLeft - container.clientWidth / 2 + targetChip.offsetWidth / 2;
container.scrollLeft = Math.max(0, scrollPos);
}
};
useEffect(() => {
scrollToCurrentProcess();
checkScrollButtons();
const container = chipsRef.current;
if (container) {
container.addEventListener("scroll", checkScrollButtons);
return () => container.removeEventListener("scroll", checkScrollButtons);
}
}, [workOrder, selectedProcess]);
const handleScroll = (direction: "left" | "right", e: React.MouseEvent) => {
e.stopPropagation();
const container = chipsRef.current;
if (!container) return;
const scrollAmount = 150;
container.scrollLeft += direction === "left" ? -scrollAmount : scrollAmount;
setTimeout(checkScrollButtons, 100);
};
// 상태 텍스트 결정
const statusText =
isReturnWork && currentStatus === "pending-accept" ? "리턴" : STATUS_TEXT[workOrder.status];
const statusClass = isReturnWork ? "return" : workOrder.status;
// 완료된 공정 수
const completedCount = workOrder.processFlow.filter((s) => s.status === "completed").length;
const totalCount = workOrder.processFlow.length;
return (
<div
className={`pop-work-card ${isReturnWork ? "return-card" : ""}`}
onClick={onClick}
>
{/* 헤더 */}
<div className="pop-work-card-header">
<div style={{ display: "flex", alignItems: "center", gap: "var(--spacing-sm)", flex: 1, flexWrap: "wrap" }}>
<span className="pop-work-number">{workOrder.id}</span>
{isReturnWork && <span className="pop-return-badge"></span>}
{workOrder.acceptedQuantity && workOrder.acceptedQuantity > 0 && workOrder.acceptedQuantity < workOrder.orderQuantity && (
<span className="pop-partial-badge">
{workOrder.acceptedQuantity}/{workOrder.orderQuantity}
</span>
)}
</div>
<span className={`pop-work-status ${statusClass}`}>{statusText}</span>
{/* 액션 버튼 */}
{currentStatus === "pending-accept" && (
<div className="pop-work-card-actions">
<button
className="pop-btn pop-btn-sm pop-btn-primary"
onClick={(e) => {
e.stopPropagation();
onAccept();
}}
>
</button>
</div>
)}
{currentStatus === "in-progress" && (
<div className="pop-work-card-actions">
<button
className="pop-btn pop-btn-sm pop-btn-ghost"
onClick={(e) => {
e.stopPropagation();
onCancelAccept();
}}
>
</button>
<button
className="pop-btn pop-btn-sm pop-btn-success"
onClick={(e) => {
e.stopPropagation();
onStartProduction();
}}
>
</button>
</div>
)}
</div>
{/* 리턴 정보 배너 */}
{isReturnWork && currentStatus === "pending-accept" && (
<div className="pop-return-banner">
<span className="pop-return-banner-icon">🔄</span>
<div>
<div className="pop-return-banner-title">
{workOrder.returnFromProcessName}
</div>
<div className="pop-return-banner-reason">{workOrder.returnReason || "사유 없음"}</div>
</div>
</div>
)}
{/* 바디 */}
<div className="pop-work-card-body">
<div className="pop-work-info-line">
<div className="pop-work-info-item">
<span className="pop-work-info-label"></span>
<span className="pop-work-info-value">{workOrder.itemName}</span>
</div>
<div className="pop-work-info-item">
<span className="pop-work-info-label"></span>
<span className="pop-work-info-value">{workOrder.spec}</span>
</div>
<div className="pop-work-info-item">
<span className="pop-work-info-label"></span>
<span className="pop-work-info-value">{workOrder.orderQuantity}</span>
</div>
<div className="pop-work-info-item">
<span className="pop-work-info-label"></span>
<span className="pop-work-info-value">{workOrder.dueDate}</span>
</div>
</div>
</div>
{/* 공정 타임라인 */}
<div className="pop-process-timeline">
<div className="pop-process-bar">
<div className="pop-process-bar-header">
<span className="pop-process-bar-label"> </span>
<span className="pop-process-bar-count">
<span>{completedCount}</span>/{totalCount}
</span>
</div>
<div className="pop-process-segments">
{workOrder.processFlow.map((step, index) => {
let segmentClass = "";
if (step.status === "completed") segmentClass = "done";
else if (step.status === "current") segmentClass = "current";
if (selectedProcess && step.id === selectedProcess.id) {
segmentClass += " my-work";
}
return <div key={index} className={`pop-process-segment ${segmentClass}`} />;
})}
</div>
</div>
<div className="pop-process-chips-container">
<button
className={`pop-process-scroll-btn left ${!showLeftBtn ? "hidden" : ""}`}
onClick={(e) => handleScroll("left", e)}
>
</button>
<div className="pop-process-chips" ref={chipsRef}>
{workOrder.processFlow.map((step, index) => {
let chipClass = "";
if (step.status === "completed") chipClass = "done";
else if (step.status === "current") chipClass = "current";
if (selectedProcess && step.id === selectedProcess.id) {
chipClass += " my-work";
}
return (
<div key={index} className={`pop-process-chip ${chipClass}`}>
<span className="pop-chip-num">{index + 1}</span>
{step.name}
</div>
);
})}
</div>
<button
className={`pop-process-scroll-btn right ${!showRightBtn ? "hidden" : ""}`}
onClick={(e) => handleScroll("right", e)}
>
</button>
</div>
</div>
{/* 진행률 바 */}
{workOrder.status !== "completed" && (
<div className="pop-work-progress">
<div className="pop-progress-info">
<span className="pop-progress-text">
{workOrder.producedQuantity} / {workOrder.orderQuantity} EA
</span>
<span className="pop-progress-percent">{progress}%</span>
</div>
<div className="pop-progress-bar">
<div className="pop-progress-fill" style={{ width: `${progress}%` }} />
</div>
</div>
)}
</div>
);
}