Add an accommodations reservations module #188
30
geneit_app/package-lock.json
generated
30
geneit_app/package-lock.json
generated
@ -37,6 +37,7 @@
|
|||||||
"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",
|
||||||
|
"mui-color-input": "^2.0.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-easy-crop": "^5.0.7",
|
"react-easy-crop": "^5.0.7",
|
||||||
@ -519,6 +520,14 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ctrl/tinycolor": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emotion/babel-plugin": {
|
"node_modules/@emotion/babel-plugin": {
|
||||||
"version": "11.11.0",
|
"version": "11.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
||||||
@ -3455,6 +3464,27 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/mui-color-input": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mui-color-input/-/mui-color-input-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-rAd040qQ0Y+8dk4gE8kkCiJ/vCgA0j4vv1quJ43BfORTFE3uHarHj0xY1Vo9CPbojtx1f5vW+CjckYPRIZPIRg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ctrl/tinycolor": "^4.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.5.0",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"@mui/material": "^5.0.0",
|
||||||
|
"@types/react": "^18.0.0",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
"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",
|
||||||
|
"mui-color-input": "^2.0.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-easy-crop": "^5.0.7",
|
"react-easy-crop": "^5.0.7",
|
||||||
|
@ -8,7 +8,8 @@ export interface Accommodation {
|
|||||||
time_update: number;
|
time_update: number;
|
||||||
name: string;
|
name: string;
|
||||||
need_validation: boolean;
|
need_validation: boolean;
|
||||||
description: string;
|
description?: string;
|
||||||
|
color?: string;
|
||||||
open_to_reservations: boolean;
|
open_to_reservations: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ export interface UpdateAccommodation {
|
|||||||
name: string;
|
name: string;
|
||||||
need_validation: boolean;
|
need_validation: boolean;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
color?: string;
|
||||||
open_to_reservations: boolean;
|
open_to_reservations: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import { UpdateAccommodation } from "../../api/accommodations/AccommodationListA
|
|||||||
import { checkConstraint } from "../../utils/from_utils";
|
import { checkConstraint } from "../../utils/from_utils";
|
||||||
import { PropCheckbox } from "../../widgets/forms/PropCheckbox";
|
import { PropCheckbox } from "../../widgets/forms/PropCheckbox";
|
||||||
import { PropEdit } from "../../widgets/forms/PropEdit";
|
import { PropEdit } from "../../widgets/forms/PropEdit";
|
||||||
|
import { PropColorPicker } from "../../widgets/forms/PropColorPicker";
|
||||||
|
|
||||||
export function UpdateAccommodationDialog(p: {
|
export function UpdateAccommodationDialog(p: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -89,6 +90,20 @@ export function UpdateAccommodationDialog(p: {
|
|||||||
helperText={descriptionErr}
|
helperText={descriptionErr}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PropColorPicker
|
||||||
|
editable
|
||||||
|
label="Couleur"
|
||||||
|
value={accommodation?.color}
|
||||||
|
onChange={(s) =>
|
||||||
|
setAccommodation((a) => {
|
||||||
|
return {
|
||||||
|
...a!,
|
||||||
|
color: s!,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<PropCheckbox
|
<PropCheckbox
|
||||||
editable
|
editable
|
||||||
label="Ouvert aux réservations"
|
label="Ouvert aux réservations"
|
||||||
|
@ -159,6 +159,12 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
|
|||||||
key={a.id}
|
key={a.id}
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
sx={{
|
||||||
|
color: "#" + a.color,
|
||||||
|
"&.Mui-checked": {
|
||||||
|
color: "#" + a.color,
|
||||||
|
},
|
||||||
|
}}
|
||||||
checked={!hiddenAccommodations.has(a.id)}
|
checked={!hiddenAccommodations.has(a.id)}
|
||||||
onChange={(_ev, v) => {
|
onChange={(_ev, v) => {
|
||||||
if (v) hiddenAccommodations.delete(a.id);
|
if (v) hiddenAccommodations.delete(a.id);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import CheckIcon from "@mui/icons-material/Check";
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import HouseIcon from "@mui/icons-material/House";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
@ -62,6 +63,7 @@ function AccommodationsListCard(): React.ReactElement {
|
|||||||
name: "",
|
name: "",
|
||||||
open_to_reservations: true,
|
open_to_reservations: true,
|
||||||
need_validation: false,
|
need_validation: false,
|
||||||
|
color: "2196f3",
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@ -178,6 +180,7 @@ function AccommodationCard(p: {
|
|||||||
Mis à jour il y a <TimeWidget time={p.accommodation.time_update} />
|
Mis à jour il y a <TimeWidget time={p.accommodation.time_update} />
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h5" component="div">
|
<Typography variant="h5" component="div">
|
||||||
|
<HouseIcon sx={{ color: "#" + p.accommodation.color }} />{" "}
|
||||||
{p.accommodation.name}
|
{p.accommodation.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography sx={{ mb: 1.5 }} color="text.secondary">
|
<Typography sx={{ mb: 1.5 }} color="text.secondary">
|
||||||
|
24
geneit_app/src/widgets/forms/PropColorPicker.tsx
Normal file
24
geneit_app/src/widgets/forms/PropColorPicker.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { MuiColorInput } from "mui-color-input";
|
||||||
|
import { PropEdit } from "./PropEdit";
|
||||||
|
|
||||||
|
export function PropColorPicker(p: {
|
||||||
|
editable: boolean;
|
||||||
|
label: string;
|
||||||
|
value?: string;
|
||||||
|
onChange: (v: string | undefined) => void;
|
||||||
|
}): React.ReactElement {
|
||||||
|
if (!p.editable) {
|
||||||
|
if (!p.value) return <></>;
|
||||||
|
|
||||||
|
return <PropEdit editable={false} label={p.label} value={`#${p.value}`} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MuiColorInput
|
||||||
|
value={"#" + (p.value ?? "")}
|
||||||
|
fallbackValue="#ffffff"
|
||||||
|
format="hex"
|
||||||
|
onChange={(_v, c) => p.onChange(c.hex.substring(1))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
24
geneit_backend/Cargo.lock
generated
24
geneit_backend/Cargo.lock
generated
@ -1415,6 +1415,7 @@ dependencies = [
|
|||||||
"httpdate",
|
"httpdate",
|
||||||
"ical",
|
"ical",
|
||||||
"image",
|
"image",
|
||||||
|
"lazy-regex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lettre",
|
"lettre",
|
||||||
"light-openid",
|
"light-openid",
|
||||||
@ -1952,6 +1953,29 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy-regex"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d12be4595afdf58bd19e4a9f4e24187da2a66700786ff660a418e9059937a4c"
|
||||||
|
dependencies = [
|
||||||
|
"lazy-regex-proc_macros",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy-regex-proc_macros"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44bcd58e6c97a7fcbaffcdc95728b393b8d98933bfadad49ed4097845b57ef0b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"syn 2.0.63",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -10,6 +10,7 @@ log = "0.4.21"
|
|||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
clap = { version = "4.5.4", features = ["derive", "env"] }
|
clap = { version = "4.5.4", features = ["derive", "env"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
lazy-regex = "3.1.0"
|
||||||
anyhow = "1.0.83"
|
anyhow = "1.0.83"
|
||||||
actix-web = "4.5.1"
|
actix-web = "4.5.1"
|
||||||
actix-cors = "0.7.0"
|
actix-cors = "0.7.0"
|
||||||
|
@ -15,6 +15,7 @@ CREATE TABLE IF NOT EXISTS accommodations_list
|
|||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR(50) NOT NULL,
|
||||||
need_validation BOOLEAN NOT NULL DEFAULT true,
|
need_validation BOOLEAN NOT NULL DEFAULT true,
|
||||||
description text NULL,
|
description text NULL,
|
||||||
|
color VARCHAR(6) NULL,
|
||||||
open_to_reservations BOOLEAN NOT NULL DEFAULT false
|
open_to_reservations BOOLEAN NOT NULL DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -8,10 +8,12 @@ use actix_web::{web, HttpResponse};
|
|||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
enum AccommodationListControllerErr {
|
enum AccommodationListControllerErr {
|
||||||
#[error("Malformed name!")]
|
#[error("Invalid name length!")]
|
||||||
MalformedName,
|
InvalidNameLength,
|
||||||
#[error("Malformed description!")]
|
#[error("Invalid description length!")]
|
||||||
MalformedDescription,
|
InvalidDescriptionLength,
|
||||||
|
#[error("Malformed color!")]
|
||||||
|
MalformedColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Clone)]
|
#[derive(serde::Deserialize, Clone)]
|
||||||
@ -19,6 +21,7 @@ pub struct AccommodationRequest {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub need_validation: bool,
|
pub need_validation: bool,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
pub color: Option<String>,
|
||||||
pub open_to_reservations: bool,
|
pub open_to_reservations: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,17 +30,24 @@ impl AccommodationRequest {
|
|||||||
let c = StaticConstraints::default();
|
let c = StaticConstraints::default();
|
||||||
|
|
||||||
if !c.accommodation_name_len.validate(&self.name) {
|
if !c.accommodation_name_len.validate(&self.name) {
|
||||||
return Err(AccommodationListControllerErr::MalformedName.into());
|
return Err(AccommodationListControllerErr::InvalidNameLength.into());
|
||||||
}
|
}
|
||||||
accommodation.name = self.name;
|
accommodation.name = self.name;
|
||||||
|
|
||||||
if let Some(d) = &self.description {
|
if let Some(d) = &self.description {
|
||||||
if !c.accommodation_description_len.validate(d) {
|
if !c.accommodation_description_len.validate(d) {
|
||||||
return Err(AccommodationListControllerErr::MalformedDescription.into());
|
return Err(AccommodationListControllerErr::InvalidDescriptionLength.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
accommodation.description.clone_from(&self.description);
|
accommodation.description.clone_from(&self.description);
|
||||||
|
|
||||||
|
if let Some(c) = &self.color {
|
||||||
|
if !lazy_regex::regex!("[a-fA-F0-9]{6}").is_match(c) {
|
||||||
|
return Err(AccommodationListControllerErr::MalformedColor.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accommodation.color.clone_from(&self.color);
|
||||||
|
|
||||||
accommodation.need_validation = self.need_validation;
|
accommodation.need_validation = self.need_validation;
|
||||||
accommodation.open_to_reservations = self.open_to_reservations;
|
accommodation.open_to_reservations = self.open_to_reservations;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -459,6 +459,7 @@ pub struct Accommodation {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub need_validation: bool,
|
pub need_validation: bool,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
pub color: Option<String>,
|
||||||
pub open_to_reservations: bool,
|
pub open_to_reservations: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ diesel::table! {
|
|||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
need_validation -> Bool,
|
need_validation -> Bool,
|
||||||
description -> Nullable<Text>,
|
description -> Nullable<Text>,
|
||||||
|
#[max_length = 6]
|
||||||
|
color -> Nullable<Varchar>,
|
||||||
open_to_reservations -> Bool,
|
open_to_reservations -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,7 @@ pub async fn update(accommodation: &mut Accommodation) -> anyhow::Result<()> {
|
|||||||
accommodations_list::dsl::name.eq(accommodation.name.to_string()),
|
accommodations_list::dsl::name.eq(accommodation.name.to_string()),
|
||||||
accommodations_list::dsl::need_validation.eq(accommodation.need_validation),
|
accommodations_list::dsl::need_validation.eq(accommodation.need_validation),
|
||||||
accommodations_list::dsl::description.eq(accommodation.description.clone()),
|
accommodations_list::dsl::description.eq(accommodation.description.clone()),
|
||||||
|
accommodations_list::dsl::color.eq(accommodation.color.clone()),
|
||||||
accommodations_list::dsl::open_to_reservations.eq(accommodation.open_to_reservations),
|
accommodations_list::dsl::open_to_reservations.eq(accommodation.open_to_reservations),
|
||||||
))
|
))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
|
Loading…
Reference in New Issue
Block a user