FPSMS-frontend
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

198 righe
8.1 KiB

  1. "use client";
  2. import React, { useState, useRef, useEffect } from 'react';
  3. import { Material, MaterialDatabase } from './types';
  4. import { Box, Typography, TextField, Paper, Stack, Button, IconButton, Chip } from '@mui/material';
  5. import CheckIcon from '@mui/icons-material/Check';
  6. import CloseIcon from '@mui/icons-material/Close';
  7. interface MaterialLotScannerProps {
  8. materials: Material[];
  9. onMaterialsChange: (materials: Material[]) => void;
  10. error?: string;
  11. }
  12. const MaterialLotScanner: React.FC<MaterialLotScannerProps> = ({
  13. materials,
  14. onMaterialsChange,
  15. error
  16. }) => {
  17. const [materialScanInput, setMaterialScanInput] = useState<string>('');
  18. const materialScanRef = useRef<HTMLInputElement>(null);
  19. useEffect(() => {
  20. if (materialScanRef.current) {
  21. materialScanRef.current.focus();
  22. }
  23. }, []);
  24. const handleMaterialInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  25. setMaterialScanInput(e.target.value.trim());
  26. };
  27. const handleMaterialInputKeyPress = async (e: React.KeyboardEvent<HTMLInputElement>) => {
  28. if (e.key === 'Enter') {
  29. const target = e.target as HTMLInputElement;
  30. const scannedLot = target.value.trim();
  31. if (scannedLot) {
  32. const response = await fetch('http://your-backend-url.com/validateLot', {
  33. method: 'POST',
  34. headers: {
  35. 'Content-Type': 'application/json'
  36. },
  37. body: JSON.stringify({ lot: scannedLot })
  38. });
  39. const data = await response.json();
  40. if (data.suggestedLot) {
  41. const updatedMaterials = materials.map(material => {
  42. if (material.name === data.matchedMaterial.name) {
  43. const isAlreadyAdded = material.lotNumbers.includes(scannedLot);
  44. if (!isAlreadyAdded) {
  45. return {
  46. ...material,
  47. isUsed: true,
  48. lotNumbers: [...material.lotNumbers, scannedLot]
  49. };
  50. }
  51. }
  52. return material;
  53. });
  54. onMaterialsChange(updatedMaterials);
  55. setMaterialScanInput('');
  56. } else if (data.matchedMaterial) {
  57. setMaterialScanInput(scannedLot + ' (Invalid lot number format. Suggested lot number: ' + data.suggestedLot + ')');
  58. } else {
  59. setMaterialScanInput(scannedLot + ' (Invalid lot number format or material not recognized.)');
  60. }
  61. }
  62. }
  63. };
  64. const removeLotNumber = (materialName: string, lotNumber: string): void => {
  65. const updatedMaterials = materials.map(material => {
  66. if (material.name === materialName) {
  67. const updatedLotNumbers = material.lotNumbers.filter(lot => lot !== lotNumber);
  68. return {
  69. ...material,
  70. lotNumbers: updatedLotNumbers,
  71. isUsed: updatedLotNumbers.length > 0
  72. };
  73. }
  74. return material;
  75. });
  76. onMaterialsChange(updatedMaterials);
  77. };
  78. const requiredMaterials = materials.filter(m => m.required);
  79. const optionalMaterials = materials.filter(m => !m.required);
  80. return (
  81. <Box>
  82. <Typography variant="h6" fontWeight={600} mb={2}>
  83. Material Lot Numbers
  84. </Typography>
  85. <Paper elevation={2} sx={{ mb: 2, p: 2, bgcolor: 'yellow.50', border: '1px solid', borderColor: 'yellow.200' }}>
  86. <TextField
  87. inputRef={materialScanRef}
  88. type="text"
  89. value={materialScanInput}
  90. onChange={handleMaterialInputChange}
  91. onKeyPress={handleMaterialInputKeyPress}
  92. fullWidth
  93. label="Scan or enter material lot number (e.g., SS-240616-001)"
  94. variant="outlined"
  95. size="small"
  96. sx={{ bgcolor: 'white' }}
  97. />
  98. <Box mt={2}>
  99. <Typography variant="body2" color="warning.main">
  100. <strong>Lot Number Formats:</strong>
  101. </Typography>
  102. <Typography variant="body2" color="warning.main">
  103. Steel Sheet: SS-YYMMDD-XXX | Aluminum: AL-YYMMDD-XXX | Plastic: PP-YYMMDD-XXX
  104. </Typography>
  105. <Typography variant="body2" color="warning.main">
  106. Copper Wire: CW-YYMMDD-XXX | Rubber: RG-YYMMDD-XXX | Glass: GP-YYMMDD-XXX
  107. </Typography>
  108. </Box>
  109. </Paper>
  110. {error && <Typography color="error" variant="body2" mb={2}>{error}</Typography>}
  111. <Stack spacing={3}>
  112. {/* Required Materials */}
  113. <Box>
  114. <Stack direction="row" alignItems="center" spacing={1} mb={1}>
  115. <Typography variant="subtitle1" fontWeight={600} color="error.main">
  116. Required Materials
  117. </Typography>
  118. <Chip label="Must scan lot numbers" color="error" size="small" />
  119. </Stack>
  120. <Stack spacing={1}>
  121. {requiredMaterials.map((material) => (
  122. <Paper key={material.id} sx={{ p: 2, bgcolor: 'red.50', border: '1px solid', borderColor: 'red.200' }}>
  123. <Stack direction="row" alignItems="center" justifyContent="space-between" mb={1}>
  124. <Stack direction="row" alignItems="center" spacing={1}>
  125. {material.isUsed && <CheckIcon fontSize="small" color="success" />}
  126. <Typography fontWeight={500}>{material.name}</Typography>
  127. </Stack>
  128. <Typography variant="body2" color="text.secondary">
  129. {material.lotNumbers.length} lot(s)
  130. </Typography>
  131. </Stack>
  132. {material.lotNumbers.length > 0 && (
  133. <Stack spacing={1}>
  134. {material.lotNumbers.map((lotNumber, index) => (
  135. <Paper key={index} sx={{ p: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', bgcolor: 'white', border: '1px solid', borderColor: 'grey.200' }}>
  136. <Typography variant="body2" fontFamily="monospace">{lotNumber}</Typography>
  137. <IconButton onClick={() => removeLotNumber(material.name, lotNumber)} color="error" size="small">
  138. <CloseIcon fontSize="small" />
  139. </IconButton>
  140. </Paper>
  141. ))}
  142. </Stack>
  143. )}
  144. </Paper>
  145. ))}
  146. </Stack>
  147. </Box>
  148. {/* Optional Materials */}
  149. <Box>
  150. <Stack direction="row" alignItems="center" spacing={1} mb={1}>
  151. <Typography variant="subtitle1" fontWeight={600} color="primary.main">
  152. Optional Materials
  153. </Typography>
  154. <Chip label="Lot numbers recommended" color="primary" size="small" />
  155. </Stack>
  156. <Stack spacing={1}>
  157. {optionalMaterials.map((material) => (
  158. <Paper key={material.id} sx={{ p: 2, bgcolor: 'blue.50', border: '1px solid', borderColor: 'blue.200' }}>
  159. <Stack direction="row" alignItems="center" justifyContent="space-between" mb={1}>
  160. <Stack direction="row" alignItems="center" spacing={1}>
  161. {material.isUsed && <CheckIcon fontSize="small" color="success" />}
  162. <Typography fontWeight={500}>{material.name}</Typography>
  163. </Stack>
  164. <Typography variant="body2" color="text.secondary">
  165. {material.lotNumbers.length} lot(s)
  166. </Typography>
  167. </Stack>
  168. {material.lotNumbers.length > 0 && (
  169. <Stack spacing={1}>
  170. {material.lotNumbers.map((lotNumber, index) => (
  171. <Paper key={index} sx={{ p: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', bgcolor: 'white', border: '1px solid', borderColor: 'grey.200' }}>
  172. <Typography variant="body2" fontFamily="monospace">{lotNumber}</Typography>
  173. <IconButton onClick={() => removeLotNumber(material.name, lotNumber)} color="error" size="small">
  174. <CloseIcon fontSize="small" />
  175. </IconButton>
  176. </Paper>
  177. ))}
  178. </Stack>
  179. )}
  180. </Paper>
  181. ))}
  182. </Stack>
  183. </Box>
  184. </Stack>
  185. </Box>
  186. );
  187. };
  188. export default MaterialLotScanner;