| @@ -4,13 +4,47 @@ import React, { useState } from "react"; | |||
| import { | |||
| Box, Grid, Paper, Typography, Button, Dialog, DialogTitle, | |||
| DialogContent, DialogActions, TextField, Stack, Table, | |||
| TableBody, TableCell, TableContainer, TableHead, TableRow | |||
| TableBody, TableCell, TableContainer, TableHead, TableRow, | |||
| Tabs, Tab // ← Added for tabs | |||
| } from "@mui/material"; | |||
| import { FileDownload, Print, SettingsEthernet, Lan, Router } from "@mui/icons-material"; | |||
| import dayjs from "dayjs"; | |||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
| // Simple TabPanel component for conditional rendering | |||
| interface TabPanelProps { | |||
| children?: React.ReactNode; | |||
| index: number; | |||
| value: number; | |||
| } | |||
| function TabPanel(props: TabPanelProps) { | |||
| const { children, value, index, ...other } = props; | |||
| return ( | |||
| <div | |||
| role="tabpanel" | |||
| hidden={value !== index} | |||
| id={`simple-tabpanel-${index}`} | |||
| aria-labelledby={`simple-tab-${index}`} | |||
| {...other} | |||
| > | |||
| {value === index && ( | |||
| <Box sx={{ p: 3 }}> | |||
| {children} | |||
| </Box> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| export default function TestingPage() { | |||
| // Tab state | |||
| const [tabValue, setTabValue] = useState(0); | |||
| const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { | |||
| setTabValue(newValue); | |||
| }; | |||
| // --- 1. TSC Section States --- | |||
| const [tscConfig, setTscConfig] = useState({ ip: '192.168.1.100', port: '9100' }); | |||
| const [tscItems, setTscItems] = useState([ | |||
| @@ -35,10 +69,22 @@ export default function TestingPage() { | |||
| }); | |||
| // --- 4. Laser Section States --- | |||
| const [laserConfig, setLaserConfig] = useState({ ip: '192.168.1.102', port: '8080' }); | |||
| const [laserItems, setLaserItems] = useState([ | |||
| { id: 1, templateId: 'JOB_001', lotNo: 'L-LASER-01', expiryDate: '2025-12-31', power: '50' }, | |||
| ]); | |||
| const [laserConfig, setLaserConfig] = useState({ ip: '192.168.1.102', port: '8080' }); | |||
| const [laserItems, setLaserItems] = useState([ | |||
| { id: 1, templateId: 'JOB_001', lotNo: 'L-LASER-01', expiryDate: '2025-12-31', power: '50' }, | |||
| ]); | |||
| // --- 5. HANS600S-M Section States --- | |||
| const [hansConfig, setHansConfig] = useState({ ip: '192.168.76.10', port: '45678' }); | |||
| const [hansItems, setHansItems] = useState([ | |||
| { | |||
| id: 1, | |||
| textChannel3: 'SN-HANS-001-20260117', // channel 3 (e.g. serial / text1) | |||
| textChannel4: 'BATCH-HK-TEST-OK', // channel 4 (e.g. batch / text2) | |||
| text3ObjectName: 'Text3', // EZCAD object name for channel 3 | |||
| text4ObjectName: 'Text4' // EZCAD object name for channel 4 | |||
| }, | |||
| ]); | |||
| // Generic handler for inline table edits | |||
| const handleItemChange = (setter: any, id: number, field: string, value: string) => { | |||
| @@ -105,6 +151,7 @@ const [laserItems, setLaserItems] = useState([ | |||
| } catch (e) { console.error("OnPack Error:", e); } | |||
| }; | |||
| // Laser Print (Section 4 - original) | |||
| const handleLaserPrint = async (row: any) => { | |||
| const token = localStorage.getItem("accessToken"); | |||
| const payload = { ...row, printerIp: laserConfig.ip, printerPort: laserConfig.port }; | |||
| @@ -122,7 +169,6 @@ const [laserItems, setLaserItems] = useState([ | |||
| const token = localStorage.getItem("accessToken"); | |||
| const payload = { ...row, printerIp: laserConfig.ip, printerPort: parseInt(laserConfig.port) }; | |||
| try { | |||
| // We'll create this endpoint in the backend next | |||
| const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/preview-laser`, { | |||
| method: 'POST', | |||
| headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, | |||
| @@ -132,24 +178,58 @@ const [laserItems, setLaserItems] = useState([ | |||
| } catch (e) { console.error("Preview Error:", e); } | |||
| }; | |||
| // HANS600S-M TCP Print (Section 5) | |||
| const handleHansPrint = async (row: any) => { | |||
| const token = localStorage.getItem("accessToken"); | |||
| const payload = { | |||
| printerIp: hansConfig.ip, | |||
| printerPort: hansConfig.port, | |||
| textChannel3: row.textChannel3, | |||
| textChannel4: row.textChannel4, | |||
| text3ObjectName: row.text3ObjectName, | |||
| text4ObjectName: row.text4ObjectName | |||
| }; | |||
| try { | |||
| const response = await fetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser-tcp`, { | |||
| method: 'POST', | |||
| headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, | |||
| body: JSON.stringify(payload) | |||
| }); | |||
| const result = await response.text(); | |||
| if (response.ok) { | |||
| alert(`HANS600S-M Mark Success: ${result}`); | |||
| } else { | |||
| alert(`HANS600S-M Failed: ${result}`); | |||
| } | |||
| } catch (e) { | |||
| console.error("HANS600S-M Error:", e); | |||
| alert("HANS600S-M Connection Error"); | |||
| } | |||
| }; | |||
| // Layout Helper | |||
| const Section = ({ title, children }: { title: string, children?: React.ReactNode }) => ( | |||
| <Grid item xs={12} md={6}> | |||
| <Paper sx={{ p: 3, minHeight: '450px', display: 'flex', flexDirection: 'column' }}> | |||
| <Typography variant="h5" gutterBottom color="primary" sx={{ borderBottom: '2px solid #f0f0f0', pb: 1, mb: 2 }}> | |||
| {title} | |||
| </Typography> | |||
| {children || <Typography color="textSecondary" sx={{ m: 'auto' }}>Waiting for implementation...</Typography>} | |||
| </Paper> | |||
| </Grid> | |||
| <Paper sx={{ p: 3, minHeight: '450px', display: 'flex', flexDirection: 'column' }}> | |||
| <Typography variant="h5" gutterBottom color="primary" sx={{ borderBottom: '2px solid #f0f0f0', pb: 1, mb: 2 }}> | |||
| {title} | |||
| </Typography> | |||
| {children || <Typography color="textSecondary" sx={{ m: 'auto' }}>Waiting for implementation...</Typography>} | |||
| </Paper> | |||
| ); | |||
| return ( | |||
| <Box sx={{ p: 4 }}> | |||
| <Typography variant="h4" sx={{ mb: 4, fontWeight: 'bold' }}>Printer Testing Dashboard</Typography> | |||
| <Typography variant="h4" sx={{ mb: 4, fontWeight: 'bold' }}>Printer Testing</Typography> | |||
| <Grid container spacing={3}> | |||
| {/* 1. TSC Section */} | |||
| <Tabs value={tabValue} onChange={handleTabChange} aria-label="printer sections tabs" centered variant="fullWidth"> | |||
| <Tab label="1. TSC" /> | |||
| <Tab label="2. DataFlex" /> | |||
| <Tab label="3. OnPack" /> | |||
| <Tab label="4. Laser" /> | |||
| <Tab label="5. HANS600S-M" /> | |||
| </Tabs> | |||
| <TabPanel value={tabValue} index={0}> | |||
| <Section title="1. TSC"> | |||
| <Stack direction="row" spacing={2} sx={{ mb: 2 }}> | |||
| <TextField size="small" label="Printer IP" value={tscConfig.ip} onChange={e => setTscConfig({...tscConfig, ip: e.target.value})} /> | |||
| @@ -181,8 +261,9 @@ const [laserItems, setLaserItems] = useState([ | |||
| </Table> | |||
| </TableContainer> | |||
| </Section> | |||
| </TabPanel> | |||
| {/* 2. DataFlex Section */} | |||
| <TabPanel value={tabValue} index={1}> | |||
| <Section title="2. DataFlex"> | |||
| <Stack direction="row" spacing={2} sx={{ mb: 2 }}> | |||
| <TextField size="small" label="Printer IP" value={dfConfig.ip} onChange={e => setDfConfig({...dfConfig, ip: e.target.value})} /> | |||
| @@ -214,8 +295,9 @@ const [laserItems, setLaserItems] = useState([ | |||
| </Table> | |||
| </TableContainer> | |||
| </Section> | |||
| </TabPanel> | |||
| {/* 3. OnPack Section */} | |||
| <TabPanel value={tabValue} index={2}> | |||
| <Section title="3. OnPack"> | |||
| <Box sx={{ m: 'auto', textAlign: 'center' }}> | |||
| <Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}> | |||
| @@ -226,8 +308,9 @@ const [laserItems, setLaserItems] = useState([ | |||
| </Button> | |||
| </Box> | |||
| </Section> | |||
| </TabPanel> | |||
| {/* 4. Laser Section (HANS600S-M) */} | |||
| <TabPanel value={tabValue} index={3}> | |||
| <Section title="4. Laser"> | |||
| <Stack direction="row" spacing={2} sx={{ mb: 2 }}> | |||
| <TextField size="small" label="Laser IP" value={laserConfig.ip} onChange={e => setLaserConfig({...laserConfig, ip: e.target.value})} /> | |||
| @@ -283,7 +366,94 @@ const [laserItems, setLaserItems] = useState([ | |||
| Note: HANS Laser requires pre-saved templates on the controller. | |||
| </Typography> | |||
| </Section> | |||
| </Grid> | |||
| </TabPanel> | |||
| <TabPanel value={tabValue} index={4}> | |||
| <Section title="5. HANS600S-M"> | |||
| <Stack direction="row" spacing={2} sx={{ mb: 2 }}> | |||
| <TextField | |||
| size="small" | |||
| label="Laser IP" | |||
| value={hansConfig.ip} | |||
| onChange={e => setHansConfig({...hansConfig, ip: e.target.value})} | |||
| /> | |||
| <TextField | |||
| size="small" | |||
| label="Port" | |||
| value={hansConfig.port} | |||
| onChange={e => setHansConfig({...hansConfig, port: e.target.value})} | |||
| /> | |||
| <Router color="action" sx={{ ml: 'auto' }} /> | |||
| </Stack> | |||
| <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 300 }}> | |||
| <Table size="small" stickyHeader> | |||
| <TableHead> | |||
| <TableRow> | |||
| <TableCell>Ch3 Text (SN)</TableCell> | |||
| <TableCell>Ch4 Text (Batch)</TableCell> | |||
| <TableCell>Obj3 Name</TableCell> | |||
| <TableCell>Obj4 Name</TableCell> | |||
| <TableCell align="center">Action</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| <TableBody> | |||
| {hansItems.map(row => ( | |||
| <TableRow key={row.id}> | |||
| <TableCell> | |||
| <TextField | |||
| variant="standard" | |||
| value={row.textChannel3} | |||
| onChange={e => handleItemChange(setHansItems, row.id, 'textChannel3', e.target.value)} | |||
| sx={{ minWidth: 180 }} | |||
| /> | |||
| </TableCell> | |||
| <TableCell> | |||
| <TextField | |||
| variant="standard" | |||
| value={row.textChannel4} | |||
| onChange={e => handleItemChange(setHansItems, row.id, 'textChannel4', e.target.value)} | |||
| sx={{ minWidth: 140 }} | |||
| /> | |||
| </TableCell> | |||
| <TableCell> | |||
| <TextField | |||
| variant="standard" | |||
| value={row.text3ObjectName} | |||
| onChange={e => handleItemChange(setHansItems, row.id, 'text3ObjectName', e.target.value)} | |||
| size="small" | |||
| /> | |||
| </TableCell> | |||
| <TableCell> | |||
| <TextField | |||
| variant="standard" | |||
| value={row.text4ObjectName} | |||
| onChange={e => handleItemChange(setHansItems, row.id, 'text4ObjectName', e.target.value)} | |||
| size="small" | |||
| /> | |||
| </TableCell> | |||
| <TableCell align="center"> | |||
| <Button | |||
| variant="contained" | |||
| color="error" | |||
| size="small" | |||
| startIcon={<Print />} | |||
| onClick={() => handleHansPrint(row)} | |||
| sx={{ minWidth: 80 }} | |||
| > | |||
| TCP Mark | |||
| </Button> | |||
| </TableCell> | |||
| </TableRow> | |||
| ))} | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| <Typography variant="caption" sx={{ mt: 2, display: 'block', color: 'text.secondary', fontSize: '0.75rem' }}> | |||
| TCP Push to EZCAD3 (Ch3/Ch4 via E3_SetTextObject) | IP:192.168.76.10:45678 | Backend: /print-laser-tcp | |||
| </Typography> | |||
| </Section> | |||
| </TabPanel> | |||
| {/* Dialog for OnPack */} | |||
| <Dialog open={isPrinterModalOpen} onClose={() => setIsPrinterModalOpen(false)} fullWidth maxWidth="sm"> | |||
| @@ -220,7 +220,7 @@ const CreateItem: React.FC<Props> = ({ | |||
| variant="scrollable" | |||
| > | |||
| <Tab label={t("Product / Material Details")} iconPosition="end" /> | |||
| <Tab label={t("Qc items")} iconPosition="end" /> | |||
| {/* <Tab label={t("Qc items")} iconPosition="end" /> */} | |||
| </Tabs> | |||
| {serverError && ( | |||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | |||
| @@ -18,6 +18,7 @@ import CollapsibleCard from "../CollapsibleCard"; | |||
| import { EscalationResult } from "@/app/api/escalation"; | |||
| import EscalationLogTable from "./escalation/EscalationLogTable"; | |||
| import { TruckScheduleDashboard } from "./truckSchedule"; | |||
| import { GoodsReceiptStatus } from "./goodsReceiptStatus"; | |||
| type Props = { | |||
| // iqc: IQCItems[] | undefined | |||
| escalationLogs: EscalationResult[] | |||
| @@ -50,20 +51,28 @@ const DashboardPage: React.FC<Props> = ({ | |||
| </CardContent> | |||
| </CollapsibleCard> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <CollapsibleCard title={t("Goods Receipt Status")} defaultOpen={true}> | |||
| <CardContent> | |||
| <GoodsReceiptStatus /> | |||
| </CardContent> | |||
| </CollapsibleCard> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <CollapsibleCard | |||
| title={`${t("Responsible Escalation List")} (${t("pending")} : ${ | |||
| getPendingLog().length > 0 ? getPendingLog().length : t("No")})`} | |||
| showFilter={true} | |||
| filterText={t("show completed logs")} | |||
| // defaultOpen={getPendingLog().length > 0} // TODO Fix default not opening | |||
| > | |||
| <CardContent> | |||
| <EscalationLogTable items={escLog}/> | |||
| </CardContent> | |||
| </CollapsibleCard> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| {/* Hidden: Progress chart - not in use currently */} | |||
| {/* <Grid item xs={12}> | |||
| <CollapsibleCard title={t("Progress chart")}> | |||
| <CardContent> | |||
| <Grid container spacing={3}> | |||
| @@ -79,9 +88,10 @@ const DashboardPage: React.FC<Props> = ({ | |||
| </Grid> | |||
| </CardContent> | |||
| </CollapsibleCard> | |||
| </Grid> | |||
| </Grid> */} | |||
| <Grid item xs={12}> | |||
| {/* Hidden: Warehouse status - not in use currently */} | |||
| {/* <Grid item xs={12}> | |||
| <CollapsibleCard title={t("Warehouse status")}> | |||
| <CardContent> | |||
| <Grid container spacing={2}> | |||
| @@ -95,31 +105,10 @@ const DashboardPage: React.FC<Props> = ({ | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| {/*<Grid item xs={12} md={6}> | |||
| <Grid container spacing={2}> | |||
| <Grid item xs={12} sm={6}> | |||
| <DashboardBox | |||
| title={t("Temperature status")} | |||
| value="--" | |||
| unit="°C" | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12} sm={6}> | |||
| <DashboardBox | |||
| title={t("Humidity status")} | |||
| value="--" | |||
| unit="%" | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <DashboardLineChart /> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid>*/} | |||
| </Grid> | |||
| </CardContent> | |||
| </CollapsibleCard> | |||
| </Grid> | |||
| </Grid> */} | |||
| </Grid> | |||
| </ThemeProvider> | |||
| ); | |||
| @@ -0,0 +1,163 @@ | |||
| "use client"; | |||
| import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; | |||
| import { | |||
| Box, | |||
| Typography, | |||
| FormControl, | |||
| InputLabel, | |||
| Select, | |||
| MenuItem, | |||
| Card, | |||
| CardContent, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableContainer, | |||
| TableHead, | |||
| TableRow, | |||
| Paper, | |||
| CircularProgress, | |||
| Chip | |||
| } from '@mui/material'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import dayjs from 'dayjs'; | |||
| interface GoodsReceiptStatusItem { | |||
| id: string; | |||
| } | |||
| const GoodsReceiptStatus: React.FC = () => { | |||
| const { t } = useTranslation("dashboard"); | |||
| const [selectedFilter, setSelectedFilter] = useState<string>(""); | |||
| const [data, setData] = useState<GoodsReceiptStatusItem[]>([]); | |||
| const [loading, setLoading] = useState<boolean>(false); | |||
| const [currentTime, setCurrentTime] = useState<dayjs.Dayjs | null>(null); | |||
| const [isClient, setIsClient] = useState<boolean>(false); | |||
| useEffect(() => { | |||
| setIsClient(true); | |||
| setCurrentTime(dayjs()); | |||
| }, []); | |||
| const loadData = useCallback(async () => { | |||
| try { | |||
| setData([]); | |||
| } catch (error) { | |||
| console.error('Error fetching goods receipt status:', error); | |||
| } finally { | |||
| setLoading(false); | |||
| } | |||
| }, []); | |||
| useEffect(() => { | |||
| loadData(); | |||
| const refreshInterval = setInterval(() => { | |||
| loadData(); | |||
| }, 5 * 60 * 1000); | |||
| return () => clearInterval(refreshInterval); | |||
| }, [loadData]); | |||
| useEffect(() => { | |||
| if (!isClient) return; | |||
| const timeInterval = setInterval(() => { | |||
| setCurrentTime(dayjs()); | |||
| }, 60 * 1000); | |||
| return () => clearInterval(timeInterval); | |||
| }, [isClient]); | |||
| const filteredData = useMemo(() => { | |||
| if (!selectedFilter) return data; | |||
| return data.filter(item => true); | |||
| }, [data, selectedFilter]); | |||
| return ( | |||
| <Card sx={{ mb: 2 }}> | |||
| <CardContent> | |||
| {/* Filter */} | |||
| <Stack direction="row" spacing={2} sx={{ mb: 3 }}> | |||
| <FormControl sx={{ minWidth: 150 }} size="small"> | |||
| <InputLabel id="filter-select-label" shrink={true}> | |||
| {t("Filter")} | |||
| </InputLabel> | |||
| <Select | |||
| labelId="filter-select-label" | |||
| id="filter-select" | |||
| value={selectedFilter} | |||
| label={t("Filter")} | |||
| onChange={(e) => setSelectedFilter(e.target.value)} | |||
| displayEmpty | |||
| > | |||
| <MenuItem value="">{t("All")}</MenuItem> | |||
| {/* TODO: Add filter options when implementing */} | |||
| </Select> | |||
| </FormControl> | |||
| <Typography variant="body2" sx={{ alignSelf: 'center', color: 'text.secondary' }}> | |||
| {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {isClient && currentTime ? currentTime.format('HH:mm:ss') : '--:--:--'} | |||
| </Typography> | |||
| </Stack> | |||
| {/* Table */} | |||
| <Box sx={{ mt: 2 }}> | |||
| {loading ? ( | |||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | |||
| <CircularProgress /> | |||
| </Box> | |||
| ) : ( | |||
| <TableContainer component={Paper}> | |||
| <Table size="small" sx={{ minWidth: 1200 }}> | |||
| <TableHead> | |||
| <TableRow sx={{ backgroundColor: 'grey.100' }}> | |||
| <TableCell sx={{ fontWeight: 600 }}>{t("Column 1")}</TableCell> | |||
| <TableCell sx={{ fontWeight: 600 }}>{t("Column 2")}</TableCell> | |||
| <TableCell sx={{ fontWeight: 600 }}>{t("Column 3")}</TableCell> | |||
| {/* TODO: Add table columns when implementing */} | |||
| </TableRow> | |||
| </TableHead> | |||
| <TableBody> | |||
| {filteredData.length === 0 ? ( | |||
| <TableRow> | |||
| <TableCell colSpan={3} align="center"> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("No data available")} | |||
| </Typography> | |||
| </TableCell> | |||
| </TableRow> | |||
| ) : ( | |||
| filteredData.map((row, index) => ( | |||
| <TableRow | |||
| key={row.id || index} | |||
| sx={{ | |||
| '&:hover': { backgroundColor: 'grey.50' } | |||
| }} | |||
| > | |||
| <TableCell> | |||
| {/* TODO: Add table cell content when implementing */} | |||
| - | |||
| </TableCell> | |||
| <TableCell> | |||
| - | |||
| </TableCell> | |||
| <TableCell> | |||
| - | |||
| </TableCell> | |||
| </TableRow> | |||
| )) | |||
| )} | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| )} | |||
| </Box> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| }; | |||
| export default GoodsReceiptStatus; | |||
| @@ -0,0 +1 @@ | |||
| export { default as GoodsReceiptStatus } from './GoodsReceiptStatus'; | |||
| @@ -237,11 +237,6 @@ const TruckScheduleDashboard: React.FC = () => { | |||
| return ( | |||
| <Card sx={{ mb: 2 }}> | |||
| <CardContent> | |||
| {/* Title */} | |||
| <Typography variant="h5" sx={{ mb: 3, fontWeight: 600 }}> | |||
| {t("Truck Schedule Dashboard")} | |||
| </Typography> | |||
| {/* Filter */} | |||
| <Stack direction="row" spacing={2} sx={{ mb: 3 }}> | |||
| <FormControl sx={{ minWidth: 150 }} size="small"> | |||
| @@ -7,14 +7,11 @@ import { useTranslation } from "react-i18next"; | |||
| import SearchResults, { Column } from "../SearchResults"; | |||
| import { EditNote } from "@mui/icons-material"; | |||
| import { useRouter, useSearchParams } from "next/navigation"; | |||
| import { GridDeleteIcon } from "@mui/x-data-grid"; | |||
| import { Chip } from "@mui/material"; | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import axios from "axios"; | |||
| import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
| import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
| import { deleteItem } from "@/app/api/settings/item/actions"; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| type Props = { | |||
| items: ItemsResult[]; | |||
| @@ -135,22 +132,6 @@ const ItemsSearch: React.FC<Props> = ({ items }) => { | |||
| refetchData, | |||
| ]); | |||
| const onDeleteClick = useCallback( | |||
| (item: ItemsResult) => { | |||
| deleteDialog(async () => { | |||
| if (item.id) { | |||
| const itemId = typeof item.id === "string" ? parseInt(item.id, 10) : item.id; | |||
| if (!isNaN(itemId)) { | |||
| await deleteItem(itemId); | |||
| await refetchData(filterObj); | |||
| await successDialog(t("Delete Success"), t); | |||
| } | |||
| } | |||
| }, t); | |||
| }, | |||
| [refetchData, filterObj, t], | |||
| ); | |||
| const columns = useMemo<Column<ItemsResultWithStatus>[]>( | |||
| () => [ | |||
| { | |||
| @@ -158,22 +139,34 @@ const ItemsSearch: React.FC<Props> = ({ items }) => { | |||
| label: t("Details"), | |||
| onClick: onDetailClick, | |||
| buttonIcon: <EditNote />, | |||
| sx: { width: 80 }, | |||
| }, | |||
| { | |||
| name: "code", | |||
| label: t("Code"), | |||
| sx: { width: 150 }, | |||
| }, | |||
| { | |||
| name: "name", | |||
| label: t("Name"), | |||
| sx: { width: 250 }, | |||
| }, | |||
| { | |||
| name: "LocationCode", | |||
| label: t("LocationCode"), | |||
| sx: { width: 150 }, | |||
| }, | |||
| { | |||
| name: "type", | |||
| label: t("Type"), | |||
| sx: { width: 120 }, | |||
| }, | |||
| { | |||
| name: "status", | |||
| label: t("Status"), | |||
| align: "center", | |||
| headerAlign: "center", | |||
| sx: { width: 120 }, | |||
| renderCell: (item) => { | |||
| const status = item.status || checkItemStatus(item); | |||
| if (status === "complete") { | |||
| @@ -183,14 +176,8 @@ const ItemsSearch: React.FC<Props> = ({ items }) => { | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| name: "action", | |||
| label: t(""), | |||
| buttonIcon: <GridDeleteIcon />, | |||
| onClick: onDeleteClick, | |||
| }, | |||
| ], | |||
| [onDeleteClick, onDetailClick, t, checkItemStatus], | |||
| [onDetailClick, t, checkItemStatus], | |||
| ); | |||
| const onReset = useCallback(() => { | |||
| @@ -247,7 +247,7 @@ const NavigationContent: React.FC = () => { | |||
| icon: <BugReportIcon />, | |||
| label: "PS", | |||
| path: "/ps", | |||
| requiredAbility: AUTH.TESTING, | |||
| requiredAbility: [AUTH.TESTING, AUTH.ADMIN], | |||
| isHidden: false, | |||
| }, | |||
| { | |||
| @@ -47,5 +47,20 @@ export const REPORTS: ReportDefinition[] = [ | |||
| ], required: false} | |||
| ] | |||
| }, | |||
| { | |||
| id: "rep-003", | |||
| title: "Report 3", | |||
| apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-report3`, | |||
| fields: [ | |||
| { label: "From Date", name: "fromDate", type: "date", required: true }, // Mandatory | |||
| { label: "To Date", name: "toDate", type: "date", required: true }, // Mandatory | |||
| { label: "Item Code", name: "itemCode", type: "text", required: false, placeholder: "e.g. FG"}, | |||
| { label: "Item Type", name: "itemType", type: "select", required: false, | |||
| options: [ | |||
| { label: "FG", value: "FG" }, | |||
| { label: "Material", value: "Mat" } | |||
| ] }, | |||
| ] | |||
| } | |||
| // Add Report 3 to 10 following the same pattern... | |||
| ]; | |||
| @@ -72,5 +72,12 @@ | |||
| "Tickets Completed": "Tickets Completed", | |||
| "Last Ticket End": "Last Ticket End", | |||
| "Pick Time (min)": "Pick Time (min)", | |||
| "No truck schedules available for today": "No truck schedules available for today" | |||
| "No truck schedules available for today": "No truck schedules available for today", | |||
| "Goods Receipt Status": "Goods Receipt Status", | |||
| "Filter": "Filter", | |||
| "All": "All", | |||
| "Column 1": "Column 1", | |||
| "Column 2": "Column 2", | |||
| "Column 3": "Column 3", | |||
| "No data available": "No data available" | |||
| } | |||
| @@ -72,5 +72,12 @@ | |||
| "Tickets Completed": "已完成成品出倉單", | |||
| "Last Ticket End": "末單結束時間", | |||
| "Pick Time (min)": "揀貨時間(分鐘)", | |||
| "No truck schedules available for today": "今日無車輛調度計劃" | |||
| "No truck schedules available for today": "今日無車輛調度計劃", | |||
| "Goods Receipt Status": "貨物接收狀態", | |||
| "Filter": "篩選", | |||
| "All": "全部", | |||
| "Column 1": "欄位1", | |||
| "Column 2": "欄位2", | |||
| "Column 3": "欄位3", | |||
| "No data available": "暫無資料" | |||
| } | |||
| @@ -33,7 +33,7 @@ | |||
| "Search": "搜尋", | |||
| "Release": "發佈", | |||
| "Actions": "操作", | |||
| "LocationCode": "位置", | |||
| "LocationCode": "預設位置", | |||
| "DefaultLocationCode": "預設位置", | |||
| "Special Type": "特殊類型", | |||
| "None": "無", | |||