Author | SHA1 | Message | Date |
---|---|---|---|
|
6f02dfda7e | Merge branch 'production_process' | 1 month ago |
|
ebc8d6f6d3 | add 2 modals to manage production process and qc | 2 months ago |
@@ -0,0 +1,46 @@ | |||||
'use server' | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { Machine, Operator } from "."; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
import { revalidateTag } from "next/cache"; | |||||
export interface IsOperatorExistResponse<T> { | |||||
id: number | null; | |||||
name: string; | |||||
code: string; | |||||
type?: string | |||||
message: string | null; | |||||
errorPosition: string | keyof T; | |||||
entity: T | |||||
} | |||||
export interface isCorrectMachineUsedResponse<T> { | |||||
id: number | null; | |||||
name: string; | |||||
code: string; | |||||
type?: string | |||||
message: string | null; | |||||
errorPosition: string | keyof T; | |||||
entity: T | |||||
} | |||||
export const isOperatorExist = async (username: string) => { | |||||
const isExist = await serverFetchJson<IsOperatorExistResponse<Operator>>(`${BASE_API_URL}/jop/isOperatorExist`, { | |||||
method: "POST", | |||||
body: JSON.stringify({ username }), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}); | |||||
revalidateTag("po"); | |||||
return isExist | |||||
} | |||||
export const isCorrectMachineUsed = async (machineCode: string) => { | |||||
const isExist = await serverFetchJson<isCorrectMachineUsedResponse<Machine>>(`${BASE_API_URL}/jop/isCorrectMachineUsed`, { | |||||
method: "POST", | |||||
body: JSON.stringify({ machineCode }), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}); | |||||
revalidateTag("po"); | |||||
return isExist | |||||
} |
@@ -0,0 +1,14 @@ | |||||
'server=only' | |||||
export interface Operator { | |||||
id: number; | |||||
name: string; | |||||
username: string; | |||||
} | |||||
export interface Machine { | |||||
id: number; | |||||
name: string; | |||||
code: string; | |||||
qrCode: string; | |||||
} |
@@ -0,0 +1,158 @@ | |||||
import React, { useState } from 'react'; | |||||
import { X } from '@mui/icons-material'; | |||||
import { DefectsSectionProps, QualityCheckItem } from './types'; | |||||
import { Autocomplete, TextField, Button, Typography, Box, Stack, Paper, IconButton } from '@mui/material'; | |||||
import { defectOptions } from './dummyData'; | |||||
const DefectsSection: React.FC<DefectsSectionProps> = ({ | |||||
defects, | |||||
onDefectsChange, | |||||
error | |||||
}) => { | |||||
const [newDefect, setNewDefect] = useState<QualityCheckItem | null>(null); | |||||
const [inputValue, setInputValue] = useState<string>(''); | |||||
const handleAddDefect = (): void => { | |||||
let defectToAdd: QualityCheckItem | null = null; | |||||
if (newDefect) { | |||||
// If user selected from options | |||||
defectToAdd = { | |||||
id: newDefect.id, | |||||
code: newDefect.code, | |||||
name: newDefect.name, | |||||
description: newDefect.description | |||||
}; | |||||
} | |||||
if (defectToAdd) { | |||||
// Check for duplicate code (skip if code is empty) | |||||
const isDuplicate = defectToAdd.code && defects.some(d => d.code === defectToAdd.code); | |||||
if (!isDuplicate) { | |||||
const updatedDefects = [...defects, defectToAdd]; | |||||
onDefectsChange(updatedDefects); | |||||
} | |||||
setNewDefect(null); | |||||
setInputValue(''); | |||||
} | |||||
}; | |||||
const handleRemoveDefect = (index: number): void => { | |||||
const updatedDefects = defects.filter((_, i) => i !== index); | |||||
onDefectsChange(updatedDefects); | |||||
}; | |||||
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>): void => { | |||||
if (e.key === 'Enter') { | |||||
e.preventDefault(); | |||||
handleAddDefect(); | |||||
} | |||||
}; | |||||
return ( | |||||
<Box> | |||||
<Typography variant="subtitle1" fontWeight={600} mb={1}> | |||||
Defects Found | |||||
</Typography> | |||||
<Stack direction="row" spacing={2} mb={2}> | |||||
<Autocomplete | |||||
value={newDefect} | |||||
onChange={(event, value) => { | |||||
if (typeof value === 'string') { | |||||
setNewDefect({ id: 0, code: '', name: value, description: '' }); | |||||
} else { | |||||
setNewDefect(value); | |||||
} | |||||
}} | |||||
inputValue={inputValue} | |||||
onInputChange={(event, newInputValue: string) => { | |||||
setInputValue(newInputValue); | |||||
}} | |||||
options={defectOptions} | |||||
getOptionLabel={(option) => typeof option === 'string' ? option : `${option.code} - ${option.name}`} | |||||
isOptionEqualToValue={(option, value) => option.id === value.id} | |||||
fullWidth | |||||
renderOption={(props, option) => ( | |||||
<Box component="li" {...props} key={option.id}> | |||||
<Box> | |||||
<Typography variant="body2" fontWeight={500}> | |||||
{option.code} - {option.name} | |||||
</Typography> | |||||
<Typography variant="caption" color="text.secondary"> | |||||
{option.description} | |||||
</Typography> | |||||
</Box> | |||||
</Box> | |||||
)} | |||||
renderInput={(params) => ( | |||||
<TextField | |||||
{...params} | |||||
size="small" | |||||
label="Enter defect description" | |||||
variant="outlined" | |||||
onKeyDown={handleKeyPress} | |||||
error={!!error} | |||||
/> | |||||
)} | |||||
/> | |||||
<Button | |||||
variant="contained" | |||||
color="primary" | |||||
onClick={handleAddDefect} | |||||
> | |||||
Add | |||||
</Button> | |||||
</Stack> | |||||
{error && ( | |||||
<Typography variant="caption" color="error" sx={{ mb: 1, display: 'block' }}> | |||||
{error} | |||||
</Typography> | |||||
)} | |||||
{defects.length > 0 && ( | |||||
<Stack spacing={1}> | |||||
<Typography variant="body2" color="text.secondary" mb={1}> | |||||
{defects.length} defect(s) found | |||||
</Typography> | |||||
{defects.map((defect: QualityCheckItem, index: number) => ( | |||||
<Paper | |||||
key={`${defect.id}-${index}`} | |||||
sx={{ | |||||
display: 'flex', | |||||
alignItems: 'center', | |||||
justifyContent: 'space-between', | |||||
bgcolor: 'red.50', | |||||
border: '1px solid', | |||||
borderColor: 'red.200', | |||||
p: 1.5 | |||||
}} | |||||
elevation={0} | |||||
> | |||||
<Box> | |||||
<Typography color="error" sx={{ wordBreak: 'break-word', fontWeight: 500 }}> | |||||
{defect.code && `${defect.code} - `}{defect.name} | |||||
</Typography> | |||||
{defect.description && ( | |||||
<Typography variant="caption" color="error" sx={{ opacity: 0.7 }}> | |||||
{defect.description} | |||||
</Typography> | |||||
)} | |||||
</Box> | |||||
<IconButton | |||||
onClick={() => handleRemoveDefect(index)} | |||||
color="error" | |||||
size="small" | |||||
> | |||||
<X fontSize="small" /> | |||||
</IconButton> | |||||
</Paper> | |||||
))} | |||||
</Stack> | |||||
)} | |||||
</Box> | |||||
); | |||||
}; | |||||
export default DefectsSection; |
@@ -1,8 +1,11 @@ | |||||
"use client"; | "use client"; | ||||
import React, { useState, useRef } from 'react'; | import React, { useState, useRef } from 'react'; | ||||
import { Machine } from './types'; | |||||
import { MachineQrCode } from './types'; | |||||
import { Button, TextField, Typography, Paper, Box, IconButton, Stack } from '@mui/material'; | import { Button, TextField, Typography, Paper, Box, IconButton, Stack } from '@mui/material'; | ||||
import CloseIcon from '@mui/icons-material/Close'; | import CloseIcon from '@mui/icons-material/Close'; | ||||
import { isCorrectMachineUsed } from '@/app/api/jo/actions'; | |||||
import { Machine } from '@/app/api/jo'; | |||||
import { useTranslation } from 'react-i18next'; | |||||
interface MachineScannerProps { | interface MachineScannerProps { | ||||
machines: Machine[]; | machines: Machine[]; | ||||
@@ -24,7 +27,9 @@ const MachineScanner: React.FC<MachineScannerProps> = ({ | |||||
error | error | ||||
}) => { | }) => { | ||||
const [scanningMode, setScanningMode] = useState<boolean>(false); | const [scanningMode, setScanningMode] = useState<boolean>(false); | ||||
const [scanError, setScanError] = useState<string | null>(null); | |||||
const machineScanRef = useRef<HTMLInputElement>(null); | const machineScanRef = useRef<HTMLInputElement>(null); | ||||
const { t } = useTranslation() | |||||
const startScanning = (): void => { | const startScanning = (): void => { | ||||
setScanningMode(true); | setScanningMode(true); | ||||
@@ -39,25 +44,27 @@ const MachineScanner: React.FC<MachineScannerProps> = ({ | |||||
setScanningMode(false); | setScanningMode(false); | ||||
}; | }; | ||||
const handleMachineScan = (e: React.KeyboardEvent<HTMLInputElement>): void => { | |||||
if (e.key === 'Enter') { | |||||
const target = e.target as HTMLInputElement; | |||||
const scannedCode = target.value.trim(); | |||||
const machine = machineDatabase.find(m => | |||||
m.qrCode === scannedCode || m.code === scannedCode | |||||
); | |||||
const handleMachineScan = async (e: React.KeyboardEvent<HTMLInputElement>): Promise<void> => { | |||||
const target = e.target as HTMLInputElement; | |||||
const scannedCodeJSON = target.value.trim(); | |||||
if (e.key === 'Enter' || scannedCodeJSON.endsWith('}') ) { | |||||
const scannedObj: MachineQrCode = JSON.parse(scannedCodeJSON) | |||||
const response = await isCorrectMachineUsed(scannedObj?.code) | |||||
if (machine) { | |||||
const isAlreadyAdded = machines.some(m => m.id === machine.id); | |||||
if (response.message === "Success") { | |||||
const isAlreadyAdded = machines.some(m => m.code === response.entity.code); | |||||
if (!isAlreadyAdded) { | if (!isAlreadyAdded) { | ||||
onMachinesChange([...machines, machine]); | |||||
onMachinesChange([...machines, response.entity]); | |||||
} | } | ||||
target.value = ''; | target.value = ''; | ||||
stopScanning(); | |||||
// stopScanning(); | |||||
} else { | } else { | ||||
alert('Machine not found. Please check the code and try again.'); | alert('Machine not found. Please check the code and try again.'); | ||||
setScanError('An error occurred while checking the operator. Please try again.'); | |||||
target.value = ''; | target.value = ''; | ||||
} | } | ||||
} | } | ||||
@@ -103,9 +110,13 @@ const MachineScanner: React.FC<MachineScannerProps> = ({ | |||||
Cancel | Cancel | ||||
</Button> | </Button> | ||||
</Stack> | </Stack> | ||||
<Typography variant="body2" color="success.main" mt={1}> | |||||
Position the QR code scanner and scan, or type the machine code manually | |||||
</Typography> | |||||
{scanError ? ( | |||||
<Typography variant="body2" color="error" mt={1}>{scanError}</Typography> | |||||
) : ( | |||||
<Typography variant="body2" color="success.main" mt={1}> | |||||
{t("Position the QR code scanner and scan, or type the machine code manually")} | |||||
</Typography> | |||||
)} | |||||
</Paper> | </Paper> | ||||
)} | )} | ||||
@@ -11,18 +11,6 @@ interface MaterialLotScannerProps { | |||||
error?: string; | error?: string; | ||||
} | } | ||||
const materialDatabase: MaterialDatabase[] = [ | |||||
{ name: 'Steel Sheet', lotPattern: /^SS-\d{6}-\d{3}$/, required: true }, | |||||
{ name: 'Aluminum Rod', lotPattern: /^AL-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Plastic Pellets', lotPattern: /^PP-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Copper Wire', lotPattern: /^CW-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Rubber Gasket', lotPattern: /^RG-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Glass Panel', lotPattern: /^GP-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Hydraulic Oil', lotPattern: /^HO-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Cutting Fluid', lotPattern: /^CF-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Welding Rod', lotPattern: /^WR-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Adhesive', lotPattern: /^AD-\d{6}-\d{3}$/, required: false } | |||||
]; | |||||
const MaterialLotScanner: React.FC<MaterialLotScannerProps> = ({ | const MaterialLotScanner: React.FC<MaterialLotScannerProps> = ({ | ||||
materials, | materials, | ||||
@@ -42,17 +30,22 @@ const MaterialLotScanner: React.FC<MaterialLotScannerProps> = ({ | |||||
setMaterialScanInput(e.target.value.trim()); | setMaterialScanInput(e.target.value.trim()); | ||||
}; | }; | ||||
const handleMaterialInputKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { | |||||
const handleMaterialInputKeyPress = async (e: React.KeyboardEvent<HTMLInputElement>) => { | |||||
if (e.key === 'Enter') { | if (e.key === 'Enter') { | ||||
const target = e.target as HTMLInputElement; | const target = e.target as HTMLInputElement; | ||||
const scannedLot = target.value.trim(); | const scannedLot = target.value.trim(); | ||||
if (scannedLot) { | if (scannedLot) { | ||||
const matchedMaterial = materialDatabase.find(material => | |||||
material.lotPattern.test(scannedLot) | |||||
); | |||||
if (matchedMaterial) { | |||||
const response = await fetch('http://your-backend-url.com/validateLot', { | |||||
method: 'POST', | |||||
headers: { | |||||
'Content-Type': 'application/json' | |||||
}, | |||||
body: JSON.stringify({ lot: scannedLot }) | |||||
}); | |||||
const data = await response.json(); | |||||
if (data.suggestedLot) { | |||||
const updatedMaterials = materials.map(material => { | const updatedMaterials = materials.map(material => { | ||||
if (material.name === matchedMaterial.name) { | |||||
if (material.name === data.matchedMaterial.name) { | |||||
const isAlreadyAdded = material.lotNumbers.includes(scannedLot); | const isAlreadyAdded = material.lotNumbers.includes(scannedLot); | ||||
if (!isAlreadyAdded) { | if (!isAlreadyAdded) { | ||||
return { | return { | ||||
@@ -66,9 +59,10 @@ const MaterialLotScanner: React.FC<MaterialLotScannerProps> = ({ | |||||
}); | }); | ||||
onMaterialsChange(updatedMaterials); | onMaterialsChange(updatedMaterials); | ||||
setMaterialScanInput(''); | setMaterialScanInput(''); | ||||
} else if (data.matchedMaterial) { | |||||
setMaterialScanInput(scannedLot + ' (Invalid lot number format. Suggested lot number: ' + data.suggestedLot + ')'); | |||||
} else { | } else { | ||||
alert('Invalid lot number format or material not recognized.'); | |||||
setMaterialScanInput(''); | |||||
setMaterialScanInput(scannedLot + ' (Invalid lot number format or material not recognized.)'); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -1,8 +1,10 @@ | |||||
"use client"; | "use client"; | ||||
import React, { useState, useRef } from 'react'; | |||||
import { Operator } from './types'; | |||||
import React, { useState, useRef, useEffect } from 'react'; | |||||
import { Operator } from "@/app/api/jo"; | |||||
import { Button, TextField, Typography, Paper, Box, IconButton, Stack } from '@mui/material'; | import { Button, TextField, Typography, Paper, Box, IconButton, Stack } from '@mui/material'; | ||||
import CloseIcon from '@mui/icons-material/Close'; | import CloseIcon from '@mui/icons-material/Close'; | ||||
import { isOperatorExist } from '@/app/api/jo/actions'; | |||||
import { OperatorQrCode } from './types'; | |||||
interface OperatorScannerProps { | interface OperatorScannerProps { | ||||
operators: Operator[]; | operators: Operator[]; | ||||
@@ -10,24 +12,19 @@ interface OperatorScannerProps { | |||||
error?: string; | error?: string; | ||||
} | } | ||||
const operatorDatabase: Operator[] = [ | |||||
{ id: 1, name: 'John Smith', employeeId: 'EMP001', cardId: '12345678' }, | |||||
{ id: 2, name: 'Maria Garcia', employeeId: 'EMP002', cardId: '23456789' }, | |||||
{ id: 3, name: 'David Chen', employeeId: 'EMP003', cardId: '34567890' }, | |||||
{ id: 4, name: 'Sarah Johnson', employeeId: 'EMP004', cardId: '45678901' }, | |||||
{ id: 5, name: 'Mike Wilson', employeeId: 'EMP005', cardId: '56789012' } | |||||
]; | |||||
const OperatorScanner: React.FC<OperatorScannerProps> = ({ | const OperatorScanner: React.FC<OperatorScannerProps> = ({ | ||||
operators, | operators, | ||||
onOperatorsChange, | onOperatorsChange, | ||||
error | error | ||||
}) => { | }) => { | ||||
const [scanningMode, setScanningMode] = useState<boolean>(false); | const [scanningMode, setScanningMode] = useState<boolean>(false); | ||||
const [scanError, setScanError] = useState<string | null>(null); | |||||
const operatorScanRef = useRef<HTMLInputElement>(null); | const operatorScanRef = useRef<HTMLInputElement>(null); | ||||
const startScanning = (): void => { | const startScanning = (): void => { | ||||
setScanningMode(true); | setScanningMode(true); | ||||
setScanError(null); | |||||
setTimeout(() => { | setTimeout(() => { | ||||
if (operatorScanRef.current) { | if (operatorScanRef.current) { | ||||
operatorScanRef.current.focus(); | operatorScanRef.current.focus(); | ||||
@@ -37,27 +34,35 @@ const OperatorScanner: React.FC<OperatorScannerProps> = ({ | |||||
const stopScanning = (): void => { | const stopScanning = (): void => { | ||||
setScanningMode(false); | setScanningMode(false); | ||||
setScanError(null); | |||||
}; | }; | ||||
const handleOperatorScan = (e: React.KeyboardEvent<HTMLInputElement>): void => { | |||||
if (e.key === 'Enter') { | |||||
const target = e.target as HTMLInputElement; | |||||
const scannedId = target.value.trim(); | |||||
const operator = operatorDatabase.find(op => | |||||
op.cardId === scannedId || op.employeeId === scannedId | |||||
); | |||||
if (operator) { | |||||
const isAlreadyAdded = operators.some(op => op.id === operator.id); | |||||
const handleOperatorScan = async (e: React.KeyboardEvent<HTMLInputElement>): Promise<void> => { | |||||
const target = e.target as HTMLInputElement; | |||||
const usernameJSON: string = target.value.trim(); | |||||
if (e.key === 'Enter' || usernameJSON.endsWith('}') ) { | |||||
console.log(usernameJSON) | |||||
try { | |||||
const usernameObj: OperatorQrCode = JSON.parse(usernameJSON) | |||||
const response = await isOperatorExist(usernameObj.username) | |||||
if (!isAlreadyAdded) { | |||||
onOperatorsChange([...operators, operator]); | |||||
if (response.message === "Success") { | |||||
const isAlreadyAdded = operators.some(op => op.username === response.entity.username); | |||||
if (!isAlreadyAdded) { | |||||
onOperatorsChange([...operators, response.entity]); | |||||
} | |||||
target.value = ''; | |||||
setScanError(null); | |||||
// stopScanning(); | |||||
} else { | |||||
setScanError('Operator not found. Please check the ID and try again.'); | |||||
target.value = ''; | |||||
} | } | ||||
target.value = ''; | |||||
stopScanning(); | |||||
} else { | |||||
alert('Operator not found. Please check the ID and try again.'); | |||||
} catch (error) { | |||||
console.error('Error checking operator:', error); | |||||
setScanError('An error occurred while checking the operator. Please try again.'); | |||||
target.value = ''; | target.value = ''; | ||||
} | } | ||||
} | } | ||||
@@ -88,12 +93,13 @@ const OperatorScanner: React.FC<OperatorScannerProps> = ({ | |||||
<TextField | <TextField | ||||
inputRef={operatorScanRef} | inputRef={operatorScanRef} | ||||
type="text" | type="text" | ||||
onKeyPress={handleOperatorScan} | |||||
onKeyDown={handleOperatorScan} | |||||
fullWidth | fullWidth | ||||
label="Scan operator ID card or enter manually..." | label="Scan operator ID card or enter manually..." | ||||
variant="outlined" | variant="outlined" | ||||
size="small" | size="small" | ||||
sx={{ bgcolor: 'white' }} | sx={{ bgcolor: 'white' }} | ||||
onInput={handleOperatorScan} | |||||
/> | /> | ||||
<Button | <Button | ||||
variant="contained" | variant="contained" | ||||
@@ -103,9 +109,13 @@ const OperatorScanner: React.FC<OperatorScannerProps> = ({ | |||||
Cancel | Cancel | ||||
</Button> | </Button> | ||||
</Stack> | </Stack> | ||||
<Typography variant="body2" color="success.main" mt={1}> | |||||
Position the ID card scanner and scan, or type the employee ID manually | |||||
</Typography> | |||||
{scanError ? ( | |||||
<Typography variant="body2" color="error" mt={1}>{scanError}</Typography> | |||||
) : ( | |||||
<Typography variant="body2" color="success.main" mt={1}> | |||||
Position the ID card scanner and scan, or type the employee ID manually | |||||
</Typography> | |||||
)} | |||||
</Paper> | </Paper> | ||||
)} | )} | ||||
@@ -116,7 +126,7 @@ const OperatorScanner: React.FC<OperatorScannerProps> = ({ | |||||
<Paper key={operator.id} sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between', bgcolor: 'blue.50', border: '1px solid', borderColor: 'blue.200' }}> | <Paper key={operator.id} sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between', bgcolor: 'blue.50', border: '1px solid', borderColor: 'blue.200' }}> | ||||
<Box> | <Box> | ||||
<Typography fontWeight={500} color="primary.dark">{operator.name}</Typography> | <Typography fontWeight={500} color="primary.dark">{operator.name}</Typography> | ||||
<Typography variant="body2" color="primary.main">{operator.employeeId}</Typography> | |||||
<Typography variant="body2" color="primary.main">{operator.username}</Typography> | |||||
</Box> | </Box> | ||||
<IconButton onClick={() => removeOperator(operator.id)} color="error"> | <IconButton onClick={() => removeOperator(operator.id)} color="error"> | ||||
<CloseIcon /> | <CloseIcon /> | ||||
@@ -1,28 +1,180 @@ | |||||
"use client"; | "use client"; | ||||
import React, { useState } from 'react'; | |||||
import React, { useTransition } from 'react'; | |||||
import ProductionRecordingModal from './ProductionRecordingModal'; | import ProductionRecordingModal from './ProductionRecordingModal'; | ||||
import { Box, Button } from '@mui/material'; | |||||
import { | |||||
Box, | |||||
Button, | |||||
Table, | |||||
TableBody, | |||||
TableCell, | |||||
TableContainer, | |||||
TableHead, | |||||
TableRow, | |||||
Paper, | |||||
Typography, | |||||
Chip | |||||
} from '@mui/material'; | |||||
import { Material } from './types'; | |||||
import { useTranslation } from 'react-i18next'; | |||||
import QualityCheckModal from './QualityCheckModal'; | |||||
import { sampleItem } from './dummyData'; | |||||
interface ProcessData { | |||||
id: string; | |||||
processName: string; | |||||
status: 'Not Started' | 'In Progress' | 'Completed'; | |||||
recordedData?: any; // You can type this based on your actual data structure | |||||
materials: Material[] | |||||
} | |||||
interface ProductionProcessProps { | |||||
processes: ProcessData[]; | |||||
} | |||||
interface ProductionRecordingModalProps { | |||||
isOpen: boolean; | |||||
onClose: () => void; | |||||
process?: ProcessData | null; | |||||
onDataRecorded?: (processId: string, data: any) => void; | |||||
} | |||||
const ProductionProcess: React.FC<ProductionProcessProps> = ({ processes }) => { | |||||
const [isModalOpen, setIsModalOpen] = React.useState<boolean>(false); | |||||
const [selectedProcess, setSelectedProcess] = React.useState<ProcessData | null>(null); | |||||
const [isQCModalOpen, setIsQCModalOpen] = React.useState<boolean>(false); | |||||
const { t } = useTranslation() | |||||
console.log("production process"); | |||||
const handleOpenModal = (process: ProcessData) => { | |||||
setSelectedProcess(process); | |||||
setIsModalOpen(true); | |||||
}; | |||||
const handleCloseModal = () => { | |||||
setIsModalOpen(false); | |||||
setSelectedProcess(null); | |||||
}; | |||||
const handleQCOpenModal = (process: ProcessData) => { | |||||
setSelectedProcess(process); | |||||
setIsQCModalOpen(true); | |||||
} | |||||
const handleQCCloseModal = () => { | |||||
setIsQCModalOpen(false); | |||||
setSelectedProcess(null); | |||||
}; | |||||
const handleDataRecorded = (processId: string, data: any) => { | |||||
const updated = processes.map(process => | |||||
process.id === processId | |||||
? { ...process, status: 'Completed', recordedData: data } | |||||
: process | |||||
); | |||||
console.log(updated) | |||||
}; | |||||
const getStatusColor = (status: ProcessData['status']) => { | |||||
switch (status) { | |||||
case 'Not Started': | |||||
return 'default'; | |||||
case 'In Progress': | |||||
return 'warning'; | |||||
case 'Completed': | |||||
return 'success'; | |||||
default: | |||||
return 'default'; | |||||
} | |||||
}; | |||||
const ProductionProcess: React.FC = () => { | |||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false); | |||||
console.log("prodcution process"); | |||||
return ( | return ( | ||||
<> | |||||
<Button | |||||
variant="contained" | |||||
color="primary" | |||||
size="large" | |||||
onClick={() => setIsModalOpen(true)} | |||||
sx={{ fontWeight: 500, borderRadius: 2, mb: 2 }} | |||||
> | |||||
Open Production Recording Modal | |||||
</Button> | |||||
<ProductionRecordingModal | |||||
isOpen={isModalOpen} | |||||
onClose={() => setIsModalOpen(false)} | |||||
<Box sx={{ p: 3 }}> | |||||
<Typography variant="h4" component="h1" gutterBottom sx={{ mb: 3 }}> | |||||
Production Process Recording | |||||
</Typography> | |||||
<TableContainer component={Paper} sx={{ boxShadow: 2 }}> | |||||
<Table sx={{ minWidth: 650 }}> | |||||
<TableHead> | |||||
<TableRow sx={{ bgcolor: 'grey.50' }}> | |||||
<TableCell sx={{ fontWeight: 'bold' }}>Process ID</TableCell> | |||||
<TableCell sx={{ fontWeight: 'bold' }}>Process Name</TableCell> | |||||
<TableCell sx={{ fontWeight: 'bold' }}>Status</TableCell> | |||||
<TableCell sx={{ fontWeight: 'bold' }}>Action</TableCell> | |||||
</TableRow> | |||||
</TableHead> | |||||
<TableBody> | |||||
{processes.map((process) => ( | |||||
<TableRow | |||||
key={process.id} | |||||
sx={{ | |||||
'&:last-child td, &:last-child th': { border: 0 }, | |||||
'&:hover': { bgcolor: 'grey.50' } | |||||
}} | |||||
> | |||||
<TableCell component="th" scope="row"> | |||||
{process.id} | |||||
</TableCell> | |||||
<TableCell>{process.processName}</TableCell> | |||||
<TableCell> | |||||
<Chip | |||||
label={process.status} | |||||
color={getStatusColor(process.status)} | |||||
size="small" | |||||
/> | |||||
</TableCell> | |||||
<TableCell> | |||||
<Button | |||||
variant="contained" | |||||
color="primary" | |||||
size="small" | |||||
onClick={() => handleOpenModal(process)} | |||||
sx={{ | |||||
fontWeight: 500, | |||||
textTransform: 'none' | |||||
}} | |||||
> | |||||
{process.status === 'Completed' ? 'View/Edit Data' : 'Record Data'} | |||||
</Button> | |||||
<Button | |||||
variant="contained" | |||||
color="primary" | |||||
size="small" | |||||
onClick={() => handleQCOpenModal(process)} | |||||
sx={{ | |||||
fontWeight: 500, | |||||
textTransform: 'none' | |||||
}} | |||||
> | |||||
{t("Quality Check")} | |||||
</Button> | |||||
</TableCell> | |||||
</TableRow> | |||||
))} | |||||
</TableBody> | |||||
</Table> | |||||
</TableContainer> | |||||
{selectedProcess && ( | |||||
<ProductionRecordingModal | |||||
isOpen={isModalOpen} | |||||
onClose={handleCloseModal} | |||||
jobProcess={selectedProcess} | |||||
onDataRecorded={handleDataRecorded} | |||||
/> | |||||
)} | |||||
{ | |||||
<QualityCheckModal | |||||
isOpen={isQCModalOpen} | |||||
onClose={() => handleQCCloseModal()} | |||||
item={sampleItem} | |||||
/> | /> | ||||
</> | |||||
} | |||||
</Box> | |||||
); | ); | ||||
}; | }; | ||||
@@ -1,3 +1,4 @@ | |||||
import { processData } from "./dummyData"; | |||||
import ProductionProcess from "./ProductionProcess"; | import ProductionProcess from "./ProductionProcess"; | ||||
import ProductionProcessLoading from "./ProductionProcessLoading"; | import ProductionProcessLoading from "./ProductionProcessLoading"; | ||||
@@ -5,9 +6,17 @@ interface SubComponents { | |||||
Loading: typeof ProductionProcessLoading; | Loading: typeof ProductionProcessLoading; | ||||
} | } | ||||
const ProductionProcessWrapper: React.FC & SubComponents = () => { | |||||
console.log("Testing") | |||||
return <ProductionProcess />; | |||||
const ProductionProcessWrapper: React.FC & SubComponents = async () => { | |||||
return ( | |||||
<> | |||||
<h3>p</h3> | |||||
<ProductionProcess | |||||
processes={processData} | |||||
/> | |||||
</> | |||||
) | |||||
}; | }; | ||||
ProductionProcessWrapper.Loading = ProductionProcessLoading; | ProductionProcessWrapper.Loading = ProductionProcessLoading; | ||||
@@ -3,7 +3,8 @@ import React from 'react'; | |||||
import { X, Save } from '@mui/icons-material'; | import { X, Save } from '@mui/icons-material'; | ||||
import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Typography, TextField, Stack, Box, Button } from '@mui/material'; | import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Typography, TextField, Stack, Box, Button } from '@mui/material'; | ||||
import { useForm, Controller } from 'react-hook-form'; | import { useForm, Controller } from 'react-hook-form'; | ||||
import { Operator, Machine, Material, FormData, ProductionRecord } from './types'; | |||||
import { Material, FormData, ProductionRecord, ProcessData } from './types'; | |||||
import { Operator, Machine } from "@/app/api/jo"; | |||||
import OperatorScanner from './OperatorScanner'; | import OperatorScanner from './OperatorScanner'; | ||||
import MachineScanner from './MachineScanner'; | import MachineScanner from './MachineScanner'; | ||||
import MaterialLotScanner from './MaterialLotScanner'; | import MaterialLotScanner from './MaterialLotScanner'; | ||||
@@ -11,24 +12,14 @@ import MaterialLotScanner from './MaterialLotScanner'; | |||||
interface ProductionRecordingModalProps { | interface ProductionRecordingModalProps { | ||||
isOpen: boolean; | isOpen: boolean; | ||||
onClose: () => void; | onClose: () => void; | ||||
jobProcess: ProcessData; | |||||
onDataRecorded?: (processId: string, data: any) => void; | |||||
} | } | ||||
const initialMaterials: Material[] = [ | |||||
{ id: 1, name: 'Steel Sheet', isUsed: false, lotNumbers: [], required: true }, | |||||
{ id: 2, name: 'Aluminum Rod', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 3, name: 'Plastic Pellets', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 4, name: 'Copper Wire', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 5, name: 'Rubber Gasket', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 6, name: 'Glass Panel', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 7, name: 'Hydraulic Oil', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 8, name: 'Cutting Fluid', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 9, name: 'Welding Rod', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 10, name: 'Adhesive', isUsed: false, lotNumbers: [], required: false } | |||||
]; | |||||
const ProductionRecordingModal: React.FC<ProductionRecordingModalProps> = ({ | const ProductionRecordingModal: React.FC<ProductionRecordingModalProps> = ({ | ||||
isOpen, | isOpen, | ||||
onClose | |||||
onClose, | |||||
jobProcess | |||||
}) => { | }) => { | ||||
const { | const { | ||||
control, | control, | ||||
@@ -45,7 +36,8 @@ const ProductionRecordingModal: React.FC<ProductionRecordingModalProps> = ({ | |||||
notes: '', | notes: '', | ||||
operators: [], | operators: [], | ||||
machines: [], | machines: [], | ||||
materials: initialMaterials | |||||
materials: jobProcess.materials || [] | |||||
} | } | ||||
}); | }); | ||||
@@ -127,7 +119,7 @@ const ProductionRecordingModal: React.FC<ProductionRecordingModalProps> = ({ | |||||
notes: '', | notes: '', | ||||
operators: [], | operators: [], | ||||
machines: [], | machines: [], | ||||
materials: initialMaterials.map(material => ({ | |||||
materials: jobProcess.materials.map(material => ({ | |||||
...material, | ...material, | ||||
isUsed: false, | isUsed: false, | ||||
lotNumbers: [] | lotNumbers: [] | ||||
@@ -0,0 +1,289 @@ | |||||
"use client"; | |||||
import React from 'react'; | |||||
import { X, Save } from '@mui/icons-material'; | |||||
import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Typography, Button, Box, Stack } from '@mui/material'; | |||||
import { useForm, Controller } from 'react-hook-form'; | |||||
import { QualityCheckModalProps, QualityCheckData, QualityCheckRecord, QualityCheckItem } from './types'; | |||||
import { getStatusIcon, getStatusColor } from './utils/QualityCheckHelper'; | |||||
import DefectsSection from './DefectsSection'; | |||||
import OperatorScanner from './OperatorScanner'; | |||||
const QualityCheckModal: React.FC<QualityCheckModalProps> = ({ | |||||
isOpen, | |||||
onClose, | |||||
item = null | |||||
}) => { | |||||
const { | |||||
control, | |||||
handleSubmit, | |||||
reset, | |||||
watch, | |||||
setValue, | |||||
setError, | |||||
clearErrors, | |||||
formState: { errors, isValid } | |||||
} = useForm<QualityCheckData>({ | |||||
defaultValues: { | |||||
inspectors: [], | |||||
checkDate: new Date().toISOString().split('T')[0], | |||||
status: 'pending', | |||||
notes: '', | |||||
defects: [] | |||||
}, | |||||
mode: 'onChange' | |||||
}); | |||||
const watchedDefects = watch('defects'); | |||||
const watchedInspectors = watch('inspectors'); | |||||
const validateForm = (): boolean => { | |||||
let isValid = true; | |||||
// Clear previous errors | |||||
clearErrors(); | |||||
// Validate inspectors | |||||
if (!watchedInspectors || watchedInspectors.length === 0) { | |||||
setError('inspectors', { | |||||
type: 'required', | |||||
message: 'At least one inspector is required' | |||||
}); | |||||
isValid = false; | |||||
} | |||||
return isValid; | |||||
}; | |||||
const onSubmit = (data: QualityCheckData): void => { | |||||
if (!validateForm()) { | |||||
return; | |||||
} | |||||
console.log(data); | |||||
const qualityRecord: QualityCheckRecord = { | |||||
...data, | |||||
itemId: item?.id?.toString(), | |||||
itemName: item?.name, | |||||
timestamp: new Date().toISOString() | |||||
}; | |||||
// Save to localStorage or your preferred storage method | |||||
// const existingRecords = JSON.parse(localStorage.getItem('qualityCheckRecords') || '[]'); | |||||
// const updatedRecords = [...existingRecords, qualityRecord]; | |||||
// localStorage.setItem('qualityCheckRecords', JSON.stringify(updatedRecords)); | |||||
// Close modal and reset form | |||||
handleClose(); | |||||
}; | |||||
const handleClose = (): void => { | |||||
reset({ | |||||
inspectors: [], | |||||
checkDate: new Date().toISOString().split('T')[0], | |||||
status: 'pending', | |||||
notes: '', | |||||
defects: [] | |||||
}); | |||||
onClose(); | |||||
}; | |||||
const statusOptions: Array<'pending' | 'passed' | 'failed'> = ['pending', 'passed', 'failed']; | |||||
if (!isOpen) return null; | |||||
return ( | |||||
<Dialog open={isOpen} onClose={handleClose} maxWidth="md" fullWidth> | |||||
<DialogTitle sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> | |||||
<Box> | |||||
<Typography variant="h5" fontWeight={700}>Quality Check</Typography> | |||||
{item && ( | |||||
<Typography variant="body2" color="text.secondary" mt={0.5}> | |||||
Item: {item.name} (ID: {item.id}) | |||||
</Typography> | |||||
)} | |||||
</Box> | |||||
<IconButton onClick={handleClose} size="large"> | |||||
<X /> | |||||
</IconButton> | |||||
</DialogTitle> | |||||
<DialogContent dividers sx={{ p: 3 }}> | |||||
<Stack spacing={4}> | |||||
{/* Inspector and Date */} | |||||
<Stack direction={{ xs: 'column', md: 'row' }} spacing={2}> | |||||
<Box sx={{ flex: 1 }}> | |||||
<Typography variant="body2" fontWeight={600} mb={1}> | |||||
Inspector * | |||||
</Typography> | |||||
<OperatorScanner | |||||
operators={watchedInspectors || []} | |||||
onOperatorsChange={(operators) => { | |||||
setValue('inspectors', operators); | |||||
if (operators.length > 0) { | |||||
clearErrors('inspectors'); | |||||
} | |||||
}} | |||||
error={errors.inspectors?.message} | |||||
/> | |||||
</Box> | |||||
</Stack> | |||||
<Stack direction={{ xs: 'column', md: 'row' }} spacing={2}> | |||||
<Box sx={{ flex: 1 }}> | |||||
<Typography variant="body2" fontWeight={600} mb={1}> | |||||
Check Date * | |||||
</Typography> | |||||
<Controller | |||||
name="checkDate" | |||||
control={control} | |||||
rules={{ required: 'Check date is required' }} | |||||
render={({ field }) => ( | |||||
<input | |||||
{...field} | |||||
type="date" | |||||
style={{ | |||||
width: '100%', | |||||
padding: '8px 12px', | |||||
border: errors.checkDate ? '1px solid #f44336' : '1px solid #ccc', | |||||
borderRadius: '4px', | |||||
fontSize: '14px', | |||||
outline: 'none' | |||||
}} | |||||
/> | |||||
)} | |||||
/> | |||||
{errors.checkDate && ( | |||||
<Typography variant="caption" color="error" sx={{ mt: 0.5 }}> | |||||
{errors.checkDate.message} | |||||
</Typography> | |||||
)} | |||||
</Box> | |||||
</Stack> | |||||
{/* Quality Status */} | |||||
<Box> | |||||
<Typography variant="body2" fontWeight={600} mb={2}> | |||||
Quality Status * | |||||
</Typography> | |||||
<Controller | |||||
name="status" | |||||
control={control} | |||||
rules={{ required: 'Please select a quality status' }} | |||||
render={({ field }) => ( | |||||
<Stack direction="row" spacing={2}> | |||||
{statusOptions.map((statusOption) => { | |||||
const IconComponent = getStatusIcon(statusOption); | |||||
const isSelected = field.value === statusOption; | |||||
return ( | |||||
<Button | |||||
key={statusOption} | |||||
variant={isSelected ? "contained" : "outlined"} | |||||
onClick={() => field.onChange(statusOption)} | |||||
startIcon={ | |||||
<IconComponent | |||||
sx={{ | |||||
fontSize: 20, | |||||
color: statusOption === 'passed' ? 'success.main' : | |||||
statusOption === 'failed' ? 'error.main' : 'warning.main' | |||||
}} | |||||
/> | |||||
} | |||||
sx={{ | |||||
flex: 1, | |||||
textTransform: 'capitalize', | |||||
py: 1.5, | |||||
backgroundColor: isSelected ? | |||||
(statusOption === 'passed' ? 'success.main' : | |||||
statusOption === 'failed' ? 'error.main' : 'warning.main') : | |||||
'transparent', | |||||
borderColor: statusOption === 'passed' ? 'success.main' : | |||||
statusOption === 'failed' ? 'error.main' : 'warning.main', | |||||
color: isSelected ? 'white' : | |||||
(statusOption === 'passed' ? 'success.main' : | |||||
statusOption === 'failed' ? 'error.main' : 'warning.main'), | |||||
'&:hover': { | |||||
backgroundColor: isSelected ? | |||||
(statusOption === 'passed' ? 'success.dark' : | |||||
statusOption === 'failed' ? 'error.dark' : 'warning.dark') : | |||||
(statusOption === 'passed' ? 'success.light' : | |||||
statusOption === 'failed' ? 'error.light' : 'warning.light') | |||||
} | |||||
}} | |||||
> | |||||
{statusOption} | |||||
</Button> | |||||
); | |||||
})} | |||||
</Stack> | |||||
)} | |||||
/> | |||||
{errors.status && ( | |||||
<Typography variant="caption" color="error" sx={{ mt: 0.5 }}> | |||||
{errors.status.message} | |||||
</Typography> | |||||
)} | |||||
</Box> | |||||
{/* Defects Section */} | |||||
<DefectsSection | |||||
defects={watchedDefects || []} | |||||
onDefectsChange={(defects) => { | |||||
setValue('defects', defects); | |||||
clearErrors('defects'); | |||||
}} | |||||
error={errors.defects?.message} | |||||
/> | |||||
{/* Additional Notes */} | |||||
<Controller | |||||
name="notes" | |||||
control={control} | |||||
render={({ field }) => ( | |||||
<Box> | |||||
<Typography variant="body2" fontWeight={600} mb={1}> | |||||
Additional Notes | |||||
</Typography> | |||||
<textarea | |||||
{...field} | |||||
rows={4} | |||||
style={{ | |||||
width: '100%', | |||||
padding: '12px', | |||||
border: '1px solid #ccc', | |||||
borderRadius: '4px', | |||||
fontSize: '14px', | |||||
fontFamily: 'inherit', | |||||
outline: 'none', | |||||
resize: 'vertical' | |||||
}} | |||||
placeholder="Enter any additional observations or notes..." | |||||
/> | |||||
</Box> | |||||
)} | |||||
/> | |||||
</Stack> | |||||
</DialogContent> | |||||
<DialogActions sx={{ p: 3 }}> | |||||
<Button | |||||
variant="outlined" | |||||
color="inherit" | |||||
onClick={handleClose} | |||||
> | |||||
Cancel | |||||
</Button> | |||||
<Button | |||||
variant="contained" | |||||
color="primary" | |||||
onClick={handleSubmit(onSubmit)} | |||||
startIcon={<Save />} | |||||
disabled={!isValid} | |||||
> | |||||
Save Quality Check | |||||
</Button> | |||||
</DialogActions> | |||||
</Dialog> | |||||
); | |||||
}; | |||||
export default QualityCheckModal; |
@@ -0,0 +1,74 @@ | |||||
import { Operator } from "@/app/api/jo"; | |||||
import { Material, MaterialDatabase, ProcessData, QualityCheckItem } from "./types"; | |||||
export const materialDatabase: MaterialDatabase[] = [ | |||||
{ name: 'Steel Sheet', lotPattern: /^SS-\d{6}-\d{3}$/, required: true }, | |||||
{ name: 'Aluminum Rod', lotPattern: /^AL-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Plastic Pellets', lotPattern: /^PP-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Copper Wire', lotPattern: /^CW-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Rubber Gasket', lotPattern: /^RG-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Glass Panel', lotPattern: /^GP-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Hydraulic Oil', lotPattern: /^HO-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Cutting Fluid', lotPattern: /^CF-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Welding Rod', lotPattern: /^WR-\d{6}-\d{3}$/, required: false }, | |||||
{ name: 'Adhesive', lotPattern: /^AD-\d{6}-\d{3}$/, required: false } | |||||
]; | |||||
const operatorDatabase: Operator[] = [ | |||||
{ id: 1, name: 'John Smith', username: 'EMP001', }, | |||||
{ id: 2, name: 'Maria Garcia', username: 'EMP002',} , | |||||
{ id: 3, name: 'David Chen', username: 'EMP003', }, | |||||
{ id: 4, name: 'Sarah Johnson', username: 'EMP004', }, | |||||
{ id: 5, name: 'Mike Wilson', username: 'EMP005', } | |||||
]; | |||||
const initialMaterial1: Material[] = [ | |||||
{ id: 1, name: 'Steel Sheet', isUsed: false, lotNumbers: [], required: true }, | |||||
{ id: 2, name: 'Aluminum Rod', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 3, name: 'Plastic Pellets', isUsed: false, lotNumbers: [], required: false }, | |||||
]; | |||||
const initialMaterial2: Material[] = [ | |||||
{ id: 4, name: 'Copper Wire', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 5, name: 'Rubber Gasket', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 6, name: 'Glass Panel', isUsed: false, lotNumbers: [], required: false }, | |||||
]; | |||||
const initialMaterial3: Material[] = [ | |||||
{ id: 7, name: 'Hydraulic Oil', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 8, name: 'Cutting Fluid', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 9, name: 'Welding Rod', isUsed: false, lotNumbers: [], required: false }, | |||||
{ id: 10, name: 'Adhesive', isUsed: false, lotNumbers: [], required: false } | |||||
]; | |||||
export const processData: ProcessData[] = [ | |||||
{ id: '1', processName: 'Material Preparation', status: 'Not Started', materials: initialMaterial1 }, | |||||
{ id: '2', processName: 'Assembly Line 1', status: 'Not Started', materials: initialMaterial2 }, | |||||
{ id: '3', processName: 'Quality Control', status: 'Not Started', materials: initialMaterial3 } | |||||
]; | |||||
export const sampleItem: QualityCheckItem = { | |||||
id: 1, | |||||
code: 'QC-001', | |||||
name: 'Steel Beam Section A', | |||||
description: 'testing', | |||||
category: 'Structural Steel' | |||||
}; | |||||
export const defectOptions: QualityCheckItem[] = [ | |||||
{ id: 1, code: 'DEF001', name: 'Crack in wall', description: 'Visible cracks in wall surface' }, | |||||
{ id: 2, code: 'DEF002', name: 'Paint peeling', description: 'Paint coming off surfaces' }, | |||||
{ id: 3, code: 'DEF003', name: 'Loose tiles', description: 'Tiles not properly secured' }, | |||||
{ id: 4, code: 'DEF004', name: 'Water damage', description: 'Damage caused by water exposure' }, | |||||
{ id: 5, code: 'DEF005', name: 'Electrical issue', description: 'Problems with electrical systems' }, | |||||
{ id: 6, code: 'DEF006', name: 'Plumbing leak', description: 'Water leaking from pipes or fixtures' }, | |||||
{ id: 7, code: 'DEF007', name: 'Door not closing properly', description: 'Door alignment or hardware issues' }, | |||||
{ id: 8, code: 'DEF008', name: 'Window seal broken', description: 'Damaged or missing window seals' }, | |||||
{ id: 9, code: 'DEF009', name: 'Missing screws', description: 'Hardware missing from fixtures' }, | |||||
{ id: 10, code: 'DEF010', name: 'Damaged flooring', description: 'Floor surface damage or wear' }, | |||||
{ id: 11, code: 'DEF011', name: 'Ceiling stain', description: 'Discoloration on ceiling surface' }, | |||||
{ id: 12, code: 'DEF012', name: 'Broken fixture', description: 'Non-functioning or damaged fixtures' }, | |||||
{ id: 13, code: 'DEF013', name: 'Poor ventilation', description: 'Inadequate air circulation' }, | |||||
{ id: 14, code: 'DEF014', name: 'Noise issue', description: 'Excessive or unwanted noise' }, | |||||
{ id: 15, code: 'DEF015', name: 'Temperature control problem', description: 'HVAC or insulation issues' } | |||||
]; |
@@ -1,15 +1,13 @@ | |||||
export interface Operator { | |||||
id: number; | |||||
name: string; | |||||
employeeId: string; | |||||
cardId: string; | |||||
import { Machine, Operator } from "@/app/api/jo"; | |||||
export interface OperatorQrCode{ | |||||
username: string | |||||
} | } | ||||
export interface Machine { | |||||
id: number; | |||||
name: string; | |||||
code: string; | |||||
qrCode: string; | |||||
export interface MachineQrCode{ | |||||
id?: string, | |||||
code: string, | |||||
name?: string, | |||||
} | } | ||||
export interface Material { | export interface Material { | ||||
@@ -46,4 +44,52 @@ export interface ValidationRule { | |||||
export interface ValidationRules { | export interface ValidationRules { | ||||
[key: string]: ValidationRule; | [key: string]: ValidationRule; | ||||
} | |||||
} | |||||
export interface ProcessData { | |||||
id: string; | |||||
processName: string; | |||||
status: 'Not Started' | 'In Progress' | 'Completed'; | |||||
recordedData?: any; // You can type this based on your actual data structure | |||||
materials: Material[]; | |||||
} | |||||
/// QC related, will be moved to api level when fetching from backend | |||||
export interface QualityCheckItem { | |||||
id: number; | |||||
code: string; | |||||
name: string; | |||||
description: string; | |||||
category?: string; | |||||
} | |||||
export interface QualityCheckData { | |||||
inspectors: Operator[]; | |||||
checkDate: string; | |||||
status: 'pending' | 'passed' | 'failed'; | |||||
notes: string; | |||||
defects: QualityCheckItem[]; | |||||
} | |||||
export interface QualityCheckRecord extends QualityCheckData { | |||||
itemId?: string; | |||||
itemName?: string; | |||||
timestamp: string; | |||||
} | |||||
export interface QualityCheckModalProps { | |||||
isOpen: boolean; | |||||
onClose: () => void; | |||||
item?: QualityCheckItem | null; | |||||
} | |||||
export interface QualityCheckModalFormProps { | |||||
item?: QualityCheckItem | null; | |||||
} | |||||
export interface DefectsSectionProps { | |||||
defects: QualityCheckItem[]; | |||||
onDefectsChange: (defects: QualityCheckItem[]) => void; | |||||
error?: string; | |||||
} |
@@ -0,0 +1,34 @@ | |||||
import { CheckCircle, HighlightOff, ErrorOutline } from '@mui/icons-material'; | |||||
export const getStatusIcon = (status: 'pending' | 'passed' | 'failed') => { | |||||
switch (status) { | |||||
case 'passed': | |||||
return CheckCircle; | |||||
case 'failed': | |||||
return HighlightOff; | |||||
default: | |||||
return ErrorOutline; | |||||
} | |||||
}; | |||||
export const getStatusColor = (status: 'pending' | 'passed' | 'failed'): string => { | |||||
switch (status) { | |||||
case 'passed': | |||||
return 'bg-green-100 text-green-800 border-green-300'; | |||||
case 'failed': | |||||
return 'bg-red-100 text-red-800 border-red-300'; | |||||
default: | |||||
return 'bg-yellow-100 text-yellow-800 border-yellow-300'; | |||||
} | |||||
}; | |||||
export const getStatusColorSimple = (status: 'pending' | 'passed' | 'failed'): string => { | |||||
switch (status) { | |||||
case 'passed': | |||||
return 'bg-green-100 text-green-800'; | |||||
case 'failed': | |||||
return 'bg-red-100 text-red-800'; | |||||
default: | |||||
return 'bg-yellow-100 text-yellow-800'; | |||||
} | |||||
}; |