FPSMS-frontend
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

392 lines
16 KiB

  1. "use client";
  2. import React, { useState, useEffect, useCallback, useMemo } from 'react';
  3. import {
  4. Box,
  5. Typography,
  6. FormControl,
  7. InputLabel,
  8. Select,
  9. MenuItem,
  10. Card,
  11. CardContent,
  12. Stack,
  13. Table,
  14. TableBody,
  15. TableCell,
  16. TableContainer,
  17. TableHead,
  18. TableRow,
  19. Paper,
  20. CircularProgress,
  21. TablePagination,
  22. Chip
  23. } from '@mui/material';
  24. import { useTranslation } from 'react-i18next';
  25. import dayjs from 'dayjs';
  26. import { arrayToDayjs } from '@/app/utils/formatUtil';
  27. import { fetchTicketReleaseTable, getTicketReleaseTable } from '@/app/api/do/actions';
  28. const FGPickOrderTicketReleaseTable: React.FC = () => {
  29. const { t } = useTranslation("ticketReleaseTable");
  30. const [selectedDate, setSelectedDate] = useState<string>("today");
  31. const [selectedFloor, setSelectedFloor] = useState<string>("");
  32. const [selectedStatus, setSelectedStatus] = useState<string>("released");
  33. const [data, setData] = useState<getTicketReleaseTable[]>([]);
  34. const [loading, setLoading] = useState<boolean>(true);
  35. const [paginationController, setPaginationController] = useState({
  36. pageNum: 0,
  37. pageSize: 5,
  38. });
  39. const formatTime = (timeData: any): string => {
  40. if (!timeData) return '';
  41. let hour: number;
  42. let minute: number;
  43. if (typeof timeData === 'string') {
  44. const parts = timeData.split(':');
  45. hour = parseInt(parts[0], 10);
  46. minute = parseInt(parts[1] || '0', 10);
  47. } else if (Array.isArray(timeData)) {
  48. hour = timeData[0] || 0;
  49. minute = timeData[1] || 0;
  50. }
  51. else {
  52. return '';
  53. }
  54. const formattedHour = hour.toString().padStart(2, '0');
  55. const formattedMinute = minute.toString().padStart(2, '0');
  56. return `${formattedHour}:${formattedMinute}`;
  57. };
  58. const getDateLabel = (offset: number) => {
  59. return dayjs().add(offset, 'day').format('YYYY-MM-DD');
  60. };
  61. const getDateRange = () => {
  62. const today = dayjs().format('YYYY-MM-DD');
  63. const dayAfterTomorrow = dayjs().add(2, 'day').format('YYYY-MM-DD');
  64. return { startDate: today, endDate: dayAfterTomorrow };
  65. };
  66. useEffect(() => {
  67. const loadData = async () => {
  68. setLoading(true);
  69. try {
  70. const { startDate, endDate } = getDateRange();
  71. const result = await fetchTicketReleaseTable(startDate, endDate);
  72. setData(result);
  73. } catch (error) {
  74. console.error('Error fetching ticket release table:', error);
  75. } finally {
  76. setLoading(false);
  77. }
  78. };
  79. loadData();
  80. }, []);
  81. const filteredData = data.filter((item) => {
  82. // Filter by floor if selected
  83. if (selectedFloor && item.storeId !== selectedFloor) {
  84. return false;
  85. }
  86. // Filter by date if selected
  87. if (selectedDate && item.requiredDeliveryDate) {
  88. const itemDate = dayjs(item.requiredDeliveryDate).format('YYYY-MM-DD');
  89. const targetDate = getDateLabel(
  90. selectedDate === "today" ? 0 : selectedDate === "tomorrow" ? 1 : 2
  91. );
  92. if (itemDate !== targetDate) {
  93. return false;
  94. }
  95. }
  96. // Filter by status if selected
  97. if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) {
  98. return false;
  99. }
  100. return true;
  101. },[data, selectedDate, selectedFloor, selectedStatus]);
  102. const handlePageChange = useCallback((event: unknown, newPage: number) => {
  103. setPaginationController(prev => ({
  104. ...prev,
  105. pageNum: newPage,
  106. }));
  107. }, []);
  108. const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  109. const newPageSize = parseInt(event.target.value, 10);
  110. setPaginationController({
  111. pageNum: 0,
  112. pageSize: newPageSize,
  113. });
  114. }, []);
  115. const paginatedData = useMemo(() => {
  116. const startIndex = paginationController.pageNum * paginationController.pageSize;
  117. const endIndex = startIndex + paginationController.pageSize;
  118. return filteredData.slice(startIndex, endIndex);
  119. }, [filteredData, paginationController]);
  120. useEffect(() => {
  121. setPaginationController(prev => ({ ...prev, pageNum: 0 }));
  122. }, [selectedDate, selectedFloor, selectedStatus]);
  123. return (
  124. <Card sx={{ mb: 2 }}>
  125. <CardContent>
  126. {/* Title */}
  127. <Typography variant="h5" sx={{ mb: 3, fontWeight: 600 }}>
  128. {t("Ticket Release Table")}
  129. </Typography>
  130. {/* Dropdown Menus */}
  131. <Stack direction="row" spacing={2} sx={{ mb: 3 }}>
  132. <FormControl sx={{ minWidth: 250 }} size="small">
  133. <InputLabel id="date-select-label">{t("Select Date")}</InputLabel>
  134. <Select
  135. labelId="date-select-label"
  136. id="date-select"
  137. value={selectedDate}
  138. label={t("Select Date")}
  139. onChange={(e) => setSelectedDate(e.target.value)}
  140. >
  141. <MenuItem value="today">
  142. {t("Today")} ({getDateLabel(0)})
  143. </MenuItem>
  144. <MenuItem value="tomorrow">
  145. {t("Tomorrow")} ({getDateLabel(1)})
  146. </MenuItem>
  147. <MenuItem value="dayAfterTomorrow">
  148. {t("Day After Tomorrow")} ({getDateLabel(2)})
  149. </MenuItem>
  150. </Select>
  151. </FormControl>
  152. <FormControl sx={{ minWidth: 150 }} size="small">
  153. <InputLabel
  154. id="floor-select-label"
  155. shrink={true}
  156. >
  157. {t("Floor")}
  158. </InputLabel>
  159. <Select
  160. labelId="floor-select-label"
  161. id="floor-select"
  162. value={selectedFloor}
  163. label={t("Floor")}
  164. onChange={(e) => setSelectedFloor(e.target.value)}
  165. displayEmpty
  166. >
  167. <MenuItem value="">{t("All Floors")}</MenuItem>
  168. <MenuItem value="2/F">2/F</MenuItem>
  169. <MenuItem value="4/F">4/F</MenuItem>
  170. </Select>
  171. </FormControl>
  172. <FormControl sx={{ minWidth: 150 }} size="small">
  173. <InputLabel
  174. id="status-select-label"
  175. shrink={true}
  176. >
  177. {t("Status")}
  178. </InputLabel>
  179. <Select
  180. labelId="status-select-label"
  181. id="status-select"
  182. value={selectedStatus}
  183. label={t("Status")}
  184. onChange={(e) => setSelectedStatus(e.target.value)}
  185. displayEmpty
  186. >
  187. <MenuItem value="">{t("All Statuses")}</MenuItem>
  188. <MenuItem value="pending">{t("pending")}</MenuItem>
  189. <MenuItem value="released">{t("released")}</MenuItem>
  190. <MenuItem value="completed">{t("completed")}</MenuItem>
  191. </Select>
  192. </FormControl>
  193. </Stack>
  194. <Box sx={{ mt: 2 }}>
  195. {loading ? (
  196. <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
  197. <CircularProgress />
  198. </Box>
  199. ) : (
  200. <>
  201. <TableContainer component={Paper}>
  202. <Table size="small" sx={{ minWidth: 650 }}>
  203. <TableHead>
  204. <TableRow>
  205. <TableCell>{t("Store ID")}</TableCell>
  206. <TableCell>{t("Required Delivery Date")}</TableCell>
  207. <TableCell>
  208. <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
  209. <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
  210. {t("Truck Information")}
  211. </Typography>
  212. <Typography variant="caption" sx={{ color: 'text.secondary' }}>
  213. {t("Truck Lane Code")} - {t("Departure Time")}
  214. </Typography>
  215. </Box>
  216. </TableCell>
  217. {/*<TableCell>{t("Truck Departure Time")}</TableCell>
  218. <TableCell>{t("Truck Lane Code")}</TableCell>*/}
  219. <TableCell sx={{ minWidth: 200, width: '20%' }}>{t("Shop Name")}</TableCell>
  220. <TableCell>{t("Loading Sequence")}</TableCell>
  221. {/*<TableCell>{t("Delivery Order Code(s)")}</TableCell>
  222. <TableCell>{t("Pick Order Code(s)")}</TableCell>
  223. <TableCell>{t("Ticket Number")}</TableCell>
  224. <TableCell>{t("Ticket Release Time")}</TableCell>
  225. <TableCell>{t("Ticket Complete Date Time")}</TableCell>
  226. <TableCell>{t("Ticket Status")}</TableCell>*/}
  227. <TableCell>
  228. <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
  229. <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
  230. {t("Ticket Information")}
  231. </Typography>
  232. <Typography variant="caption" sx={{ color: 'text.secondary' }}>
  233. {t("Ticket No.")} ({t("Status")})
  234. </Typography>
  235. <Typography variant="caption" sx={{ color: 'text.secondary' }}>
  236. {t("Released Time")} - {t("Completed Time")}
  237. </Typography>
  238. </Box>
  239. </TableCell>
  240. <TableCell>{t("Handler Name")}</TableCell>
  241. <TableCell sx={{ minWidth: 100, width: '8%', whiteSpace: 'nowrap' }}>{t("Number of FG Items (Order Item(s) Count)")}</TableCell>
  242. </TableRow>
  243. </TableHead>
  244. <TableBody>
  245. {paginatedData.length === 0 ? (
  246. <TableRow>
  247. <TableCell colSpan={12} align="center">
  248. {t("No data available")}
  249. </TableCell>
  250. </TableRow>
  251. ) : (
  252. paginatedData.map((row) => {
  253. return (
  254. <TableRow key={row.id}>
  255. <TableCell>{row.storeId || '-'}</TableCell>
  256. <TableCell>
  257. {row.requiredDeliveryDate
  258. ? dayjs(row.requiredDeliveryDate).format('YYYY-MM-DD')
  259. : '-'}
  260. </TableCell>
  261. <TableCell>
  262. <Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', alignItems: 'center' }}>
  263. {row.truckLanceCode && (
  264. <Chip
  265. label={row.truckLanceCode}
  266. size="small"
  267. color="primary"
  268. />
  269. )}
  270. {row.truckDepartureTime && (
  271. <Chip
  272. label={formatTime(row.truckDepartureTime)}
  273. size="small"
  274. color="secondary"
  275. />
  276. )}
  277. {!row.truckLanceCode && !row.truckDepartureTime && (
  278. <Typography variant="body2" sx={{ color: 'text.secondary' }}>
  279. -
  280. </Typography>
  281. )}
  282. </Box>
  283. </TableCell>
  284. <TableCell sx={{ minWidth: 200, width: '20%' }}>{row.shopName || '-'}</TableCell>
  285. <TableCell>{row.loadingSequence || '-'}</TableCell>
  286. {/*<TableCell>{row.deliveryOrderCode || '-'}</TableCell>
  287. <TableCell>{row.pickOrderCode || '-'}</TableCell>
  288. <TableCell>{row.ticketNo || '-'}</TableCell>
  289. <TableCell>
  290. {row.ticketReleaseTime
  291. ? dayjs(row.ticketReleaseTime).format('YYYY-MM-DD HH:mm:ss')
  292. : '-'}
  293. </TableCell>
  294. <TableCell>
  295. {row.ticketCompleteDateTime
  296. ? dayjs(row.ticketCompleteDateTime).format('YYYY-MM-DD HH:mm:ss')
  297. : '-'}
  298. </TableCell>
  299. <TableCell>{row.ticketStatus || '-'}</TableCell>*/}
  300. <TableCell>
  301. <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
  302. <Typography variant="body2">
  303. {row.ticketNo || '-'} ({row.ticketStatus ? t(row.ticketStatus.toLowerCase()) : '-'})
  304. </Typography>
  305. <Typography variant="body2">
  306. {t("Released Time")}: {row.ticketReleaseTime
  307. ? (() => {
  308. if (Array.isArray(row.ticketReleaseTime)) {
  309. return arrayToDayjs(row.ticketReleaseTime, true).format('HH:mm');
  310. }
  311. const parsedDate = dayjs(row.ticketReleaseTime, 'YYYYMMDDHHmmss');
  312. if (!parsedDate.isValid()) {
  313. return dayjs(row.ticketReleaseTime).format('HH:mm');
  314. }
  315. return parsedDate.format('HH:mm');
  316. })()
  317. : '-'}
  318. </Typography>
  319. <Typography variant="body2">
  320. {t("Completed Time")}: {row.ticketCompleteDateTime
  321. ? (() => {
  322. if (Array.isArray(row.ticketCompleteDateTime)) {
  323. return arrayToDayjs(row.ticketCompleteDateTime, true).format('HH:mm');
  324. }
  325. const parsedDate = dayjs(row.ticketCompleteDateTime, 'YYYYMMDDHHmmss');
  326. if (!parsedDate.isValid()) {
  327. return dayjs(row.ticketCompleteDateTime).format('HH:mm');
  328. }
  329. return parsedDate.format('HH:mm');
  330. })()
  331. : '-'}
  332. </Typography>
  333. </Box>
  334. </TableCell>
  335. <TableCell>{row.handlerName || '-'}</TableCell>
  336. <TableCell sx={{ minWidth: 100, width: '8%' }}>{row.numberOfFGItems ?? 0}</TableCell>
  337. </TableRow>
  338. );
  339. })
  340. )}
  341. </TableBody>
  342. </Table>
  343. </TableContainer>
  344. {filteredData.length > 0 && (
  345. <TablePagination
  346. component="div"
  347. count={filteredData.length}
  348. page={paginationController.pageNum}
  349. rowsPerPage={paginationController.pageSize}
  350. onPageChange={handlePageChange}
  351. onRowsPerPageChange={handlePageSizeChange}
  352. rowsPerPageOptions={[5, 10, 15]}
  353. labelRowsPerPage={t("Rows per page")}
  354. />
  355. )}
  356. </>
  357. )}
  358. </Box>
  359. </CardContent>
  360. </Card>
  361. );
  362. };
  363. export default FGPickOrderTicketReleaseTable;