|
|
@@ -0,0 +1,338 @@ |
|
|
|
import React, { useState } from 'react'; |
|
|
|
import { |
|
|
|
Box, |
|
|
|
Paper, |
|
|
|
Table, |
|
|
|
TableBody, |
|
|
|
TableCell, |
|
|
|
TableContainer, |
|
|
|
TableHead, |
|
|
|
TableRow, |
|
|
|
Typography, |
|
|
|
Chip, |
|
|
|
Card, |
|
|
|
CardContent, |
|
|
|
} from '@mui/material'; |
|
|
|
import { |
|
|
|
useReactTable, |
|
|
|
getCoreRowModel, |
|
|
|
createColumnHelper, |
|
|
|
flexRender, |
|
|
|
} from '@tanstack/react-table'; |
|
|
|
import DescriptionIcon from '@mui/icons-material/Description'; |
|
|
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle'; |
|
|
|
import CancelIcon from '@mui/icons-material/Cancel'; |
|
|
|
import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; |
|
|
|
|
|
|
|
interface QCReport { |
|
|
|
id: string; |
|
|
|
productName: string; |
|
|
|
isQualified: boolean; |
|
|
|
remarks: string; |
|
|
|
reportDate: string; |
|
|
|
batchNumber: string; |
|
|
|
inspector: string; |
|
|
|
testResults: string; |
|
|
|
specifications: string; |
|
|
|
} |
|
|
|
|
|
|
|
interface QCReportFormProps { |
|
|
|
reports: QCReport[]; |
|
|
|
} |
|
|
|
|
|
|
|
function QCReportForm({ reports }: QCReportFormProps): JSX.Element { |
|
|
|
const [selectedReport, setSelectedReport] = useState<QCReport | null>(null); |
|
|
|
|
|
|
|
const handleReportClick = (report: QCReport): void => { |
|
|
|
setSelectedReport(report); |
|
|
|
}; |
|
|
|
|
|
|
|
const getQualifiedStatus = (isQualified: boolean) => { |
|
|
|
return isQualified ? ( |
|
|
|
<Chip |
|
|
|
icon={<CheckCircleIcon />} |
|
|
|
label="合格" |
|
|
|
color="success" |
|
|
|
size="small" |
|
|
|
sx={{ fontWeight: 'medium' }} |
|
|
|
/> |
|
|
|
) : ( |
|
|
|
<Chip |
|
|
|
icon={<CancelIcon />} |
|
|
|
label="不合格" |
|
|
|
color="error" |
|
|
|
size="small" |
|
|
|
sx={{ fontWeight: 'medium' }} |
|
|
|
/> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
const columnHelper = createColumnHelper<QCReport>(); |
|
|
|
|
|
|
|
const columns = [ |
|
|
|
// columnHelper.accessor('productName', { |
|
|
|
// header: '產品', |
|
|
|
// cell: info => ( |
|
|
|
// <Typography variant="body2" fontWeight="medium"> |
|
|
|
// {info.getValue()} |
|
|
|
// </Typography> |
|
|
|
// ), |
|
|
|
// }), |
|
|
|
columnHelper.accessor('isQualified', { |
|
|
|
header: '合格', |
|
|
|
cell: info => getQualifiedStatus(info.getValue()), |
|
|
|
}), |
|
|
|
columnHelper.accessor('remarks', { |
|
|
|
header: '備註', |
|
|
|
cell: info => ( |
|
|
|
<Typography |
|
|
|
variant="body2" |
|
|
|
color="text.secondary" |
|
|
|
sx={{ maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis' }} |
|
|
|
> |
|
|
|
{info.getValue()} |
|
|
|
</Typography> |
|
|
|
), |
|
|
|
}), |
|
|
|
columnHelper.accessor('reportDate', { |
|
|
|
header: '上報日期', |
|
|
|
cell: info => ( |
|
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
|
{info.getValue()} |
|
|
|
</Typography> |
|
|
|
), |
|
|
|
}), |
|
|
|
]; |
|
|
|
|
|
|
|
const table = useReactTable({ |
|
|
|
data: reports, |
|
|
|
columns, |
|
|
|
getCoreRowModel: getCoreRowModel(), |
|
|
|
}); |
|
|
|
|
|
|
|
return ( |
|
|
|
<Box sx={{ maxWidth: '1200px', mx: 'auto', p: 3 }}> |
|
|
|
{/* Title */} |
|
|
|
{/* <Box sx={{ mb: 4 }}> |
|
|
|
<Typography variant="h5" fontWeight="bold" color="text.primary"> |
|
|
|
品質控制上報系統 |
|
|
|
</Typography> |
|
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
|
品質檢測結果上報與管理 |
|
|
|
</Typography> |
|
|
|
</Box> */} |
|
|
|
|
|
|
|
{/* <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', lg: '2fr 1fr' }, gap: 3 }}> */} |
|
|
|
<Box sx={{ display: 'grid' }}> |
|
|
|
{/* Table Section */} |
|
|
|
<Box> |
|
|
|
<Typography variant="h6" fontWeight="medium" sx={{ mb: 2 }}> |
|
|
|
上報資料 |
|
|
|
</Typography> |
|
|
|
<TableContainer component={Paper} elevation={2}> |
|
|
|
<Table> |
|
|
|
<TableHead sx={{ bgcolor: 'grey.50' }}> |
|
|
|
{table.getHeaderGroups().map(headerGroup => ( |
|
|
|
<TableRow key={headerGroup.id}> |
|
|
|
{headerGroup.headers.map(header => ( |
|
|
|
<TableCell |
|
|
|
key={header.id} |
|
|
|
sx={{ |
|
|
|
fontSize: '0.75rem', |
|
|
|
fontWeight: 'medium', |
|
|
|
color: 'text.secondary', |
|
|
|
textTransform: 'uppercase', |
|
|
|
py: 2, |
|
|
|
px: 3, |
|
|
|
}} |
|
|
|
> |
|
|
|
{header.isPlaceholder |
|
|
|
? null |
|
|
|
: flexRender(header.column.columnDef.header, header.getContext())} |
|
|
|
</TableCell> |
|
|
|
))} |
|
|
|
</TableRow> |
|
|
|
))} |
|
|
|
</TableHead> |
|
|
|
<TableBody> |
|
|
|
{table.getRowModel().rows.map(row => ( |
|
|
|
<TableRow |
|
|
|
key={row.id} |
|
|
|
onClick={() => handleReportClick(row.original)} |
|
|
|
sx={{ |
|
|
|
cursor: 'pointer', |
|
|
|
'&:hover': { bgcolor: 'grey.50' }, |
|
|
|
bgcolor: selectedReport?.id === row.original.id ? 'primary.light' : 'inherit', |
|
|
|
transition: 'background-color 0.15s', |
|
|
|
}} |
|
|
|
> |
|
|
|
{row.getVisibleCells().map(cell => ( |
|
|
|
<TableCell key={cell.id} sx={{ py: 2, px: 3 }}> |
|
|
|
{flexRender(cell.column.columnDef.cell, cell.getContext())} |
|
|
|
</TableCell> |
|
|
|
))} |
|
|
|
</TableRow> |
|
|
|
))} |
|
|
|
</TableBody> |
|
|
|
</Table> |
|
|
|
</TableContainer> |
|
|
|
</Box> |
|
|
|
|
|
|
|
{/* Details Section */} |
|
|
|
{/* <Box> |
|
|
|
<Typography variant="h6" fontWeight="medium" sx={{ mb: 2 }}> |
|
|
|
詳細資訊 |
|
|
|
</Typography> |
|
|
|
<Card elevation={2} sx={{ minHeight: '300px', bgcolor: 'grey.50' }}> |
|
|
|
<CardContent> |
|
|
|
{selectedReport ? ( |
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> |
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, color: 'primary.main', mb: 2 }}> |
|
|
|
<DescriptionIcon /> |
|
|
|
<Typography variant="subtitle1" fontWeight="medium"> |
|
|
|
上報詳情 |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary" fontWeight="medium"> |
|
|
|
產品名稱: |
|
|
|
</Typography> |
|
|
|
<Typography variant="body2">{selectedReport.productName}</Typography> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary" fontWeight="medium"> |
|
|
|
檢測結果: |
|
|
|
</Typography> |
|
|
|
<Box sx={{ mt: 1 }}>{getQualifiedStatus(selectedReport.isQualified)}</Box> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary" fontWeight="medium"> |
|
|
|
批次號碼: |
|
|
|
</Typography> |
|
|
|
<Typography variant="body2">{selectedReport.batchNumber}</Typography> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary" fontWeight="medium"> |
|
|
|
檢驗員: |
|
|
|
</Typography> |
|
|
|
<Typography variant="body2">{selectedReport.inspector}</Typography> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> |
|
|
|
<CalendarTodayIcon fontSize="small" sx={{ color: 'text.secondary' }} /> |
|
|
|
<Typography variant="body2"> |
|
|
|
上報日期:{selectedReport.reportDate} |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary" fontWeight="medium" sx={{ mb: 1 }}> |
|
|
|
檢測規格: |
|
|
|
</Typography> |
|
|
|
<Paper sx={{ p: 1, bgcolor: 'background.paper' }}> |
|
|
|
<Typography variant="body2">{selectedReport.specifications}</Typography> |
|
|
|
</Paper> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary" fontWeight="medium" sx={{ mb: 1 }}> |
|
|
|
檢測結果詳情: |
|
|
|
</Typography> |
|
|
|
<Paper sx={{ p: 1, bgcolor: 'background.paper' }}> |
|
|
|
<Typography variant="body2">{selectedReport.testResults}</Typography> |
|
|
|
</Paper> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary" fontWeight="medium" sx={{ mb: 1 }}> |
|
|
|
備註 (REMARKS): |
|
|
|
</Typography> |
|
|
|
<Paper sx={{ p: 1, bgcolor: 'background.paper' }}> |
|
|
|
<Typography variant="body2">{selectedReport.remarks}</Typography> |
|
|
|
</Paper> |
|
|
|
</Box> |
|
|
|
</Box> |
|
|
|
) : ( |
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}> |
|
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
|
請點擊表格中的記錄來查看詳細資訊 |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
)} |
|
|
|
</CardContent> |
|
|
|
</Card> |
|
|
|
</Box> */} |
|
|
|
</Box> |
|
|
|
</Box> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// Dummy data |
|
|
|
const dummyReports = [ |
|
|
|
// { |
|
|
|
// id: '1', |
|
|
|
// productName: '智慧手機 Pro Max', |
|
|
|
// isQualified: true, |
|
|
|
// remarks: '所有檢測項目均符合標準規格', |
|
|
|
// reportDate: '2024-08-07', |
|
|
|
// batchNumber: 'SPM-240807-001', |
|
|
|
// inspector: '張品質', |
|
|
|
// testResults: '外觀檢查:無瑕疵,功能測試:100%通過,性能測試:符合標準', |
|
|
|
// specifications: '螢幕解析度≥1080p,電池續航≥24小時,防水等級IPX7', |
|
|
|
// }, |
|
|
|
{ |
|
|
|
id: '2', |
|
|
|
productName: '無線藍牙耳機', |
|
|
|
isQualified: false, |
|
|
|
remarks: '包裝有破損', |
|
|
|
reportDate: '2024-08-06', |
|
|
|
batchNumber: 'WBE-240806-002', |
|
|
|
inspector: '李測試', |
|
|
|
testResults: '音質測試:左右聲道音量差異>3dB,不符合標準', |
|
|
|
specifications: '頻響範圍20Hz-20kHz,左右聲道音量差≤1dB,電池續航≥8小時', |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: '3', |
|
|
|
productName: '筆記型電腦', |
|
|
|
isQualified: true, |
|
|
|
remarks: '經檢查,無損壞', |
|
|
|
reportDate: '2024-08-05', |
|
|
|
batchNumber: 'NB-240805-003', |
|
|
|
inspector: '王檢驗', |
|
|
|
testResults: '溫度測試:CPU最高溫度75°C,性能測試:符合預期', |
|
|
|
specifications: 'CPU溫度≤85°C,開機時間≤30秒,電池續航≥6小時', |
|
|
|
}, |
|
|
|
// { |
|
|
|
// id: '4', |
|
|
|
// productName: '智慧手錶', |
|
|
|
// isQualified: false, |
|
|
|
// remarks: '防水測試未通過,密封性能不足', |
|
|
|
// reportDate: '2024-08-04', |
|
|
|
// batchNumber: 'SW-240804-004', |
|
|
|
// inspector: '劉品管', |
|
|
|
// testResults: '防水測試:IPX5等級測試失敗,發現進水現象', |
|
|
|
// specifications: '防水等級≥IPX7,電池續航≥48小時,心率監測精度≥95%', |
|
|
|
// }, |
|
|
|
// { |
|
|
|
// id: '5', |
|
|
|
// productName: '平板電腦', |
|
|
|
// isQualified: true, |
|
|
|
// remarks: '觸控靈敏度優良,顯示效果佳', |
|
|
|
// reportDate: '2024-08-03', |
|
|
|
// batchNumber: 'TB-240803-005', |
|
|
|
// inspector: '陳測試', |
|
|
|
// testResults: '觸控測試:反應時間<10ms,顯示測試:色彩準確度98%', |
|
|
|
// specifications: '螢幕尺寸10.1吋,解析度2K,觸控反應時間≤15ms', |
|
|
|
// }, |
|
|
|
]; |
|
|
|
|
|
|
|
// Main component |
|
|
|
const EscalationLog: React.FC = () => { |
|
|
|
return <QCReportForm reports={dummyReports} />; |
|
|
|
}; |
|
|
|
|
|
|
|
export default EscalationLog; |