FPSMS-frontend
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

245 行
8.0 KiB

  1. import React, { useCallback } from 'react';
  2. import {
  3. Box,
  4. Typography,
  5. Table,
  6. TableBody,
  7. TableCell,
  8. TableContainer,
  9. TableHead,
  10. TableRow,
  11. Paper,
  12. Checkbox,
  13. TextField,
  14. TablePagination,
  15. FormControl,
  16. Select,
  17. MenuItem,
  18. } from '@mui/material';
  19. import { useTranslation } from 'react-i18next';
  20. import { OUTPUT_DATE_FORMAT } from '@/app/utils/formatUtil';
  21. import dayjs from 'dayjs';
  22. interface SearchItemWithQty {
  23. id: number;
  24. label: string;
  25. qty: number | null;
  26. currentStockBalance?: number;
  27. uomDesc?: string;
  28. targetDate?: string | null;
  29. groupId?: number | null;
  30. }
  31. interface Group {
  32. id: number;
  33. name: string;
  34. targetDate: string;
  35. }
  36. interface SearchResultsTableProps {
  37. items: SearchItemWithQty[];
  38. selectedItemIds: (string | number)[];
  39. groups: Group[];
  40. onItemSelect: (itemId: number, checked: boolean) => void;
  41. onQtyChange: (itemId: number, qty: number | null) => void;
  42. onQtyBlur: (itemId: number) => void;
  43. onGroupChange: (itemId: number, groupId: string) => void;
  44. isItemInCreated: (itemId: number) => boolean;
  45. pageNum: number;
  46. pageSize: number;
  47. onPageChange: (event: unknown, newPage: number) => void;
  48. onPageSizeChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  49. }
  50. const SearchResultsTable: React.FC<SearchResultsTableProps> = ({
  51. items,
  52. selectedItemIds,
  53. groups,
  54. onItemSelect,
  55. onQtyChange,
  56. onGroupChange,
  57. onQtyBlur,
  58. isItemInCreated,
  59. pageNum,
  60. pageSize,
  61. onPageChange,
  62. onPageSizeChange,
  63. }) => {
  64. const { t } = useTranslation("pickOrder");
  65. // Calculate pagination
  66. const startIndex = (pageNum - 1) * pageSize;
  67. const endIndex = startIndex + pageSize;
  68. const paginatedResults = items.slice(startIndex, endIndex);
  69. const handleQtyChange = useCallback((itemId: number, value: string) => {
  70. // Only allow numbers
  71. if (value === "" || /^\d+$/.test(value)) {
  72. const numValue = value === "" ? null : Number(value);
  73. onQtyChange(itemId, numValue);
  74. }
  75. }, [onQtyChange]);
  76. return (
  77. <>
  78. <TableContainer component={Paper}>
  79. <Table>
  80. <TableHead>
  81. <TableRow>
  82. <TableCell padding="checkbox" sx={{ width: '80px', minWidth: '80px' }}>
  83. {t("Selected")}
  84. </TableCell>
  85. <TableCell>
  86. {t("Item")}
  87. </TableCell>
  88. <TableCell>
  89. {t("Group")}
  90. </TableCell>
  91. <TableCell align="right">
  92. {t("Current Stock")}
  93. </TableCell>
  94. <TableCell align="right">
  95. {t("Stock Unit")}
  96. </TableCell>
  97. <TableCell align="right">
  98. {t("Order Quantity")}
  99. </TableCell>
  100. <TableCell align="right">
  101. {t("Target Date")}
  102. </TableCell>
  103. </TableRow>
  104. </TableHead>
  105. <TableBody>
  106. {paginatedResults.length === 0 ? (
  107. <TableRow>
  108. <TableCell colSpan={12} align="center">
  109. <Typography variant="body2" color="text.secondary">
  110. {t("No data available")}
  111. </Typography>
  112. </TableCell>
  113. </TableRow>
  114. ) : (
  115. paginatedResults.map((item) => (
  116. <TableRow key={item.id}>
  117. <TableCell padding="checkbox">
  118. <Checkbox
  119. checked={selectedItemIds.includes(item.id)}
  120. onChange={(e) => onItemSelect(item.id, e.target.checked)}
  121. disabled={isItemInCreated(item.id)}
  122. />
  123. </TableCell>
  124. {/* Item */}
  125. <TableCell>
  126. <Box>
  127. <Typography variant="body2">
  128. {item.label.split(' - ')[1] || item.label}
  129. </Typography>
  130. <Typography variant="caption" color="textSecondary">
  131. {item.label.split(' - ')[0] || ''}
  132. </Typography>
  133. </Box>
  134. </TableCell>
  135. {/* Group */}
  136. <TableCell>
  137. <FormControl size="small" sx={{ minWidth: 120 }}>
  138. <Select
  139. value={item.groupId?.toString() || ""}
  140. onChange={(e) => onGroupChange(item.id, e.target.value)}
  141. displayEmpty
  142. disabled={isItemInCreated(item.id)}
  143. >
  144. <MenuItem value="">
  145. <em>{t("No Group")}</em>
  146. </MenuItem>
  147. {groups.map((group) => (
  148. <MenuItem key={group.id} value={group.id.toString()}>
  149. {group.name}
  150. </MenuItem>
  151. ))}
  152. </Select>
  153. </FormControl>
  154. </TableCell>
  155. {/* Current Stock */}
  156. <TableCell align="right">
  157. <Typography
  158. variant="body2"
  159. color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
  160. sx={{ fontWeight: item.currentStockBalance && item.currentStockBalance > 0 ? 'bold' : 'normal' }}
  161. >
  162. {item.currentStockBalance?.toLocaleString()||0}
  163. </Typography>
  164. </TableCell>
  165. {/* Stock Unit */}
  166. <TableCell align="right">
  167. <Typography variant="body2">
  168. {item.uomDesc || "-"}
  169. </Typography>
  170. </TableCell>
  171. <TableCell align="right">
  172. {/* Order Quantity */}
  173. <TextField
  174. type="number"
  175. size="small"
  176. value={item.qty || ""}
  177. onChange={(e) => {
  178. const value = e.target.value;
  179. // Only allow numbers
  180. if (value === "" || /^\d+$/.test(value)) {
  181. const numValue = value === "" ? null : Number(value);
  182. onQtyChange(item.id, numValue);
  183. }
  184. }}
  185. onBlur={() => {
  186. // Trigger auto-add check when user finishes input (clicks elsewhere)
  187. onQtyBlur(item.id); // ← Change this to call onQtyBlur instead!
  188. }}
  189. inputProps={{
  190. style: { textAlign: 'center' }
  191. }}
  192. sx={{
  193. width: '80px',
  194. '& .MuiInputBase-input': {
  195. textAlign: 'center',
  196. cursor: 'text'
  197. }
  198. }}
  199. disabled={isItemInCreated(item.id)}
  200. />
  201. </TableCell>
  202. {/* Target Date */}
  203. <TableCell align="right">
  204. <Typography variant="body2">
  205. {item.targetDate ? dayjs(item.targetDate).format(OUTPUT_DATE_FORMAT) : "-"}
  206. </Typography>
  207. </TableCell>
  208. </TableRow>
  209. ))
  210. )}
  211. </TableBody>
  212. </Table>
  213. </TableContainer>
  214. <TablePagination
  215. component="div"
  216. count={items.length}
  217. page={(pageNum - 1)}
  218. rowsPerPage={pageSize}
  219. onPageChange={onPageChange}
  220. onRowsPerPageChange={onPageSizeChange}
  221. rowsPerPageOptions={[10, 25, 50]}
  222. labelRowsPerPage={t("Rows per page")}
  223. labelDisplayedRows={({ from, to, count }) =>
  224. `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
  225. }
  226. />
  227. </>
  228. );
  229. };
  230. export default SearchResultsTable;