@@ -20,6 +20,7 @@ | |||||
"@mui/material-nextjs": "^5.15.0", | "@mui/material-nextjs": "^5.15.0", | ||||
"@mui/x-data-grid": "^6.18.7", | "@mui/x-data-grid": "^6.18.7", | ||||
"@mui/x-date-pickers": "^6.18.7", | "@mui/x-date-pickers": "^6.18.7", | ||||
"@tanstack/react-table": "^8.21.3", | |||||
"@tiptap/core": "^2.14.0", | "@tiptap/core": "^2.14.0", | ||||
"@tiptap/extension-color": "^2.14.0", | "@tiptap/extension-color": "^2.14.0", | ||||
"@tiptap/extension-document": "^2.14.0", | "@tiptap/extension-document": "^2.14.0", | ||||
@@ -46,6 +47,7 @@ | |||||
"i18next": "^23.7.11", | "i18next": "^23.7.11", | ||||
"i18next-resources-to-backend": "^1.2.0", | "i18next-resources-to-backend": "^1.2.0", | ||||
"lodash": "^4.17.21", | "lodash": "^4.17.21", | ||||
"lucide-react": "^0.536.0", | |||||
"mui-color-input": "^7.0.0", | "mui-color-input": "^7.0.0", | ||||
"next": "14.0.4", | "next": "14.0.4", | ||||
"next-auth": "^4.24.5", | "next-auth": "^4.24.5", | ||||
@@ -30,7 +30,7 @@ interface FormData { | |||||
message: string; | message: string; | ||||
} | } | ||||
function CollapsibleSelectForm(): JSX.Element { | |||||
function EscalationComponent(): JSX.Element { | |||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(false); | const [isCollapsed, setIsCollapsed] = useState<boolean>(false); | ||||
const [formData, setFormData] = useState<FormData>({ | const [formData, setFormData] = useState<FormData>({ | ||||
name: '', | name: '', | ||||
@@ -162,4 +162,4 @@ function CollapsibleSelectForm(): JSX.Element { | |||||
); | ); | ||||
} | } | ||||
export default CollapsibleSelectForm; | |||||
export default EscalationComponent; |
@@ -112,160 +112,58 @@ function QCReportForm({ reports }: QCReportFormProps): JSX.Element { | |||||
return ( | return ( | ||||
<Box sx={{ maxWidth: '1200px', mx: 'auto', p: 3 }}> | <Box sx={{ maxWidth: '1200px', mx: 'auto', p: 3 }}> | ||||
{/* Title */} | |||||
{/* <Box sx={{ mb: 4 }}> | |||||
<Typography variant="h5" fontWeight="bold" color="text.primary"> | |||||
品質控制上報系統 | |||||
{/* Table Section */} | |||||
<Box> | |||||
<Typography variant="h6" fontWeight="medium" sx={{ mb: 2 }}> | |||||
上報資料 | |||||
</Typography> | </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> */} | |||||
<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> | </Box> | ||||
</Box> | </Box> | ||||
); | ); | ||||
@@ -273,19 +171,8 @@ function QCReportForm({ reports }: QCReportFormProps): JSX.Element { | |||||
// Dummy data | // Dummy data | ||||
const dummyReports = [ | 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', | |||||
id: '1', | |||||
productName: '無線藍牙耳機', | productName: '無線藍牙耳機', | ||||
isQualified: false, | isQualified: false, | ||||
remarks: '包裝有破損', | remarks: '包裝有破損', | ||||
@@ -296,7 +183,7 @@ const dummyReports = [ | |||||
specifications: '頻響範圍20Hz-20kHz,左右聲道音量差≤1dB,電池續航≥8小時', | specifications: '頻響範圍20Hz-20kHz,左右聲道音量差≤1dB,電池續航≥8小時', | ||||
}, | }, | ||||
{ | { | ||||
id: '3', | |||||
id: '2', | |||||
productName: '筆記型電腦', | productName: '筆記型電腦', | ||||
isQualified: true, | isQualified: true, | ||||
remarks: '經檢查,無損壞', | remarks: '經檢查,無損壞', | ||||
@@ -306,32 +193,10 @@ const dummyReports = [ | |||||
testResults: '溫度測試:CPU最高溫度75°C,性能測試:符合預期', | testResults: '溫度測試:CPU最高溫度75°C,性能測試:符合預期', | ||||
specifications: 'CPU溫度≤85°C,開機時間≤30秒,電池續航≥6小時', | 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 | // Main component | ||||
const EscalationLog: React.FC = () => { | |||||
function EscalationLog(): JSX.Element { | |||||
return <QCReportForm reports={dummyReports} />; | return <QCReportForm reports={dummyReports} />; | ||||
}; | }; | ||||
@@ -0,0 +1,23 @@ | |||||
import StockInForm from "./StockInForm"; | |||||
import EscalationLog from "./EscalationLog"; | |||||
import EscalationComponent from "./EscalationComponent"; | |||||
import React from "react"; | |||||
import { PurchaseQcResult } from "@/app/api/po/actions"; | |||||
import { StockInLine } from "@/app/api/po"; | |||||
interface Props { | |||||
itemDetail: StockInLine & { | |||||
qcResult?: PurchaseQcResult[] | undefined; | |||||
}; | |||||
disabled: boolean | |||||
} | |||||
const EscalationTab:React.FC<Props> = ({itemDetail, disabled}) => { | |||||
return <> | |||||
<StockInForm itemDetail={itemDetail} disabled={disabled}/> | |||||
<EscalationLog/> | |||||
<EscalationComponent/> | |||||
</> | |||||
}; | |||||
export default EscalationTab; |
@@ -2,6 +2,7 @@ import { ThemeOptions, createTheme } from "@mui/material"; | |||||
import palette from "./palette"; | import palette from "./palette"; | ||||
// Used only to create transitions | // Used only to create transitions | ||||
const defaultFontSize = 18; | |||||
const muiTheme = createTheme(); | const muiTheme = createTheme(); | ||||
const components: ThemeOptions["components"] = { | const components: ThemeOptions["components"] = { | ||||
@@ -15,7 +16,7 @@ const components: ThemeOptions["components"] = { | |||||
MuiAvatar: { | MuiAvatar: { | ||||
styleOverrides: { | styleOverrides: { | ||||
root: { | root: { | ||||
fontSize: 14, | |||||
fontSize: defaultFontSize, | |||||
fontWeight: 600, | fontWeight: 600, | ||||
letterSpacing: 0, | letterSpacing: 0, | ||||
}, | }, | ||||
@@ -163,7 +164,7 @@ const components: ThemeOptions["components"] = { | |||||
MuiInput: { | MuiInput: { | ||||
styleOverrides: { | styleOverrides: { | ||||
input: { | input: { | ||||
fontSize: 14, | |||||
fontSize: defaultFontSize, | |||||
fontWeight: 500, | fontWeight: 500, | ||||
lineHeight: "24px", | lineHeight: "24px", | ||||
"&::placeholder": { | "&::placeholder": { | ||||
@@ -215,7 +216,7 @@ const components: ThemeOptions["components"] = { | |||||
}, | }, | ||||
}, | }, | ||||
input: { | input: { | ||||
fontSize: 14, | |||||
fontSize: defaultFontSize, | |||||
fontWeight: 500, | fontWeight: 500, | ||||
lineHeight: "24px", | lineHeight: "24px", | ||||
}, | }, | ||||
@@ -245,7 +246,7 @@ const components: ThemeOptions["components"] = { | |||||
}, | }, | ||||
}, | }, | ||||
input: { | input: { | ||||
fontSize: 14, | |||||
fontSize: defaultFontSize, | |||||
fontWeight: 500, | fontWeight: 500, | ||||
lineHeight: "24px", | lineHeight: "24px", | ||||
}, | }, | ||||
@@ -259,7 +260,7 @@ const components: ThemeOptions["components"] = { | |||||
MuiFormLabel: { | MuiFormLabel: { | ||||
styleOverrides: { | styleOverrides: { | ||||
root: { | root: { | ||||
fontSize: 14, | |||||
fontSize: defaultFontSize, | |||||
fontWeight: 500, | fontWeight: 500, | ||||
[`&.MuiInputLabel-filled`]: { | [`&.MuiInputLabel-filled`]: { | ||||
transform: "translate(12px, 18px) scale(1)", | transform: "translate(12px, 18px) scale(1)", | ||||
@@ -283,7 +284,7 @@ const components: ThemeOptions["components"] = { | |||||
MuiTab: { | MuiTab: { | ||||
styleOverrides: { | styleOverrides: { | ||||
root: { | root: { | ||||
fontSize: 14, | |||||
fontSize: defaultFontSize, | |||||
fontWeight: 500, | fontWeight: 500, | ||||
lineHeight: 1.71, | lineHeight: 1.71, | ||||
minWidth: "auto", | minWidth: "auto", | ||||
@@ -297,6 +298,13 @@ const components: ThemeOptions["components"] = { | |||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
MuiTableRow: { | |||||
styleOverrides: { | |||||
root: { | |||||
lineHeight: 0.5, | |||||
}, | |||||
}, | |||||
}, | |||||
MuiTableCell: { | MuiTableCell: { | ||||
styleOverrides: { | styleOverrides: { | ||||
root: { | root: { | ||||
@@ -313,7 +321,7 @@ const components: ThemeOptions["components"] = { | |||||
borderBottom: "none", | borderBottom: "none", | ||||
backgroundColor: palette.neutral[50], | backgroundColor: palette.neutral[50], | ||||
color: palette.neutral[700], | color: palette.neutral[700], | ||||
fontSize: 12, | |||||
fontSize: defaultFontSize - 2, | |||||
fontWeight: 600, | fontWeight: 600, | ||||
lineHeight: 2, | lineHeight: 2, | ||||
letterSpacing: 0.5, | letterSpacing: 0.5, | ||||
@@ -9,12 +9,14 @@ const typography: TypographyOptions = { | |||||
fontFamily: | fontFamily: | ||||
'"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"', | '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"', | ||||
body1: { | body1: { | ||||
fontSize: "1rem", | |||||
// fontSize: "1rem", | |||||
fontSize: "1.25rem", | |||||
fontWeight: 400, | fontWeight: 400, | ||||
lineHeight: 1.5, | lineHeight: 1.5, | ||||
}, | }, | ||||
body2: { | body2: { | ||||
fontSize: "0.875rem", | |||||
// fontSize: "0.875rem", | |||||
fontSize: "1rem", | |||||
fontWeight: 400, | fontWeight: 400, | ||||
lineHeight: 1.57, | lineHeight: 1.57, | ||||
}, | }, | ||||
@@ -22,22 +24,26 @@ const typography: TypographyOptions = { | |||||
fontWeight: 600, | fontWeight: 600, | ||||
}, | }, | ||||
caption: { | caption: { | ||||
fontSize: "0.75rem", | |||||
// fontSize: "0.75rem", | |||||
fontSize: "0.875rem", | |||||
fontWeight: 500, | fontWeight: 500, | ||||
lineHeight: 1.66, | lineHeight: 1.66, | ||||
}, | }, | ||||
subtitle1: { | subtitle1: { | ||||
fontSize: "1rem", | |||||
// fontSize: "1rem", | |||||
fontSize: "1.25rem", | |||||
fontWeight: 500, | fontWeight: 500, | ||||
lineHeight: 1.57, | lineHeight: 1.57, | ||||
}, | }, | ||||
subtitle2: { | subtitle2: { | ||||
fontSize: "0.875rem", | |||||
// fontSize: "0.875rem", | |||||
fontSize: "1rem", | |||||
fontWeight: 500, | fontWeight: 500, | ||||
lineHeight: 1.57, | lineHeight: 1.57, | ||||
}, | }, | ||||
overline: { | overline: { | ||||
fontSize: "0.75rem", | |||||
// fontSize: "0.75rem", | |||||
fontSize: "0.875rem", | |||||
fontWeight: 600, | fontWeight: 600, | ||||
letterSpacing: "0.5px", | letterSpacing: "0.5px", | ||||
lineHeight: 2.5, | lineHeight: 2.5, | ||||
@@ -46,37 +52,43 @@ const typography: TypographyOptions = { | |||||
h1: { | h1: { | ||||
fontFamily: "'Plus Jakarta Sans', sans-serif", | fontFamily: "'Plus Jakarta Sans', sans-serif", | ||||
fontWeight: 700, | fontWeight: 700, | ||||
fontSize: "3.5rem", | |||||
// fontSize: "3.5rem", | |||||
fontSize: "3.75rem", | |||||
lineHeight: 1.2, | lineHeight: 1.2, | ||||
}, | }, | ||||
h2: { | h2: { | ||||
fontFamily: "'Plus Jakarta Sans', sans-serif", | fontFamily: "'Plus Jakarta Sans', sans-serif", | ||||
fontWeight: 700, | fontWeight: 700, | ||||
fontSize: "3rem", | |||||
// fontSize: "3rem", | |||||
fontSize: "3.25rem", | |||||
lineHeight: 1.2, | lineHeight: 1.2, | ||||
}, | }, | ||||
h3: { | h3: { | ||||
fontFamily: "'Plus Jakarta Sans', sans-serif", | fontFamily: "'Plus Jakarta Sans', sans-serif", | ||||
fontWeight: 700, | fontWeight: 700, | ||||
fontSize: "2.25rem", | |||||
// fontSize: "2.25rem", | |||||
fontSize: "2.5rem", | |||||
lineHeight: 1.2, | lineHeight: 1.2, | ||||
}, | }, | ||||
h4: { | h4: { | ||||
fontFamily: "'Plus Jakarta Sans', sans-serif", | fontFamily: "'Plus Jakarta Sans', sans-serif", | ||||
fontWeight: 700, | fontWeight: 700, | ||||
fontSize: "2rem", | |||||
// fontSize: "2rem", | |||||
fontSize: "2.25rem", | |||||
lineHeight: 1.2, | lineHeight: 1.2, | ||||
}, | }, | ||||
h5: { | h5: { | ||||
fontFamily: "'Plus Jakarta Sans', sans-serif", | fontFamily: "'Plus Jakarta Sans', sans-serif", | ||||
fontWeight: 700, | fontWeight: 700, | ||||
fontSize: "1.5rem", | |||||
// fontSize: "1.5rem", | |||||
fontSize: "1.75rem", | |||||
lineHeight: 1.2, | lineHeight: 1.2, | ||||
}, | }, | ||||
h6: { | h6: { | ||||
fontFamily: "'Plus Jakarta Sans', sans-serif", | fontFamily: "'Plus Jakarta Sans', sans-serif", | ||||
fontWeight: 700, | fontWeight: 700, | ||||
fontSize: "1.125rem", | |||||
// fontSize: "1.125rem", | |||||
fontSize: "1.25rem", | |||||
lineHeight: 1.2, | lineHeight: 1.2, | ||||
}, | }, | ||||
}; | }; | ||||