diff --git a/src/app/(main)/production/page.tsx b/src/app/(main)/production/page.tsx
index 180b348..59093d2 100644
--- a/src/app/(main)/production/page.tsx
+++ b/src/app/(main)/production/page.tsx
@@ -1,5 +1,6 @@
import { preloadClaims } from "@/app/api/claims";
import ClaimSearch from "@/components/ClaimSearch";
+import ProductionProcess from "@/components/ProductionProcess";
import { getServerI18n } from "@/i18n";
import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button";
@@ -38,7 +39,7 @@ const production: React.FC = async () => {
}>
-
+
>
);
diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx
index 4817500..3470431 100644
--- a/src/components/NavigationContent/NavigationContent.tsx
+++ b/src/components/NavigationContent/NavigationContent.tsx
@@ -89,7 +89,7 @@ const NavigationContent: React.FC = () => {
{
icon: ,
label: "Job Order",
- path: "",
+ path: "/production",
},
{
icon: ,
diff --git a/src/components/PoDetail/PoInputGrid.tsx b/src/components/PoDetail/PoInputGrid.tsx
index 09b54ee..54a4711 100644
--- a/src/components/PoDetail/PoInputGrid.tsx
+++ b/src/components/PoDetail/PoInputGrid.tsx
@@ -485,10 +485,10 @@ function PoInputGrid({
// marginRight: 1,
}}
disabled={
- // stockInLineStatusMap[status] === 9 ||
- // stockInLineStatusMap[status] <= 2 ||
- // stockInLineStatusMap[status] >= 7 ||
- ((stockInLineStatusMap[status] >= 3 && stockInLineStatusMap[status] <= 5) ? !session?.user?.abilities?.includes("APPROVAL"): false)
+ stockInLineStatusMap[status] === 9 ||
+ stockInLineStatusMap[status] <= 2 ||
+ stockInLineStatusMap[status] >= 7 ||
+ ((stockInLineStatusMap[status] >= 3 && stockInLineStatusMap[status] <= 5) && !session?.user?.abilities?.includes("APPROVAL"))
}
// set _isNew to false after posting
// or check status
diff --git a/src/components/ProductionProcess/MachineScanner.tsx b/src/components/ProductionProcess/MachineScanner.tsx
new file mode 100644
index 0000000..9b49d42
--- /dev/null
+++ b/src/components/ProductionProcess/MachineScanner.tsx
@@ -0,0 +1,138 @@
+"use client";
+import React, { useState, useRef } from 'react';
+import { Machine } from './types';
+import { Button, TextField, Typography, Paper, Box, IconButton, Stack } from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
+
+interface MachineScannerProps {
+ machines: Machine[];
+ onMachinesChange: (machines: Machine[]) => void;
+ error?: string;
+}
+
+const machineDatabase: Machine[] = [
+ { id: 1, name: 'CNC Mill #1', code: 'CNC001', qrCode: 'QR-CNC001' },
+ { id: 2, name: 'Lathe #2', code: 'LAT002', qrCode: 'QR-LAT002' },
+ { id: 3, name: 'Press #3', code: 'PRS003', qrCode: 'QR-PRS003' },
+ { id: 4, name: 'Welder #4', code: 'WLD004', qrCode: 'QR-WLD004' },
+ { id: 5, name: 'Drill Press #5', code: 'DRL005', qrCode: 'QR-DRL005' }
+];
+
+const MachineScanner: React.FC = ({
+ machines,
+ onMachinesChange,
+ error
+}) => {
+ const [scanningMode, setScanningMode] = useState(false);
+ const machineScanRef = useRef(null);
+
+ const startScanning = (): void => {
+ setScanningMode(true);
+ setTimeout(() => {
+ if (machineScanRef.current) {
+ machineScanRef.current.focus();
+ }
+ }, 100);
+ };
+
+ const stopScanning = (): void => {
+ setScanningMode(false);
+ };
+
+ const handleMachineScan = (e: React.KeyboardEvent): 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
+ );
+
+ if (machine) {
+ const isAlreadyAdded = machines.some(m => m.id === machine.id);
+
+ if (!isAlreadyAdded) {
+ onMachinesChange([...machines, machine]);
+ }
+
+ target.value = '';
+ stopScanning();
+ } else {
+ alert('Machine not found. Please check the code and try again.');
+ target.value = '';
+ }
+ }
+ };
+
+ const removeMachine = (machineId: number): void => {
+ onMachinesChange(machines.filter(m => m.id !== machineId));
+ };
+
+ return (
+
+
+
+ Machines *
+
+
+
+
+ {scanningMode && (
+
+
+
+
+
+
+ Position the QR code scanner and scan, or type the machine code manually
+
+
+ )}
+
+ {error && {error}}
+
+
+ {machines.map((machine) => (
+
+
+ {machine.name}
+ {machine.code}
+
+ removeMachine(machine.id)} color="error">
+
+
+
+ ))}
+ {machines.length === 0 && (
+
+
+ No machines added yet. Use the scan button to add machines.
+
+
+ )}
+
+
+ );
+};
+
+export default MachineScanner;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/MaterialLotScanner.tsx b/src/components/ProductionProcess/MaterialLotScanner.tsx
new file mode 100644
index 0000000..c8bbb9c
--- /dev/null
+++ b/src/components/ProductionProcess/MaterialLotScanner.tsx
@@ -0,0 +1,204 @@
+"use client";
+import React, { useState, useRef, useEffect } from 'react';
+import { Material, MaterialDatabase } from './types';
+import { Box, Typography, TextField, Paper, Stack, Button, IconButton, Chip } from '@mui/material';
+import CheckIcon from '@mui/icons-material/Check';
+import CloseIcon from '@mui/icons-material/Close';
+
+interface MaterialLotScannerProps {
+ materials: Material[];
+ onMaterialsChange: (materials: Material[]) => void;
+ 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 = ({
+ materials,
+ onMaterialsChange,
+ error
+}) => {
+ const [materialScanInput, setMaterialScanInput] = useState('');
+ const materialScanRef = useRef(null);
+
+ useEffect(() => {
+ if (materialScanRef.current) {
+ materialScanRef.current.focus();
+ }
+ }, []);
+
+ const handleMaterialInputChange = (e: React.ChangeEvent) => {
+ setMaterialScanInput(e.target.value.trim());
+ };
+
+ const handleMaterialInputKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ const target = e.target as HTMLInputElement;
+ const scannedLot = target.value.trim();
+ if (scannedLot) {
+ const matchedMaterial = materialDatabase.find(material =>
+ material.lotPattern.test(scannedLot)
+ );
+ if (matchedMaterial) {
+ const updatedMaterials = materials.map(material => {
+ if (material.name === matchedMaterial.name) {
+ const isAlreadyAdded = material.lotNumbers.includes(scannedLot);
+ if (!isAlreadyAdded) {
+ return {
+ ...material,
+ isUsed: true,
+ lotNumbers: [...material.lotNumbers, scannedLot]
+ };
+ }
+ }
+ return material;
+ });
+ onMaterialsChange(updatedMaterials);
+ setMaterialScanInput('');
+ } else {
+ alert('Invalid lot number format or material not recognized.');
+ setMaterialScanInput('');
+ }
+ }
+ }
+ };
+
+ const removeLotNumber = (materialName: string, lotNumber: string): void => {
+ const updatedMaterials = materials.map(material => {
+ if (material.name === materialName) {
+ const updatedLotNumbers = material.lotNumbers.filter(lot => lot !== lotNumber);
+ return {
+ ...material,
+ lotNumbers: updatedLotNumbers,
+ isUsed: updatedLotNumbers.length > 0
+ };
+ }
+ return material;
+ });
+ onMaterialsChange(updatedMaterials);
+ };
+
+ const requiredMaterials = materials.filter(m => m.required);
+ const optionalMaterials = materials.filter(m => !m.required);
+
+ return (
+
+
+ Material Lot Numbers
+
+
+
+
+
+ Lot Number Formats:
+
+
+ Steel Sheet: SS-YYMMDD-XXX | Aluminum: AL-YYMMDD-XXX | Plastic: PP-YYMMDD-XXX
+
+
+ Copper Wire: CW-YYMMDD-XXX | Rubber: RG-YYMMDD-XXX | Glass: GP-YYMMDD-XXX
+
+
+
+ {error && {error}}
+
+ {/* Required Materials */}
+
+
+
+ Required Materials
+
+
+
+
+ {requiredMaterials.map((material) => (
+
+
+
+ {material.isUsed && }
+ {material.name}
+
+
+ {material.lotNumbers.length} lot(s)
+
+
+ {material.lotNumbers.length > 0 && (
+
+ {material.lotNumbers.map((lotNumber, index) => (
+
+ {lotNumber}
+ removeLotNumber(material.name, lotNumber)} color="error" size="small">
+
+
+
+ ))}
+
+ )}
+
+ ))}
+
+
+ {/* Optional Materials */}
+
+
+
+ Optional Materials
+
+
+
+
+ {optionalMaterials.map((material) => (
+
+
+
+ {material.isUsed && }
+ {material.name}
+
+
+ {material.lotNumbers.length} lot(s)
+
+
+ {material.lotNumbers.length > 0 && (
+
+ {material.lotNumbers.map((lotNumber, index) => (
+
+ {lotNumber}
+ removeLotNumber(material.name, lotNumber)} color="error" size="small">
+
+
+
+ ))}
+
+ )}
+
+ ))}
+
+
+
+
+ );
+};
+
+export default MaterialLotScanner;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/OperatorScanner.tsx b/src/components/ProductionProcess/OperatorScanner.tsx
new file mode 100644
index 0000000..ce521f5
--- /dev/null
+++ b/src/components/ProductionProcess/OperatorScanner.tsx
@@ -0,0 +1,138 @@
+"use client";
+import React, { useState, useRef } from 'react';
+import { Operator } from './types';
+import { Button, TextField, Typography, Paper, Box, IconButton, Stack } from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
+
+interface OperatorScannerProps {
+ operators: Operator[];
+ onOperatorsChange: (operators: Operator[]) => void;
+ 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 = ({
+ operators,
+ onOperatorsChange,
+ error
+}) => {
+ const [scanningMode, setScanningMode] = useState(false);
+ const operatorScanRef = useRef(null);
+
+ const startScanning = (): void => {
+ setScanningMode(true);
+ setTimeout(() => {
+ if (operatorScanRef.current) {
+ operatorScanRef.current.focus();
+ }
+ }, 100);
+ };
+
+ const stopScanning = (): void => {
+ setScanningMode(false);
+ };
+
+ const handleOperatorScan = (e: React.KeyboardEvent): 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);
+
+ if (!isAlreadyAdded) {
+ onOperatorsChange([...operators, operator]);
+ }
+
+ target.value = '';
+ stopScanning();
+ } else {
+ alert('Operator not found. Please check the ID and try again.');
+ target.value = '';
+ }
+ }
+ };
+
+ const removeOperator = (operatorId: number): void => {
+ onOperatorsChange(operators.filter(op => op.id !== operatorId));
+ };
+
+ return (
+
+
+
+ Operators *
+
+
+
+
+ {scanningMode && (
+
+
+
+
+
+
+ Position the ID card scanner and scan, or type the employee ID manually
+
+
+ )}
+
+ {error && {error}}
+
+
+ {operators.map((operator) => (
+
+
+ {operator.name}
+ {operator.employeeId}
+
+ removeOperator(operator.id)} color="error">
+
+
+
+ ))}
+ {operators.length === 0 && (
+
+
+ No operators added yet. Use the scan button to add operators.
+
+
+ )}
+
+
+ );
+};
+
+export default OperatorScanner;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/ProductionProcess.tsx b/src/components/ProductionProcess/ProductionProcess.tsx
new file mode 100644
index 0000000..f5d22ca
--- /dev/null
+++ b/src/components/ProductionProcess/ProductionProcess.tsx
@@ -0,0 +1,29 @@
+"use client";
+import React, { useState } from 'react';
+import ProductionRecordingModal from './ProductionRecordingModal';
+import { Box, Button } from '@mui/material';
+
+const ProductionProcess: React.FC = () => {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ console.log("prodcution process");
+ return (
+ <>
+
+
+ setIsModalOpen(false)}
+ />
+ >
+ );
+};
+
+export default ProductionProcess;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/ProductionProcessLoading.tsx b/src/components/ProductionProcess/ProductionProcessLoading.tsx
new file mode 100644
index 0000000..caef2c1
--- /dev/null
+++ b/src/components/ProductionProcess/ProductionProcessLoading.tsx
@@ -0,0 +1,40 @@
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Skeleton from "@mui/material/Skeleton";
+import Stack from "@mui/material/Stack";
+import React from "react";
+
+// Can make this nicer
+export const ProductionProcessLoading: React.FC = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default ProductionProcessLoading;
diff --git a/src/components/ProductionProcess/ProductionProcessWrapper.tsx b/src/components/ProductionProcess/ProductionProcessWrapper.tsx
new file mode 100644
index 0000000..6d00afb
--- /dev/null
+++ b/src/components/ProductionProcess/ProductionProcessWrapper.tsx
@@ -0,0 +1,15 @@
+import ProductionProcess from "./ProductionProcess";
+import ProductionProcessLoading from "./ProductionProcessLoading";
+
+interface SubComponents {
+ Loading: typeof ProductionProcessLoading;
+}
+
+const ProductionProcessWrapper: React.FC & SubComponents = () => {
+ console.log("Testing")
+ return ;
+ };
+
+ ProductionProcessWrapper.Loading = ProductionProcessLoading;
+
+ export default ProductionProcessWrapper;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/ProductionRecordingModal.tsx b/src/components/ProductionProcess/ProductionRecordingModal.tsx
new file mode 100644
index 0000000..28a9544
--- /dev/null
+++ b/src/components/ProductionProcess/ProductionRecordingModal.tsx
@@ -0,0 +1,257 @@
+"use client";
+import React from 'react';
+import { X, Save } from '@mui/icons-material';
+import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Typography, TextField, Stack, Box, Button } from '@mui/material';
+import { useForm, Controller } from 'react-hook-form';
+import { Operator, Machine, Material, FormData, ProductionRecord } from './types';
+import OperatorScanner from './OperatorScanner';
+import MachineScanner from './MachineScanner';
+import MaterialLotScanner from './MaterialLotScanner';
+
+interface ProductionRecordingModalProps {
+ isOpen: boolean;
+ onClose: () => 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 = ({
+ isOpen,
+ onClose
+}) => {
+ const {
+ control,
+ handleSubmit,
+ reset,
+ watch,
+ setValue,
+ setError,
+ clearErrors,
+ formState: { errors }
+ } = useForm({
+ defaultValues: {
+ productionDate: new Date().toISOString().split('T')[0],
+ notes: '',
+ operators: [],
+ machines: [],
+ materials: initialMaterials
+ }
+ });
+
+ const watchedOperators = watch('operators');
+ const watchedMachines = watch('machines');
+ const watchedMaterials = watch('materials');
+
+ const validateForm = (): boolean => {
+ let isValid = true;
+
+ // Clear previous errors
+ clearErrors();
+
+ // Validate operators
+ if (!watchedOperators || watchedOperators.length === 0) {
+ setError('operators', {
+ type: 'required',
+ message: 'At least one operator is required'
+ });
+ isValid = false;
+ }
+
+ // Validate machines
+ if (!watchedMachines || watchedMachines.length === 0) {
+ setError('machines', {
+ type: 'required',
+ message: 'At least one machine is required'
+ });
+ isValid = false;
+ }
+
+ // Validate materials
+ console.log(watchedMaterials)
+ if (watchedMaterials) {
+ const missingLotNumbers = watchedMaterials
+ .filter(m => m.required && m.isUsed && (!m.lotNumbers || m.lotNumbers.length === 0))
+ .length;
+
+ if (missingLotNumbers > 0) {
+ setError('materials', {
+ type: 'custom',
+ message: `${missingLotNumbers} required material(s) missing lot numbers`
+ });
+ isValid = false;
+ }
+ }
+
+ return isValid;
+ };
+
+ const onSubmit = (data: FormData) => {
+ if (!validateForm()) {
+ return;
+ }
+
+ const usedMaterials = data.materials
+ .filter(material => material.isUsed)
+ .map(material => ({
+ id: material.id,
+ name: material.name,
+ lotNumbers: material.lotNumbers,
+ required: material.required,
+ isUsed: false
+ }));
+
+ const recordData: ProductionRecord = {
+ ...data,
+ materials: usedMaterials,
+ timestamp: new Date().toISOString()
+ };
+
+ console.log('Production record saved:', recordData);
+ handleClose();
+ };
+
+ const handleClose = (): void => {
+ reset({
+ productionDate: new Date().toISOString().split('T')[0],
+ notes: '',
+ operators: [],
+ machines: [],
+ materials: initialMaterials.map(material => ({
+ ...material,
+ isUsed: false,
+ lotNumbers: []
+ }))
+ });
+ onClose();
+ };
+
+ const getUsedMaterialsCount = (): number =>
+ watchedMaterials?.filter(m => m.isUsed).length || 0;
+
+ if (!isOpen) return null;
+
+ return (
+
+ );
+};
+
+export default ProductionRecordingModal;
\ No newline at end of file
diff --git a/src/components/ProductionProcess/index.ts b/src/components/ProductionProcess/index.ts
new file mode 100644
index 0000000..c022da4
--- /dev/null
+++ b/src/components/ProductionProcess/index.ts
@@ -0,0 +1 @@
+export { default } from "./ProductionProcessWrapper";
\ No newline at end of file
diff --git a/src/components/ProductionProcess/types.ts b/src/components/ProductionProcess/types.ts
new file mode 100644
index 0000000..addfeb6
--- /dev/null
+++ b/src/components/ProductionProcess/types.ts
@@ -0,0 +1,49 @@
+export interface Operator {
+ id: number;
+ name: string;
+ employeeId: string;
+ cardId: string;
+}
+
+export interface Machine {
+ id: number;
+ name: string;
+ code: string;
+ qrCode: string;
+}
+
+export interface Material {
+ id: number;
+ name: string;
+ isUsed: boolean;
+ lotNumbers: string[];
+ required: boolean;
+}
+
+export interface MaterialDatabase {
+ name: string;
+ lotPattern: RegExp;
+ required: boolean;
+}
+
+export interface FormData {
+ productionDate: string;
+ notes: string;
+ operators: Operator[];
+ machines: Machine[];
+ materials: Material[];
+}
+
+export interface ProductionRecord extends FormData {
+ timestamp: string;
+}
+
+export interface ValidationRule {
+ required?: boolean;
+ message?: string;
+ custom?: (value: any, allValues?: any) => string | null;
+}
+
+export interface ValidationRules {
+ [key: string]: ValidationRule;
+}
\ No newline at end of file