"use client"; import React, { useState, useEffect, useMemo } from "react"; import { Box, Paper, Typography, Button, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, CircularProgress, Tooltip } from "@mui/material"; import { Search, Visibility, ListAlt, CalendarMonth, OnlinePrediction, FileDownload, SettingsEthernet } from "@mui/icons-material"; import dayjs from "dayjs"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; export default function ProductionSchedulePage() { // --- 1. States --- const [searchDate, setSearchDate] = useState(dayjs().format('YYYY-MM-DD')); const [schedules, setSchedules] = useState([]); const [selectedLines, setSelectedLines] = useState([]); const [isDetailOpen, setIsDetailOpen] = useState(false); const [selectedPs, setSelectedPs] = useState(null); const [loading, setLoading] = useState(false); const [isGenerating, setIsGenerating] = useState(false); // --- 2. Auto-search on page entry --- useEffect(() => { handleSearch(); }, []); // --- 3. Formatters & Helpers --- // Handles [YYYY, MM, DD] format from Kotlin/Java LocalDate const formatBackendDate = (dateVal: any) => { if (Array.isArray(dateVal)) { const [year, month, day] = dateVal; return dayjs(new Date(year, month - 1, day)).format('DD MMM (dddd)'); } return dayjs(dateVal).format('DD MMM (dddd)'); }; // Adds commas as thousands separators const formatNum = (num: any) => { return new Intl.NumberFormat('en-US').format(Number(num) || 0); }; // Logic to determine if the selected row's produceAt is TODAY const isDateToday = useMemo(() => { if (!selectedPs?.produceAt) return false; const todayStr = dayjs().format('YYYY-MM-DD'); let scheduleDateStr = ""; if (Array.isArray(selectedPs.produceAt)) { const [y, m, d] = selectedPs.produceAt; scheduleDateStr = dayjs(new Date(y, m - 1, d)).format('YYYY-MM-DD'); } else { scheduleDateStr = dayjs(selectedPs.produceAt).format('YYYY-MM-DD'); } return todayStr === scheduleDateStr; }, [selectedPs]); // --- 4. API Actions --- // Main Grid Query const handleSearch = async () => { const token = localStorage.getItem("accessToken"); setLoading(true); try { const response = await fetch(`${NEXT_PUBLIC_API_URL}/ps/search-ps?produceAt=${searchDate}`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); setSchedules(Array.isArray(data) ? data : []); } catch (e) { console.error("Search Error:", e); } finally { setLoading(false); } }; // Forecast API const handleForecast = async () => { const token = localStorage.getItem("accessToken"); setLoading(true); try { const response = await fetch(`${NEXT_PUBLIC_API_URL}/productionSchedule/testDetailedSchedule`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { await handleSearch(); // Refresh grid after successful forecast } } catch (e) { console.error("Forecast Error:", e); } finally { setLoading(false); } }; // Export Excel API const handleExport = async () => { const token = localStorage.getItem("accessToken"); try { const response = await fetch(`${NEXT_PUBLIC_API_URL}/ps/export-prod-schedule`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error("Export failed"); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `production_schedule_${dayjs().format('YYYYMMDD')}.xlsx`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (e) { console.error("Export Error:", e); } }; // Get Detail Lines const handleViewDetail = async (ps: any) => { const token = localStorage.getItem("accessToken"); setSelectedPs(ps); try { const response = await fetch(`${NEXT_PUBLIC_API_URL}/ps/search-ps-line?psId=${ps.id}`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); setSelectedLines(data || []); setIsDetailOpen(true); } catch (e) { console.error("Detail Error:", e); } }; // Auto Gen Job API (Only allowed for Today's date) const handleAutoGenJob = async () => { if (!isDateToday) return; const token = localStorage.getItem("accessToken"); setIsGenerating(true); try { const response = await fetch(`${NEXT_PUBLIC_API_URL}/productionSchedule/detail/detailed/release`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ id: selectedPs.id }) }); if (response.ok) { alert("Job Orders generated successfully!"); setIsDetailOpen(false); } else { alert("Failed to generate jobs."); } } catch (e) { console.error("Release Error:", e); } finally { setIsGenerating(false); } }; return ( {/* Top Header Buttons */} Production Planning {/* Query Bar */} setSearchDate(e.target.value)} /> {/* Main Grid Table */} Action ID Production Date Est. Prod Count Total FG Types {schedules.map((ps) => ( handleViewDetail(ps)}> #{ps.id} {formatBackendDate(ps.produceAt)} {formatNum(ps.totalEstProdCount)} {formatNum(ps.totalFGType)} ))}
{/* Detailed Lines Dialog */} setIsDetailOpen(false)} maxWidth="lg" fullWidth> Schedule Details: {selectedPs?.id} ({formatBackendDate(selectedPs?.produceAt)}) Job Order Item Code Item Name Avg Last Month Stock Days Left Batch Need Prod Qty Priority {selectedLines.map((line: any) => ( {line.joCode || '-'} {line.itemCode} {line.itemName} {formatNum(line.avgQtyLastMonth)} {formatNum(line.stockQty)} {line.daysLeft} {formatNum(line.batchNeed)} {formatNum(line.prodQty)} {line.itemPriority} ))}
{/* Footer Actions */}
); }