347 lines
13 KiB
TypeScript
347 lines
13 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { X, Play, Square, ChevronRight } from "lucide-react";
|
|
import { WorkOrder, WorkStep } from "./types";
|
|
|
|
interface PopProductionPanelProps {
|
|
isOpen: boolean;
|
|
workOrder: WorkOrder | null;
|
|
workSteps: WorkStep[];
|
|
currentStepIndex: number;
|
|
currentDateTime: Date;
|
|
onStepChange: (index: number) => void;
|
|
onStepsUpdate: (steps: WorkStep[]) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function PopProductionPanel({
|
|
isOpen,
|
|
workOrder,
|
|
workSteps,
|
|
currentStepIndex,
|
|
currentDateTime,
|
|
onStepChange,
|
|
onStepsUpdate,
|
|
onClose,
|
|
}: PopProductionPanelProps) {
|
|
if (!isOpen || !workOrder) return null;
|
|
|
|
const currentStep = workSteps[currentStepIndex];
|
|
|
|
const formatDate = (date: Date) => {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
const formatTime = (date: Date | null) => {
|
|
if (!date) return "--:--";
|
|
const d = new Date(date);
|
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
};
|
|
|
|
const handleStartStep = () => {
|
|
const newSteps = [...workSteps];
|
|
newSteps[currentStepIndex] = {
|
|
...newSteps[currentStepIndex],
|
|
status: "in-progress",
|
|
startTime: new Date(),
|
|
};
|
|
onStepsUpdate(newSteps);
|
|
};
|
|
|
|
const handleEndStep = () => {
|
|
const newSteps = [...workSteps];
|
|
newSteps[currentStepIndex] = {
|
|
...newSteps[currentStepIndex],
|
|
endTime: new Date(),
|
|
};
|
|
onStepsUpdate(newSteps);
|
|
};
|
|
|
|
const handleSaveAndNext = () => {
|
|
const newSteps = [...workSteps];
|
|
const step = newSteps[currentStepIndex];
|
|
|
|
// 시간 자동 설정
|
|
if (!step.startTime) step.startTime = new Date();
|
|
if (!step.endTime) step.endTime = new Date();
|
|
step.status = "completed";
|
|
|
|
onStepsUpdate(newSteps);
|
|
|
|
// 다음 단계로 이동
|
|
if (currentStepIndex < workSteps.length - 1) {
|
|
onStepChange(currentStepIndex + 1);
|
|
}
|
|
};
|
|
|
|
const renderStepForm = () => {
|
|
if (!currentStep) return null;
|
|
|
|
const isCompleted = currentStep.status === "completed";
|
|
|
|
if (currentStep.type === "work" || currentStep.type === "record") {
|
|
return (
|
|
<div className="pop-step-form-section">
|
|
<h4 className="pop-step-form-title">작업 내용 입력</h4>
|
|
<div className="pop-form-row">
|
|
<div className="pop-form-group">
|
|
<label className="pop-form-label">생산수량</label>
|
|
<input
|
|
type="number"
|
|
className="pop-input"
|
|
placeholder="0"
|
|
disabled={isCompleted}
|
|
/>
|
|
</div>
|
|
<div className="pop-form-group">
|
|
<label className="pop-form-label">불량수량</label>
|
|
<input
|
|
type="number"
|
|
className="pop-input"
|
|
placeholder="0"
|
|
disabled={isCompleted}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="pop-form-group">
|
|
<label className="pop-form-label">비고</label>
|
|
<textarea
|
|
className="pop-input"
|
|
rows={2}
|
|
placeholder="특이사항을 입력하세요"
|
|
disabled={isCompleted}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (currentStep.type === "equipment-check" || currentStep.type === "inspection") {
|
|
return (
|
|
<div className="pop-step-form-section">
|
|
<h4 className="pop-step-form-title">점검 항목</h4>
|
|
<div style={{ display: "flex", flexDirection: "column", gap: "var(--spacing-sm)" }}>
|
|
<label style={{ display: "flex", alignItems: "center", gap: "var(--spacing-sm)" }}>
|
|
<input type="checkbox" disabled={isCompleted} />
|
|
<span>장비 상태 확인</span>
|
|
</label>
|
|
<label style={{ display: "flex", alignItems: "center", gap: "var(--spacing-sm)" }}>
|
|
<input type="checkbox" disabled={isCompleted} />
|
|
<span>안전 장비 착용</span>
|
|
</label>
|
|
<label style={{ display: "flex", alignItems: "center", gap: "var(--spacing-sm)" }}>
|
|
<input type="checkbox" disabled={isCompleted} />
|
|
<span>작업 환경 확인</span>
|
|
</label>
|
|
</div>
|
|
<div className="pop-form-group" style={{ marginTop: "var(--spacing-md)" }}>
|
|
<label className="pop-form-label">비고</label>
|
|
<textarea
|
|
className="pop-input"
|
|
rows={2}
|
|
placeholder="점검 결과를 입력하세요"
|
|
disabled={isCompleted}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="pop-step-form-section">
|
|
<h4 className="pop-step-form-title">작업 메모</h4>
|
|
<div className="pop-form-group">
|
|
<textarea
|
|
className="pop-input"
|
|
rows={3}
|
|
placeholder="메모를 입력하세요"
|
|
disabled={isCompleted}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="pop-slide-panel active">
|
|
<div className="pop-slide-panel-overlay" onClick={onClose} />
|
|
<div className="pop-slide-panel-content">
|
|
{/* 헤더 */}
|
|
<div className="pop-slide-panel-header">
|
|
<div style={{ display: "flex", alignItems: "center", gap: "var(--spacing-md)" }}>
|
|
<h2 className="pop-slide-panel-title">생산진행</h2>
|
|
<span className="pop-badge pop-badge-primary">{workOrder.processName}</span>
|
|
</div>
|
|
<div style={{ display: "flex", alignItems: "center", gap: "var(--spacing-md)" }}>
|
|
<div style={{ fontSize: "var(--text-xs)", color: "rgb(var(--text-muted))" }}>
|
|
<span>{formatDate(currentDateTime)}</span>
|
|
<span style={{ marginLeft: "var(--spacing-sm)", color: "rgb(var(--neon-cyan))", fontWeight: 700 }}>
|
|
{formatTime(currentDateTime)}
|
|
</span>
|
|
</div>
|
|
<button className="pop-icon-btn" onClick={onClose}>
|
|
<X size={16} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 작업지시 정보 */}
|
|
<div className="pop-work-order-info-section">
|
|
<div className="pop-work-order-info-card">
|
|
<div className="pop-work-order-info-item">
|
|
<span className="label">작업지시</span>
|
|
<span className="value primary">{workOrder.id}</span>
|
|
</div>
|
|
<div className="pop-work-order-info-item">
|
|
<span className="label">품목</span>
|
|
<span className="value">{workOrder.itemName}</span>
|
|
</div>
|
|
<div className="pop-work-order-info-item">
|
|
<span className="label">규격</span>
|
|
<span className="value">{workOrder.spec}</span>
|
|
</div>
|
|
<div className="pop-work-order-info-item">
|
|
<span className="label">지시수량</span>
|
|
<span className="value">{workOrder.orderQuantity} EA</span>
|
|
</div>
|
|
<div className="pop-work-order-info-item">
|
|
<span className="label">생산수량</span>
|
|
<span className="value">{workOrder.producedQuantity} EA</span>
|
|
</div>
|
|
<div className="pop-work-order-info-item">
|
|
<span className="label">납기일</span>
|
|
<span className="value">{workOrder.dueDate}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 바디 */}
|
|
<div className="pop-slide-panel-body">
|
|
<div className="pop-panel-body-content">
|
|
{/* 작업순서 사이드바 */}
|
|
<div className="pop-work-steps-sidebar">
|
|
<div className="pop-work-steps-header">작업순서</div>
|
|
<div className="pop-work-steps-list">
|
|
{workSteps.map((step, index) => (
|
|
<div
|
|
key={step.id}
|
|
className={`pop-work-step-item ${index === currentStepIndex ? "active" : ""} ${step.status}`}
|
|
onClick={() => onStepChange(index)}
|
|
>
|
|
<div className="pop-work-step-number">{index + 1}</div>
|
|
<div className="pop-work-step-info">
|
|
<div className="pop-work-step-name">{step.name}</div>
|
|
<div className="pop-work-step-time">
|
|
{formatTime(step.startTime)} ~ {formatTime(step.endTime)}
|
|
</div>
|
|
</div>
|
|
<span className={`pop-work-step-status ${step.status}`}>
|
|
{step.status === "completed" ? "완료" : step.status === "in-progress" ? "진행중" : "대기"}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 작업 콘텐츠 영역 */}
|
|
<div className="pop-work-content-area">
|
|
{currentStep && (
|
|
<>
|
|
{/* 스텝 헤더 */}
|
|
<div className="pop-step-header">
|
|
<h3 className="pop-step-title">{currentStep.name}</h3>
|
|
<p className="pop-step-description">{currentStep.description}</p>
|
|
</div>
|
|
|
|
{/* 시간 컨트롤 */}
|
|
{currentStep.status !== "completed" && (
|
|
<div className="pop-step-time-controls">
|
|
<button
|
|
className="pop-time-control-btn start"
|
|
onClick={handleStartStep}
|
|
disabled={!!currentStep.startTime}
|
|
>
|
|
<Play size={16} />
|
|
시작 {currentStep.startTime ? formatTime(currentStep.startTime) : ""}
|
|
</button>
|
|
<button
|
|
className="pop-time-control-btn end"
|
|
onClick={handleEndStep}
|
|
disabled={!currentStep.startTime || !!currentStep.endTime}
|
|
>
|
|
<Square size={16} />
|
|
종료 {currentStep.endTime ? formatTime(currentStep.endTime) : ""}
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* 폼 */}
|
|
{renderStepForm()}
|
|
|
|
{/* 액션 버튼 */}
|
|
{currentStep.status !== "completed" && (
|
|
<div style={{ marginTop: "auto", display: "flex", gap: "var(--spacing-md)" }}>
|
|
<button
|
|
className="pop-btn pop-btn-outline"
|
|
style={{ flex: 1 }}
|
|
onClick={() => onStepChange(Math.max(0, currentStepIndex - 1))}
|
|
disabled={currentStepIndex === 0}
|
|
>
|
|
이전
|
|
</button>
|
|
<button
|
|
className="pop-btn pop-btn-primary"
|
|
style={{ flex: 1 }}
|
|
onClick={handleSaveAndNext}
|
|
>
|
|
{currentStepIndex === workSteps.length - 1 ? "완료" : "저장 후 다음"}
|
|
<ChevronRight size={16} />
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* 완료 메시지 */}
|
|
{currentStep.status === "completed" && (
|
|
<div
|
|
style={{
|
|
padding: "var(--spacing-md)",
|
|
background: "rgba(0, 255, 136, 0.1)",
|
|
border: "1px solid rgba(0, 255, 136, 0.3)",
|
|
borderRadius: "var(--radius-md)",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "var(--spacing-sm)",
|
|
color: "rgb(var(--success))",
|
|
}}
|
|
>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<polyline points="20 6 9 17 4 12" />
|
|
</svg>
|
|
<span style={{ fontWeight: 600 }}>작업이 완료되었습니다</span>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 푸터 */}
|
|
<div className="pop-slide-panel-footer">
|
|
<button className="pop-btn pop-btn-outline" style={{ flex: 1 }} onClick={onClose}>
|
|
닫기
|
|
</button>
|
|
<button className="pop-btn pop-btn-primary" style={{ flex: 1 }}>
|
|
작업 완료
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|