diff --git a/geneit_app/package-lock.json b/geneit_app/package-lock.json index 8622b02..01a852e 100644 --- a/geneit_app/package-lock.json +++ b/geneit_app/package-lock.json @@ -37,6 +37,7 @@ "email-validator": "^2.0.4", "filesize": "^10.1.2", "jspdf": "^2.5.1", + "mui-color-input": "^2.0.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-easy-crop": "^5.0.7", @@ -519,6 +520,14 @@ "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": { "version": "11.11.0", "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", "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": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", diff --git a/geneit_app/package.json b/geneit_app/package.json index f7872ec..c9a3f05 100644 --- a/geneit_app/package.json +++ b/geneit_app/package.json @@ -33,6 +33,7 @@ "email-validator": "^2.0.4", "filesize": "^10.1.2", "jspdf": "^2.5.1", + "mui-color-input": "^2.0.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-easy-crop": "^5.0.7", diff --git a/geneit_app/src/api/accommodations/AccommodationListApi.tsx b/geneit_app/src/api/accommodations/AccommodationListApi.tsx index e9a595d..9a98fed 100644 --- a/geneit_app/src/api/accommodations/AccommodationListApi.tsx +++ b/geneit_app/src/api/accommodations/AccommodationListApi.tsx @@ -8,7 +8,8 @@ export interface Accommodation { time_update: number; name: string; need_validation: boolean; - description: string; + description?: string; + color?: string; open_to_reservations: boolean; } @@ -58,6 +59,7 @@ export interface UpdateAccommodation { name: string; need_validation: boolean; description?: string; + color?: string; open_to_reservations: boolean; } diff --git a/geneit_app/src/dialogs/accommodations/UpdateAccommodationDialog.tsx b/geneit_app/src/dialogs/accommodations/UpdateAccommodationDialog.tsx index 3ffaf1a..a6d720e 100644 --- a/geneit_app/src/dialogs/accommodations/UpdateAccommodationDialog.tsx +++ b/geneit_app/src/dialogs/accommodations/UpdateAccommodationDialog.tsx @@ -12,6 +12,7 @@ import { UpdateAccommodation } from "../../api/accommodations/AccommodationListA import { checkConstraint } from "../../utils/from_utils"; import { PropCheckbox } from "../../widgets/forms/PropCheckbox"; import { PropEdit } from "../../widgets/forms/PropEdit"; +import { PropColorPicker } from "../../widgets/forms/PropColorPicker"; export function UpdateAccommodationDialog(p: { open: boolean; @@ -89,6 +90,20 @@ export function UpdateAccommodationDialog(p: { helperText={descriptionErr} /> + + setAccommodation((a) => { + return { + ...a!, + color: s!, + }; + }) + } + /> + { if (v) hiddenAccommodations.delete(a.id); diff --git a/geneit_app/src/routes/family/accommodations/AccommodationsSettingsRoute.tsx b/geneit_app/src/routes/family/accommodations/AccommodationsSettingsRoute.tsx index eb7f187..cb26ab1 100644 --- a/geneit_app/src/routes/family/accommodations/AccommodationsSettingsRoute.tsx +++ b/geneit_app/src/routes/family/accommodations/AccommodationsSettingsRoute.tsx @@ -1,6 +1,7 @@ import AddIcon from "@mui/icons-material/Add"; import CheckIcon from "@mui/icons-material/Check"; import CloseIcon from "@mui/icons-material/Close"; +import HouseIcon from "@mui/icons-material/House"; import { Button, Card, @@ -62,6 +63,7 @@ function AccommodationsListCard(): React.ReactElement { name: "", open_to_reservations: true, need_validation: false, + color: "2196f3", }, true ); @@ -178,6 +180,7 @@ function AccommodationCard(p: { Mis à jour il y a + {" "} {p.accommodation.name} diff --git a/geneit_app/src/widgets/forms/PropColorPicker.tsx b/geneit_app/src/widgets/forms/PropColorPicker.tsx new file mode 100644 index 0000000..11205b6 --- /dev/null +++ b/geneit_app/src/widgets/forms/PropColorPicker.tsx @@ -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 ; + } + + return ( + p.onChange(c.hex.substring(1))} + /> + ); +} diff --git a/geneit_backend/Cargo.lock b/geneit_backend/Cargo.lock index b0851f1..ce9b2bb 100644 --- a/geneit_backend/Cargo.lock +++ b/geneit_backend/Cargo.lock @@ -1415,6 +1415,7 @@ dependencies = [ "httpdate", "ical", "image", + "lazy-regex", "lazy_static", "lettre", "light-openid", @@ -1952,6 +1953,29 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "lazy_static" version = "1.4.0" diff --git a/geneit_backend/Cargo.toml b/geneit_backend/Cargo.toml index 13b4c0f..e7585ed 100644 --- a/geneit_backend/Cargo.toml +++ b/geneit_backend/Cargo.toml @@ -10,6 +10,7 @@ log = "0.4.21" env_logger = "0.11.3" clap = { version = "4.5.4", features = ["derive", "env"] } lazy_static = "1.4.0" +lazy-regex = "3.1.0" anyhow = "1.0.83" actix-web = "4.5.1" actix-cors = "0.7.0" diff --git a/geneit_backend/migrations/2024-05-23-163128_accommodation_module/up.sql b/geneit_backend/migrations/2024-05-23-163128_accommodation_module/up.sql index 79d45ec..42d2dd8 100644 --- a/geneit_backend/migrations/2024-05-23-163128_accommodation_module/up.sql +++ b/geneit_backend/migrations/2024-05-23-163128_accommodation_module/up.sql @@ -15,6 +15,7 @@ CREATE TABLE IF NOT EXISTS accommodations_list name VARCHAR(50) NOT NULL, need_validation BOOLEAN NOT NULL DEFAULT true, description text NULL, + color VARCHAR(6) NULL, open_to_reservations BOOLEAN NOT NULL DEFAULT false ); diff --git a/geneit_backend/src/controllers/accommodations_list_controller.rs b/geneit_backend/src/controllers/accommodations_list_controller.rs index 6d0028f..cf640ce 100644 --- a/geneit_backend/src/controllers/accommodations_list_controller.rs +++ b/geneit_backend/src/controllers/accommodations_list_controller.rs @@ -8,10 +8,12 @@ use actix_web::{web, HttpResponse}; #[derive(thiserror::Error, Debug)] enum AccommodationListControllerErr { - #[error("Malformed name!")] - MalformedName, - #[error("Malformed description!")] - MalformedDescription, + #[error("Invalid name length!")] + InvalidNameLength, + #[error("Invalid description length!")] + InvalidDescriptionLength, + #[error("Malformed color!")] + MalformedColor, } #[derive(serde::Deserialize, Clone)] @@ -19,6 +21,7 @@ pub struct AccommodationRequest { pub name: String, pub need_validation: bool, pub description: Option, + pub color: Option, pub open_to_reservations: bool, } @@ -27,17 +30,24 @@ impl AccommodationRequest { let c = StaticConstraints::default(); if !c.accommodation_name_len.validate(&self.name) { - return Err(AccommodationListControllerErr::MalformedName.into()); + return Err(AccommodationListControllerErr::InvalidNameLength.into()); } accommodation.name = self.name; if let Some(d) = &self.description { if !c.accommodation_description_len.validate(d) { - return Err(AccommodationListControllerErr::MalformedDescription.into()); + return Err(AccommodationListControllerErr::InvalidDescriptionLength.into()); } } 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.open_to_reservations = self.open_to_reservations; Ok(()) diff --git a/geneit_backend/src/models.rs b/geneit_backend/src/models.rs index 720a39d..65389d2 100644 --- a/geneit_backend/src/models.rs +++ b/geneit_backend/src/models.rs @@ -459,6 +459,7 @@ pub struct Accommodation { pub name: String, pub need_validation: bool, pub description: Option, + pub color: Option, pub open_to_reservations: bool, } diff --git a/geneit_backend/src/schema.rs b/geneit_backend/src/schema.rs index 6f6200d..655f46f 100644 --- a/geneit_backend/src/schema.rs +++ b/geneit_backend/src/schema.rs @@ -10,6 +10,8 @@ diesel::table! { name -> Varchar, need_validation -> Bool, description -> Nullable, + #[max_length = 6] + color -> Nullable, open_to_reservations -> Bool, } } diff --git a/geneit_backend/src/services/accommodations_list_service.rs b/geneit_backend/src/services/accommodations_list_service.rs index 6d44bc0..ba0f7e8 100644 --- a/geneit_backend/src/services/accommodations_list_service.rs +++ b/geneit_backend/src/services/accommodations_list_service.rs @@ -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::need_validation.eq(accommodation.need_validation), 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), )) .execute(conn)