|
- "use client";
-
- import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
- import {
- Box,
- Typography,
- Card,
- CardContent,
- Table,
- TableBody,
- TableCell,
- TableContainer,
- TableHead,
- TableRow,
- Paper,
- CircularProgress,
- TablePagination,
- } from '@mui/material';
- import { useTranslation } from 'react-i18next';
- import dayjs from 'dayjs';
- import { arrayToDayjs } from '@/app/utils/formatUtil';
- import { fetchMaterialPickStatus, MaterialPickStatusItem } from '@/app/api/jo/actions';
-
- const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes in milliseconds
-
- const MaterialPickStatusTable: React.FC = () => {
- const { t } = useTranslation("jo");
- const [data, setData] = useState<MaterialPickStatusItem[]>([]);
- const [loading, setLoading] = useState<boolean>(true);
- const refreshCountRef = useRef<number>(0);
- const [paginationController, setPaginationController] = useState({
- pageNum: 0,
- pageSize: 10,
- });
-
- const loadData = useCallback(async () => {
- setLoading(true);
- try {
- const result = await fetchMaterialPickStatus();
- // On second refresh, clear completed pick orders
- if (refreshCountRef.current >= 1) {
- // const filtered = result.filter(item =>
- // item.pickStatus?.toLowerCase() !== 'completed'
- //);
- setData(result);
- } else {
- setData(result || []);
- }
- refreshCountRef.current += 1;
- } catch (error) {
- console.error('Error fetching material pick status:', error);
- setData([]); // Set empty array on error to stop loading
- } finally {
- setLoading(false);
- }
- }, []); // Remove refreshCount from dependencies
-
- useEffect(() => {
- // Initial load
- loadData();
-
- // Set up auto-refresh every 10 minutes
- const interval = setInterval(() => {
- loadData();
- }, REFRESH_INTERVAL);
-
- return () => clearInterval(interval);
- }, [loadData]); // Only depend on loadData, which is now stable
-
- const formatTime = (timeData: any): string => {
- if (!timeData) return '';
-
- // Handle LocalDateTime ISO string format (e.g., "2026-01-09T18:01:54")
- if (typeof timeData === 'string') {
- // Try parsing as ISO string first (most common format from LocalDateTime)
- const parsed = dayjs(timeData);
- if (parsed.isValid()) {
- return parsed.format('HH:mm');
- }
-
- // Try parsing as custom format YYYYMMDDHHmmss
- const customParsed = dayjs(timeData, 'YYYYMMDDHHmmss');
- if (customParsed.isValid()) {
- return customParsed.format('HH:mm');
- }
-
- // Try parsing as time string (HH:mm or HH:mm:ss)
- const parts = timeData.split(':');
- if (parts.length >= 2) {
- const hour = parseInt(parts[0], 10);
- const minute = parseInt(parts[1] || '0', 10);
- if (!isNaN(hour) && !isNaN(minute)) {
- return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
- }
- }
- } else if (Array.isArray(timeData)) {
- // Handle array format [year, month, day, hour, minute, second]
- const hour = timeData[3] ?? timeData[0] ?? 0;
- const minute = timeData[4] ?? timeData[1] ?? 0;
- return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
- }
-
- return '';
- };
-
- const calculatePickTime = (startTime: any, endTime: any): number => {
- if (!startTime || !endTime) return 0;
-
- let start: dayjs.Dayjs;
- let end: dayjs.Dayjs;
-
- // Parse start time
- if (Array.isArray(startTime)) {
- // Array format: [year, month, day, hour, minute, second]
- if (startTime.length >= 5) {
- const year = startTime[0] || 0;
- const month = (startTime[1] || 1) - 1; // month is 0-indexed in JS Date
- const day = startTime[2] || 1;
- const hour = startTime[3] || 0;
- const minute = startTime[4] || 0;
- const second = startTime[5] || 0;
-
- // Create Date object and convert to dayjs
- const date = new Date(year, month, day, hour, minute, second);
- start = dayjs(date);
- console.log('Parsed start time:', {
- array: startTime,
- date: date.toISOString(),
- dayjs: start.format('YYYY-MM-DD HH:mm:ss'),
- isValid: start.isValid()
- });
- } else {
- // Fallback to arrayToDayjs for shorter arrays
- start = arrayToDayjs(startTime, true);
- }
- } else if (typeof startTime === 'string') {
- // Try ISO format first
- start = dayjs(startTime);
- if (!start.isValid()) {
- // Try custom format
- start = dayjs(startTime, 'YYYYMMDDHHmmss');
- }
- } else {
- start = dayjs(startTime);
- }
-
- // Parse end time
- if (Array.isArray(endTime)) {
- // Array format: [year, month, day, hour, minute, second]
- if (endTime.length >= 5) {
- const year = endTime[0] || 0;
- const month = (endTime[1] || 1) - 1; // month is 0-indexed in JS Date
- const day = endTime[2] || 1;
- const hour = endTime[3] || 0;
- const minute = endTime[4] || 0;
- const second = endTime[5] || 0;
-
- // Create Date object and convert to dayjs
- const date = new Date(year, month, day, hour, minute, second);
- end = dayjs(date);
- console.log('Parsed end time:', {
- array: endTime,
- date: date.toISOString(),
- dayjs: end.format('YYYY-MM-DD HH:mm:ss'),
- isValid: end.isValid()
- });
- } else {
- // Fallback to arrayToDayjs for shorter arrays
- end = arrayToDayjs(endTime, true);
- }
- } else if (typeof endTime === 'string') {
- // Try ISO format first
- end = dayjs(endTime);
- if (!end.isValid()) {
- // Try custom format
- end = dayjs(endTime, 'YYYYMMDDHHmmss');
- }
- } else {
- end = dayjs(endTime);
- }
-
- if (!start.isValid() || !end.isValid()) {
- console.warn('Invalid time values:', {
- startTime,
- endTime,
- startValid: start.isValid(),
- endValid: end.isValid(),
- startFormat: start.isValid() ? start.format() : 'invalid',
- endFormat: end.isValid() ? end.format() : 'invalid'
- });
- return 0;
- }
-
- // Calculate difference in seconds first, then convert to minutes
- // This handles sub-minute differences correctly
- const diffSeconds = end.diff(start, 'second');
- const diffMinutes = Math.ceil(diffSeconds / 60); // Round up to nearest minute
-
- console.log('Time calculation:', {
- start: start.format('YYYY-MM-DD HH:mm:ss'),
- end: end.format('YYYY-MM-DD HH:mm:ss'),
- diffSeconds,
- diffMinutes
- });
-
- return diffMinutes > 0 ? diffMinutes : 0;
- };
-
- const handlePageChange = useCallback((event: unknown, newPage: number) => {
- setPaginationController(prev => ({
- ...prev,
- pageNum: newPage,
- }));
- }, []);
-
- const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
- const newPageSize = parseInt(event.target.value, 10);
- setPaginationController({
- pageNum: 0,
- pageSize: newPageSize,
- });
- }, []);
-
- const paginatedData = useMemo(() => {
- const startIndex = paginationController.pageNum * paginationController.pageSize;
- const endIndex = startIndex + paginationController.pageSize;
- return data.slice(startIndex, endIndex);
- }, [data, paginationController]);
-
- return (
- <Card sx={{ mb: 2 }}>
- <CardContent>
- {/* Title */}
- <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
- <Typography variant="h5" sx={{ fontWeight: 600 }}>
- {t("Material Pick Status")}
- </Typography>
-
- </Box>
-
-
-
- <Box sx={{ mt: 2 }}>
- {loading ? (
- <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
- <CircularProgress />
- </Box>
- ) : (
- <>
- <TableContainer component={Paper}>
- <Table size="small" sx={{ minWidth: 650 }}>
- <TableHead>
- <TableRow>
- <TableCell>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
- {t("Pick Order No.- Job Order No.- Item")}
- </Typography>
-
- </Box>
- </TableCell>
-
- <TableCell>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
- {t("Job Order Qty")}
- </Typography>
-
- </Box>
- </TableCell>
-
- <TableCell>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
- {t("No. of Items to be Picked")}
- </Typography>
-
- </Box>
- </TableCell>
- <TableCell>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
- {t("No. of Items with Issue During Pick")}
- </Typography>
-
- </Box>
- </TableCell>
- <TableCell>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
- {t("Pick Start Time")}
- </Typography>
-
- </Box>
- </TableCell>
- <TableCell>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
- {t("Pick End Time")}
- </Typography>
-
- </Box>
- </TableCell>
- <TableCell sx={{
-
- }}>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
- {t("Pick Time Taken (minutes)")}
- </Typography>
-
- </Box>
- </TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {paginatedData.length === 0 ? (
- <TableRow>
- <TableCell colSpan={9} align="center">
- {t("No data available")}
- </TableCell>
- </TableRow>
- ) : (
- paginatedData.map((row) => {
- const pickTimeTaken = calculatePickTime(row.pickStartTime, row.pickEndTime);
-
- return (
- <TableRow key={row.id}>
- <TableCell>
- <Box> {row.pickOrderCode || '-'}</Box>
- <br />
- <Box>{row.jobOrderCode || '-'}</Box>
- <br />
- <Box>{row.itemCode || '-'} {row.itemName || '-'}</Box>
-
-
- </TableCell>
-
- <TableCell>
- {row.jobOrderQty !== null && row.jobOrderQty !== undefined
- ? `${row.jobOrderQty} ${row.uom || ''}`
- : '-'}
- </TableCell>
- <TableCell>{row.numberOfItemsToPick ?? 0}</TableCell>
- <TableCell>{row.numberOfItemsWithIssue ?? 0}</TableCell>
- <TableCell>{formatTime(row.pickStartTime) || '-'}</TableCell>
- <TableCell>{formatTime(row.pickEndTime) || '-'}</TableCell>
- <TableCell sx={{
- backgroundColor: 'rgba(76, 175, 80, 0.1)',
- fontWeight: 600
- }}>
- {pickTimeTaken > 0 ? `${pickTimeTaken} ${t("minutes")}` : '-'}
- </TableCell>
- </TableRow>
- );
- })
- )}
- </TableBody>
- </Table>
- </TableContainer>
- {data.length > 0 && (
- <TablePagination
- component="div"
- count={data.length}
- page={paginationController.pageNum}
- rowsPerPage={paginationController.pageSize}
- onPageChange={handlePageChange}
- onRowsPerPageChange={handlePageSizeChange}
- rowsPerPageOptions={[5, 10, 15, 25]}
- labelRowsPerPage={t("Rows per page")}
- />
- )}
- </>
- )}
- </Box>
- </CardContent>
- </Card>
- );
- };
-
- export default MaterialPickStatusTable;
|