"use client"; import React, { useCallback, useState } from "react"; import { Box, Typography, Skeleton, Alert, TextField } from "@mui/material"; import dynamic from "next/dynamic"; import dayjs from "dayjs"; import Assignment from "@mui/icons-material/Assignment"; import { fetchJobOrderByStatus, fetchJobOrderCountByDate, fetchJobOrderCreatedCompletedByDate, fetchJobMaterialPendingPickedByDate, fetchJobProcessPendingCompletedByDate, fetchJobEquipmentWorkingWorkedByDate, } from "@/app/api/chart/client"; import ChartCard from "../_components/ChartCard"; import DateRangeSelect from "../_components/DateRangeSelect"; import { toDateRange, DEFAULT_RANGE_DAYS } from "../_components/constants"; const ApexCharts = dynamic(() => import("react-apexcharts"), { ssr: false }); const PAGE_TITLE = "工單"; type Criteria = { joCountByDate: { rangeDays: number }; joCreatedCompleted: { rangeDays: number }; joDetail: { rangeDays: number }; }; const defaultCriteria: Criteria = { joCountByDate: { rangeDays: DEFAULT_RANGE_DAYS }, joCreatedCompleted: { rangeDays: DEFAULT_RANGE_DAYS }, joDetail: { rangeDays: DEFAULT_RANGE_DAYS }, }; export default function JobOrderChartPage() { const [joTargetDate, setJoTargetDate] = useState(() => dayjs().format("YYYY-MM-DD")); const [criteria, setCriteria] = useState(defaultCriteria); const [error, setError] = useState(null); const [chartData, setChartData] = useState<{ joStatus: { status: string; count: number }[]; joCountByDate: { date: string; orderCount: number }[]; joCreatedCompleted: { date: string; createdCount: number; completedCount: number }[]; joMaterial: { date: string; pendingCount: number; pickedCount: number }[]; joProcess: { date: string; pendingCount: number; completedCount: number }[]; joEquipment: { date: string; workingCount: number; workedCount: number }[]; }>({ joStatus: [], joCountByDate: [], joCreatedCompleted: [], joMaterial: [], joProcess: [], joEquipment: [], }); const [loadingCharts, setLoadingCharts] = useState>({}); const updateCriteria = useCallback( (key: K, updater: (prev: Criteria[K]) => Criteria[K]) => { setCriteria((prev) => ({ ...prev, [key]: updater(prev[key]) })); }, [] ); const setChartLoading = useCallback((key: string, value: boolean) => { setLoadingCharts((prev) => (prev[key] === value ? prev : { ...prev, [key]: value })); }, []); React.useEffect(() => { setChartLoading("joStatus", true); fetchJobOrderByStatus(joTargetDate) .then((data) => setChartData((prev) => ({ ...prev, joStatus: data as { status: string; count: number }[], })) ) .catch((err) => setError(err instanceof Error ? err.message : "Request failed")) .finally(() => setChartLoading("joStatus", false)); }, [joTargetDate, setChartLoading]); React.useEffect(() => { const { startDate: s, endDate: e } = toDateRange(criteria.joCountByDate.rangeDays); setChartLoading("joCountByDate", true); fetchJobOrderCountByDate(s, e) .then((data) => setChartData((prev) => ({ ...prev, joCountByDate: data as { date: string; orderCount: number }[], })) ) .catch((err) => setError(err instanceof Error ? err.message : "Request failed")) .finally(() => setChartLoading("joCountByDate", false)); }, [criteria.joCountByDate, setChartLoading]); React.useEffect(() => { const { startDate: s, endDate: e } = toDateRange(criteria.joCreatedCompleted.rangeDays); setChartLoading("joCreatedCompleted", true); fetchJobOrderCreatedCompletedByDate(s, e) .then((data) => setChartData((prev) => ({ ...prev, joCreatedCompleted: data as { date: string; createdCount: number; completedCount: number; }[], })) ) .catch((err) => setError(err instanceof Error ? err.message : "Request failed")) .finally(() => setChartLoading("joCreatedCompleted", false)); }, [criteria.joCreatedCompleted, setChartLoading]); React.useEffect(() => { const { startDate: s, endDate: e } = toDateRange(criteria.joDetail.rangeDays); setChartLoading("joMaterial", true); fetchJobMaterialPendingPickedByDate(s, e) .then((data) => setChartData((prev) => ({ ...prev, joMaterial: data as { date: string; pendingCount: number; pickedCount: number }[], })) ) .catch((err) => setError(err instanceof Error ? err.message : "Request failed")) .finally(() => setChartLoading("joMaterial", false)); }, [criteria.joDetail, setChartLoading]); React.useEffect(() => { const { startDate: s, endDate: e } = toDateRange(criteria.joDetail.rangeDays); setChartLoading("joProcess", true); fetchJobProcessPendingCompletedByDate(s, e) .then((data) => setChartData((prev) => ({ ...prev, joProcess: data as { date: string; pendingCount: number; completedCount: number }[], })) ) .catch((err) => setError(err instanceof Error ? err.message : "Request failed")) .finally(() => setChartLoading("joProcess", false)); }, [criteria.joDetail, setChartLoading]); React.useEffect(() => { const { startDate: s, endDate: e } = toDateRange(criteria.joDetail.rangeDays); setChartLoading("joEquipment", true); fetchJobEquipmentWorkingWorkedByDate(s, e) .then((data) => setChartData((prev) => ({ ...prev, joEquipment: data as { date: string; workingCount: number; workedCount: number }[], })) ) .catch((err) => setError(err instanceof Error ? err.message : "Request failed")) .finally(() => setChartLoading("joEquipment", false)); }, [criteria.joDetail, setChartLoading]); return ( {PAGE_TITLE} {error && ( setError(null)}> {error} )} ({ 狀態: p.status, 數量: p.count }))} filters={ setJoTargetDate(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 180 }} /> } > {loadingCharts.joStatus ? ( ) : ( p.status), legend: { position: "bottom" }, }} series={chartData.joStatus.map((p) => p.count)} type="donut" width="100%" height={320} /> )} ({ 日期: d.date, 工單數: d.orderCount }))} filters={ updateCriteria("joCountByDate", (c) => ({ ...c, rangeDays: v }))} /> } > {loadingCharts.joCountByDate ? ( ) : ( d.date) }, yaxis: { title: { text: "單數" } }, plotOptions: { bar: { columnWidth: "60%" } }, dataLabels: { enabled: false }, }} series={[{ name: "工單數", data: chartData.joCountByDate.map((d) => d.orderCount) }]} type="bar" width="100%" height={320} /> )} ({ 日期: d.date, 創建: d.createdCount, 完成: d.completedCount }))} filters={ updateCriteria("joCreatedCompleted", (c) => ({ ...c, rangeDays: v }))} /> } > {loadingCharts.joCreatedCompleted ? ( ) : ( d.date) }, yaxis: { title: { text: "數量" } }, stroke: { curve: "smooth" }, dataLabels: { enabled: false }, }} series={[ { name: "創建", data: chartData.joCreatedCompleted.map((d) => d.createdCount) }, { name: "完成", data: chartData.joCreatedCompleted.map((d) => d.completedCount) }, ]} type="line" width="100%" height={320} /> )} 工單物料/工序/設備 ({ 日期: d.date, 待領: d.pendingCount, 已揀: d.pickedCount }))} filters={ updateCriteria("joDetail", (c) => ({ ...c, rangeDays: v }))} /> } > {loadingCharts.joMaterial ? ( ) : ( d.date) }, yaxis: { title: { text: "筆數" } }, plotOptions: { bar: { columnWidth: "60%" } }, dataLabels: { enabled: false }, legend: { position: "top" }, }} series={[ { name: "待領", data: chartData.joMaterial.map((d) => d.pendingCount) }, { name: "已揀", data: chartData.joMaterial.map((d) => d.pickedCount) }, ]} type="bar" width="100%" height={320} /> )} ({ 日期: d.date, 待完成: d.pendingCount, 已完成: d.completedCount }))} filters={ updateCriteria("joDetail", (c) => ({ ...c, rangeDays: v }))} /> } > {loadingCharts.joProcess ? ( ) : ( d.date) }, yaxis: { title: { text: "筆數" } }, plotOptions: { bar: { columnWidth: "60%" } }, dataLabels: { enabled: false }, legend: { position: "top" }, }} series={[ { name: "待完成", data: chartData.joProcess.map((d) => d.pendingCount) }, { name: "已完成", data: chartData.joProcess.map((d) => d.completedCount) }, ]} type="bar" width="100%" height={320} /> )} ({ 日期: d.date, 使用中: d.workingCount, 已使用: d.workedCount }))} filters={ updateCriteria("joDetail", (c) => ({ ...c, rangeDays: v }))} /> } > {loadingCharts.joEquipment ? ( ) : ( d.date) }, yaxis: { title: { text: "筆數" } }, plotOptions: { bar: { columnWidth: "60%" } }, dataLabels: { enabled: false }, legend: { position: "top" }, }} series={[ { name: "使用中", data: chartData.joEquipment.map((d) => d.workingCount) }, { name: "已使用", data: chartData.joEquipment.map((d) => d.workedCount) }, ]} type="bar" width="100%" height={320} /> )} ); }