@movalib/movalib-commons 1.64.8 → 1.65.0
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.
- package/.env.development +2 -5
- package/devIndex.tsx +20 -21
- package/dist/devIndex.js +16 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/src/helpers/Enums.d.ts +10 -0
- package/dist/src/helpers/Enums.js +12 -1
- package/dist/src/helpers/Tools.d.ts +9 -2
- package/dist/src/helpers/Tools.js +184 -1
- package/dist/src/models/Garage.d.ts +3 -1
- package/dist/src/models/Garage.js +4 -1
- package/dist/src/models/Operation.d.ts +4 -2
- package/dist/src/models/Operation.js +3 -1
- package/dist/src/models/Prestation.d.ts +2 -1
- package/dist/src/models/Prestation.js +3 -1
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/helpers/Enums.ts +11 -2
- package/src/helpers/Tools.ts +207 -5
- package/src/models/Garage.ts +7 -1
- package/src/models/Operation.ts +34 -19
- package/src/models/Prestation.ts +65 -49
- package/webpack.config.js +47 -47
package/.env.development
CHANGED
package/devIndex.tsx
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
3
|
// Import des composants de la bibliothèque
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import { MovaAppType } from './src/helpers/Enums';
|
|
7
|
-
import { ThemeProvider, darken } from '@mui/material/styles';
|
|
8
|
-
import theme from './theme'; // Import du thème personnalisé
|
|
9
|
-
import MovaSignUp from './src/MovaSignUp';
|
|
10
|
-
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
|
4
|
+
import { Alert, Box, Button, Container, SelectChangeEvent } from '@mui/material';
|
|
5
|
+
import { ThemeProvider } from '@mui/material/styles';
|
|
11
6
|
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
|
12
|
-
import
|
|
7
|
+
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
|
13
8
|
import frLocale from 'date-fns/locale/fr';
|
|
14
|
-
import QRCode from './src/QRCode';
|
|
15
|
-
import { Alert, Box, Button, Container, SelectChangeEvent, Typography } from '@mui/material';
|
|
16
|
-
import GarageService from './src/services/GarageService';
|
|
17
|
-
import AuthenticationService from './src/services/AuthenticationService';
|
|
18
|
-
import Logger from './src/helpers/Logger';
|
|
19
|
-
import Garage from './src/models/Garage';
|
|
20
|
-
import ScheduleFields, { DaySchedule } from './src/ScheduleFields';
|
|
21
|
-
import Schedule from './src/models/Schedule';
|
|
22
|
-
import { flexCenter, formatVehicleTire } from './src/helpers/Tools';
|
|
23
9
|
import AccountValidation from './src/AccountValidation';
|
|
10
|
+
import AddressFields from './src/AddressFields';
|
|
24
11
|
import VehiclePlateField from './src/components/vehicle/VehiclePlateField';
|
|
25
12
|
import GaragePLV from './src/GaragePLV';
|
|
26
|
-
import AddressFields from './src/AddressFields';
|
|
27
|
-
import IbanInput from './src/IbanInput';
|
|
28
13
|
import GenderSelector from './src/GenderSelector';
|
|
14
|
+
import { MovaAppType } from './src/helpers/Enums';
|
|
15
|
+
import Logger from './src/helpers/Logger';
|
|
16
|
+
import { flexCenter, formatVehicleTire } from './src/helpers/Tools';
|
|
17
|
+
import { AddressFieldName, MovaLoginForm, MovaUserSignUpForm } from './src/helpers/Types';
|
|
18
|
+
import IbanInput from './src/IbanInput';
|
|
19
|
+
import Garage from './src/models/Garage';
|
|
20
|
+
import Schedule from './src/models/Schedule';
|
|
21
|
+
import MovaLogin from './src/MovaLogin';
|
|
22
|
+
import MovaSignUp from './src/MovaSignUp';
|
|
29
23
|
import MovaVehicleTireField from "./src/MovaVehicleTireField";
|
|
24
|
+
import QRCode from './src/QRCode';
|
|
25
|
+
import ScheduleFields, { DaySchedule } from './src/ScheduleFields';
|
|
26
|
+
import AuthenticationService from './src/services/AuthenticationService';
|
|
27
|
+
import GarageService from './src/services/GarageService';
|
|
28
|
+
import theme from './theme'; // Import du thème personnalisé
|
|
30
29
|
|
|
31
30
|
const App = () => {
|
|
32
31
|
|
|
@@ -78,7 +77,7 @@ const App = () => {
|
|
|
78
77
|
const getQRCodeData = ():string => {
|
|
79
78
|
// On renvoie les données pour le QR Code, l'url change selon l'environnement (variables d'environnement)
|
|
80
79
|
//return `https://app.movalib.com/#/garage/2?redirect=garage`;
|
|
81
|
-
return `https://
|
|
80
|
+
return `https://movalib.com/info-contact-agiless-2/`;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
const handleScheduleChange = (schedule: DaySchedule[]) => {
|
package/dist/devIndex.js
CHANGED
|
@@ -41,29 +41,29 @@ var jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
41
41
|
var react_1 = __importStar(require("react"));
|
|
42
42
|
var client_1 = require("react-dom/client");
|
|
43
43
|
// Import des composants de la bibliothèque
|
|
44
|
-
var
|
|
45
|
-
var Enums_1 = require("./src/helpers/Enums");
|
|
44
|
+
var material_1 = require("@mui/material");
|
|
46
45
|
var styles_1 = require("@mui/material/styles");
|
|
47
|
-
var theme_1 = __importDefault(require("./theme")); // Import du thème personnalisé
|
|
48
|
-
var MovaSignUp_1 = __importDefault(require("./src/MovaSignUp"));
|
|
49
|
-
var LocalizationProvider_1 = require("@mui/x-date-pickers/LocalizationProvider");
|
|
50
46
|
var AdapterDateFns_1 = require("@mui/x-date-pickers/AdapterDateFns");
|
|
47
|
+
var LocalizationProvider_1 = require("@mui/x-date-pickers/LocalizationProvider");
|
|
51
48
|
var fr_1 = __importDefault(require("date-fns/locale/fr"));
|
|
52
|
-
var QRCode_1 = __importDefault(require("./src/QRCode"));
|
|
53
|
-
var material_1 = require("@mui/material");
|
|
54
|
-
var GarageService_1 = __importDefault(require("./src/services/GarageService"));
|
|
55
|
-
var AuthenticationService_1 = __importDefault(require("./src/services/AuthenticationService"));
|
|
56
|
-
var Logger_1 = __importDefault(require("./src/helpers/Logger"));
|
|
57
|
-
var ScheduleFields_1 = __importDefault(require("./src/ScheduleFields"));
|
|
58
|
-
var Schedule_1 = __importDefault(require("./src/models/Schedule"));
|
|
59
|
-
var Tools_1 = require("./src/helpers/Tools");
|
|
60
49
|
var AccountValidation_1 = __importDefault(require("./src/AccountValidation"));
|
|
50
|
+
var AddressFields_1 = __importDefault(require("./src/AddressFields"));
|
|
61
51
|
var VehiclePlateField_1 = __importDefault(require("./src/components/vehicle/VehiclePlateField"));
|
|
62
52
|
var GaragePLV_1 = __importDefault(require("./src/GaragePLV"));
|
|
63
|
-
var AddressFields_1 = __importDefault(require("./src/AddressFields"));
|
|
64
|
-
var IbanInput_1 = __importDefault(require("./src/IbanInput"));
|
|
65
53
|
var GenderSelector_1 = __importDefault(require("./src/GenderSelector"));
|
|
54
|
+
var Enums_1 = require("./src/helpers/Enums");
|
|
55
|
+
var Logger_1 = __importDefault(require("./src/helpers/Logger"));
|
|
56
|
+
var Tools_1 = require("./src/helpers/Tools");
|
|
57
|
+
var IbanInput_1 = __importDefault(require("./src/IbanInput"));
|
|
58
|
+
var Schedule_1 = __importDefault(require("./src/models/Schedule"));
|
|
59
|
+
var MovaLogin_1 = __importDefault(require("./src/MovaLogin"));
|
|
60
|
+
var MovaSignUp_1 = __importDefault(require("./src/MovaSignUp"));
|
|
66
61
|
var MovaVehicleTireField_1 = __importDefault(require("./src/MovaVehicleTireField"));
|
|
62
|
+
var QRCode_1 = __importDefault(require("./src/QRCode"));
|
|
63
|
+
var ScheduleFields_1 = __importDefault(require("./src/ScheduleFields"));
|
|
64
|
+
var AuthenticationService_1 = __importDefault(require("./src/services/AuthenticationService"));
|
|
65
|
+
var GarageService_1 = __importDefault(require("./src/services/GarageService"));
|
|
66
|
+
var theme_1 = __importDefault(require("./theme")); // Import du thème personnalisé
|
|
67
67
|
var App = function () {
|
|
68
68
|
Logger_1.default.enableLogging();
|
|
69
69
|
// Chargement de données garage de test
|
|
@@ -105,7 +105,7 @@ var App = function () {
|
|
|
105
105
|
var getQRCodeData = function () {
|
|
106
106
|
// On renvoie les données pour le QR Code, l'url change selon l'environnement (variables d'environnement)
|
|
107
107
|
//return `https://app.movalib.com/#/garage/2?redirect=garage`;
|
|
108
|
-
return "https://
|
|
108
|
+
return "https://movalib.com/info-contact-agiless-2/";
|
|
109
109
|
};
|
|
110
110
|
var handleScheduleChange = function (schedule) {
|
|
111
111
|
if (schedule) {
|
package/dist/index.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export { deleteCookie, readCookie } from "./src/helpers/CookieUtils";
|
|
|
57
57
|
export { formatDateByTimezone, getLongFormattedDateTime, } from "./src/helpers/DateUtils";
|
|
58
58
|
export { capitalizeFirstLetter, findScheduleByDayOfWeek, flexLeftRow, formatPhoneNumber, formatVehiclePlate, formatVehicleTire, getApplicationShortLabel, getApplicationsShortLabels, getDayOfWeekLabel, getFormattedIntervals, getFormattedSchedule, isEmpty, isSafariOniOS, validateField, } from "./src/helpers/Tools";
|
|
59
59
|
export { validateEmail, validatePhoneNumber, validateText, } from "./src/helpers/Validator";
|
|
60
|
-
export { APIMethod, CountryCode, CustomerType, DateFormatTypes, DayOfWeek, DigitalPassportIndex, DocumentState, DocumentType, EventState, EventType, Gender, MovaAppType, OrderPreference, OrderState, PartsApplicationType, PrestationState, PrestationType, ProductType, RegistrationState, RoleType, SlotAlgorithm, SubscriptionPaymentInterval, SubscriptionState, SubscriptionType, VehiclePlateFormat as VehiclePlateType, } from "./src/helpers/Enums";
|
|
60
|
+
export { APIMethod, CountryCode, CustomerType, DateFormatTypes, DayOfWeek, DigitalPassportIndex, DocumentState, DocumentType, EventState, EventType, Gender, MovaAppType, OrderPreference, OrderState, PartsApplicationType, PrestationState, PrestationType, ProductType, RegistrationState, RoleType, SecondaryPartsApplicationType, SlotAlgorithm, SubscriptionPaymentInterval, SubscriptionState, SubscriptionType, VehiclePlateFormat as VehiclePlateType, } from "./src/helpers/Enums";
|
|
61
61
|
export { openDialogPrint } from "./src/utils/DialogPrint";
|
|
62
62
|
export { getQrCodeBase64 } from "./src/utils/getQRCodeBase64";
|
|
63
63
|
export { StyledToggleButton, StyledToggleButtonGroup, } from "./src/style/styled";
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.Supplier = exports.Subscription = exports.Schedule = exports.Role = exports.Product = exports.Prestation = exports.Operation = exports.LoanVehicle = exports.Garage = exports.Event = exports.Employee = exports.Document = exports.Customer = exports.CategoryPrestation = exports.Address = exports.Absence = exports.Logger = exports.TestButton = exports.ScheduleFields = exports.QRCode = exports.MovaVehicleTireField = exports.MovaSnackbar = exports.MovaSignUp = exports.MovaLogin = exports.MovaDigitalPassport = exports.MovaDialog = exports.MovaCopyright = exports.Loader = exports.IbanInput = exports.GenderSelector = exports.GaragePLV = exports.DialogForgotPassword = exports.ConfirmationDialog = exports.regexPlate = exports.oldRegexPlate = exports.VehiclePlateField = exports.VehicleFullCard = exports.ActivateAccount = exports.QrCodePLVContainer = exports.PrintSize = exports.PLVComponent = exports.MovaTableBack = exports.MovaTable = exports.LinkedDocumentDialog = exports.AddressFields = exports.AccountValidation = exports.VehicleService = exports.UserService = exports.GarageService = exports.AuthenticationService = void 0;
|
|
8
|
-
exports.
|
|
9
|
-
exports.StyledToggleButtonGroup = exports.StyledToggleButton = exports.getQrCodeBase64 = exports.openDialogPrint = exports.VehiclePlateType = exports.SubscriptionType = void 0;
|
|
8
|
+
exports.SubscriptionPaymentInterval = exports.SlotAlgorithm = exports.SecondaryPartsApplicationType = exports.RoleType = exports.RegistrationState = exports.ProductType = exports.PrestationType = exports.PrestationState = exports.PartsApplicationType = exports.OrderState = exports.OrderPreference = exports.MovaAppType = exports.Gender = exports.EventType = exports.EventState = exports.DocumentType = exports.DocumentState = exports.DigitalPassportIndex = exports.DayOfWeek = exports.DateFormatTypes = exports.CustomerType = exports.CountryCode = exports.APIMethod = exports.validateText = exports.validatePhoneNumber = exports.validateEmail = exports.validateField = exports.isSafariOniOS = exports.isEmpty = exports.getFormattedSchedule = exports.getFormattedIntervals = exports.getDayOfWeekLabel = exports.getApplicationsShortLabels = exports.getApplicationShortLabel = exports.formatVehicleTire = exports.formatVehiclePlate = exports.formatPhoneNumber = exports.flexLeftRow = exports.findScheduleByDayOfWeek = exports.capitalizeFirstLetter = exports.getLongFormattedDateTime = exports.formatDateByTimezone = exports.readCookie = exports.deleteCookie = exports.request = exports.API_BASE_URL = exports.VehicleTire = exports.VehicleGarage = exports.Vehicle = exports.User = void 0;
|
|
9
|
+
exports.StyledToggleButtonGroup = exports.StyledToggleButton = exports.getQrCodeBase64 = exports.openDialogPrint = exports.VehiclePlateType = exports.SubscriptionType = exports.SubscriptionState = void 0;
|
|
10
10
|
// Export des services
|
|
11
11
|
var AuthenticationService_1 = require("./src/services/AuthenticationService");
|
|
12
12
|
Object.defineProperty(exports, "AuthenticationService", { enumerable: true, get: function () { return __importDefault(AuthenticationService_1).default; } });
|
|
@@ -166,6 +166,7 @@ Object.defineProperty(exports, "PrestationType", { enumerable: true, get: functi
|
|
|
166
166
|
Object.defineProperty(exports, "ProductType", { enumerable: true, get: function () { return Enums_1.ProductType; } });
|
|
167
167
|
Object.defineProperty(exports, "RegistrationState", { enumerable: true, get: function () { return Enums_1.RegistrationState; } });
|
|
168
168
|
Object.defineProperty(exports, "RoleType", { enumerable: true, get: function () { return Enums_1.RoleType; } });
|
|
169
|
+
Object.defineProperty(exports, "SecondaryPartsApplicationType", { enumerable: true, get: function () { return Enums_1.SecondaryPartsApplicationType; } });
|
|
169
170
|
Object.defineProperty(exports, "SlotAlgorithm", { enumerable: true, get: function () { return Enums_1.SlotAlgorithm; } });
|
|
170
171
|
Object.defineProperty(exports, "SubscriptionPaymentInterval", { enumerable: true, get: function () { return Enums_1.SubscriptionPaymentInterval; } });
|
|
171
172
|
Object.defineProperty(exports, "SubscriptionState", { enumerable: true, get: function () { return Enums_1.SubscriptionState; } });
|
|
@@ -12,6 +12,16 @@ export declare enum SubscriptionState {
|
|
|
12
12
|
PAUSED = "PAUSED",
|
|
13
13
|
CANCELED = "CANCELED"
|
|
14
14
|
}
|
|
15
|
+
export declare enum SecondaryPartsApplicationType {
|
|
16
|
+
HOOD = "HOOD",
|
|
17
|
+
ROOF = "ROOF",
|
|
18
|
+
DOOR = "DOOR",
|
|
19
|
+
FENDER = "FENDER",
|
|
20
|
+
JAMB = "JAMB",
|
|
21
|
+
UNDERBODY = "UNDERBODY",
|
|
22
|
+
BUMPER = "BUMPER",
|
|
23
|
+
TRUNK = "TRUNK"
|
|
24
|
+
}
|
|
15
25
|
export declare enum CountryCode {
|
|
16
26
|
FR = "FR"
|
|
17
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RoleType = exports.MovaAppType = exports.DocumentType = exports.QuoteState = exports.DocumentState = exports.DayOfWeek = exports.EventType = exports.EventState = exports.DigitalPassportIndex = exports.Gender = exports.DateFormatTypes = exports.APIMethod = exports.PartsApplicationType = exports.ProductType = exports.OrderPreference = exports.OrderState = exports.SlotAlgorithm = exports.VehiclePlateFormat = exports.RegistrationState = exports.SubscriptionPaymentInterval = exports.PrestationType = exports.PrestationState = exports.CountryCode = exports.SubscriptionState = exports.SubscriptionType = exports.CustomerType = void 0;
|
|
3
|
+
exports.RoleType = exports.MovaAppType = exports.DocumentType = exports.QuoteState = exports.DocumentState = exports.DayOfWeek = exports.EventType = exports.EventState = exports.DigitalPassportIndex = exports.Gender = exports.DateFormatTypes = exports.APIMethod = exports.PartsApplicationType = exports.ProductType = exports.OrderPreference = exports.OrderState = exports.SlotAlgorithm = exports.VehiclePlateFormat = exports.RegistrationState = exports.SubscriptionPaymentInterval = exports.PrestationType = exports.PrestationState = exports.CountryCode = exports.SecondaryPartsApplicationType = exports.SubscriptionState = exports.SubscriptionType = exports.CustomerType = void 0;
|
|
4
4
|
var CustomerType;
|
|
5
5
|
(function (CustomerType) {
|
|
6
6
|
CustomerType["INDIVIDUAL"] = "INDIVIDUAL";
|
|
@@ -18,6 +18,17 @@ var SubscriptionState;
|
|
|
18
18
|
SubscriptionState["PAUSED"] = "PAUSED";
|
|
19
19
|
SubscriptionState["CANCELED"] = "CANCELED";
|
|
20
20
|
})(SubscriptionState = exports.SubscriptionState || (exports.SubscriptionState = {}));
|
|
21
|
+
var SecondaryPartsApplicationType;
|
|
22
|
+
(function (SecondaryPartsApplicationType) {
|
|
23
|
+
SecondaryPartsApplicationType["HOOD"] = "HOOD";
|
|
24
|
+
SecondaryPartsApplicationType["ROOF"] = "ROOF";
|
|
25
|
+
SecondaryPartsApplicationType["DOOR"] = "DOOR";
|
|
26
|
+
SecondaryPartsApplicationType["FENDER"] = "FENDER";
|
|
27
|
+
SecondaryPartsApplicationType["JAMB"] = "JAMB";
|
|
28
|
+
SecondaryPartsApplicationType["UNDERBODY"] = "UNDERBODY";
|
|
29
|
+
SecondaryPartsApplicationType["BUMPER"] = "BUMPER";
|
|
30
|
+
SecondaryPartsApplicationType["TRUNK"] = "TRUNK";
|
|
31
|
+
})(SecondaryPartsApplicationType = exports.SecondaryPartsApplicationType || (exports.SecondaryPartsApplicationType = {}));
|
|
21
32
|
var CountryCode;
|
|
22
33
|
(function (CountryCode) {
|
|
23
34
|
CountryCode["FR"] = "FR";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CSSProperties } from "react";
|
|
2
|
+
import Operation from "../models/Operation";
|
|
2
3
|
import Schedule from "../models/Schedule";
|
|
3
4
|
import VehicleTire from "../models/VehicleTire";
|
|
4
|
-
import { DayOfWeek, PartsApplicationType } from "./Enums";
|
|
5
|
+
import { DayOfWeek, PartsApplicationType, SecondaryPartsApplicationType } from "./Enums";
|
|
5
6
|
import { MovaFormField, MovaInterval } from "./Types";
|
|
6
7
|
export declare const getApplicationsShortLabels: (applications: PartsApplicationType[] | undefined) => string;
|
|
7
8
|
export declare const flexStart: CSSProperties;
|
|
@@ -20,7 +21,8 @@ export declare const getDayOfWeek: (index: number) => DayOfWeek | undefined;
|
|
|
20
21
|
export declare const getFrenchDayLabel: (day: DayOfWeek | undefined) => "Lundi" | "Mardi" | "Mercredi" | "Jeudi" | "Vendredi" | "Samedi" | "Dimanche" | undefined;
|
|
21
22
|
export declare const flexLeftRow: CSSProperties;
|
|
22
23
|
export declare const capitalizeFirstLetter: (str: string) => string;
|
|
23
|
-
export declare const getApplicationShortLabel: (application: PartsApplicationType | undefined) => "" | "
|
|
24
|
+
export declare const getApplicationShortLabel: (application: PartsApplicationType | undefined) => "" | "AVG" | "AVD" | "ARG" | "ARD" | "AV" | "AR" | "G" | "D" | "AV + AR" | "G + D";
|
|
25
|
+
export declare const getApplicationSecondaryLabel: (secondaryApplication: SecondaryPartsApplicationType) => string;
|
|
24
26
|
export declare const flexEnd: CSSProperties;
|
|
25
27
|
export declare const flexCenter: CSSProperties;
|
|
26
28
|
export declare const isEmpty: (data: Object) => boolean;
|
|
@@ -36,3 +38,8 @@ export declare const formatVehicleTireStr: (input: string) => string;
|
|
|
36
38
|
* @returns {MovaFormField} - Le champ de formulaire avec les propriétés `error` et `isValid` mises à jour.
|
|
37
39
|
*/
|
|
38
40
|
export declare const validateField: (field: MovaFormField | undefined, validator: (value: string) => boolean, errorMsg: string) => MovaFormField;
|
|
41
|
+
export declare function buildSelectedOperationsLabel(selectedOperationIds: number[], allOperations: Operation[], options?: {
|
|
42
|
+
prestationName?: string;
|
|
43
|
+
}): string;
|
|
44
|
+
export declare function hasSecondaryApplication(items: Operation[], ids: number[], needle: string, caseInsensitive?: boolean): boolean;
|
|
45
|
+
export declare function buildPositionsLabelFromShorts(selectedOperationIds: number[], allOperations: Operation[]): string;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
3
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
4
|
+
if (ar || !(i in from)) {
|
|
5
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
6
|
+
ar[i] = from[i];
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
10
|
+
};
|
|
2
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
13
|
};
|
|
5
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.validateField = exports.formatVehicleTireStr = exports.formatVehicleTire = exports.formatVehiclePlate = exports.isEmpty = exports.flexCenter = exports.flexEnd = exports.getApplicationShortLabel = exports.capitalizeFirstLetter = exports.flexLeftRow = exports.getFrenchDayLabel = exports.getDayOfWeek = exports.findScheduleByDayOfWeek = exports.formatTime = exports.getFormattedIntervals = exports.getFormattedSchedule = exports.formatPhoneNumber = exports.getDayOfWeekIndex = exports.getDayOfWeekLabel = exports.FR_WEEK_DAYS = exports.isInvalidPhoneNumber = exports.isInvalidMobileNumber = exports.isSafariOniOS = exports.flexStart = exports.getApplicationsShortLabels = void 0;
|
|
15
|
+
exports.buildPositionsLabelFromShorts = exports.hasSecondaryApplication = exports.buildSelectedOperationsLabel = exports.validateField = exports.formatVehicleTireStr = exports.formatVehicleTire = exports.formatVehiclePlate = exports.isEmpty = exports.flexCenter = exports.flexEnd = exports.getApplicationSecondaryLabel = exports.getApplicationShortLabel = exports.capitalizeFirstLetter = exports.flexLeftRow = exports.getFrenchDayLabel = exports.getDayOfWeek = exports.findScheduleByDayOfWeek = exports.formatTime = exports.getFormattedIntervals = exports.getFormattedSchedule = exports.formatPhoneNumber = exports.getDayOfWeekIndex = exports.getDayOfWeekLabel = exports.FR_WEEK_DAYS = exports.isInvalidPhoneNumber = exports.isInvalidMobileNumber = exports.isSafariOniOS = exports.flexStart = exports.getApplicationsShortLabels = void 0;
|
|
7
16
|
var max_1 = __importDefault(require("libphonenumber-js/max"));
|
|
8
17
|
var Enums_1 = require("./Enums");
|
|
9
18
|
var getApplicationsShortLabels = function (applications) {
|
|
@@ -252,6 +261,27 @@ var getApplicationShortLabel = function (application) {
|
|
|
252
261
|
return "";
|
|
253
262
|
};
|
|
254
263
|
exports.getApplicationShortLabel = getApplicationShortLabel;
|
|
264
|
+
var getApplicationSecondaryLabel = function (secondaryApplication) {
|
|
265
|
+
switch (secondaryApplication) {
|
|
266
|
+
case Enums_1.SecondaryPartsApplicationType.HOOD:
|
|
267
|
+
return "Capot";
|
|
268
|
+
case Enums_1.SecondaryPartsApplicationType.ROOF:
|
|
269
|
+
return "Toit";
|
|
270
|
+
case Enums_1.SecondaryPartsApplicationType.DOOR:
|
|
271
|
+
return "Portière";
|
|
272
|
+
case Enums_1.SecondaryPartsApplicationType.TRUNK:
|
|
273
|
+
return "Coffre";
|
|
274
|
+
case Enums_1.SecondaryPartsApplicationType.FENDER:
|
|
275
|
+
return "Aile";
|
|
276
|
+
case Enums_1.SecondaryPartsApplicationType.JAMB:
|
|
277
|
+
return "Montant";
|
|
278
|
+
case Enums_1.SecondaryPartsApplicationType.UNDERBODY:
|
|
279
|
+
return "Bas de caisse";
|
|
280
|
+
case Enums_1.SecondaryPartsApplicationType.BUMPER:
|
|
281
|
+
return "Pare-chocs";
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
exports.getApplicationSecondaryLabel = getApplicationSecondaryLabel;
|
|
255
285
|
exports.flexEnd = {
|
|
256
286
|
display: "flex",
|
|
257
287
|
justifyContent: "end",
|
|
@@ -357,3 +387,156 @@ var validateField = function (field, validator, errorMsg) {
|
|
|
357
387
|
}
|
|
358
388
|
};
|
|
359
389
|
exports.validateField = validateField;
|
|
390
|
+
function shortFromApplication(app) {
|
|
391
|
+
var A = (app || "").toUpperCase();
|
|
392
|
+
var has = function (s) { return A.includes(s); };
|
|
393
|
+
if (has("FT") && has("LT"))
|
|
394
|
+
return "AVG";
|
|
395
|
+
if (has("FT") && has("RT"))
|
|
396
|
+
return "AVD";
|
|
397
|
+
if (has("RR") && has("LT"))
|
|
398
|
+
return "ARG";
|
|
399
|
+
if (has("RR") && has("RT"))
|
|
400
|
+
return "ARD";
|
|
401
|
+
if (has("FT"))
|
|
402
|
+
return "AV";
|
|
403
|
+
if (has("RR"))
|
|
404
|
+
return "AR";
|
|
405
|
+
if (has("LT"))
|
|
406
|
+
return "G";
|
|
407
|
+
if (has("RT"))
|
|
408
|
+
return "D";
|
|
409
|
+
return "";
|
|
410
|
+
}
|
|
411
|
+
var CATEGORY = {
|
|
412
|
+
DOOR: { title: "PORTE", useApp: true },
|
|
413
|
+
FENDER: { title: "AILE", useApp: true },
|
|
414
|
+
JAMB: { title: "MONTANT", useApp: true },
|
|
415
|
+
UNDERBODY: { title: "BAS DE CAISSE", useApp: true },
|
|
416
|
+
BUMPER: { title: "PARE-CHOCS", useApp: true },
|
|
417
|
+
HOOD: { title: "CAPOT", useApp: false },
|
|
418
|
+
TRUNK: { title: "COFFRE", useApp: false },
|
|
419
|
+
ROOF: { title: "TOIT", useApp: false },
|
|
420
|
+
};
|
|
421
|
+
function buildSelectedOperationsLabel(selectedOperationIds, allOperations, options) {
|
|
422
|
+
var _a, _b;
|
|
423
|
+
if (options === void 0) { options = {}; }
|
|
424
|
+
var prestationName = options.prestationName;
|
|
425
|
+
var byId = new Map(allOperations.map(function (o) { return [o.id, o]; }));
|
|
426
|
+
var groups = new Map(); // key = secondaryApplication UPPER
|
|
427
|
+
for (var _i = 0, selectedOperationIds_1 = selectedOperationIds; _i < selectedOperationIds_1.length; _i++) {
|
|
428
|
+
var id = selectedOperationIds_1[_i];
|
|
429
|
+
var op = byId.get(id);
|
|
430
|
+
if (!op)
|
|
431
|
+
continue;
|
|
432
|
+
var key = (op.secondaryApplication || "OTHER").toUpperCase();
|
|
433
|
+
var cfg = (_a = CATEGORY[key]) !== null && _a !== void 0 ? _a : { title: key, useApp: true };
|
|
434
|
+
if (!groups.has(key)) {
|
|
435
|
+
groups.set(key, cfg.useApp
|
|
436
|
+
? {
|
|
437
|
+
kind: "withApp",
|
|
438
|
+
key: key,
|
|
439
|
+
title: cfg.title,
|
|
440
|
+
counts: new Map(),
|
|
441
|
+
}
|
|
442
|
+
: { kind: "single", key: key, title: cfg.title, seen: false });
|
|
443
|
+
}
|
|
444
|
+
var g = groups.get(key);
|
|
445
|
+
if (g.kind === "withApp") {
|
|
446
|
+
var label = shortFromApplication(op.application) || op.application || "";
|
|
447
|
+
if (label)
|
|
448
|
+
g.counts.set(label, ((_b = g.counts.get(label)) !== null && _b !== void 0 ? _b : 0) + 1);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
g.seen = true; // HOOD/TRUNK/ROOF -> présent une fois, sans compteur
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Ordre des catégories dans le rendu final
|
|
455
|
+
var desiredOrder = [
|
|
456
|
+
"DOOR",
|
|
457
|
+
"FENDER",
|
|
458
|
+
"HOOD",
|
|
459
|
+
"BUMPER",
|
|
460
|
+
"TRUNK",
|
|
461
|
+
"ROOF",
|
|
462
|
+
"JAMB",
|
|
463
|
+
"UNDERBODY",
|
|
464
|
+
];
|
|
465
|
+
var weight = function (key) {
|
|
466
|
+
var idx = desiredOrder.indexOf(key);
|
|
467
|
+
return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
|
|
468
|
+
};
|
|
469
|
+
// Construire les segments
|
|
470
|
+
var segments = [];
|
|
471
|
+
var _loop_1 = function (g) {
|
|
472
|
+
if (g.kind === "withApp") {
|
|
473
|
+
var order_1 = ["AV", "AR", "AVG", "AVD", "ARG", "ARD", "G", "D"];
|
|
474
|
+
var parts = Array.from(g.counts.entries())
|
|
475
|
+
.sort(function (a, b) { return order_1.indexOf(a[0]) - order_1.indexOf(b[0]); })
|
|
476
|
+
.map(function (_a) {
|
|
477
|
+
var lbl = _a[0], n = _a[1];
|
|
478
|
+
return (n > 1 ? "".concat(lbl, " (x").concat(n, ")") : lbl);
|
|
479
|
+
});
|
|
480
|
+
if (parts.length)
|
|
481
|
+
segments.push("".concat(g.title, " ").concat(parts.join(" + ")));
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
if (g.seen)
|
|
485
|
+
segments.push(g.title); // capot / toit / coffre (sans position, sans xN)
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
for (var _c = 0, _d = Array.from(groups.values()).sort(function (a, b) { return weight(a.key) - weight(b.key); }); _c < _d.length; _c++) {
|
|
489
|
+
var g = _d[_c];
|
|
490
|
+
_loop_1(g);
|
|
491
|
+
}
|
|
492
|
+
return __spreadArray([prestationName], segments, true).filter(Boolean).join(" , ");
|
|
493
|
+
}
|
|
494
|
+
exports.buildSelectedOperationsLabel = buildSelectedOperationsLabel;
|
|
495
|
+
function hasSecondaryApplication(items, ids, needle, caseInsensitive) {
|
|
496
|
+
var _a;
|
|
497
|
+
if (caseInsensitive === void 0) { caseInsensitive = false; }
|
|
498
|
+
var byId = new Map(items.map(function (it) { return [it.id, it]; }));
|
|
499
|
+
var target = caseInsensitive ? needle.toLowerCase() : needle;
|
|
500
|
+
for (var _i = 0, ids_1 = ids; _i < ids_1.length; _i++) {
|
|
501
|
+
var id = ids_1[_i];
|
|
502
|
+
var it = byId.get(id);
|
|
503
|
+
if (!it)
|
|
504
|
+
continue;
|
|
505
|
+
var value = ((_a = it.secondaryApplication) !== null && _a !== void 0 ? _a : "");
|
|
506
|
+
if ((caseInsensitive ? value.toLowerCase() : value) === target)
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
exports.hasSecondaryApplication = hasSecondaryApplication;
|
|
512
|
+
var ORDER = ["AVG", "AVD", "ARG", "ARD", "AV", "AR", "G", "D"];
|
|
513
|
+
function buildPositionsLabelFromShorts(selectedOperationIds, allOperations) {
|
|
514
|
+
var _a, _b;
|
|
515
|
+
// index
|
|
516
|
+
var byId = new Map(allOperations.map(function (o) { return [o.id, o]; }));
|
|
517
|
+
// collecte des tokens (AV, AR, AVG, …)
|
|
518
|
+
var tokens = new Set();
|
|
519
|
+
for (var _i = 0, selectedOperationIds_2 = selectedOperationIds; _i < selectedOperationIds_2.length; _i++) {
|
|
520
|
+
var id = selectedOperationIds_2[_i];
|
|
521
|
+
var appCode = (_b = (_a = byId.get(id)) === null || _a === void 0 ? void 0 : _a.application) === null || _b === void 0 ? void 0 : _b.toUpperCase();
|
|
522
|
+
if (!appCode)
|
|
523
|
+
continue;
|
|
524
|
+
var appEnum = appCode;
|
|
525
|
+
if (!appEnum)
|
|
526
|
+
continue;
|
|
527
|
+
var short = (0, exports.getApplicationShortLabel)(appEnum); // ex: "AV + AR" ou "AVG"
|
|
528
|
+
if (!short)
|
|
529
|
+
continue;
|
|
530
|
+
// ⚠️ splitter par " + " (pas par caractère) pour éviter "a + v"
|
|
531
|
+
for (var _c = 0, _d = short.split(" + "); _c < _d.length; _c++) {
|
|
532
|
+
var t = _d[_c];
|
|
533
|
+
var s = t.trim();
|
|
534
|
+
if (s)
|
|
535
|
+
tokens.add(s);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// tri esthétique
|
|
539
|
+
var parts = Array.from(tokens).sort(function (a, b) { return ORDER.indexOf(a) - ORDER.indexOf(b); });
|
|
540
|
+
return parts.join(" + ");
|
|
541
|
+
}
|
|
542
|
+
exports.buildPositionsLabelFromShorts = buildPositionsLabelFromShorts;
|
|
@@ -64,5 +64,7 @@ export default class Garage {
|
|
|
64
64
|
companyRegistrationNumber?: string;
|
|
65
65
|
establishmentRegistrationNumber?: string;
|
|
66
66
|
reopeningDate?: Date;
|
|
67
|
-
|
|
67
|
+
targetMargin?: number;
|
|
68
|
+
onlyBilling?: boolean;
|
|
69
|
+
constructor(id: string, adminId: string, name: string, address: Address, workforce: number, prestations: Prestation[], schedules: Schedule[], contactPhone: string, prestationCategories: CategoryPrestation[], dayPeriodFastServiceExcluded: boolean, loanerVehicleFastServiceExcluded: boolean, fastServiceThreshold: number, timezone: string, vehicles?: VehicleGarage[], contactEmail?: string, logo?: string, suppliers?: Supplier[], documents?: Document[], subscriptions?: Subscription[], loanerVehicleActive?: boolean, loanerVehicleRequestActive?: boolean, customStyle?: string, subscription?: Subscription, partialWorkforce?: number, mailCustomization?: boolean, billingActive?: boolean, billingToken?: string, billingSimulationActive?: boolean, appId?: number, establishmentRegistrationNumber?: string, companyRegistrationNumber?: string, quoteRequestStart?: Date, reopeningDate?: Date, targetMargin?: number, demoBillingActive?: boolean, onlyBilling?: boolean);
|
|
68
70
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
var Garage = /** @class */ (function () {
|
|
4
|
-
function Garage(id, adminId, name, address, workforce, prestations, schedules, contactPhone, prestationCategories, dayPeriodFastServiceExcluded, loanerVehicleFastServiceExcluded, fastServiceThreshold, timezone, vehicles, contactEmail, logo, suppliers, documents, subscriptions, loanerVehicleActive, loanerVehicleRequestActive, customStyle, subscription, partialWorkforce, mailCustomization, billingActive, billingToken, billingSimulationActive, appId, establishmentRegistrationNumber, companyRegistrationNumber, quoteRequestStart, reopeningDate, demoBillingActive) {
|
|
4
|
+
function Garage(id, adminId, name, address, workforce, prestations, schedules, contactPhone, prestationCategories, dayPeriodFastServiceExcluded, loanerVehicleFastServiceExcluded, fastServiceThreshold, timezone, vehicles, contactEmail, logo, suppliers, documents, subscriptions, loanerVehicleActive, loanerVehicleRequestActive, customStyle, subscription, partialWorkforce, mailCustomization, billingActive, billingToken, billingSimulationActive, appId, establishmentRegistrationNumber, companyRegistrationNumber, quoteRequestStart, reopeningDate, targetMargin, demoBillingActive, onlyBilling) {
|
|
5
|
+
if (onlyBilling === void 0) { onlyBilling = false; }
|
|
5
6
|
this.id = id;
|
|
6
7
|
this.adminId = adminId;
|
|
7
8
|
this.name = name;
|
|
@@ -35,7 +36,9 @@ var Garage = /** @class */ (function () {
|
|
|
35
36
|
this.companyRegistrationNumber = companyRegistrationNumber;
|
|
36
37
|
this.establishmentRegistrationNumber = establishmentRegistrationNumber;
|
|
37
38
|
this.reopeningDate = reopeningDate;
|
|
39
|
+
this.targetMargin = targetMargin;
|
|
38
40
|
this.demoBillingActive = demoBillingActive;
|
|
41
|
+
this.onlyBilling = onlyBilling;
|
|
39
42
|
}
|
|
40
43
|
return Garage;
|
|
41
44
|
}());
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { PartsApplicationType } from "../helpers/Enums";
|
|
1
|
+
import { PartsApplicationType, SecondaryPartsApplicationType } from "../helpers/Enums";
|
|
2
2
|
import Product from "./Product";
|
|
3
3
|
export default class Operation {
|
|
4
4
|
id: number;
|
|
5
5
|
code: string;
|
|
6
6
|
name: string;
|
|
7
7
|
description: string;
|
|
8
|
+
prestationId: number;
|
|
8
9
|
application?: PartsApplicationType;
|
|
9
10
|
products?: Product[];
|
|
10
|
-
|
|
11
|
+
secondaryApplication?: SecondaryPartsApplicationType;
|
|
12
|
+
constructor(id: number, code: string, name: string, description: string, application: PartsApplicationType, products: Product[], prestationId: number, secondaryApplication?: SecondaryPartsApplicationType);
|
|
11
13
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
var Operation = /** @class */ (function () {
|
|
4
|
-
function Operation(id, code, name, description, application, products) {
|
|
4
|
+
function Operation(id, code, name, description, application, products, prestationId, secondaryApplication) {
|
|
5
|
+
this.prestationId = prestationId;
|
|
5
6
|
this.id = id;
|
|
6
7
|
this.code = code;
|
|
7
8
|
this.name = name;
|
|
8
9
|
this.description = description;
|
|
9
10
|
this.application = application;
|
|
10
11
|
this.products = products;
|
|
12
|
+
this.secondaryApplication = secondaryApplication;
|
|
11
13
|
}
|
|
12
14
|
return Operation;
|
|
13
15
|
}());
|
|
@@ -28,5 +28,6 @@ export default class Prestation {
|
|
|
28
28
|
multipleApplication: boolean;
|
|
29
29
|
operationsVisible: boolean;
|
|
30
30
|
categoryCode: string;
|
|
31
|
-
|
|
31
|
+
secondaryMultipleApplication: boolean;
|
|
32
|
+
constructor(id: number, code: string, name: string, description: string, category: string, downtime: number, appointmentDelay: number, position: number, active: boolean, state: PrestationState, multipleApplication: boolean, operationsVisible: boolean, categoryCode: string, availableOnline: boolean, operations?: Operation[], secondaryMultipleApplication?: boolean);
|
|
32
33
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
var Prestation = /** @class */ (function () {
|
|
4
|
-
function Prestation(id, code, name, description, category, downtime, appointmentDelay, position, active, state, multipleApplication, operationsVisible, categoryCode, availableOnline, operations) {
|
|
4
|
+
function Prestation(id, code, name, description, category, downtime, appointmentDelay, position, active, state, multipleApplication, operationsVisible, categoryCode, availableOnline, operations, secondaryMultipleApplication) {
|
|
5
|
+
if (secondaryMultipleApplication === void 0) { secondaryMultipleApplication = false; }
|
|
5
6
|
this.id = id;
|
|
6
7
|
this.code = code;
|
|
7
8
|
this.name = name;
|
|
@@ -17,6 +18,7 @@ var Prestation = /** @class */ (function () {
|
|
|
17
18
|
this.state = state;
|
|
18
19
|
this.categoryCode = categoryCode;
|
|
19
20
|
this.availableOnline = availableOnline;
|
|
21
|
+
this.secondaryMultipleApplication = secondaryMultipleApplication;
|
|
20
22
|
}
|
|
21
23
|
return Prestation;
|
|
22
24
|
}());
|
package/index.ts
CHANGED
package/package.json
CHANGED
package/src/helpers/Enums.ts
CHANGED
|
@@ -14,7 +14,16 @@ export enum SubscriptionState {
|
|
|
14
14
|
PAUSED = "PAUSED",
|
|
15
15
|
CANCELED = "CANCELED",
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
export enum SecondaryPartsApplicationType {
|
|
18
|
+
HOOD = "HOOD",
|
|
19
|
+
ROOF = "ROOF",
|
|
20
|
+
DOOR = "DOOR",
|
|
21
|
+
FENDER = "FENDER",
|
|
22
|
+
JAMB = "JAMB",
|
|
23
|
+
UNDERBODY = "UNDERBODY",
|
|
24
|
+
BUMPER = "BUMPER",
|
|
25
|
+
TRUNK = "TRUNK",
|
|
26
|
+
}
|
|
18
27
|
export enum CountryCode {
|
|
19
28
|
FR = "FR",
|
|
20
29
|
}
|
|
@@ -239,7 +248,7 @@ export enum QuoteState {
|
|
|
239
248
|
QUOTE_SENT = "QUOTE_SENT",
|
|
240
249
|
QUOTE_ACCEPTED = "QUOTE_ACCEPTED",
|
|
241
250
|
QUOTE_REJECTED = "QUOTE_REJECTED",
|
|
242
|
-
QUOTE_CANCELLED = "CANCELLED"
|
|
251
|
+
QUOTE_CANCELLED = "CANCELLED",
|
|
243
252
|
}
|
|
244
253
|
|
|
245
254
|
export enum DocumentType {
|
package/src/helpers/Tools.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import parsePhoneNumberFromString from "libphonenumber-js/max";
|
|
2
2
|
import { CSSProperties } from "react";
|
|
3
|
+
import Operation from "../models/Operation";
|
|
3
4
|
import Schedule from "../models/Schedule";
|
|
4
5
|
import VehicleTire from "../models/VehicleTire";
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
DayOfWeek,
|
|
8
|
+
PartsApplicationType,
|
|
9
|
+
SecondaryPartsApplicationType,
|
|
10
|
+
VehiclePlateFormat,
|
|
11
|
+
} from "./Enums";
|
|
6
12
|
import { MovaFormField, MovaInterval } from "./Types";
|
|
7
13
|
|
|
8
14
|
export const getApplicationsShortLabels = (
|
|
@@ -278,6 +284,29 @@ export const getApplicationShortLabel = (
|
|
|
278
284
|
return "";
|
|
279
285
|
};
|
|
280
286
|
|
|
287
|
+
export const getApplicationSecondaryLabel = (
|
|
288
|
+
secondaryApplication: SecondaryPartsApplicationType
|
|
289
|
+
): string => {
|
|
290
|
+
switch (secondaryApplication) {
|
|
291
|
+
case SecondaryPartsApplicationType.HOOD:
|
|
292
|
+
return "Capot";
|
|
293
|
+
case SecondaryPartsApplicationType.ROOF:
|
|
294
|
+
return "Toit";
|
|
295
|
+
case SecondaryPartsApplicationType.DOOR:
|
|
296
|
+
return "Portière";
|
|
297
|
+
case SecondaryPartsApplicationType.TRUNK:
|
|
298
|
+
return "Coffre";
|
|
299
|
+
case SecondaryPartsApplicationType.FENDER:
|
|
300
|
+
return "Aile";
|
|
301
|
+
case SecondaryPartsApplicationType.JAMB:
|
|
302
|
+
return "Montant";
|
|
303
|
+
case SecondaryPartsApplicationType.UNDERBODY:
|
|
304
|
+
return "Bas de caisse";
|
|
305
|
+
case SecondaryPartsApplicationType.BUMPER:
|
|
306
|
+
return "Pare-chocs";
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
281
310
|
export const flexEnd: CSSProperties = {
|
|
282
311
|
display: "flex",
|
|
283
312
|
justifyContent: "end",
|
|
@@ -294,12 +323,14 @@ export const isEmpty = (data: Object): boolean => {
|
|
|
294
323
|
return Object.keys(data).length === 0;
|
|
295
324
|
};
|
|
296
325
|
|
|
297
|
-
export const formatVehiclePlate = (
|
|
326
|
+
export const formatVehiclePlate = (
|
|
327
|
+
input: string | undefined,
|
|
328
|
+
isForeignPlate: boolean
|
|
329
|
+
): string => {
|
|
298
330
|
if (input) {
|
|
299
|
-
|
|
300
331
|
let plateFormat: VehiclePlateFormat | null = null;
|
|
301
332
|
|
|
302
|
-
if(isForeignPlate) {
|
|
333
|
+
if (isForeignPlate) {
|
|
303
334
|
// Plaque étrangère
|
|
304
335
|
plateFormat = VehiclePlateFormat.FOREIGN;
|
|
305
336
|
} else if (/^[A-Za-z]/.test(input)) {
|
|
@@ -338,7 +369,6 @@ export const formatVehiclePlate = (input: string | undefined, isForeignPlate: bo
|
|
|
338
369
|
|
|
339
370
|
case VehiclePlateFormat.FOREIGN: {
|
|
340
371
|
// On retourne la plaque telle qu'enregistrée par l'utilisateur, ras
|
|
341
|
-
|
|
342
372
|
}
|
|
343
373
|
}
|
|
344
374
|
return cleanedInput;
|
|
@@ -397,3 +427,175 @@ export const validateField = (
|
|
|
397
427
|
return { value: "", error: "", isValid: true };
|
|
398
428
|
}
|
|
399
429
|
};
|
|
430
|
+
|
|
431
|
+
function shortFromApplication(app: string): string {
|
|
432
|
+
const A = (app || "").toUpperCase();
|
|
433
|
+
const has = (s: string) => A.includes(s);
|
|
434
|
+
if (has("FT") && has("LT")) return "AVG";
|
|
435
|
+
if (has("FT") && has("RT")) return "AVD";
|
|
436
|
+
if (has("RR") && has("LT")) return "ARG";
|
|
437
|
+
if (has("RR") && has("RT")) return "ARD";
|
|
438
|
+
if (has("FT")) return "AV";
|
|
439
|
+
if (has("RR")) return "AR";
|
|
440
|
+
if (has("LT")) return "G";
|
|
441
|
+
if (has("RT")) return "D";
|
|
442
|
+
return "";
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const CATEGORY: Record<string, { title: string; useApp: boolean }> = {
|
|
446
|
+
DOOR: { title: "PORTE", useApp: true },
|
|
447
|
+
FENDER: { title: "AILE", useApp: true },
|
|
448
|
+
JAMB: { title: "MONTANT", useApp: true },
|
|
449
|
+
UNDERBODY: { title: "BAS DE CAISSE", useApp: true },
|
|
450
|
+
BUMPER: { title: "PARE-CHOCS", useApp: true },
|
|
451
|
+
HOOD: { title: "CAPOT", useApp: false }, // pas de position ni (xN)
|
|
452
|
+
TRUNK: { title: "COFFRE", useApp: false },
|
|
453
|
+
ROOF: { title: "TOIT", useApp: false },
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Discriminated union pour bien typer les groupes
|
|
457
|
+
type GroupWithApp = {
|
|
458
|
+
kind: "withApp";
|
|
459
|
+
key: string;
|
|
460
|
+
title: string;
|
|
461
|
+
counts: Map<string, number>;
|
|
462
|
+
};
|
|
463
|
+
type GroupSingle = {
|
|
464
|
+
kind: "single";
|
|
465
|
+
key: string;
|
|
466
|
+
title: string;
|
|
467
|
+
seen: boolean;
|
|
468
|
+
};
|
|
469
|
+
type Group = GroupWithApp | GroupSingle;
|
|
470
|
+
|
|
471
|
+
export function buildSelectedOperationsLabel(
|
|
472
|
+
selectedOperationIds: number[],
|
|
473
|
+
allOperations: Operation[],
|
|
474
|
+
options: { prestationName?: string } = {}
|
|
475
|
+
): string {
|
|
476
|
+
const { prestationName } = options;
|
|
477
|
+
|
|
478
|
+
const byId = new Map<number, Operation>(allOperations.map((o) => [o.id, o]));
|
|
479
|
+
const groups = new Map<string, Group>(); // key = secondaryApplication UPPER
|
|
480
|
+
|
|
481
|
+
for (const id of selectedOperationIds) {
|
|
482
|
+
const op = byId.get(id);
|
|
483
|
+
if (!op) continue;
|
|
484
|
+
|
|
485
|
+
const key = (op.secondaryApplication || "OTHER").toUpperCase();
|
|
486
|
+
const cfg = CATEGORY[key] ?? { title: key, useApp: true };
|
|
487
|
+
|
|
488
|
+
if (!groups.has(key)) {
|
|
489
|
+
groups.set(
|
|
490
|
+
key,
|
|
491
|
+
cfg.useApp
|
|
492
|
+
? {
|
|
493
|
+
kind: "withApp",
|
|
494
|
+
key,
|
|
495
|
+
title: cfg.title,
|
|
496
|
+
counts: new Map<string, number>(),
|
|
497
|
+
}
|
|
498
|
+
: { kind: "single", key, title: cfg.title, seen: false }
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const g = groups.get(key)!;
|
|
503
|
+
|
|
504
|
+
if (g.kind === "withApp") {
|
|
505
|
+
const label =
|
|
506
|
+
shortFromApplication(op.application as string) || op.application || "";
|
|
507
|
+
if (label) g.counts.set(label, (g.counts.get(label) ?? 0) + 1);
|
|
508
|
+
} else {
|
|
509
|
+
g.seen = true; // HOOD/TRUNK/ROOF -> présent une fois, sans compteur
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Ordre des catégories dans le rendu final
|
|
514
|
+
const desiredOrder = [
|
|
515
|
+
"DOOR",
|
|
516
|
+
"FENDER",
|
|
517
|
+
"HOOD",
|
|
518
|
+
"BUMPER",
|
|
519
|
+
"TRUNK",
|
|
520
|
+
"ROOF",
|
|
521
|
+
"JAMB",
|
|
522
|
+
"UNDERBODY",
|
|
523
|
+
];
|
|
524
|
+
const weight = (key: string) => {
|
|
525
|
+
const idx = desiredOrder.indexOf(key);
|
|
526
|
+
return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// Construire les segments
|
|
530
|
+
const segments: string[] = [];
|
|
531
|
+
|
|
532
|
+
for (const g of Array.from(groups.values()).sort(
|
|
533
|
+
(a, b) => weight(a.key) - weight(b.key)
|
|
534
|
+
)) {
|
|
535
|
+
if (g.kind === "withApp") {
|
|
536
|
+
const order = ["AV", "AR", "AVG", "AVD", "ARG", "ARD", "G", "D"];
|
|
537
|
+
const parts = Array.from(g.counts.entries())
|
|
538
|
+
.sort((a, b) => order.indexOf(a[0]) - order.indexOf(b[0]))
|
|
539
|
+
.map(([lbl, n]) => (n > 1 ? `${lbl} (x${n})` : lbl));
|
|
540
|
+
if (parts.length) segments.push(`${g.title} ${parts.join(" + ")}`);
|
|
541
|
+
} else {
|
|
542
|
+
if (g.seen) segments.push(g.title); // capot / toit / coffre (sans position, sans xN)
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return [prestationName, ...segments].filter(Boolean).join(" , ");
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export function hasSecondaryApplication(
|
|
550
|
+
items: Operation[],
|
|
551
|
+
ids: number[],
|
|
552
|
+
needle: string,
|
|
553
|
+
caseInsensitive = false
|
|
554
|
+
): boolean {
|
|
555
|
+
const byId = new Map<number, Operation>(items.map((it) => [it.id, it]));
|
|
556
|
+
const target = caseInsensitive ? needle.toLowerCase() : needle;
|
|
557
|
+
|
|
558
|
+
for (const id of ids) {
|
|
559
|
+
const it = byId.get(id);
|
|
560
|
+
if (!it) continue;
|
|
561
|
+
const value = (it.secondaryApplication ?? "") as string;
|
|
562
|
+
if ((caseInsensitive ? value.toLowerCase() : value) === target) return true;
|
|
563
|
+
}
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const ORDER = ["AVG", "AVD", "ARG", "ARD", "AV", "AR", "G", "D"];
|
|
568
|
+
|
|
569
|
+
export function buildPositionsLabelFromShorts(
|
|
570
|
+
selectedOperationIds: number[],
|
|
571
|
+
allOperations: Operation[]
|
|
572
|
+
): string {
|
|
573
|
+
// index
|
|
574
|
+
const byId = new Map<number, Operation>(allOperations.map((o) => [o.id, o]));
|
|
575
|
+
|
|
576
|
+
// collecte des tokens (AV, AR, AVG, …)
|
|
577
|
+
const tokens = new Set<string>();
|
|
578
|
+
|
|
579
|
+
for (const id of selectedOperationIds) {
|
|
580
|
+
const appCode = byId.get(id)?.application?.toUpperCase();
|
|
581
|
+
if (!appCode) continue;
|
|
582
|
+
|
|
583
|
+
const appEnum = appCode as PartsApplicationType;
|
|
584
|
+
if (!appEnum) continue;
|
|
585
|
+
|
|
586
|
+
const short = getApplicationShortLabel(appEnum); // ex: "AV + AR" ou "AVG"
|
|
587
|
+
if (!short) continue;
|
|
588
|
+
|
|
589
|
+
// ⚠️ splitter par " + " (pas par caractère) pour éviter "a + v"
|
|
590
|
+
for (const t of short.split(" + ")) {
|
|
591
|
+
const s = t.trim();
|
|
592
|
+
if (s) tokens.add(s);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// tri esthétique
|
|
597
|
+
const parts = Array.from(tokens).sort(
|
|
598
|
+
(a, b) => ORDER.indexOf(a) - ORDER.indexOf(b)
|
|
599
|
+
);
|
|
600
|
+
return parts.join(" + ");
|
|
601
|
+
}
|
package/src/models/Garage.ts
CHANGED
|
@@ -70,6 +70,8 @@ export default class Garage {
|
|
|
70
70
|
companyRegistrationNumber?: string;
|
|
71
71
|
establishmentRegistrationNumber?: string;
|
|
72
72
|
reopeningDate?: Date;
|
|
73
|
+
targetMargin?: number;
|
|
74
|
+
onlyBilling?: boolean;
|
|
73
75
|
constructor(
|
|
74
76
|
id: string,
|
|
75
77
|
adminId: string,
|
|
@@ -104,7 +106,9 @@ export default class Garage {
|
|
|
104
106
|
companyRegistrationNumber?: string,
|
|
105
107
|
quoteRequestStart?: Date,
|
|
106
108
|
reopeningDate?: Date,
|
|
107
|
-
|
|
109
|
+
targetMargin?: number,
|
|
110
|
+
demoBillingActive?: boolean,
|
|
111
|
+
onlyBilling: boolean = false
|
|
108
112
|
) {
|
|
109
113
|
this.id = id;
|
|
110
114
|
this.adminId = adminId;
|
|
@@ -139,6 +143,8 @@ export default class Garage {
|
|
|
139
143
|
this.companyRegistrationNumber = companyRegistrationNumber;
|
|
140
144
|
this.establishmentRegistrationNumber = establishmentRegistrationNumber;
|
|
141
145
|
this.reopeningDate = reopeningDate;
|
|
146
|
+
this.targetMargin = targetMargin;
|
|
142
147
|
this.demoBillingActive = demoBillingActive;
|
|
148
|
+
this.onlyBilling = onlyBilling;
|
|
143
149
|
}
|
|
144
150
|
}
|
package/src/models/Operation.ts
CHANGED
|
@@ -1,23 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
PartsApplicationType,
|
|
3
|
+
SecondaryPartsApplicationType,
|
|
4
|
+
} from "../helpers/Enums";
|
|
2
5
|
import Product from "./Product";
|
|
3
6
|
|
|
4
7
|
export default class Operation {
|
|
8
|
+
// Properties
|
|
9
|
+
id: number;
|
|
10
|
+
code: string;
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
prestationId: number;
|
|
14
|
+
// Eventuelle application de l'opération (avant, arrière)
|
|
15
|
+
application?: PartsApplicationType;
|
|
16
|
+
products?: Product[];
|
|
17
|
+
secondaryApplication?: SecondaryPartsApplicationType;
|
|
5
18
|
|
|
6
|
-
|
|
7
|
-
id: number
|
|
8
|
-
code: string
|
|
9
|
-
name: string
|
|
10
|
-
description: string
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
constructor(
|
|
20
|
+
id: number,
|
|
21
|
+
code: string,
|
|
22
|
+
name: string,
|
|
23
|
+
description: string,
|
|
24
|
+
application: PartsApplicationType,
|
|
25
|
+
products: Product[],
|
|
26
|
+
prestationId: number,
|
|
27
|
+
secondaryApplication?: SecondaryPartsApplicationType
|
|
28
|
+
) {
|
|
29
|
+
this.prestationId = prestationId;
|
|
30
|
+
this.id = id;
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.name = name;
|
|
33
|
+
this.description = description;
|
|
34
|
+
this.application = application;
|
|
35
|
+
this.products = products;
|
|
36
|
+
this.secondaryApplication = secondaryApplication;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/models/Prestation.ts
CHANGED
|
@@ -2,57 +2,73 @@ import { PrestationState } from "../helpers/Enums";
|
|
|
2
2
|
import Operation from "./Operation";
|
|
3
3
|
|
|
4
4
|
export default class Prestation {
|
|
5
|
+
// Properties
|
|
6
|
+
id: number;
|
|
7
|
+
code: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
category: string;
|
|
11
|
+
/**
|
|
12
|
+
* Durée d'immobilisation pour cette prestation (en jours)
|
|
13
|
+
*/
|
|
14
|
+
downtime: number;
|
|
15
|
+
/**
|
|
16
|
+
* Délai avant prise de rendez-vous pour cette prestation (en jours)
|
|
17
|
+
*/
|
|
18
|
+
appointmentDelay: number;
|
|
19
|
+
/**
|
|
20
|
+
* Opérations inhérentes à cette prestation
|
|
21
|
+
*/
|
|
22
|
+
operations?: Operation[];
|
|
23
|
+
/**
|
|
24
|
+
* La notion de position (ou d'ordre) peut être utile à l'affichage d'une liste de prestation par exemple
|
|
25
|
+
*/
|
|
26
|
+
position: number;
|
|
27
|
+
availableOnline: boolean;
|
|
5
28
|
|
|
6
|
-
|
|
7
|
-
id: number;
|
|
8
|
-
code: string;
|
|
9
|
-
name: string;
|
|
10
|
-
description: string;
|
|
11
|
-
category: string;
|
|
12
|
-
/**
|
|
13
|
-
* Durée d'immobilisation pour cette prestation (en jours)
|
|
14
|
-
*/
|
|
15
|
-
downtime: number;
|
|
16
|
-
/**
|
|
17
|
-
* Délai avant prise de rendez-vous pour cette prestation (en jours)
|
|
18
|
-
*/
|
|
19
|
-
appointmentDelay: number;
|
|
20
|
-
/**
|
|
21
|
-
* Opérations inhérentes à cette prestation
|
|
22
|
-
*/
|
|
23
|
-
operations?: Operation[];
|
|
24
|
-
/**
|
|
25
|
-
* La notion de position (ou d'ordre) peut être utile à l'affichage d'une liste de prestation par exemple
|
|
26
|
-
*/
|
|
27
|
-
position: number;
|
|
28
|
-
availableOnline: boolean;
|
|
29
|
+
active: boolean;
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
state: PrestationState;
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
multipleApplication: boolean;
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
operationsVisible: boolean;
|
|
36
|
+
categoryCode: string;
|
|
37
|
+
secondaryMultipleApplication: boolean;
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
constructor(
|
|
40
|
+
id: number,
|
|
41
|
+
code: string,
|
|
42
|
+
name: string,
|
|
43
|
+
description: string,
|
|
44
|
+
category: string,
|
|
45
|
+
downtime: number,
|
|
46
|
+
appointmentDelay: number,
|
|
47
|
+
position: number,
|
|
48
|
+
active: boolean,
|
|
49
|
+
state: PrestationState,
|
|
50
|
+
multipleApplication: boolean,
|
|
51
|
+
operationsVisible: boolean,
|
|
52
|
+
categoryCode: string,
|
|
53
|
+
availableOnline: boolean,
|
|
54
|
+
operations?: Operation[],
|
|
55
|
+
secondaryMultipleApplication: boolean = false
|
|
56
|
+
) {
|
|
57
|
+
this.id = id;
|
|
58
|
+
this.code = code;
|
|
59
|
+
this.name = name;
|
|
60
|
+
this.description = description;
|
|
61
|
+
this.category = category;
|
|
62
|
+
this.downtime = downtime;
|
|
63
|
+
this.appointmentDelay = appointmentDelay;
|
|
64
|
+
this.position = position;
|
|
65
|
+
this.active = active;
|
|
66
|
+
this.multipleApplication = multipleApplication;
|
|
67
|
+
this.operationsVisible = operationsVisible;
|
|
68
|
+
this.operations = operations;
|
|
69
|
+
this.state = state;
|
|
70
|
+
this.categoryCode = categoryCode;
|
|
71
|
+
this.availableOnline = availableOnline;
|
|
72
|
+
this.secondaryMultipleApplication = secondaryMultipleApplication;
|
|
73
|
+
}
|
|
74
|
+
}
|
package/webpack.config.js
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
const HtmlWebpackPlugin = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const webpack = require(
|
|
1
|
+
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const webpack = require("webpack");
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
mode: "development", // ou 'production' ou 'none'
|
|
7
|
+
module: {
|
|
8
|
+
rules: [
|
|
9
|
+
{
|
|
10
|
+
test: /\.css$/i,
|
|
11
|
+
use: ["style-loader", "css-loader"],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
test: /\.tsx?$/,
|
|
15
|
+
use: "ts-loader",
|
|
16
|
+
exclude: /node_modules/,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
test: /\.(png|jpg|gif|svg)$/,
|
|
20
|
+
use: [
|
|
18
21
|
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
name: '[name].[ext]',
|
|
25
|
-
outputPath: 'images/',
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
],
|
|
22
|
+
loader: "file-loader",
|
|
23
|
+
options: {
|
|
24
|
+
name: "[name].[ext]",
|
|
25
|
+
outputPath: "images/",
|
|
26
|
+
},
|
|
29
27
|
},
|
|
30
|
-
],
|
|
28
|
+
],
|
|
31
29
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
resolve: {
|
|
33
|
+
extensions: [".tsx", ".ts", ".js"],
|
|
34
|
+
},
|
|
35
|
+
plugins: [
|
|
36
|
+
new HtmlWebpackPlugin({
|
|
37
|
+
template: path.resolve(__dirname, "public", "index.html"), // Chemin vers votre fichier HTML de base
|
|
38
|
+
}),
|
|
39
|
+
new webpack.DefinePlugin({
|
|
40
|
+
"process.env.REACT_APP_API_URL": JSON.stringify(
|
|
41
|
+
process.env.REACT_APP_API_URL
|
|
42
|
+
),
|
|
43
|
+
"process.env.REACT_APP_MOVALIB_APP_URL": JSON.stringify(
|
|
44
|
+
process.env.REACT_APP_MOVALIB_APP_URL
|
|
45
|
+
),
|
|
46
|
+
}),
|
|
47
|
+
],
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
open: true,
|
|
51
|
-
port: 8080, // ou un autre port si nécessaire
|
|
52
|
-
},
|
|
53
|
-
};
|
|
49
|
+
devServer: {
|
|
50
|
+
open: true,
|
|
51
|
+
port: 8080, // ou un autre port si nécessaire
|
|
52
|
+
},
|
|
53
|
+
};
|