FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

213 line
7.4 KiB

  1. import { useUiText } from "@/i18n/useUiText";
  2. import React, { useState } from 'react';
  3. import {
  4. Box,
  5. Paper,
  6. Table,
  7. TableBody,
  8. TableCell,
  9. TableContainer,
  10. TableHead,
  11. TableRow,
  12. Typography,
  13. Chip,
  14. Card,
  15. CardContent,
  16. } from '@mui/material';
  17. import {
  18. useReactTable,
  19. getCoreRowModel,
  20. createColumnHelper,
  21. flexRender,
  22. } from '@tanstack/react-table';
  23. import DescriptionIcon from '@mui/icons-material/Description';
  24. import CheckCircleIcon from '@mui/icons-material/CheckCircle';
  25. import CancelIcon from '@mui/icons-material/Cancel';
  26. import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
  27. interface QCReport {
  28. id: string;
  29. productName: string;
  30. isQualified: boolean;
  31. remarks: string;
  32. reportDate: string;
  33. batchNumber: string;
  34. inspector: string;
  35. testResults: string;
  36. specifications: string;
  37. }
  38. interface QCReportFormProps {
  39. reports: QCReport[];
  40. }
  41. function QCReportForm({ reports }: QCReportFormProps): JSX.Element {
  42. const L = useUiText();
  43. const [selectedReport, setSelectedReport] = useState<QCReport | null>(null);
  44. const handleReportClick = (report: QCReport): void => {
  45. setSelectedReport(report);
  46. };
  47. const getQualifiedStatus = (isQualified: boolean) => {
  48. return isQualified ? (
  49. <Chip
  50. icon={<CheckCircleIcon />}
  51. label={L("合格", "Pass")}
  52. color="success"
  53. size="small"
  54. sx={{ fontWeight: 'medium' }}
  55. />
  56. ) : (
  57. <Chip
  58. icon={<CancelIcon />}
  59. label={L("不合格", "Fail")}
  60. color="error"
  61. size="small"
  62. sx={{ fontWeight: 'medium' }}
  63. />
  64. );
  65. };
  66. const columnHelper = createColumnHelper<QCReport>();
  67. const columns = [
  68. // columnHelper.accessor('productName', {
  69. // header: '產品',
  70. // cell: info => (
  71. // <Typography variant="body2" fontWeight="medium">
  72. // {info.getValue()}
  73. // </Typography>
  74. // ),
  75. // }),
  76. columnHelper.accessor('isQualified', {
  77. header: L('合格', 'Pass'),
  78. cell: info => getQualifiedStatus(info.getValue()),
  79. }),
  80. columnHelper.accessor('remarks', {
  81. header: L('備註', 'Remarks'),
  82. cell: info => (
  83. <Typography
  84. variant="body2"
  85. color="text.secondary"
  86. sx={{ maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis' }}
  87. >
  88. {info.getValue()}
  89. </Typography>
  90. ),
  91. }),
  92. columnHelper.accessor('reportDate', {
  93. header: L('上報日期', 'Escalation date'),
  94. cell: info => (
  95. <Typography variant="body2" color="text.secondary">
  96. {info.getValue()}
  97. </Typography>
  98. ),
  99. }),
  100. ];
  101. const table = useReactTable({
  102. data: reports,
  103. columns,
  104. getCoreRowModel: getCoreRowModel(),
  105. });
  106. return (
  107. <Box sx={{ maxWidth: '1200px', mx: 'auto', p: 3 }}>
  108. {/* Table Section */}
  109. <Box>
  110. <Typography variant="h6" fontWeight="medium" sx={{ mb: 2 }}>
  111. {L("上報資料", "Escalation records")}
  112. </Typography>
  113. <TableContainer component={Paper} elevation={2}>
  114. <Table>
  115. <TableHead sx={{ bgcolor: 'grey.50' }}>
  116. {table.getHeaderGroups().map(headerGroup => (
  117. <TableRow key={headerGroup.id}>
  118. {headerGroup.headers.map(header => (
  119. <TableCell
  120. key={header.id}
  121. sx={{
  122. fontSize: '0.75rem',
  123. fontWeight: 'medium',
  124. color: 'text.secondary',
  125. textTransform: 'uppercase',
  126. py: 2,
  127. px: 3,
  128. }}
  129. >
  130. {header.isPlaceholder
  131. ? null
  132. : flexRender(header.column.columnDef.header, header.getContext())}
  133. </TableCell>
  134. ))}
  135. </TableRow>
  136. ))}
  137. </TableHead>
  138. <TableBody>
  139. {table.getRowModel().rows.map(row => (
  140. <TableRow
  141. key={row.id}
  142. onClick={() => handleReportClick(row.original)}
  143. sx={{
  144. cursor: 'pointer',
  145. '&:hover': { bgcolor: 'grey.50' },
  146. bgcolor: selectedReport?.id === row.original.id ? 'primary.light' : 'inherit',
  147. transition: 'background-color 0.15s',
  148. }}
  149. >
  150. {row.getVisibleCells().map(cell => (
  151. <TableCell key={cell.id} sx={{ py: 2, px: 3 }}>
  152. {flexRender(cell.column.columnDef.cell, cell.getContext())}
  153. </TableCell>
  154. ))}
  155. </TableRow>
  156. ))}
  157. </TableBody>
  158. </Table>
  159. </TableContainer>
  160. </Box>
  161. </Box>
  162. );
  163. }
  164. // Dummy data
  165. const dummyReports = [
  166. {
  167. id: '1',
  168. productName: '無線藍牙耳機',
  169. isQualified: false,
  170. remarks: '包裝有破損',
  171. reportDate: '2024-08-06',
  172. batchNumber: 'WBE-240806-002',
  173. inspector: '李測試',
  174. testResults: '音質測試:左右聲道音量差異>3dB,不符合標準',
  175. specifications: '頻響範圍20Hz-20kHz,左右聲道音量差≤1dB,電池續航≥8小時',
  176. },
  177. {
  178. id: '2',
  179. productName: '筆記型電腦',
  180. isQualified: true,
  181. remarks: '經檢查,無損壞',
  182. reportDate: '2024-08-05',
  183. batchNumber: 'NB-240805-003',
  184. inspector: '王檢驗',
  185. testResults: '溫度測試:CPU最高溫度75°C,性能測試:符合預期',
  186. specifications: 'CPU溫度≤85°C,開機時間≤30秒,電池續航≥6小時',
  187. },
  188. ];
  189. // Main component
  190. function EscalationLog(): JSX.Element {
  191. const L = useUiText();
  192. const reports = dummyReports.map((row) => ({
  193. ...row,
  194. remarks:
  195. row.id === "1"
  196. ? L("包裝有破損", "Packaging is damaged")
  197. : L("經檢查,無損壞", "Inspected; no damage"),
  198. }));
  199. return <QCReportForm reports={reports} />;
  200. };
  201. export default EscalationLog;