Add an accommodations reservations module #188
72
geneit_app/package-lock.json
generated
72
geneit_app/package-lock.json
generated
@ -23,6 +23,7 @@
|
|||||||
"@mui/lab": "^5.0.0-alpha.140",
|
"@mui/lab": "^5.0.0-alpha.140",
|
||||||
"@mui/material": "^5.15.17",
|
"@mui/material": "^5.15.17",
|
||||||
"@mui/x-data-grid": "^7.1.1",
|
"@mui/x-data-grid": "^7.1.1",
|
||||||
|
"@mui/x-date-pickers": "^7.7.0",
|
||||||
"@mui/x-tree-view": "^7.4.0",
|
"@mui/x-tree-view": "^7.4.0",
|
||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
@ -32,6 +33,7 @@
|
|||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"date-and-time": "^3.2.0",
|
"date-and-time": "^3.2.0",
|
||||||
|
"dayjs": "^1.11.11",
|
||||||
"email-validator": "^2.0.4",
|
"email-validator": "^2.0.4",
|
||||||
"filesize": "^10.1.2",
|
"filesize": "^10.1.2",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
@ -1579,6 +1581,71 @@
|
|||||||
"react-dom": "^17.0.0 || ^18.0.0"
|
"react-dom": "^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/x-date-pickers": {
|
||||||
|
"version": "7.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.7.0.tgz",
|
||||||
|
"integrity": "sha512-huyoA22Vi8iCkee6ro0sX7CcFIcPV/Fl7ZGWwaQC8PTAheXhz823DjMYAiwRU/imF+UFYfUInWQ4XZCIkM+2Dw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.24.7",
|
||||||
|
"@mui/base": "^5.0.0-beta.40",
|
||||||
|
"@mui/system": "^5.15.15",
|
||||||
|
"@mui/utils": "^5.15.14",
|
||||||
|
"@types/react-transition-group": "^4.4.10",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.9.0",
|
||||||
|
"@emotion/styled": "^11.8.1",
|
||||||
|
"@mui/material": "^5.15.14",
|
||||||
|
"date-fns": "^2.25.0 || ^3.2.0",
|
||||||
|
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0",
|
||||||
|
"dayjs": "^1.10.7",
|
||||||
|
"luxon": "^3.0.2",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-hijri": "^2.1.2",
|
||||||
|
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns-jalali": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"luxon": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-hijri": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-jalaali": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mui/x-tree-view": {
|
"node_modules/@mui/x-tree-view": {
|
||||||
"version": "7.7.0",
|
"version": "7.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-7.7.0.tgz",
|
||||||
@ -2552,6 +2619,11 @@
|
|||||||
"integrity": "sha512-UguWfh9LkUecVrGSE0B7SpAnGRMPATmpwSoSij24/lDnwET3A641abfDBD/TdL0T+E04f8NWlbMkD9BscVvIZg==",
|
"integrity": "sha512-UguWfh9LkUecVrGSE0B7SpAnGRMPATmpwSoSij24/lDnwET3A641abfDBD/TdL0T+E04f8NWlbMkD9BscVvIZg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
|
||||||
|
"integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"@mui/lab": "^5.0.0-alpha.140",
|
"@mui/lab": "^5.0.0-alpha.140",
|
||||||
"@mui/material": "^5.15.17",
|
"@mui/material": "^5.15.17",
|
||||||
"@mui/x-data-grid": "^7.1.1",
|
"@mui/x-data-grid": "^7.1.1",
|
||||||
|
"@mui/x-date-pickers": "^7.7.0",
|
||||||
"@mui/x-tree-view": "^7.4.0",
|
"@mui/x-tree-view": "^7.4.0",
|
||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"date-and-time": "^3.2.0",
|
"date-and-time": "^3.2.0",
|
||||||
|
"dayjs": "^1.11.11",
|
||||||
"email-validator": "^2.0.4",
|
"email-validator": "^2.0.4",
|
||||||
"filesize": "^10.1.2",
|
"filesize": "^10.1.2",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
|
@ -9,9 +9,9 @@ import React from "react";
|
|||||||
import { ServerApi } from "../../api/ServerApi";
|
import { ServerApi } from "../../api/ServerApi";
|
||||||
import { NewCalendarURL } from "../../api/accommodations/AccommodationsCalendarURLApi";
|
import { NewCalendarURL } from "../../api/accommodations/AccommodationsCalendarURLApi";
|
||||||
import { checkConstraint } from "../../utils/from_utils";
|
import { checkConstraint } from "../../utils/from_utils";
|
||||||
|
import { useAccommodations } from "../../widgets/accommodations/BaseAccommodationsRoute";
|
||||||
import { PropEdit } from "../../widgets/forms/PropEdit";
|
import { PropEdit } from "../../widgets/forms/PropEdit";
|
||||||
import { PropSelect } from "../../widgets/forms/PropSelect";
|
import { PropSelect } from "../../widgets/forms/PropSelect";
|
||||||
import { useAccommodations } from "../../widgets/accommodations/BaseAccommodationsRoute";
|
|
||||||
|
|
||||||
export function CreateAccommodationCalendarURLDialog(p: {
|
export function CreateAccommodationCalendarURLDialog(p: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { UpdateAccommodationReservation } from "../../api/accommodations/AccommodationsReservationsApi";
|
import { UpdateAccommodationReservation } from "../../api/accommodations/AccommodationsReservationsApi";
|
||||||
import { useAccommodations } from "../../widgets/accommodations/BaseAccommodationsRoute";
|
import { useAccommodations } from "../../widgets/accommodations/BaseAccommodationsRoute";
|
||||||
|
import { PropDateInput } from "../../widgets/forms/PropDateInput";
|
||||||
import { PropSelect } from "../../widgets/forms/PropSelect";
|
import { PropSelect } from "../../widgets/forms/PropSelect";
|
||||||
|
|
||||||
export function UpdateReservationDialog(p: {
|
export function UpdateReservationDialog(p: {
|
||||||
@ -41,6 +42,8 @@ export function UpdateReservationDialog(p: {
|
|||||||
if (!reservation) setReservation(p.reservation);
|
if (!reservation) setReservation(p.reservation);
|
||||||
}, [p.open, p.reservation]);
|
}, [p.open, p.reservation]);
|
||||||
|
|
||||||
|
// TODO : check availability
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={p.open} onClose={cancel}>
|
<Dialog open={p.open} onClose={cancel}>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
@ -66,6 +69,31 @@ export function UpdateReservationDialog(p: {
|
|||||||
value={reservation?.accommodation_id?.toString()}
|
value={reservation?.accommodation_id?.toString()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PropDateInput
|
||||||
|
editable
|
||||||
|
label="Date de début"
|
||||||
|
value={reservation?.start}
|
||||||
|
onChange={(s) => {
|
||||||
|
setReservation((r) => {
|
||||||
|
return { ...r!, start: s ?? -1 };
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PropDateInput
|
||||||
|
editable
|
||||||
|
label="Date de fin"
|
||||||
|
value={reservation?.end}
|
||||||
|
lastSecOfDay={true}
|
||||||
|
onChange={(s) => {
|
||||||
|
setReservation((r) => {
|
||||||
|
return { ...r!, end: s ?? -1 };
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Constraint start and end */}
|
||||||
|
|
||||||
{/* TODO : la suite */}
|
{/* TODO : la suite */}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
@ -65,8 +65,8 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
|
|||||||
updateReservation(
|
updateReservation(
|
||||||
{
|
{
|
||||||
accommodation_id: -1,
|
accommodation_id: -1,
|
||||||
start: Math.floor(d.start.getDate() / 1000),
|
start: Math.floor(d.start.getTime() / 1000),
|
||||||
end: Math.floor(d.end.getDate() / 1000),
|
end: Math.floor(d.end.getTime() / 1000),
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
6
geneit_app/src/utils/time_utils.ts
Normal file
6
geneit_app/src/utils/time_utils.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Get formatted UNIX date
|
||||||
|
*/
|
||||||
|
export function fmtUnixDate(time: number): string {
|
||||||
|
return new Date(time * 1000).toLocaleString("fr-FR");
|
||||||
|
}
|
53
geneit_app/src/widgets/forms/PropDateInput.tsx
Normal file
53
geneit_app/src/widgets/forms/PropDateInput.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||||
|
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||||
|
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { fmtUnixDate } from "../../utils/time_utils";
|
||||||
|
import { PropEdit } from "./PropEdit";
|
||||||
|
|
||||||
|
export function PropDateInput(p: {
|
||||||
|
editable: boolean;
|
||||||
|
label: string;
|
||||||
|
value: number | undefined;
|
||||||
|
onChange: (v: number | undefined) => void;
|
||||||
|
lastSecOfDay?: boolean;
|
||||||
|
}): React.ReactElement {
|
||||||
|
let shiftV = p.value;
|
||||||
|
if (shiftV && p.lastSecOfDay) {
|
||||||
|
const d = new Date(shiftV * 1000);
|
||||||
|
if (d.getHours() === 0) {
|
||||||
|
shiftV -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.editable) {
|
||||||
|
if (!shiftV) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PropEdit editable={false} label={p.label} value={fmtUnixDate(shiftV)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = dayjs(
|
||||||
|
shiftV && p.value! > 0 ? new Date(shiftV * 1000) : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ margin: "10px auto" }}>
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
|
<DatePicker
|
||||||
|
label={p.label}
|
||||||
|
value={value}
|
||||||
|
onChange={(v) => {
|
||||||
|
if (v && p.lastSecOfDay) {
|
||||||
|
v.set("hours", 23);
|
||||||
|
v.set("minutes", 59);
|
||||||
|
v.set("seconds", 59);
|
||||||
|
}
|
||||||
|
p.onChange?.(v ? v.unix() : undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user