@movalib/movalib-commons 1.68.8 → 1.68.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,7 @@ import {
11
11
  Card,
12
12
  CardActions,
13
13
  CardContent,
14
+ Chip,
14
15
  FormControl,
15
16
  FormHelperText,
16
17
  Grid,
@@ -29,7 +30,6 @@ import {
29
30
  import { DatePicker } from "@mui/x-date-pickers";
30
31
  import moment, { Moment } from 'moment';
31
32
  import { useEffect, useRef, useState, type FC } from "react";
32
- import CatPlateBg from "../../assets/images/car_plate_bg.png";
33
33
  import ConfirmationDialog from "../../ConfirmationDialog";
34
34
  import { formatDateByTimezone } from "../../helpers/DateUtils";
35
35
  import {
@@ -70,7 +70,6 @@ const initialUserFormState = {
70
70
  tireHeight: { value: "", isValid: true },
71
71
  tireDiameter: { value: "", isValid: true },
72
72
  tireSpeedIndex: { value: "", isValid: true },
73
-
74
73
  lastInspectionDate: { value: null, isValid: true },
75
74
  lastMaintenanceDate: { value: null, isValid: true },
76
75
  tireBrand: { value: "", isValid: true },
@@ -82,6 +81,19 @@ const initialUserFormState = {
82
81
  secondaryTireSize: { value: null, isValid: true },
83
82
  };
84
83
 
84
+ // Styles partagés pour les lignes label/valeur
85
+ const rowSx = {
86
+ display: "flex",
87
+ justifyContent: "space-between",
88
+ alignItems: "center",
89
+ py: 0.75,
90
+ borderBottom: "0.5px solid",
91
+ borderColor: "divider",
92
+ "&:last-child": { borderBottom: "none" },
93
+ };
94
+
95
+
96
+
85
97
  const VehicleFullCard: FC<VehicleFullCardProps> = ({
86
98
  vehicle,
87
99
  fullwidth,
@@ -97,6 +109,12 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
97
109
  currentUser,
98
110
  }) => {
99
111
  const theme = useTheme();
112
+ const sectionTitleSx = {
113
+ fontWeight: "bold",
114
+ textTransform: "uppercase" as const,
115
+ mb: 1,
116
+ color: theme.palette.primary.main
117
+ };
100
118
  const [localEditMode, setLocalEditMode] = useState(editMode);
101
119
  const [openConfirmDocumentDelete, setOpenConfirmDocumentDelete] =
102
120
  useState(false);
@@ -104,9 +122,7 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
104
122
  useState(false);
105
123
  const [documentToDelete, setDocumentToDelete] = useState("");
106
124
  const [sizeLimit, setSizeLimit] = useState(false);
107
- // Formulaire utilisé pour les modifications d'informations sur le véhicule
108
125
  const [form, setForm] = useState<MovaVehicleForm>(initialUserFormState);
109
- // Références aux éventuels documents uploadés depuis la fiche
110
126
  const invoiceInputRef = useRef<HTMLInputElement>(null);
111
127
  const tirePictureInputRef = useRef(null);
112
128
  const [isShowLinkedDocument, setShowLinkedDocument] = useState(false);
@@ -139,75 +155,38 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
139
155
  setForm((prevForm) => {
140
156
  const updatedForm = {
141
157
  ...prevForm,
142
- currentMileage: {
143
- ...prevForm.currentMileage,
144
- value: vehicle.currentMileage,
145
- },
146
- averageMileagePerYear: {
147
- ...prevForm.averageMileagePerYear,
148
- value: vehicle.averageMileagePerYear,
149
- },
158
+ currentMileage: { ...prevForm.currentMileage, value: vehicle.currentMileage },
159
+ averageMileagePerYear: { ...prevForm.averageMileagePerYear, value: vehicle.averageMileagePerYear },
150
160
  tireWidth: { ...prevForm.tireWidth, value: vehicle.tireWidth },
151
161
  tireHeight: { ...prevForm.tireHeight, value: vehicle.tireHeight },
152
162
  tireDiameter: { ...prevForm.tireDiameter, value: vehicle.tireDiameter },
153
- tireSpeedIndex: {
154
- ...prevForm.tireSpeedIndex,
155
- value: vehicle.tireSpeedIndex,
156
- },
157
- secondaryTireWidth: {
158
- ...prevForm.secondaryTireWidth,
159
- value: vehicle.secondaryTireWidth,
160
- },
161
- secondaryTireHeight: {
162
- ...prevForm.secondaryTireHeight,
163
- value: vehicle.secondaryTireHeight,
164
- },
165
- secondaryTireDiameter: {
166
- ...prevForm.secondaryTireDiameter,
167
- value: vehicle.secondaryTireDiameter,
168
- },
169
- secondaryTireSpeedIndex: {
170
- ...prevForm.secondaryTireSpeedIndex,
171
- value: vehicle.secondaryTireSpeedIndex,
172
- },
163
+ tireSpeedIndex: { ...prevForm.tireSpeedIndex, value: vehicle.tireSpeedIndex },
164
+ secondaryTireWidth: { ...prevForm.secondaryTireWidth, value: vehicle.secondaryTireWidth },
165
+ secondaryTireHeight: { ...prevForm.secondaryTireHeight, value: vehicle.secondaryTireHeight },
166
+ secondaryTireDiameter: { ...prevForm.secondaryTireDiameter, value: vehicle.secondaryTireDiameter },
167
+ secondaryTireSpeedIndex: { ...prevForm.secondaryTireSpeedIndex, value: vehicle.secondaryTireSpeedIndex },
173
168
  lastInspectionDate: {
174
169
  ...prevForm.lastInspectionDate,
175
- value: vehicle.lastInspectionDate
176
- ? new Date(vehicle.lastInspectionDate)
177
- : null,
170
+ value: vehicle.lastInspectionDate ? new Date(vehicle.lastInspectionDate) : null,
178
171
  },
179
172
  lastMaintenanceDate: {
180
173
  ...prevForm.lastMaintenanceDate,
181
- value: vehicle.lastMaintenanceDate
182
- ? new Date(vehicle.lastMaintenanceDate)
183
- : null,
174
+ value: vehicle.lastMaintenanceDate ? new Date(vehicle.lastMaintenanceDate) : null,
184
175
  },
185
176
  tireBrand: { ...prevForm.tireBrand, value: vehicle.tireBrand },
186
177
  tireProfile: { ...prevForm.tireProfile, value: vehicle.tireProfile },
187
178
  };
188
179
  if (vehicle.foreignPlate) {
189
- updatedForm.vehicleModel = {
190
- ...prevForm.vehicleModel,
191
- value: vehicle.model,
192
- };
180
+ updatedForm.vehicleModel = { ...prevForm.vehicleModel, value: vehicle.model };
193
181
  }
194
-
195
182
  if (isVehicleTireSizeDefined(vehicle)) {
196
- updatedForm.tireSize = {
197
- ...prevForm.tireSize,
198
- value: vehicle.tireSize,
199
- };
183
+ updatedForm.tireSize = { ...prevForm.tireSize, value: vehicle.tireSize };
200
184
  }
201
185
  if (isVehicleSecondaryTireSizeDefined(vehicle)) {
202
- updatedForm.secondaryTireSize = {
203
- ...prevForm.secondaryTireSize,
204
- value: vehicle.secondaryTireSize,
205
- };
186
+ updatedForm.secondaryTireSize = { ...prevForm.secondaryTireSize, value: vehicle.secondaryTireSize };
206
187
  }
207
-
208
188
  return updatedForm;
209
189
  });
210
-
211
190
  Logger.info(form);
212
191
  };
213
192
 
@@ -221,30 +200,17 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
221
200
  }
222
201
  };
223
202
 
224
- const isVehicleTireSizeDefined = (vehicle: Vehicle) => {
225
- return (
226
- vehicle.tireSize && vehicle.tireSize.diameter && vehicle.tireSize.height
227
- );
228
- };
229
- const isVehicleSecondaryTireSizeDefined = (vehicle: Vehicle) => {
230
- return (
231
- vehicle.secondaryTireSize &&
232
- vehicle.secondaryTireSize.diameter &&
233
- vehicle.secondaryTireSize.height
234
- );
235
- };
203
+ const isVehicleTireSizeDefined = (vehicle: Vehicle) =>
204
+ vehicle.tireSize && vehicle.tireSize.diameter && vehicle.tireSize.height;
205
+
206
+ const isVehicleSecondaryTireSizeDefined = (vehicle: Vehicle) =>
207
+ vehicle.secondaryTireSize &&
208
+ vehicle.secondaryTireSize.diameter &&
209
+ vehicle.secondaryTireSize.height;
236
210
 
237
211
  const validateForm = () => {
238
212
  let newForm: MovaVehicleForm = { ...form };
239
-
240
- // Validator pour les champs obligatoires
241
- // newForm.currentMileage = validateField(form.currentMileage, value => !!value, 'Champ obligatoire');
242
- // newForm.averageMileagePerYear = validateField(form.averageMileagePerYear, value => !!value, 'Champ obligatoire');
243
-
244
- // La validation de la saisie des pneumatiques se fait dans le composant "MovaVehicleTireField", traitée dans le callback "handleOnChangeVehicleTire"
245
-
246
213
  setForm(newForm);
247
-
248
214
  return (
249
215
  newForm.currentMileage.isValid &&
250
216
  newForm.averageMileagePerYear.isValid &&
@@ -252,9 +218,7 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
252
218
  );
253
219
  };
254
220
 
255
- const handleInputChange = (
256
- e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
257
- ): void => {
221
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
258
222
  handleChange(e.target.name, e.target.value);
259
223
  };
260
224
 
@@ -263,99 +227,52 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
263
227
  };
264
228
 
265
229
  const handleChange = (fieldName: string, fieldValue: string): void => {
266
- const newField: MovaFormField = {
267
- [fieldName]: { value: fieldValue, isValid: true },
268
- };
269
-
230
+ const newField: MovaFormField = { [fieldName]: { value: fieldValue, isValid: true } };
270
231
  setForm({ ...form, ...newField });
271
232
  };
272
233
 
273
- const uploadVehicleDocument = (
274
- document: File,
275
- documentType: DocumentType
276
- ) => {
234
+ const uploadVehicleDocument = (document: File, documentType: DocumentType) => {
277
235
  if (document.size > 10000000) {
278
236
  setSizeLimit(true);
279
237
  return;
280
238
  }
281
-
282
239
  if (vehicle && document && documentType) {
283
- // Utilisation d'un formData pour permettre le trasnfert de fichier vers l'API
284
240
  let formData = new FormData();
285
241
  formData.append("documentType", documentType);
286
- // Ajouter la facture à FormData
287
242
  formData.append("file", document);
288
-
289
- // Appel du callback correspondant
290
243
  if (onUploadDocument) onUploadDocument(formData);
291
244
  }
292
245
  };
293
246
 
294
- /**
295
- *
296
- * @param event L'upload des documents se fait directement lors du téléchargement
297
- * @param docType
298
- */
299
- const handleFileChange = (
300
- event: React.ChangeEvent<HTMLInputElement>,
301
- docType: DocumentType
302
- ) => {
247
+ const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>, docType: DocumentType) => {
303
248
  event.preventDefault();
304
-
305
- if (
306
- event &&
307
- event.target.files &&
308
- event.target.files.length > 0 &&
309
- docType
310
- ) {
311
- uploadVehicleDocument(event.target.files[0], docType);
249
+ if ((event?.target.files?.length ?? 0) > 0 && docType) {
250
+ uploadVehicleDocument((event.target.files!)[0], docType);
312
251
  }
313
252
  };
314
253
 
315
- const handleOnClickEdit = () => {
316
- // On passe la fiche véhicule en mode édition
317
- setLocalEditMode(true);
318
- };
254
+ const handleOnClickEdit = () => setLocalEditMode(true);
319
255
 
320
- const handleOnChangeVehicleTire = (
321
- vehicleTire: VehicleTire,
322
- isValid: boolean
323
- ) => {
256
+ const handleOnChangeVehicleTire = (vehicleTire: VehicleTire, isValid: boolean) => {
324
257
  setForm((prevForm) => ({
325
258
  ...prevForm,
326
- tireSize: {
327
- ...prevForm["tireSize"],
328
- value: vehicleTire,
329
- isValid: isValid,
330
- },
259
+ tireSize: { ...prevForm["tireSize"], value: vehicleTire, isValid },
331
260
  }));
332
261
  };
333
262
 
334
- const handleOnChangeVehicleSecondaryTire = (
335
- vehicleTire: VehicleTire,
336
- isValid: boolean
337
- ) => {
263
+ const handleOnChangeVehicleSecondaryTire = (vehicleTire: VehicleTire, isValid: boolean) => {
338
264
  setForm((prevForm) => ({
339
265
  ...prevForm,
340
- secondaryTireSize: {
341
- ...prevForm["secondaryTireSize"],
342
- value: vehicleTire,
343
- isValid: isValid,
344
- },
266
+ secondaryTireSize: { ...prevForm["secondaryTireSize"], value: vehicleTire, isValid },
345
267
  }));
346
268
  };
347
269
 
348
- const handleOnClickDeleteVehicle = (
349
- e: React.MouseEvent<HTMLButtonElement, MouseEvent>
350
- ) => {
270
+ const handleOnClickDeleteVehicle = (e: React.MouseEvent<HTMLButtonElement>) => {
351
271
  e.preventDefault();
352
272
  setOpenConfirmVehicleDelete(true);
353
273
  };
354
274
 
355
- const handleDeleteDocument = (
356
- e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
357
- documentId: string
358
- ) => {
275
+ const handleDeleteDocument = (e: React.MouseEvent<HTMLButtonElement>, documentId: string) => {
359
276
  e.preventDefault();
360
277
  setDocumentToDelete(documentId);
361
278
  setOpenConfirmDocumentDelete(true);
@@ -364,51 +281,6 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
364
281
  const handleOnClickValidate = () => {
365
282
  if (validateForm()) {
366
283
  Logger.info(form.tireSize.value);
367
-
368
- let query = {
369
- currentMileage: form.currentMileage.value,
370
- averageMileagePerYear: form.averageMileagePerYear.value,
371
- tireWidth:
372
- form.tireSize.isValid && form.tireSize.value
373
- ? (form.tireSize.value as VehicleTire).width
374
- : undefined,
375
- tireHeight:
376
- form.tireSize.isValid && form.tireSize.value
377
- ? (form.tireSize.value as VehicleTire).height
378
- : undefined,
379
- tireDiameter:
380
- form.tireSize.isValid && form.tireSize.value
381
- ? (form.tireSize.value as VehicleTire).diameter
382
- : undefined,
383
- tireSpeedIndex:
384
- form.tireSize.isValid && form.tireSize.value
385
- ? (form.tireSize.value as VehicleTire).speedIndex
386
- : undefined,
387
- secondaryTireWidth:
388
- form.secondaryTireSize && form.secondaryTireSize.value
389
- ? (form.secondaryTireSize.value as VehicleTire).width
390
- : undefined,
391
- secondaryTireHeight:
392
- form.secondaryTireSize && form.secondaryTireSize.value
393
- ? (form.secondaryTireSize.value as VehicleTire).height
394
- : undefined,
395
- secondaryTireDiameter:
396
- form.secondaryTireSize && form.secondaryTireSize.value
397
- ? (form.secondaryTireSize.value as VehicleTire).diameter
398
- : undefined,
399
- secondaryTireSpeedIndex:
400
- form.secondaryTireSize && form.secondaryTireSize.value
401
- ? (form.secondaryTireSize.value as VehicleTire).speedIndex
402
- : undefined,
403
-
404
- lastInspectionDate: form.lastInspectionDate.value,
405
- lastMaintenanceDate: form.lastMaintenanceDate.value,
406
- tireBrand: form.tireBrand.value,
407
- tireProfile: form.tireProfile.value,
408
- };
409
- Logger.info(query);
410
-
411
- // Appel du callback correspondant
412
284
  if (onUpdate) onUpdate(form);
413
285
  }
414
286
  };
@@ -423,29 +295,21 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
423
295
  setDocumentToDelete("");
424
296
  };
425
297
 
426
- const handleCloseConfirmVehicleDelete = () => {
427
- setOpenConfirmVehicleDelete(false);
428
- };
298
+ const handleCloseConfirmVehicleDelete = () => setOpenConfirmVehicleDelete(false);
429
299
 
430
- /**
431
- *
432
- */
433
300
  const handleConfirmDocumentDelete = () => {
434
301
  setOpenConfirmDocumentDelete(false);
435
-
436
- if (vehicle && documentToDelete) {
437
- // Appel du callback correspondant
438
- if (onDeleteDocument) onDeleteDocument(documentToDelete);
439
- }
302
+ if (vehicle && documentToDelete && onDeleteDocument) onDeleteDocument(documentToDelete);
440
303
  };
441
304
 
442
305
  const handleConfirmVehicleDelete = () => {
443
306
  setOpenConfirmVehicleDelete(false);
307
+ if (vehicle && onDelete) onDelete();
308
+ };
444
309
 
445
- if (vehicle && onDelete) {
446
- // Appel du callback correspondant
447
- onDelete();
448
- }
310
+ const formatDate = (date: any) => {
311
+ const formatted = formatDateByTimezone(date, "Europe/Paris", DateFormatTypes.SHORT_FORMAT_DATE);
312
+ return formatted !== "" ? formatted : "-";
449
313
  };
450
314
 
451
315
  return (
@@ -454,399 +318,399 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
454
318
  <Card
455
319
  variant="outlined"
456
320
  sx={{
457
- maxWidth: fullwidth ? "80%" : 500,
321
+ width: "100%",
458
322
  backgroundColor: focused ? theme.palette.primary.light : "white",
459
323
  overflow: "visible",
460
324
  mt: 4,
461
- pb: 1,
325
+ borderRadius: 2,
326
+ border: "0.5px solid",
327
+ borderColor: "divider",
462
328
  }}
463
329
  >
464
330
  <CardContent sx={{ pt: 0, pb: 0 }}>
465
- <Typography
466
- variant="h6"
467
- component="div"
468
- align="center"
469
- sx={{ mb: 1 }}
470
- color={darken(theme.palette.primary.main, 0.2)}
331
+
332
+ {/* ── HEADER ── */}
333
+ <Box
334
+ sx={{
335
+ display: "flex",
336
+ justifyContent: "space-between",
337
+ alignItems: "flex-start",
338
+ px: 2,
339
+ pt: 2,
340
+ pb: 2,
341
+ borderBottom: "0.5px solid",
342
+ borderColor: "divider",
343
+ }}
471
344
  >
472
- {vehicle.brand && `${vehicle.brand} `}
473
- {vehicle.model && `${vehicle.model} `}
474
- {vehicle.version && `${vehicle.version}`}
475
- </Typography>
476
- <Grid container justifyContent="space-between">
477
- <Grid
478
- container
479
- sx={{ mb: 1, alignItems: "center", justifyContent: "center" }}
480
- >
481
- <Grid
482
- item
483
- xs={6}
484
- sx={{ position: "relative", minWidth: "234px" }}
345
+ <Box>
346
+ <Typography
347
+ variant="h6"
348
+ sx={{ fontWeight: 500, lineHeight: 1.2, color: "text.primary" }}
349
+ >
350
+ {vehicle.brand && `${vehicle.brand} `}
351
+ {vehicle.model && `${vehicle.model} `}
352
+ </Typography>
353
+ {vehicle.version && (
354
+ <Typography variant="body2" color="text.secondary" sx={{ mt: 0.25 }}>
355
+ {vehicle.version}
356
+ </Typography>
357
+ )}
358
+
359
+ {/* Plaque */}
360
+ <Box
361
+ sx={{
362
+ display: "inline-flex",
363
+ alignItems: "center",
364
+ gap: 0.75,
365
+ mt: 1.25,
366
+ px: 1.25,
367
+ py: 0.5,
368
+ bgcolor: "#F5F4F0",
369
+ border: "0.5px solid #D3D1C7",
370
+ borderRadius: "6px",
371
+ }}
485
372
  >
486
- <img
487
- src={CatPlateBg}
488
- alt="Plaque d'immatriculation"
489
- style={{ height: "50px", position: "relative" }}
490
- />
491
373
  <Typography
492
- variant="h6"
493
- color={theme.palette.text.primary}
374
+ variant="body1"
494
375
  sx={{
495
- position: "absolute",
496
- top: "8px",
497
- left: "76px",
498
- display: "flex",
376
+ fontWeight: 600,
377
+ letterSpacing: "0.1em",
378
+ color: "text.primary",
379
+ fontSize: "14px",
499
380
  }}
500
381
  >
501
- <b>
502
- {formatVehiclePlate(vehicle.plate, vehicle.foreignPlate)}
503
- </b>
504
- <IconButton
505
- sx={{ ml: 1 }}
506
- onClick={async () =>
507
- await navigator.clipboard.writeText(
508
- formatVehiclePlate(
509
- vehicle.plate,
510
- vehicle.foreignPlate
511
- )
512
- )
513
- }
514
- size="small"
515
- aria-label="Copier le VIN"
516
- >
517
- <ContentCopy fontSize="small" />
518
- </IconButton>
382
+ {formatVehiclePlate(vehicle.plate, vehicle.foreignPlate)}
519
383
  </Typography>
520
- </Grid>
384
+ <IconButton
385
+ size="small"
386
+ sx={{ p: 0.25 }}
387
+ onClick={async () =>
388
+ await navigator.clipboard.writeText(
389
+ formatVehiclePlate(vehicle.plate, vehicle.foreignPlate)
390
+ )
391
+ }
392
+ aria-label="Copier la plaque"
393
+ >
394
+ <ContentCopy sx={{ fontSize: 14, color: "text.disabled" }} />
395
+ </IconButton>
396
+ </Box>
397
+ </Box>
398
+
399
+ {/* Bouton modifier / supprimer */}
400
+ <Box sx={{ display: "flex", flexDirection: "row", alignItems: "flex-end", gap: 1 }}>
401
+
521
402
  {onDelete && (
522
- <Grid
523
- item
524
- xs={6}
525
- style={{
526
- display: "flex",
527
- alignItems: "center",
528
- justifyContent: "center",
403
+ <Button
404
+ variant="outlined"
405
+ color={"error"}
406
+ size="small"
407
+ onClick={(e) => handleOnClickDeleteVehicle(e)}
408
+ >
409
+ Supprimer
410
+ </Button>
411
+ )}
412
+ {!localEditMode && (
413
+ <Button
414
+ onClick={handleOnClickEdit}
415
+ size="small"
416
+ variant="outlined"
417
+ startIcon={<EditIcon sx={{ fontSize: "14px !important" }} />}
418
+ sx={{
419
+ fontSize: 12,
420
+ borderColor: "divider",
529
421
  }}
530
422
  >
531
- <Button
532
- variant="contained"
533
- color="error"
534
- onClick={(e) => handleOnClickDeleteVehicle(e)}
535
- >
536
- Supprimer
537
- </Button>
538
- </Grid>
423
+ Modifier
424
+ </Button>
539
425
  )}
540
- </Grid>
541
-
542
- {!localEditMode && (
543
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
544
- <Grid item xs={8}>
545
- <Typography variant="body1" color="text.secondary">
546
- Km actuel :
426
+ </Box>
427
+ </Box>
428
+
429
+ {/* ── KILOMÉTRAGE ── */}
430
+ <Box sx={{ px: 2, py: 1.5, borderBottom: "0.5px solid", borderColor: "divider" }}>
431
+ <Typography variant={"body2"} sx={sectionTitleSx}>Kilométrage</Typography>
432
+ <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
433
+ <Box sx={{ bgcolor: "grey.50", borderRadius: 1, p: 1.25 }}>
434
+ <Typography variant="caption" color="text.secondary" display="block">
435
+ Actuel
436
+ </Typography>
437
+ {!localEditMode ? (
438
+ <Typography variant="body1" fontWeight={500}>
439
+ {vehicle.currentMileage ?? "-"}{" "}
440
+ <Typography component="span" variant="caption" color="text.secondary">
441
+ km
442
+ </Typography>
443
+ </Typography>
444
+ ) : (
445
+ <TextField
446
+ name="currentMileage"
447
+ variant="standard"
448
+ type="number"
449
+ required
450
+ value={form.currentMileage.value ?? ""}
451
+ onChange={handleInputChange}
452
+ error={Boolean(form.currentMileage.error)}
453
+ helperText={
454
+ Boolean(form.currentMileage.error && form.currentMileage.value > 0)
455
+ ? form.currentMileage.error
456
+ : "Sur votre tableau de bord 😉"
457
+ }
458
+ InputProps={{ disableUnderline: false }}
459
+ sx={{ mt: 0.5, width: "100%" }}
460
+ />
461
+ )}
462
+ </Box>
463
+ <Box sx={{ bgcolor: "grey.50", borderRadius: 1, p: 1.25 }}>
464
+ <Typography variant="caption" color="text.secondary" display="block">
465
+ Moyen annuel
466
+ </Typography>
467
+ {!localEditMode ? (
468
+ <Typography variant="body1" fontWeight={500}>
469
+ {vehicle.averageMileagePerYear ?? "-"}{" "}
470
+ <Typography component="span" variant="caption" color="text.secondary">
471
+ km
472
+ </Typography>
547
473
  </Typography>
548
- </Grid>
549
- <Grid item xs={4} sx={{ textAlign: "right" }}>
550
- <Typography variant="body1" color="text.secondary">
551
- <b>{vehicle.currentMileage} km</b>
474
+ ) : (
475
+ <FormControl fullWidth variant="standard" error={Boolean(form.averageMileagePerYear.error)}>
476
+ <Select
477
+ name="averageMileagePerYear"
478
+ value={form.averageMileagePerYear.value ? String(form.averageMileagePerYear.value) : ""}
479
+ onChange={handleSelectChange}
480
+ disableUnderline={false}
481
+ sx={{ mt: 0.5, fontSize: 14 }}
482
+ >
483
+ {[5000, 10000, 15000, 20000, 25000, 30000, 50000, 75000, 100000].map((v) => (
484
+ <MenuItem key={v} value={v}>{v.toLocaleString("fr-FR")}</MenuItem>
485
+ ))}
486
+ <MenuItem value={999999}>+100 000</MenuItem>
487
+ </Select>
488
+ <FormHelperText>{form.averageMileagePerYear.error}</FormHelperText>
489
+ </FormControl>
490
+ )}
491
+ </Box>
492
+ </Box>
493
+ </Box>
494
+
495
+ {/* ── IDENTITÉ TECHNIQUE ── */}
496
+ <Box sx={{ px: 2, py: 1.5, borderBottom: "0.5px solid", borderColor: "divider" }}>
497
+ <Typography variant={"body2"} sx={sectionTitleSx}>Identité technique</Typography>
498
+
499
+ {localEditMode && form.vehicleModel && vehicle.foreignPlate && (
500
+ <TextField
501
+ label="Modèle de la voiture"
502
+ name="vehicleModel"
503
+ variant="outlined"
504
+ type="text"
505
+ required
506
+ fullWidth
507
+ value={form.vehicleModel.value}
508
+ onChange={handleInputChange}
509
+ error={Boolean(form.vehicleModel.error)}
510
+ sx={{ mb: 1.5, "& input": { textTransform: "uppercase" } }}
511
+ />
512
+ )}
513
+ {vehicle.firstRegistrationDate && (
514
+ <Box sx={rowSx}>
515
+ <Typography variant="body2" color="text.secondary">Mise en circulation</Typography>
516
+ <Typography variant="body2" fontWeight={500}>
517
+ {formatDate(vehicle.firstRegistrationDate)}
552
518
  </Typography>
553
- </Grid>
554
- </Grid>
519
+ </Box>
555
520
  )}
556
521
 
557
- {!localEditMode && vehicle.vin && (
558
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
559
- <Grid item xs={6}>
560
- <Typography variant="body1" color="text.secondary">
561
- Vin :
522
+ {vehicle.vin && (
523
+ <Box sx={rowSx}>
524
+ <Typography variant="body2" color="text.secondary">VIN</Typography>
525
+ <Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
526
+ <Typography variant="body2" fontWeight={500}>
527
+ {vehicle.vin}
562
528
  </Typography>
563
- </Grid>
564
- <Grid item xs={6} sx={{ textAlign: "right" }}>
565
- <Typography
566
- variant="body1"
567
- color="text.secondary"
568
- sx={{
569
- display: "flex",
570
- flexDirection: "row",
571
- alignItems: "center",
572
- justifyContent: "flex-end",
573
- }}
529
+ <IconButton
530
+ size="small"
531
+ sx={{ p: 0.25 }}
532
+ onClick={async () => await navigator.clipboard.writeText(vehicle.vin)}
533
+ aria-label="Copier le VIN"
574
534
  >
575
- <b>{vehicle.vin}</b>
576
- <IconButton
577
- sx={{ ml: 1, height: "24px" }}
578
- onClick={async () =>
579
- await navigator.clipboard.writeText(vehicle.vin)
580
- }
581
- size="small"
582
- aria-label="Copier le VIN"
583
- >
584
- <ContentCopy fontSize="small" />
585
- </IconButton>
586
- </Typography>
587
- </Grid>
588
- </Grid>
535
+ <ContentCopy sx={{ fontSize: 13, color: "text.disabled" }} />
536
+ </IconButton>
537
+ </Box>
538
+ </Box>
589
539
  )}
590
- {localEditMode && form.vehicleModel && vehicle.foreignPlate && (
591
- <Grid item xs={12}>
592
- <TextField
593
- label="Modèle de la voiture "
594
- name="vehicleModel"
595
- variant="outlined"
596
- type="text"
597
- required
598
- value={form.vehicleModel.value}
599
- onChange={(e) => handleInputChange(e)}
600
- error={Boolean(form.vehicleModel.error)}
601
- sx={{
602
- width: "100%",
603
- mt: 2,
604
- "& input": { textTransform: "uppercase" }, // CSS pour forcer les majuscules dans l'input
605
- }}
606
- />
607
- </Grid>
540
+ {vehicle.energy && (
541
+ <Box sx={rowSx}>
542
+ <Typography variant="body2" color="text.secondary">Énergie</Typography>
543
+ <Typography variant="body2" fontWeight={500}>{vehicle.energy}</Typography>
544
+ </Box>
608
545
  )}
609
-
610
- {localEditMode && (
611
- <Grid item xs={12}>
612
- <TextField
613
- label="Kilométrage actuel"
614
- name="currentMileage"
615
- variant="outlined"
616
- type="number"
617
- required
618
- value={form.currentMileage.value}
619
- onChange={(e) => handleInputChange(e)}
620
- error={Boolean(form.currentMileage.error)}
621
- sx={{
622
- width: "100%",
623
- mt: 2,
624
- "& input": { textTransform: "uppercase" }, // CSS pour forcer les majuscules dans l'input
625
- }}
626
- helperText={
627
- Boolean(
628
- form.currentMileage.error &&
629
- form.currentMileage.value > 0
630
- )
631
- ? form.currentMileage.error
632
- : "Sur votre tableau de bord 😉"
633
- }
634
- />
635
- </Grid>
546
+ {vehicle.gearboxType && (
547
+ <Box sx={rowSx}>
548
+ <Typography variant="body2" color="text.secondary">Type boîte</Typography>
549
+ <Typography variant="body2" fontWeight={500}>{vehicle.gearboxType}</Typography>
550
+ </Box>
636
551
  )}
637
-
638
- {!localEditMode && (
639
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
640
- <Grid item xs={8}>
641
- <Typography variant="body1" color="text.secondary">
642
- Km moyen annuel :
643
- </Typography>
644
- </Grid>
645
- <Grid item xs={4} sx={{ textAlign: "right" }}>
646
- <Typography variant="body1" color="text.secondary">
647
- <b>{vehicle.averageMileagePerYear} km</b>
648
- </Typography>
649
- </Grid>
650
- </Grid>
552
+ {vehicle.gearboxCode && (
553
+ <Box sx={rowSx}>
554
+ <Typography variant="body2" color="text.secondary">Code boîte</Typography>
555
+ <Typography variant="body2" fontWeight={500}>
556
+ {vehicle.gearboxCode}
557
+ </Typography>
558
+ </Box>
651
559
  )}
652
-
653
- {localEditMode && (
654
- <Grid item xs={12}>
655
- <FormControl
656
- fullWidth
657
- margin="normal"
658
- error={Boolean(form.averageMileagePerYear.error)}
659
- >
660
- <InputLabel id="averageMileagePerYear-label">
661
- Kilométrage moyen annuel
662
- </InputLabel>
663
- <Select
664
- labelId="averageMileagePerYear-label"
665
- id="averageMileagePerYear"
666
- name="averageMileagePerYear"
667
- value={
668
- form.averageMileagePerYear.value
669
- ? String(form.averageMileagePerYear.value)
670
- : ""
671
- }
672
- onChange={(e) => handleSelectChange(e)}
673
- label="Kilométrage moyen annuel"
674
- >
675
- <MenuItem value={5000}>5 000</MenuItem>
676
- <MenuItem value={10000}>10 000</MenuItem>
677
- <MenuItem value={15000}>15 000</MenuItem>
678
- <MenuItem value={20000}>20 000</MenuItem>
679
- <MenuItem value={25000}>25 000</MenuItem>
680
- <MenuItem value={30000}>30 000</MenuItem>
681
- <MenuItem value={50000}>50 000</MenuItem>
682
- <MenuItem value={75000}>75 000</MenuItem>
683
- <MenuItem value={100000}>100 000</MenuItem>
684
- <MenuItem value={999999}>+100 000</MenuItem>
685
- </Select>
686
- <FormHelperText>
687
- {form.averageMileagePerYear.error}
688
- </FormHelperText>
689
- </FormControl>
690
- </Grid>
560
+ {vehicle.engineCode && (
561
+ <Box sx={rowSx}>
562
+ <Typography variant="body2" color="text.secondary">Code moteur</Typography>
563
+ <Typography variant="body2" fontWeight={500}>
564
+ {vehicle.engineCode}
565
+ </Typography>
566
+ </Box>
691
567
  )}
568
+ </Box>
692
569
 
693
- {!localEditMode && (
694
- <>
695
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
696
- <Grid item xs={6}>
697
- <Typography variant="body1" color="text.secondary">
698
- Pneumatiques{" "}
699
- {isVehicleSecondaryTireSizeDefined(vehicle) ? "AV" : ""}{" "}
700
- :
701
- </Typography>
702
- </Grid>
703
- <Grid item xs={6} sx={{ textAlign: "right" }}>
704
- <Typography variant="body1" color="text.secondary">
705
- {isVehicleTireSizeDefined(vehicle) ? (
706
- <b>{formatVehicleTire(vehicle.tireSize)}</b>
707
- ) : (
708
- "-"
709
- )}
710
- </Typography>
711
- </Grid>
712
- </Grid>
570
+ {/* ── PNEUMATIQUES ── */}
571
+ <Box sx={{ px: 2, py: 1.5, borderBottom: "0.5px solid", borderColor: "divider" }}>
572
+ <Typography variant={"body2"} sx={sectionTitleSx}>Pneumatiques</Typography>
713
573
 
714
- {isVehicleSecondaryTireSizeDefined(vehicle) && (
715
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
716
- <Grid item xs={6}>
717
- <Typography variant="body1" color="text.secondary">
718
- Pneumatiques AR:
719
- </Typography>
720
- </Grid>
721
- <Grid item xs={6} sx={{ textAlign: "right" }}>
722
- <Typography variant="body1" color="text.secondary">
723
- {isVehicleSecondaryTireSizeDefined(vehicle) ? (
724
- <b>
725
- {formatVehicleTire(vehicle.secondaryTireSize)}
726
- </b>
727
- ) : (
728
- "-"
729
- )}
730
- </Typography>
731
- </Grid>
732
- </Grid>
733
- )}
734
- </>
735
- )}
736
- {!localEditMode && (
737
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
738
- <Grid item xs={6}>
739
- <Typography variant="body1" color="text.secondary">
740
- Marque pneumatiques :
741
- </Typography>
742
- </Grid>
743
- <Grid item xs={6} sx={{ textAlign: "right" }}>
744
- <Typography variant="body1" color="text.secondary">
745
- {vehicle.tireBrand ? <b>{vehicle.tireBrand}</b> : "-"}
574
+ {!localEditMode ? (
575
+ <>
576
+ <Box sx={rowSx}>
577
+ <Typography variant="body2" color="text.secondary">
578
+ {isVehicleSecondaryTireSizeDefined(vehicle) ? "Avant" : "Taille"}
746
579
  </Typography>
747
- </Grid>
748
- </Grid>
749
- )}
580
+ {isVehicleTireSizeDefined(vehicle) ? (
581
+ <Chip
582
+ label={formatVehicleTire(vehicle.tireSize)}
583
+ size="small"
584
+ color="primary"
585
+ sx={{
586
+ fontWeight: 500,
587
+ fontSize: 12,
588
+ height: 24,
589
+ "& .MuiChip-label": { px: 1 },
590
+ }}
591
+ />
592
+ ) : (
593
+ <Typography variant="body2" color="text.disabled">—</Typography>
594
+ )}
595
+ </Box>
596
+ {isVehicleSecondaryTireSizeDefined(vehicle) && (
597
+ <Box sx={rowSx}>
598
+ <Typography variant="body2" color="text.secondary">Arrière</Typography>
599
+ <Chip
600
+ label={formatVehicleTire(vehicle.secondaryTireSize)}
601
+ size="small"
602
+ color="primary"
603
+ sx={{
750
604
 
751
- {!localEditMode && (
752
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
753
- <Grid item xs={6}>
754
- <Typography variant="body1" color="text.secondary">
755
- Modèle pneumatiques :
605
+ fontWeight: 500,
606
+ fontSize: 12,
607
+ height: 24,
608
+ "& .MuiChip-label": { px: 1 },
609
+ }}
610
+ />
611
+ </Box>
612
+ )}
613
+ <Box sx={rowSx}>
614
+ <Typography variant="body2" color="text.secondary">Marque</Typography>
615
+ <Typography variant="body2" fontWeight={500}>
616
+ {vehicle.tireBrand || "—"}
756
617
  </Typography>
757
- </Grid>
758
- <Grid item xs={6} sx={{ textAlign: "right" }}>
759
- <Typography variant="body1" color="text.secondary">
760
- {vehicle.tireProfile ? <b>{vehicle.tireProfile}</b> : "-"}
618
+ </Box>
619
+ <Box sx={rowSx}>
620
+ <Typography variant="body2" color="text.secondary">Modèle</Typography>
621
+ <Typography variant="body2" fontWeight={500}>
622
+ {vehicle.tireProfile || "—"}
761
623
  </Typography>
762
- </Grid>
763
- </Grid>
764
- )}
765
-
766
- {localEditMode && (
624
+ </Box>
625
+ </>
626
+ ) : (
767
627
  <>
768
- <Grid item xs={12} sx={{ mt: 1 }}>
628
+ <Box sx={{ mt: 1 }}>
769
629
  <MovaVehicleTireField
770
630
  label="AV"
771
631
  vehicleTire={form.tireSize.value}
772
632
  onChangeVehicleTire={handleOnChangeVehicleTire}
773
633
  />
774
- </Grid>
775
- <Grid item xs={12} sx={{ mt: 1 }}>
634
+ </Box>
635
+ <Box sx={{ mt: 1 }}>
776
636
  <MovaVehicleTireField
777
637
  label="AR"
778
638
  vehicleTire={form.secondaryTireSize.value}
779
639
  onChangeVehicleTire={handleOnChangeVehicleSecondaryTire}
780
640
  />
781
- </Grid>
782
- </>
783
- )}
784
- {localEditMode && (
785
- <Grid item xs={12}>
641
+ </Box>
642
+ <TextField
643
+ label="Marque pneumatique"
644
+ name="tireBrand"
645
+ variant="outlined"
646
+ fullWidth
647
+ value={form.tireBrand.value}
648
+ onChange={handleInputChange}
649
+ error={Boolean(form.tireBrand.error)}
650
+ sx={{ mt: 2, "& input": { textTransform: "uppercase" } }}
651
+ />
786
652
  <TextField
787
653
  label="Modèle pneumatique"
788
654
  name="tireProfile"
789
655
  variant="outlined"
656
+ fullWidth
790
657
  value={form.tireProfile.value}
791
- onChange={(e) => handleInputChange(e)}
658
+ onChange={handleInputChange}
792
659
  error={Boolean(form.tireProfile.error)}
793
- sx={{
794
- width: "100%",
795
- mt: 2,
796
- "& input": { textTransform: "uppercase" }, // CSS pour forcer les majuscules dans l'input
797
- }}
660
+ sx={{ mt: 1, "& input": { textTransform: "uppercase" } }}
798
661
  />
799
- </Grid>
662
+ </>
800
663
  )}
664
+ </Box>
801
665
 
802
- {localEditMode && (
803
- <Grid item xs={12}>
804
- <TextField
805
- label="Marque pneumatique"
806
- name="tireBrand"
807
- variant="outlined"
808
- value={form.tireBrand.value}
809
- onChange={(e) => handleInputChange(e)}
810
- error={Boolean(form.tireBrand.error)}
811
- sx={{
812
- width: "100%",
813
- mt: 2,
814
- "& input": { textTransform: "uppercase" }, // CSS pour forcer les majuscules dans l'input
815
- }}
816
- />
817
- </Grid>
818
- )}
666
+ {/* ── ENTRETIEN ── */}
667
+ <Box sx={{ px: 2, py: 1.5, borderBottom: "0.5px solid", borderColor: "divider" }}>
668
+ <Typography variant={"body2"} sx={sectionTitleSx}>Entretien</Typography>
819
669
 
820
- {!localEditMode && (
821
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
822
- <Grid item xs={8}>
823
- <Typography variant="body1" color="text.secondary">
824
- Dernier contrôle technique :
670
+ {!localEditMode ? (
671
+ <>
672
+ <Box sx={rowSx}>
673
+ <Box sx={{ display: "flex", alignItems: "center", gap: 0.75 }}>
674
+ <Box
675
+ sx={{
676
+ width: 6,
677
+ height: 6,
678
+ borderRadius: "50%",
679
+ bgcolor: vehicle.lastInspectionDate ? "success.main" : "warning.main",
680
+ flexShrink: 0,
681
+ }}
682
+ />
683
+ <Typography variant="body2" color="text.secondary">
684
+ Contrôle technique
685
+ </Typography>
686
+ </Box>
687
+ <Typography variant="body2" fontWeight={500}>
688
+ {formatDate(vehicle.lastInspectionDate)}
825
689
  </Typography>
826
- </Grid>
827
- <Grid item xs={4} sx={{ textAlign: "right" }}>
828
- <Typography variant="body1" color="text.secondary">
829
- <b>
830
- {formatDateByTimezone(
831
- vehicle.lastInspectionDate,
832
- "Europe/Paris",
833
- DateFormatTypes.SHORT_FORMAT_DATE
834
- ) !== ""
835
- ? formatDateByTimezone(
836
- vehicle.lastInspectionDate,
837
- "Europe/Paris",
838
- DateFormatTypes.SHORT_FORMAT_DATE
839
- )
840
- : "-"}
841
- </b>
690
+ </Box>
691
+ <Box sx={rowSx}>
692
+ <Box sx={{ display: "flex", alignItems: "center", gap: 0.75 }}>
693
+ <Box
694
+ sx={{
695
+ width: 6,
696
+ height: 6,
697
+ borderRadius: "50%",
698
+ bgcolor: vehicle.lastMaintenanceDate ? "success.main" : "warning.main",
699
+ flexShrink: 0,
700
+ }}
701
+ />
702
+ <Typography variant="body2" color="text.secondary">
703
+ Dernier entretien
704
+ </Typography>
705
+ </Box>
706
+ <Typography variant="body2" fontWeight={500}>
707
+ {formatDate(vehicle.lastMaintenanceDate)}
842
708
  </Typography>
843
- </Grid>
844
- </Grid>
845
- )}
846
-
847
- {localEditMode && (
848
- <Grid item xs={12}>
849
- <FormControl fullWidth sx={{ marginTop: 2 }}>
709
+ </Box>
710
+ </>
711
+ ) : (
712
+ <>
713
+ <FormControl fullWidth sx={{ mt: 1.5 }}>
850
714
  <DatePicker
851
715
  label={"Dernier contrôle technique"}
852
716
  name={"lastInspectionDate"}
@@ -865,42 +729,10 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
865
729
 
866
730
  />
867
731
  </FormControl>
868
- </Grid>
869
- )}
870
-
871
- {!localEditMode && (
872
- <Grid container textAlign="justify" sx={{ pt: 2 }}>
873
- <Grid item xs={8}>
874
- <Typography variant="body1" color="text.secondary">
875
- Dernier entretien :
876
- </Typography>
877
- </Grid>
878
- <Grid item xs={4} sx={{ textAlign: "right" }}>
879
- <Typography variant="body1" color="text.secondary">
880
- <b>
881
- {formatDateByTimezone(
882
- vehicle.lastMaintenanceDate,
883
- "Europe/Paris",
884
- DateFormatTypes.SHORT_FORMAT_DATE
885
- ) !== ""
886
- ? formatDateByTimezone(
887
- vehicle.lastMaintenanceDate,
888
- "Europe/Paris",
889
- DateFormatTypes.SHORT_FORMAT_DATE
890
- )
891
- : "-"}
892
- </b>
893
- </Typography>
894
- </Grid>
895
- </Grid>
896
- )}
897
-
898
- {localEditMode && (
899
- <Grid item xs={12}>
900
- <FormControl fullWidth sx={{ marginTop: 2 }}>
732
+ <FormControl fullWidth sx={{ mt: 2 }}>
901
733
  <DatePicker
902
- label={"Dernier entretien"}
903
- name={"lastMaintenanceDate"}
734
+ label="Dernier entretien"
735
+ name="lastMaintenanceDate"
904
736
  value={
905
737
  (form.lastMaintenanceDate.value
906
738
  ? moment(form.lastMaintenanceDate.value)
@@ -918,218 +750,126 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
918
750
  }
919
751
  />
920
752
  </FormControl>
921
- </Grid>
753
+ </>
922
754
  )}
923
- </Grid>
755
+ </Box>
924
756
 
757
+ {/* ── CARNET DU VÉHICULE ── */}
925
758
  {!localEditMode && (
926
- <>
927
- <Grid item xs={12}>
928
- <Typography
929
- variant="h6"
930
- component="div"
931
- align="center"
932
- sx={{ mt: 3, mb: 1 }}
933
- color={darken(theme.palette.primary.main, 0.2)}
934
- >
935
- CARNET DU VÉHICULE
936
- </Typography>
937
- </Grid>
938
-
939
- {vehicle.documents &&
940
- vehicle.documents
941
- ?.filter((doc) => doc.fileSignedUrl)
942
- .map((invoice, index) => (
943
- <Grid
944
- container
945
- sx={{
946
- justifyContent: "space-between",
947
- alignItems: "center",
948
- }}
949
- key={index + 1}
950
- >
951
- <Grid
952
- item
953
- xs={11}
954
- key={(index + 1) * 50}
955
- sx={{ textAlign: "left" }}
956
- >
957
- <Tooltip title={invoice.originalFileName}>
958
- <Link
959
- color={darken("#F29ABA", 0.2)}
960
- href={invoice.fileSignedUrl}
961
- target="_blank"
962
- rel="noopener"
963
- >
964
- <Typography variant="body1">
965
- {invoice.fileName}
966
- </Typography>
967
- </Link>
968
- </Tooltip>
969
- </Grid>
970
- <Grid
971
- item
972
- xs={1}
973
- key={(index + 1) * 100}
974
- sx={{ textAlign: "right" }}
759
+ <Box sx={{ px: 2, py: 1.5 }}>
760
+ <Typography variant={"body2"} sx={sectionTitleSx}>Carnet du véhicule</Typography>
761
+
762
+ {vehicle.documents
763
+ ?.filter((doc) => doc.fileSignedUrl)
764
+ .map((invoice, index) => (
765
+ <Box
766
+ key={index + 1}
767
+ sx={{
768
+ display: "flex",
769
+ alignItems: "center",
770
+ justifyContent: "space-between",
771
+ bgcolor: "grey.50",
772
+ borderRadius: 1,
773
+ px: 1.5,
774
+ py: 0.75,
775
+ mb: 0.75,
776
+ }}
777
+ >
778
+ <Tooltip title={invoice.originalFileName}>
779
+ <Link
780
+ href={invoice.fileSignedUrl}
781
+ target="_blank"
782
+ rel="noopener"
783
+ underline="hover"
784
+ sx={{ fontSize: 13, color: "#185FA5", flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontFamily: "Outfit" }}
975
785
  >
976
- <IconButton
977
- disabled={
978
- !(invoice.ownerId.toString() == currentUser.id)
979
- }
980
- onClick={(e) =>
981
- handleDeleteDocument(e, invoice?.id)
982
- }
983
- >
984
- <CloseIcon />
985
- </IconButton>
986
- </Grid>
987
- </Grid>
988
- ))}
989
-
990
- {/* * Les FACTURES du véhicule
991
- {vehicle.documents && vehicle.documents?.filter(doc => doc.type === DocumentType.VEHICLE_MAINTENANCE_INVOICE)
992
- .map((invoice, index) => (
993
- <Grid container sx={{ justifyContent: 'space-between', alignItems: 'center' }} key={index+1}>
994
- <Grid item xs={11} key={(index+1)*50} sx={{ textAlign: 'left' }} >
995
- <Link color={darken('#F29ABA', 0.2)} href={invoice.fileSignedUrl} target="_blank" rel="noopener">
996
- <Typography variant="body1">
997
- Facture du {formatDateByTimezone(invoice.creationDate, 'fr', DateFormatTypes.SHORT_FORMAT_DATE)}
998
- </Typography>
999
- </Link>
1000
- </Grid>
1001
- <Grid item xs={1} key={(index+1)*100} sx={{ textAlign: 'right' }}>
1002
- <IconButton onClick={(e) => handleDeleteDocument(e, invoice.id)}>
1003
- <CloseIcon />
1004
- </IconButton>
1005
- </Grid>
1006
- </Grid>
1007
- ))}
1008
-
1009
- {vehicle.documents && vehicle.documents?.filter(doc => doc.type === DocumentType.VEHICLE_TIRE_PHOTO)
1010
- .map((tirePhoto, index) => (
1011
- <Grid container sx={{ justifyContent: 'space-between', alignItems: 'center' }} key={index+1}>
1012
- <Grid item xs={11} key={(index+1)*50} sx={{ textAlign: 'left' }} >
1013
- <Link color={darken('#F29ABA', 0.2)} href={tirePhoto.fileSignedUrl} target="_blank" rel="noopener">
1014
- <Typography variant="body1">
1015
- Photo pneu du {formatDateByTimezone(tirePhoto.creationDate, 'fr', DateFormatTypes.SHORT_FORMAT_DATE)}
1016
- </Typography>
1017
- </Link>
1018
- </Grid>
1019
- <Grid item xs={1} key={(index+1)*100} sx={{ textAlign: 'right' }}>
1020
- <IconButton onClick={(e) => handleDeleteDocument(e, tirePhoto.id)}>
1021
- <CloseIcon />
1022
- </IconButton>
1023
- </Grid>
1024
- </Grid>
1025
- ))} */}
1026
-
1027
- <Grid container>
1028
- <Grid item xs={12} sx={{ mt: 2, textAlign: "center" }}>
1029
- <div>
1030
- <input
1031
- accept="image/*, application/pdf"
1032
- type="file"
1033
- style={{ display: "none" }}
1034
- ref={invoiceInputRef}
1035
- id="raised-button-invoice"
1036
- onChange={(e) =>
1037
- handleFileChange(e, docTypeCurrent.current!)
1038
- }
1039
- />
1040
- <Button
1041
- size="large"
1042
- disabled={currentUpload}
1043
- onClick={() => setShowLinkedDocument(true)}
1044
- component="span"
1045
- variant="outlined"
1046
- startIcon={<AttachFile />}
1047
- sx={{
1048
- alignItems: "center",
1049
- width: "90%",
1050
- mt: 2,
1051
- mb: 1,
1052
- height: "50px",
1053
- p: 1,
1054
- color: darken(theme.palette.primary.main, 0.2),
1055
- }}
786
+ {invoice.fileName}
787
+ </Link>
788
+ </Tooltip>
789
+ <IconButton
790
+ size="small"
791
+ disabled={!(invoice.ownerId.toString() === currentUser.id)}
792
+ onClick={(e) => handleDeleteDocument(e, invoice?.id)}
793
+ sx={{ ml: 1, flexShrink: 0 }}
1056
794
  >
1057
- Ajouter un document
1058
- </Button>
1059
- {currentUpload && (
1060
- <Typography
1061
- variant="body2"
1062
- sx={{ animation: "blink 1.5s infinite" }}
1063
- >
1064
- Document en cours d'importation...
1065
- </Typography>
1066
- )}
1067
- {sizeLimit && (
1068
- <Typography
1069
- variant="body1"
1070
- sx={{ animation: "blink 1.5s infinite" }}
1071
- color={theme.palette.warning.dark}
1072
- >
1073
- Echec de l'importation car la taille du fichier
1074
- dépasse 10Mo
1075
- </Typography>
1076
- )}
1077
- {!sizeLimit && (
1078
- <Typography variant="body2">
1079
- Taille maximale du fichier : 10Mo
1080
- </Typography>
1081
- )}
1082
- </div>
1083
- </Grid>
1084
- </Grid>
1085
- </>
795
+ <CloseIcon sx={{ fontSize: 16 }} />
796
+ </IconButton>
797
+ </Box>
798
+ ))}
799
+
800
+ <Box sx={{ mt: 1.5 }}>
801
+ <input
802
+ accept="image/*, application/pdf"
803
+ type="file"
804
+ style={{ display: "none" }}
805
+ ref={invoiceInputRef}
806
+ id="raised-button-invoice"
807
+ onChange={(e) => handleFileChange(e, docTypeCurrent.current!)}
808
+ />
809
+ <Button
810
+ fullWidth
811
+ size="medium"
812
+ disabled={currentUpload}
813
+ onClick={() => setShowLinkedDocument(true)}
814
+ component="span"
815
+ variant="outlined"
816
+ startIcon={<AttachFile />}
817
+ sx={{
818
+ border: "1.5px dashed",
819
+ borderColor: "divider",
820
+ color: "text.secondary",
821
+ "&:hover": { borderColor: "text.secondary", bgcolor: "grey.50" },
822
+ }}
823
+ >
824
+ Ajouter un document
825
+ </Button>
826
+ {currentUpload && (
827
+ <Typography variant="caption" color="text.secondary" display="block" textAlign="center" sx={{ mt: 0.75 }}>
828
+ Document en cours d'importation...
829
+ </Typography>
830
+ )}
831
+ {sizeLimit ? (
832
+ <Typography variant="caption" color="warning.dark" display="block" textAlign="center" sx={{ mt: 0.5 }}>
833
+ Échec : fichier supérieur à 10 Mo
834
+ </Typography>
835
+ ) : (
836
+ <Typography variant="caption" color="text.disabled" display="block" textAlign="center" sx={{ mt: 0.5 }}>
837
+ Taille maximale : 10 Mo
838
+ </Typography>
839
+ )}
840
+ </Box>
841
+ </Box>
1086
842
  )}
1087
843
  </CardContent>
1088
844
 
1089
- <CardActions
1090
- sx={{ mt: 3, justifyContent: localEditMode ? "center" : "end" }}
1091
- >
1092
- {!localEditMode && (
1093
- <>
1094
- <Button
1095
- onClick={handleOnClickEdit}
1096
- color="inherit"
1097
- sx={{ width: "45%" }}
1098
- variant="text"
1099
- >
1100
- <EditIcon sx={{ mr: 1 }} />
1101
- MODIFIER
1102
- </Button>
1103
- </>
1104
- )}
1105
-
1106
- {localEditMode && (
1107
- <>
1108
- <Button
1109
- onClick={handleOnClickCancel}
1110
- sx={{ width: "45%", color: theme.palette.text.secondary }}
1111
- variant="text"
1112
- >
1113
- <CancelIcon sx={{ mr: 1 }} />
1114
- ANNULER
1115
- </Button>
1116
-
1117
- <Button
1118
- onClick={handleOnClickValidate}
1119
- sx={{
1120
- width: "45%",
1121
- color: darken(theme.palette.primary.main, 0.2),
1122
- }}
1123
- variant="text"
1124
- >
1125
- <EditIcon sx={{ mr: 1 }} />
1126
- VALIDER
1127
- </Button>
1128
- </>
1129
- )}
1130
- </CardActions>
845
+ {/* ── ACTIONS ── */}
846
+ {localEditMode && (
847
+ <CardActions sx={{ justifyContent: "center", px: 2, pb: 2, gap: 1 }}>
848
+ <Button
849
+ onClick={handleOnClickCancel}
850
+ variant="outlined"
851
+ startIcon={<CancelIcon />}
852
+ sx={{ width: "45%", color: "text.secondary", borderColor: "divider" }}
853
+ >
854
+ Annuler
855
+ </Button>
856
+ <Button
857
+ onClick={handleOnClickValidate}
858
+ variant="contained"
859
+ startIcon={<EditIcon />}
860
+ sx={{
861
+ width: "45%",
862
+ bgcolor: darken(theme.palette.primary.main, 0.1),
863
+ "&:hover": { bgcolor: darken(theme.palette.primary.main, 0.25) },
864
+ }}
865
+ >
866
+ Valider
867
+ </Button>
868
+ </CardActions>
869
+ )}
1131
870
  </Card>
1132
871
  )}
872
+
1133
873
  {isShowLinkedDocument && (
1134
874
  <LinkedDocumentDialog
1135
875
  isVehicle={true}
@@ -1157,4 +897,4 @@ const VehicleFullCard: FC<VehicleFullCardProps> = ({
1157
897
  );
1158
898
  };
1159
899
 
1160
- export default VehicleFullCard;
900
+ export default VehicleFullCard;