@movalib/movalib-commons 1.0.67 → 1.0.68
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 +6 -0
- package/.env.production +4 -0
- package/devIndex.tsx +119 -10
- package/dist/devIndex.js +101 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/src/MovaLogin.js +2 -2
- package/dist/src/MovaSignUp.js +1 -1
- package/dist/src/QRCode.d.ts +1 -0
- package/dist/src/QRCode.js +13 -12
- package/dist/src/ScheduleFields.d.ts +24 -0
- package/dist/src/ScheduleFields.js +202 -0
- package/dist/src/helpers/ApiHelper.d.ts +20 -0
- package/dist/src/helpers/ApiHelper.js +74 -0
- package/dist/src/helpers/CookieUtils.d.ts +6 -0
- package/dist/src/helpers/CookieUtils.js +28 -0
- package/dist/src/helpers/Enums.d.ts +8 -1
- package/dist/src/helpers/Enums.js +10 -2
- package/dist/src/helpers/Tools.d.ts +6 -2
- package/dist/src/helpers/Tools.js +20 -7
- package/dist/src/services/AuthenticationService.d.ts +6 -0
- package/dist/src/services/AuthenticationService.js +106 -0
- package/dist/src/services/GarageService.d.ts +5 -0
- package/dist/src/services/GarageService.js +16 -0
- package/dist/src/services/UserService.d.ts +10 -0
- package/dist/src/services/UserService.js +76 -0
- package/index.ts +3 -1
- package/package.json +9 -8
- package/src/MovaLogin.tsx +2 -2
- package/src/MovaSignUp.tsx +1 -1
- package/src/QRCode.css +4 -0
- package/src/QRCode.tsx +12 -13
- package/src/ScheduleFields.tsx +301 -0
- package/src/helpers/ApiHelper.ts +95 -0
- package/src/helpers/CookieUtils.ts +23 -0
- package/src/helpers/Enums.ts +8 -1
- package/src/helpers/Tools.ts +20 -2
- package/src/services/AuthenticationService.ts +68 -0
- package/src/services/GarageService.ts +14 -0
- package/src/services/UserService.ts +25 -0
- package/tsconfig.json +1 -1
- package/webpack.config.js +9 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
var ApiHelper_1 = require("../helpers/ApiHelper");
|
|
43
|
+
var Enums_1 = require("../helpers/Enums");
|
|
44
|
+
var Logger_1 = __importDefault(require("../helpers/Logger"));
|
|
45
|
+
var UserService = /** @class */ (function () {
|
|
46
|
+
function UserService() {
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @param email
|
|
50
|
+
* @param password
|
|
51
|
+
* @returns
|
|
52
|
+
*/
|
|
53
|
+
UserService.getCurrentUser = function () {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
55
|
+
var error_1;
|
|
56
|
+
return __generator(this, function (_a) {
|
|
57
|
+
switch (_a.label) {
|
|
58
|
+
case 0:
|
|
59
|
+
_a.trys.push([0, 2, , 3]);
|
|
60
|
+
return [4 /*yield*/, (0, ApiHelper_1.request)({
|
|
61
|
+
url: "".concat(ApiHelper_1.API_BASE_URL, "/user/me"),
|
|
62
|
+
method: Enums_1.APIMethod.GET
|
|
63
|
+
})];
|
|
64
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
65
|
+
case 2:
|
|
66
|
+
error_1 = _a.sent();
|
|
67
|
+
Logger_1.default.error('Error occurred during getting user:', error_1);
|
|
68
|
+
return [2 /*return*/, Promise.reject(error_1)];
|
|
69
|
+
case 3: return [2 /*return*/];
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
return UserService;
|
|
75
|
+
}());
|
|
76
|
+
exports.default = UserService;
|
package/index.ts
CHANGED
|
@@ -30,11 +30,13 @@ export { default as Event } from './src/models/Event';
|
|
|
30
30
|
export { default as VehicleTire } from './src/models/VehicleTire';
|
|
31
31
|
|
|
32
32
|
// Export des types
|
|
33
|
+
export type { APIRequest, APIResponse } from './src/helpers/ApiHelper';
|
|
33
34
|
export type { MovaFormField, MovaLoginForm, MovaUserSignUpForm, MovaInterval, OriginReference,
|
|
34
35
|
MovaVehicleForm } from './src/helpers/Types';
|
|
35
36
|
|
|
36
37
|
// Export des méthodes utilitaires
|
|
37
|
-
export {
|
|
38
|
+
export { readCookie, deleteCookie } from './src/helpers/CookieUtils';
|
|
39
|
+
export { validateField, formatVehicleTire, formatFrenchVehiclePlate, isEmpty } from './src/helpers/Tools';
|
|
38
40
|
export { validatePhoneNumber, validateText, validateEmail } from './src/helpers/Validator';
|
|
39
41
|
export { formatDateByCountryCode, getLongFormattedDateTime, capitalizeFirstLetter } from './src/helpers/DateUtils';
|
|
40
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@movalib/movalib-commons",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.68",
|
|
4
4
|
"description": "Bibliothèque d'objets communs à l'ensemble des projets React de Movalib",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,21 +20,21 @@
|
|
|
20
20
|
"@emotion/styled": "^11.11.0",
|
|
21
21
|
"@mui/icons-material": "^5.11.16",
|
|
22
22
|
"@mui/lab": "^5.0.0-alpha.134",
|
|
23
|
-
"@mui/x-date-pickers": "^6.9.1",
|
|
24
23
|
"@mui/material": "^5.13.5",
|
|
24
|
+
"@mui/x-date-pickers": "^6.9.1",
|
|
25
25
|
"@types/react": "^18.2.22",
|
|
26
26
|
"@types/react-dom": "^18.2.7",
|
|
27
|
-
"file-loader": "^6.2.0",
|
|
28
|
-
"@types/node": "^16.18.53",
|
|
29
27
|
"@types/react-router-dom": "^5.3.0",
|
|
28
|
+
"date-fns": "^2.29.3",
|
|
29
|
+
"date-fns-tz": "^2.0.0",
|
|
30
|
+
"file-loader": "^6.2.0",
|
|
31
|
+
"js-cookie": "^3.0.5",
|
|
32
|
+
"qr-code-styling": "^1.4.4",
|
|
30
33
|
"react": "^18.2.0",
|
|
31
34
|
"react-dom": "^18.2.0",
|
|
32
35
|
"react-router-dom": "^5.1.2",
|
|
33
36
|
"react-scripts": "5.0.1",
|
|
34
|
-
"
|
|
35
|
-
"typescript": "^4.9.5",
|
|
36
|
-
"date-fns": "^2.29.3",
|
|
37
|
-
"date-fns-tz": "^2.0.0"
|
|
37
|
+
"typescript": "^4.9.5"
|
|
38
38
|
},
|
|
39
39
|
"eslintConfig": {
|
|
40
40
|
"extends": [
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"@types/js-cookie": "^3.0.4",
|
|
45
46
|
"html-webpack-plugin": "^5.5.3",
|
|
46
47
|
"ts-loader": "^9.4.4",
|
|
47
48
|
"webpack-cli": "^5.1.4"
|
package/src/MovaLogin.tsx
CHANGED
|
@@ -103,7 +103,7 @@ const MovaLogin: FunctionComponent<MovaLoginProps> = ({ loading, movaAppType, on
|
|
|
103
103
|
const getMovaLogo = () => {
|
|
104
104
|
|
|
105
105
|
return movaAppType === MovaAppType.GARAGE ? LogoProLarge :
|
|
106
|
-
movaAppType === MovaAppType.
|
|
106
|
+
movaAppType === MovaAppType.INDIVIDUAL ? LogoLarge :
|
|
107
107
|
movaAppType === MovaAppType.ADMIN ? LogoLarge : LogoLarge;
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -267,7 +267,7 @@ const MovaLogin: FunctionComponent<MovaLoginProps> = ({ loading, movaAppType, on
|
|
|
267
267
|
Mot de passe oublié ?
|
|
268
268
|
</Link>
|
|
269
269
|
</Grid>
|
|
270
|
-
{movaAppType === MovaAppType.
|
|
270
|
+
{movaAppType === MovaAppType.INDIVIDUAL && <Grid item>
|
|
271
271
|
<Link variant="body2" color="text.secondary" onClick={(e) => handleOnClickSignUp(e)}>
|
|
272
272
|
Créer mon compte
|
|
273
273
|
</Link>
|
package/src/MovaSignUp.tsx
CHANGED
|
@@ -145,7 +145,7 @@ const MovaLogin: FunctionComponent<MovaSignUpProps> = ({ loading, movaAppType, o
|
|
|
145
145
|
const getMovaLogo = () => {
|
|
146
146
|
|
|
147
147
|
return movaAppType === MovaAppType.GARAGE ? LogoProLarge :
|
|
148
|
-
movaAppType === MovaAppType.
|
|
148
|
+
movaAppType === MovaAppType.INDIVIDUAL ? LogoLarge :
|
|
149
149
|
movaAppType === MovaAppType.ADMIN ? LogoLarge : LogoLarge;
|
|
150
150
|
}
|
|
151
151
|
|
package/src/QRCode.css
ADDED
package/src/QRCode.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import QRCodeStyling, {
|
|
|
12
12
|
Extension,
|
|
13
13
|
Options
|
|
14
14
|
} from "qr-code-styling";
|
|
15
|
+
import './QRCode.css';
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
interface QRCodeProps {
|
|
@@ -19,18 +20,14 @@ interface QRCodeProps {
|
|
|
19
20
|
showDownload?:boolean
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
23
|
const styles = {
|
|
24
24
|
inputWrapper: {
|
|
25
25
|
margin: "20px 0",
|
|
26
26
|
display: "flex",
|
|
27
27
|
alignItems: 'center',
|
|
28
28
|
justifyContent: "center",
|
|
29
|
-
width: '
|
|
30
|
-
|
|
31
|
-
inputBox: {
|
|
32
|
-
flexGrow: 1,
|
|
33
|
-
marginRight: 20
|
|
29
|
+
width: '300px',
|
|
30
|
+
height: '300px'
|
|
34
31
|
}
|
|
35
32
|
};
|
|
36
33
|
|
|
@@ -42,9 +39,9 @@ const QRCode: FC<QRCodeProps> = ({ data, showDownload=false }) => {
|
|
|
42
39
|
const defaultData:string = 'https://movalib.com';
|
|
43
40
|
|
|
44
41
|
const [options, setOptions] = useState<Options>({
|
|
45
|
-
width:
|
|
46
|
-
height:
|
|
47
|
-
type: '
|
|
42
|
+
width: 600,
|
|
43
|
+
height: 600,
|
|
44
|
+
type: 'canvas' as DrawType,
|
|
48
45
|
data: data ?? defaultData,
|
|
49
46
|
image: QRCodeImage,
|
|
50
47
|
margin: 10,
|
|
@@ -56,8 +53,7 @@ const QRCode: FC<QRCodeProps> = ({ data, showDownload=false }) => {
|
|
|
56
53
|
imageOptions: {
|
|
57
54
|
hideBackgroundDots: true,
|
|
58
55
|
imageSize: 0.4,
|
|
59
|
-
margin: 15
|
|
60
|
-
crossOrigin: 'anonymous',
|
|
56
|
+
margin: 15
|
|
61
57
|
},
|
|
62
58
|
dotsOptions: {
|
|
63
59
|
color: darken(theme.palette.primary.main, 0.3),
|
|
@@ -126,8 +122,11 @@ const QRCode: FC<QRCodeProps> = ({ data, showDownload=false }) => {
|
|
|
126
122
|
};
|
|
127
123
|
|
|
128
124
|
return (
|
|
129
|
-
<Box
|
|
130
|
-
|
|
125
|
+
<Box sx={{
|
|
126
|
+
display: 'flex',
|
|
127
|
+
flexDirection: 'column',
|
|
128
|
+
alignItems: 'center' }} >
|
|
129
|
+
<div ref={ref} style={styles.inputWrapper} className='qr-code' />
|
|
131
130
|
{/* <select onChange={onExtensionChange} value={fileExt}>
|
|
132
131
|
<option value="svg">SVG</option>
|
|
133
132
|
<option value="png">PNG</option>
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { FunctionComponent, useEffect, useState } from 'react';
|
|
3
|
+
import { Checkbox, FormControlLabel, Grid, IconButton, FormHelperText, FormControl, RadioGroup, Radio } from '@mui/material';
|
|
4
|
+
import AddIcon from '@mui/icons-material/AddRounded';
|
|
5
|
+
import DeleteIcon from '@mui/icons-material/DeleteRounded';
|
|
6
|
+
import { TimePicker } from '@mui/x-date-pickers';
|
|
7
|
+
import theme from '../theme'; // Import du thème personnalisé
|
|
8
|
+
import Schedule from './models/Schedule';
|
|
9
|
+
import { DayOfWeek } from './helpers/Enums';
|
|
10
|
+
import Logger from './helpers/Logger';
|
|
11
|
+
import { flexCenter } from './helpers/Tools';
|
|
12
|
+
|
|
13
|
+
export const FR_WEEK_DAYS: string[] = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'];
|
|
14
|
+
|
|
15
|
+
type DayInterval = {
|
|
16
|
+
startTime: Date | '' | null,
|
|
17
|
+
endTime: Date | '' | null,
|
|
18
|
+
countryCode: string,
|
|
19
|
+
error: string | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type DaySchedule = {
|
|
23
|
+
day: DayOfWeek,
|
|
24
|
+
checked: boolean,
|
|
25
|
+
allDay: boolean,
|
|
26
|
+
intervals: DayInterval[],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const initialSchedules: DaySchedule[] = [
|
|
30
|
+
{day: DayOfWeek.MONDAY, checked: false, allDay: false, intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
|
|
31
|
+
{day: DayOfWeek.TUESDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
|
|
32
|
+
{day: DayOfWeek.WEDNESDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
|
|
33
|
+
{day: DayOfWeek.THURSDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
|
|
34
|
+
{day: DayOfWeek.FRIDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
|
|
35
|
+
{day: DayOfWeek.SATURDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
|
|
36
|
+
{day: DayOfWeek.SUNDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
interface ScheduleFieldsProps {
|
|
40
|
+
timePickerStep: number,
|
|
41
|
+
size: 'small' | 'medium',
|
|
42
|
+
onChange: (schedules: DaySchedule[]) => void,
|
|
43
|
+
schedules?: Schedule[] | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ScheduleFields: FunctionComponent<ScheduleFieldsProps> = ({ timePickerStep, schedules, size, onChange }) => {
|
|
47
|
+
|
|
48
|
+
const mergeSchedules = (apiSchedules: Schedule[]): DaySchedule[] => {
|
|
49
|
+
// Créez une copie profonde du tableau initialSchedules pour éviter toute mutation accidentelle
|
|
50
|
+
const mergedSchedules = JSON.parse(JSON.stringify(initialSchedules));
|
|
51
|
+
|
|
52
|
+
// Parcourez chaque élément de apiSchedules et mettez à jour updatedSchedules
|
|
53
|
+
const today = new Date().toISOString().substring(0, 10);
|
|
54
|
+
|
|
55
|
+
for (const apiSchedule of apiSchedules) {
|
|
56
|
+
const dayIndex = mergedSchedules.findIndex((schedule: { day: DayOfWeek; }) => schedule.day === apiSchedule.dayOfWeek);
|
|
57
|
+
if (dayIndex !== -1) {
|
|
58
|
+
mergedSchedules[dayIndex].checked = apiSchedule.intervals.length > 0;
|
|
59
|
+
mergedSchedules[dayIndex].intervals = apiSchedule.intervals.map(interval => ({
|
|
60
|
+
...interval,
|
|
61
|
+
// On injecte une date fictive même si nous n'utilisons que l'heure, le Timepicker attend une Date JS Valide
|
|
62
|
+
startTime: new Date(`${today}T${interval.startTime!}`),
|
|
63
|
+
endTime: new Date(`${today}T${interval.endTime!}`),
|
|
64
|
+
error: null, // Réinitialisez l'erreur car les données sont maintenant mises à jour
|
|
65
|
+
}));
|
|
66
|
+
mergedSchedules[dayIndex].allDay = apiSchedule.isAllWorkingDay;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
Logger.info(mergedSchedules);
|
|
71
|
+
|
|
72
|
+
return mergedSchedules;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Cette fonction renvoie une liste de DaySchedule depuis une liste de Schedule
|
|
77
|
+
* @returns
|
|
78
|
+
*/
|
|
79
|
+
const initializeFromSchedules = () => {
|
|
80
|
+
|
|
81
|
+
if (schedules && schedules.length > 0 ){
|
|
82
|
+
|
|
83
|
+
return schedules.map(s => {
|
|
84
|
+
const iList: DayInterval[] = s.intervals.map(i => ({
|
|
85
|
+
startTime: new Date(i.startTime!),
|
|
86
|
+
endTime: new Date(i.endTime!),
|
|
87
|
+
countryCode: i.countryCode,
|
|
88
|
+
error: null,
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
day: s.dayOfWeek,
|
|
93
|
+
checked: iList.length > 0, // le checked est à true si il y a au moins un interval de saisi
|
|
94
|
+
intervals: iList,
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Si des schedules sont transmises on initialise un nouveau tableau de DaySchedule[]
|
|
102
|
+
const [schedule, setSchedule] = useState<DaySchedule[]>(initialSchedules);
|
|
103
|
+
|
|
104
|
+
// Chargement initial des données du garage
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
|
|
107
|
+
if(schedules && schedules.length > 0){
|
|
108
|
+
setSchedule(mergeSchedules(schedules));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
}, [schedules]); // Exécute le hook à chaque fois que les schedules transmis en props changent
|
|
112
|
+
|
|
113
|
+
const handleDayChecked = (dayIndex: number) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
114
|
+
const newSchedule = [...schedule];
|
|
115
|
+
newSchedule[dayIndex].checked = event.target.checked;
|
|
116
|
+
// On réinitialise aussi les intervals
|
|
117
|
+
newSchedule[dayIndex].intervals = [{startTime: null, endTime: null, countryCode: 'fr', error: null}];
|
|
118
|
+
onChange(newSchedule);
|
|
119
|
+
setSchedule(newSchedule);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleAllDayChecked = (e: React.ChangeEvent<HTMLInputElement>, value: string, dayIndex: number) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
|
|
125
|
+
const newSchedule = [...schedule];
|
|
126
|
+
newSchedule[dayIndex].allDay = event.target.checked;
|
|
127
|
+
onChange(newSchedule);
|
|
128
|
+
setSchedule(newSchedule);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const validateInterval = (start: Date | '', end: Date | ''): string | null => {
|
|
132
|
+
if (start instanceof Date && end instanceof Date && start.getTime() > end.getTime()) {
|
|
133
|
+
return "L'heure de début doit être antérieure à l'heure de fin";
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const validateIntervals = (intervals: DayInterval[]): string | null => {
|
|
139
|
+
|
|
140
|
+
// On tri les intervals par date de début
|
|
141
|
+
const sortedIntervals = [...intervals].sort((a, b) =>
|
|
142
|
+
a.startTime && b.startTime ? a.startTime.toISOString().localeCompare(b.startTime.toISOString()) : 0);
|
|
143
|
+
|
|
144
|
+
console.log(sortedIntervals)
|
|
145
|
+
for (let i = 0; i < sortedIntervals.length; i++) {
|
|
146
|
+
if (
|
|
147
|
+
sortedIntervals[i].startTime?.toString().localeCompare(sortedIntervals[i].endTime?.toString()!)! >= 0 ||
|
|
148
|
+
(i < sortedIntervals.length - 1 &&
|
|
149
|
+
sortedIntervals[i].endTime?.toString().localeCompare(sortedIntervals[i + 1].startTime?.toString()!)! > 0)
|
|
150
|
+
) {
|
|
151
|
+
return 'Attention vos intervales se chevauchent'; // start time is after or equal to end time, or end time of current interval is after start time of next interval
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null; // all intervals are valid and there are no overlapping intervals
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
const handleIntervalChange = (dayIndex: number, intervalIndex: number, type: 'startTime' | 'endTime') =>
|
|
159
|
+
(newValue: "" | Date | null) => {
|
|
160
|
+
|
|
161
|
+
const newSchedule = [...schedule];
|
|
162
|
+
|
|
163
|
+
newSchedule[dayIndex].intervals[intervalIndex][type] = newValue!;
|
|
164
|
+
|
|
165
|
+
newSchedule[dayIndex].intervals[intervalIndex].error = validateInterval(
|
|
166
|
+
newSchedule[dayIndex].intervals[intervalIndex].startTime!,
|
|
167
|
+
newSchedule[dayIndex].intervals[intervalIndex].endTime!
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
//Contrôle le chevauchement d'intervales
|
|
171
|
+
if(type === 'endTime' && intervalIndex > 0 && !newSchedule[dayIndex].intervals[intervalIndex].error){
|
|
172
|
+
console.log(dayIndex);
|
|
173
|
+
console.log(newSchedule[dayIndex].intervals);
|
|
174
|
+
newSchedule[dayIndex].intervals[intervalIndex].error = validateIntervals(
|
|
175
|
+
newSchedule[dayIndex].intervals);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
setSchedule(newSchedule);
|
|
179
|
+
onChange(newSchedule);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const addInterval = (dayIndex: number) => () => {
|
|
183
|
+
Logger.info(dayIndex);
|
|
184
|
+
|
|
185
|
+
const newSchedule = [...schedule];
|
|
186
|
+
newSchedule[dayIndex].intervals.push({startTime: null, endTime: null, countryCode: 'fr', error: null});
|
|
187
|
+
Logger.info(newSchedule);
|
|
188
|
+
setSchedule(newSchedule);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const deleteInterval = (dayIndex: number, intervalIndex: number) => () => {
|
|
192
|
+
const newSchedule = [...schedule];
|
|
193
|
+
newSchedule[dayIndex].intervals.splice(intervalIndex, 1);
|
|
194
|
+
onChange(newSchedule);
|
|
195
|
+
setSchedule(newSchedule);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const getDayOfWeekIndex = (day: DayOfWeek) => {
|
|
199
|
+
if(day){
|
|
200
|
+
// Convertir les valeurs de l'énumération en un tableau
|
|
201
|
+
const daysArray = Object.values(DayOfWeek);
|
|
202
|
+
return daysArray.indexOf(day);
|
|
203
|
+
}
|
|
204
|
+
return -1;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<Grid container spacing={1} sx={{ minWidth: '650px', maxWidth: '800px'}} >
|
|
209
|
+
{schedule.map((daySchedule, dayIndex) => (
|
|
210
|
+
<>
|
|
211
|
+
<Grid item xs={1} />
|
|
212
|
+
|
|
213
|
+
<Grid item xs={2} key={dayIndex+1}>
|
|
214
|
+
<FormControlLabel
|
|
215
|
+
control={
|
|
216
|
+
<Checkbox
|
|
217
|
+
checked={daySchedule.checked}
|
|
218
|
+
onChange={handleDayChecked(dayIndex)}
|
|
219
|
+
/>
|
|
220
|
+
}
|
|
221
|
+
label={FR_WEEK_DAYS[getDayOfWeekIndex(daySchedule.day)]}
|
|
222
|
+
/>
|
|
223
|
+
</Grid>
|
|
224
|
+
|
|
225
|
+
<Grid item xs={7} key={(dayIndex+1)*Math.random()}>
|
|
226
|
+
{daySchedule.intervals.map((interval, intervalIndex) => (
|
|
227
|
+
<Grid container key={intervalIndex+1}
|
|
228
|
+
style={{ paddingTop: intervalIndex > 0 ? theme.spacing(1) : 0 }}>
|
|
229
|
+
<Grid item xs={6} key={(intervalIndex+1)*Math.random()} sx={{ textAlign: 'center' }}>
|
|
230
|
+
<TimePicker
|
|
231
|
+
views={['hours', 'minutes']}
|
|
232
|
+
minutesStep={timePickerStep ?? 30} // Valeur par défaut de 30 minutes au cas ou
|
|
233
|
+
disabled={!daySchedule.checked}
|
|
234
|
+
value={interval.startTime}
|
|
235
|
+
formatDensity='dense'
|
|
236
|
+
onChange={handleIntervalChange(dayIndex, intervalIndex, 'startTime')}
|
|
237
|
+
slotProps={{
|
|
238
|
+
textField: {
|
|
239
|
+
size: 'small',
|
|
240
|
+
sx: {
|
|
241
|
+
width:'150px',
|
|
242
|
+
minWidth:'100px',
|
|
243
|
+
padding: 0,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
actionBar: {
|
|
247
|
+
sx: { display: 'none' },
|
|
248
|
+
},
|
|
249
|
+
}}
|
|
250
|
+
/>
|
|
251
|
+
</Grid>
|
|
252
|
+
<Grid item xs={6} key={(intervalIndex+1)*Math.random()} sx={{ display: 'contents'}}>
|
|
253
|
+
<TimePicker
|
|
254
|
+
minutesStep={timePickerStep ?? 30} // Valeur par défaut de 30 minutes au cas ou
|
|
255
|
+
disabled={!(interval.startTime instanceof Date)}
|
|
256
|
+
value={interval.endTime}
|
|
257
|
+
formatDensity='dense'
|
|
258
|
+
onChange={handleIntervalChange(dayIndex, intervalIndex, 'endTime')}
|
|
259
|
+
//onClose={handleIntervalClose}
|
|
260
|
+
closeOnSelect={false}
|
|
261
|
+
slotProps={{
|
|
262
|
+
textField: {
|
|
263
|
+
size: 'small',
|
|
264
|
+
sx: {
|
|
265
|
+
width:'150px',
|
|
266
|
+
minWidth:'100px',
|
|
267
|
+
padding: 0,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
actionBar: {
|
|
271
|
+
sx: { display: 'none' },
|
|
272
|
+
},
|
|
273
|
+
}}
|
|
274
|
+
/>
|
|
275
|
+
{intervalIndex > 0 &&
|
|
276
|
+
<IconButton onClick={deleteInterval(dayIndex, intervalIndex)} disabled={!daySchedule.checked} sx={{ ml: 2 }}>
|
|
277
|
+
<DeleteIcon />
|
|
278
|
+
</IconButton>
|
|
279
|
+
}
|
|
280
|
+
</Grid>
|
|
281
|
+
<Grid item xs={12} key={(intervalIndex+1)*Math.random()} >
|
|
282
|
+
{interval.error && <FormHelperText error sx={{mb:1}}>{interval.error}</FormHelperText>}
|
|
283
|
+
</Grid>
|
|
284
|
+
</Grid>
|
|
285
|
+
))}
|
|
286
|
+
</Grid>
|
|
287
|
+
|
|
288
|
+
<Grid item xs={1} key={(dayIndex+1)*Math.random()} sx={{ pl: 0 }}>
|
|
289
|
+
<IconButton onClick={addInterval(dayIndex)} disabled={!daySchedule.checked}>
|
|
290
|
+
<AddIcon />
|
|
291
|
+
</IconButton>
|
|
292
|
+
</Grid>
|
|
293
|
+
|
|
294
|
+
<Grid item xs={1} />
|
|
295
|
+
</>
|
|
296
|
+
))}
|
|
297
|
+
</Grid>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export default ScheduleFields;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { APIMethod } from "./Enums";
|
|
2
|
+
import { COOKIE_GARAGE_TOKEN, readCookie } from "./CookieUtils";
|
|
3
|
+
import Logger from "./Logger";
|
|
4
|
+
|
|
5
|
+
export const API_BASE_URL = process.env.REACT_APP_API_URL ?? 'https://localhost:8443/api';
|
|
6
|
+
|
|
7
|
+
export type APIResponse<T> = {
|
|
8
|
+
success: boolean;
|
|
9
|
+
data?: T;
|
|
10
|
+
error?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type APIRequest = {
|
|
14
|
+
url: string,
|
|
15
|
+
method: APIMethod,
|
|
16
|
+
body?: string | FormData
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function handleError(error: Error): APIResponse<null> {
|
|
20
|
+
let msg = error.message.includes('fetch') ? "Connexion impossible" : error.message;
|
|
21
|
+
return {
|
|
22
|
+
success: false,
|
|
23
|
+
error: msg
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleResponse(response: Response): Promise<APIResponse<any>> {
|
|
28
|
+
|
|
29
|
+
const contentType = response.headers.get("content-type");
|
|
30
|
+
const isJson = contentType && contentType.includes("application/json");
|
|
31
|
+
|
|
32
|
+
const dataPromise = isJson ? response.json() : response.text();
|
|
33
|
+
|
|
34
|
+
return dataPromise.then(data => {
|
|
35
|
+
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
|
|
38
|
+
Logger.error(data);
|
|
39
|
+
|
|
40
|
+
let errorMsg: string;
|
|
41
|
+
|
|
42
|
+
switch(response.status){
|
|
43
|
+
case 403:
|
|
44
|
+
errorMsg = 'Accès non autorisé (403)'; break;
|
|
45
|
+
case 404:
|
|
46
|
+
errorMsg = 'La ressource demandée est introuvable (404)'; break;
|
|
47
|
+
case 500:
|
|
48
|
+
errorMsg = 'Une erreur interne du serveur est survenue (500)'; break;
|
|
49
|
+
default:
|
|
50
|
+
errorMsg = (typeof data === 'string') ? data : `Une erreur est survenue (${response.status})`; break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { success: false, error: errorMsg };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { success: true, data };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* ATTENTION : cela signifie que toutes les requêtes du Front seront de type "preflight"
|
|
63
|
+
* Il faut penser à configurer l'API pour autoriser les headers
|
|
64
|
+
* @param options
|
|
65
|
+
* @param dispatch
|
|
66
|
+
* @returns
|
|
67
|
+
*/
|
|
68
|
+
export const request = (options:APIRequest): Promise<APIResponse<any>> => {
|
|
69
|
+
|
|
70
|
+
const headers = new Headers();
|
|
71
|
+
|
|
72
|
+
// Si le corps est de type FormData, il ne faut pas définir le content-type manuellement (le navigateur prend le relai est injecte un boundary (délimiteur)
|
|
73
|
+
if (!(options.body instanceof FormData)) {
|
|
74
|
+
headers.append('Content-Type', 'application/json');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Récupération du TOKEN depuis les cookies et intégration au header
|
|
78
|
+
if(readCookie(COOKIE_GARAGE_TOKEN)) {
|
|
79
|
+
headers.append('Authorization', 'Bearer ' + readCookie(COOKIE_GARAGE_TOKEN))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// On intègre les headers aux options
|
|
83
|
+
const defaults = {headers: headers};
|
|
84
|
+
options = Object.assign({}, defaults, options);
|
|
85
|
+
|
|
86
|
+
Logger.info(options);
|
|
87
|
+
|
|
88
|
+
// Appel API
|
|
89
|
+
return fetch(options.url, options)
|
|
90
|
+
.then(handleResponse)
|
|
91
|
+
.catch(error => {
|
|
92
|
+
Logger.error('There has been a problem with your fetch operation : ', error.message);
|
|
93
|
+
return handleError(error);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Cookies from "js-cookie";
|
|
2
|
+
|
|
3
|
+
export const COOKIE_GARAGE_TOKEN: string = 'movalibGarageToken';
|
|
4
|
+
export const COOKIE_INDIVIDUAL_TOKEN: string = 'movalibIndividualToken';
|
|
5
|
+
export const COOKIE_DEFAULT_EXPIRES: number = 7;
|
|
6
|
+
|
|
7
|
+
export const createCookie = (name:string, value:string) => {
|
|
8
|
+
if(name && value){
|
|
9
|
+
Cookies.set(name, value, { expires: COOKIE_DEFAULT_EXPIRES, sameSite: 'None', secure: true });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const deleteCookie = (name:string) => {
|
|
14
|
+
if(name){
|
|
15
|
+
Cookies.remove(name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const readCookie = (name:string) => {
|
|
20
|
+
if(name){
|
|
21
|
+
return Cookies.get(name);
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/helpers/Enums.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export enum APIMethod {
|
|
2
|
+
GET = 'GET',
|
|
3
|
+
PUT = 'PUT',
|
|
4
|
+
PATCH = 'PATCH',
|
|
5
|
+
POST = 'POST',
|
|
6
|
+
DELETE = 'DELETE'
|
|
7
|
+
}
|
|
1
8
|
|
|
2
9
|
export enum DateFormatTypes {
|
|
3
10
|
SHORT_FORMAT_DATE = 'dd/MM/yyyy',
|
|
@@ -81,7 +88,7 @@ export enum MovaAppType {
|
|
|
81
88
|
/**
|
|
82
89
|
* Application Utilisateur (automobiliste)
|
|
83
90
|
*/
|
|
84
|
-
|
|
91
|
+
INDIVIDUAL = "INDIVIDUAL",
|
|
85
92
|
/**
|
|
86
93
|
* Application Administrateur (MovaTeam)
|
|
87
94
|
*/
|