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.
 
 

289 righe
9.6 KiB

  1. "use client";
  2. import React from 'react';
  3. import { X, Save } from '@mui/icons-material';
  4. import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Typography, Button, Box, Stack } from '@mui/material';
  5. import { useForm, Controller } from 'react-hook-form';
  6. import { QualityCheckModalProps, QualityCheckData, QualityCheckRecord, QualityCheckItem } from './types';
  7. import { getStatusIcon, getStatusColor } from './utils/QualityCheckHelper';
  8. import DefectsSection from './DefectsSection';
  9. import OperatorScanner from './OperatorScanner';
  10. const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
  11. isOpen,
  12. onClose,
  13. item = null
  14. }) => {
  15. const {
  16. control,
  17. handleSubmit,
  18. reset,
  19. watch,
  20. setValue,
  21. setError,
  22. clearErrors,
  23. formState: { errors, isValid }
  24. } = useForm<QualityCheckData>({
  25. defaultValues: {
  26. inspectors: [],
  27. checkDate: new Date().toISOString().split('T')[0],
  28. status: 'pending',
  29. notes: '',
  30. defects: []
  31. },
  32. mode: 'onChange'
  33. });
  34. const watchedDefects = watch('defects');
  35. const watchedInspectors = watch('inspectors');
  36. const validateForm = (): boolean => {
  37. let isValid = true;
  38. // Clear previous errors
  39. clearErrors();
  40. // Validate inspectors
  41. if (!watchedInspectors || watchedInspectors.length === 0) {
  42. setError('inspectors', {
  43. type: 'required',
  44. message: 'At least one inspector is required'
  45. });
  46. isValid = false;
  47. }
  48. return isValid;
  49. };
  50. const onSubmit = (data: QualityCheckData): void => {
  51. if (!validateForm()) {
  52. return;
  53. }
  54. console.log(data);
  55. const qualityRecord: QualityCheckRecord = {
  56. ...data,
  57. itemId: item?.id?.toString(),
  58. itemName: item?.name,
  59. timestamp: new Date().toISOString()
  60. };
  61. // Save to localStorage or your preferred storage method
  62. // const existingRecords = JSON.parse(localStorage.getItem('qualityCheckRecords') || '[]');
  63. // const updatedRecords = [...existingRecords, qualityRecord];
  64. // localStorage.setItem('qualityCheckRecords', JSON.stringify(updatedRecords));
  65. // Close modal and reset form
  66. handleClose();
  67. };
  68. const handleClose = (): void => {
  69. reset({
  70. inspectors: [],
  71. checkDate: new Date().toISOString().split('T')[0],
  72. status: 'pending',
  73. notes: '',
  74. defects: []
  75. });
  76. onClose();
  77. };
  78. const statusOptions: Array<'pending' | 'passed' | 'failed'> = ['pending', 'passed', 'failed'];
  79. if (!isOpen) return null;
  80. return (
  81. <Dialog open={isOpen} onClose={handleClose} maxWidth="md" fullWidth>
  82. <DialogTitle sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
  83. <Box>
  84. <Typography variant="h5" fontWeight={700}>Quality Check</Typography>
  85. {item && (
  86. <Typography variant="body2" color="text.secondary" mt={0.5}>
  87. Item: {item.name} (ID: {item.id})
  88. </Typography>
  89. )}
  90. </Box>
  91. <IconButton onClick={handleClose} size="large">
  92. <X />
  93. </IconButton>
  94. </DialogTitle>
  95. <DialogContent dividers sx={{ p: 3 }}>
  96. <Stack spacing={4}>
  97. {/* Inspector and Date */}
  98. <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>
  99. <Box sx={{ flex: 1 }}>
  100. <Typography variant="body2" fontWeight={600} mb={1}>
  101. Inspector *
  102. </Typography>
  103. <OperatorScanner
  104. operators={watchedInspectors || []}
  105. onOperatorsChange={(operators) => {
  106. setValue('inspectors', operators);
  107. if (operators.length > 0) {
  108. clearErrors('inspectors');
  109. }
  110. }}
  111. error={errors.inspectors?.message}
  112. />
  113. </Box>
  114. </Stack>
  115. <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>
  116. <Box sx={{ flex: 1 }}>
  117. <Typography variant="body2" fontWeight={600} mb={1}>
  118. Check Date *
  119. </Typography>
  120. <Controller
  121. name="checkDate"
  122. control={control}
  123. rules={{ required: 'Check date is required' }}
  124. render={({ field }) => (
  125. <input
  126. {...field}
  127. type="date"
  128. style={{
  129. width: '100%',
  130. padding: '8px 12px',
  131. border: errors.checkDate ? '1px solid #f44336' : '1px solid #ccc',
  132. borderRadius: '4px',
  133. fontSize: '14px',
  134. outline: 'none'
  135. }}
  136. />
  137. )}
  138. />
  139. {errors.checkDate && (
  140. <Typography variant="caption" color="error" sx={{ mt: 0.5 }}>
  141. {errors.checkDate.message}
  142. </Typography>
  143. )}
  144. </Box>
  145. </Stack>
  146. {/* Quality Status */}
  147. <Box>
  148. <Typography variant="body2" fontWeight={600} mb={2}>
  149. Quality Status *
  150. </Typography>
  151. <Controller
  152. name="status"
  153. control={control}
  154. rules={{ required: 'Please select a quality status' }}
  155. render={({ field }) => (
  156. <Stack direction="row" spacing={2}>
  157. {statusOptions.map((statusOption) => {
  158. const IconComponent = getStatusIcon(statusOption);
  159. const isSelected = field.value === statusOption;
  160. return (
  161. <Button
  162. key={statusOption}
  163. variant={isSelected ? "contained" : "outlined"}
  164. onClick={() => field.onChange(statusOption)}
  165. startIcon={
  166. <IconComponent
  167. sx={{
  168. fontSize: 20,
  169. color: statusOption === 'passed' ? 'success.main' :
  170. statusOption === 'failed' ? 'error.main' : 'warning.main'
  171. }}
  172. />
  173. }
  174. sx={{
  175. flex: 1,
  176. textTransform: 'capitalize',
  177. py: 1.5,
  178. backgroundColor: isSelected ?
  179. (statusOption === 'passed' ? 'success.main' :
  180. statusOption === 'failed' ? 'error.main' : 'warning.main') :
  181. 'transparent',
  182. borderColor: statusOption === 'passed' ? 'success.main' :
  183. statusOption === 'failed' ? 'error.main' : 'warning.main',
  184. color: isSelected ? 'white' :
  185. (statusOption === 'passed' ? 'success.main' :
  186. statusOption === 'failed' ? 'error.main' : 'warning.main'),
  187. '&:hover': {
  188. backgroundColor: isSelected ?
  189. (statusOption === 'passed' ? 'success.dark' :
  190. statusOption === 'failed' ? 'error.dark' : 'warning.dark') :
  191. (statusOption === 'passed' ? 'success.light' :
  192. statusOption === 'failed' ? 'error.light' : 'warning.light')
  193. }
  194. }}
  195. >
  196. {statusOption}
  197. </Button>
  198. );
  199. })}
  200. </Stack>
  201. )}
  202. />
  203. {errors.status && (
  204. <Typography variant="caption" color="error" sx={{ mt: 0.5 }}>
  205. {errors.status.message}
  206. </Typography>
  207. )}
  208. </Box>
  209. {/* Defects Section */}
  210. <DefectsSection
  211. defects={watchedDefects || []}
  212. onDefectsChange={(defects) => {
  213. setValue('defects', defects);
  214. clearErrors('defects');
  215. }}
  216. error={errors.defects?.message}
  217. />
  218. {/* Additional Notes */}
  219. <Controller
  220. name="notes"
  221. control={control}
  222. render={({ field }) => (
  223. <Box>
  224. <Typography variant="body2" fontWeight={600} mb={1}>
  225. Additional Notes
  226. </Typography>
  227. <textarea
  228. {...field}
  229. rows={4}
  230. style={{
  231. width: '100%',
  232. padding: '12px',
  233. border: '1px solid #ccc',
  234. borderRadius: '4px',
  235. fontSize: '14px',
  236. fontFamily: 'inherit',
  237. outline: 'none',
  238. resize: 'vertical'
  239. }}
  240. placeholder="Enter any additional observations or notes..."
  241. />
  242. </Box>
  243. )}
  244. />
  245. </Stack>
  246. </DialogContent>
  247. <DialogActions sx={{ p: 3 }}>
  248. <Button
  249. variant="outlined"
  250. color="inherit"
  251. onClick={handleClose}
  252. >
  253. Cancel
  254. </Button>
  255. <Button
  256. variant="contained"
  257. color="primary"
  258. onClick={handleSubmit(onSubmit)}
  259. startIcon={<Save />}
  260. disabled={!isValid}
  261. >
  262. Save Quality Check
  263. </Button>
  264. </DialogActions>
  265. </Dialog>
  266. );
  267. };
  268. export default QualityCheckModal;