diff --git a/web/src/endpoints/availability/get_new.rs b/web/src/endpoints/availability/get_new.rs index 7a6e579c..fca70341 100644 --- a/web/src/endpoints/availability/get_new.rs +++ b/web/src/endpoints/availability/get_new.rs @@ -2,9 +2,10 @@ use actix_web::{web, HttpResponse, Responder}; use chrono::NaiveDate; use rinja::Template; use serde::Deserialize; +use sqlx::PgPool; -use crate::endpoints::availability::NewOrEditAvailabilityTemplate; -use crate::models::User; +use crate::endpoints::availability::{calc_free_slots_cor, NewOrEditAvailabilityTemplate}; +use crate::models::{Availabillity, User}; use crate::utils::ApplicationError; #[derive(Deserialize)] @@ -17,8 +18,32 @@ struct AvailabilityNewQuery { #[actix_web::get("/availabillity/new")] pub async fn get( user: web::ReqData, + pool: web::Data, query: web::Query, ) -> Result { + let availabillities = Availabillity::read_by_date_and_area_including_user( + pool.get_ref(), + query.date, + user.area_id, + ) + .await?; + + let availabilities_from_user: Vec<&Availabillity> = availabillities + .iter() + .filter(|a| a.user_id == user.id) + .collect(); + let only_one_availability_is_wholeday = availabilities_from_user.len() == 1 + && availabilities_from_user[0].start_time.is_none() + && availabilities_from_user[0].end_time.is_none(); + + let user_can_create_availabillity = availabilities_from_user.len() == 0 + || !(only_one_availability_is_wholeday + || calc_free_slots_cor(&availabilities_from_user).len() == 0); + + if !user_can_create_availabillity { + return Ok(HttpResponse::BadRequest().finish()); + } + let template = NewOrEditAvailabilityTemplate { user: user.into_inner(), date: query.date, @@ -27,6 +52,7 @@ pub async fn get( start_time: None, end_time: None, comment: None, + slot_suggestions: calc_free_slots_cor(&availabilities_from_user), }; Ok(HttpResponse::Ok().body(template.render()?)) diff --git a/web/src/endpoints/availability/get_overview.rs b/web/src/endpoints/availability/get_overview.rs index c0949d98..0519aca0 100644 --- a/web/src/endpoints/availability/get_overview.rs +++ b/web/src/endpoints/availability/get_overview.rs @@ -1,10 +1,8 @@ use crate::{ - filters, - models::{Assignment, Function, Vehicle}, - utils::{event_planning_template::generate_vehicles_assigned_and_available, ApplicationError}, + endpoints::availability::calc_free_slots_cor, filters, models::{Assignment, Function, Vehicle}, utils::{event_planning_template::generate_vehicles_assigned_and_available, ApplicationError} }; use actix_web::{web, HttpResponse, Responder}; -use chrono::{NaiveDate, Utc}; +use chrono::{NaiveDate, NaiveTime, Utc}; use rinja::Template; use serde::Deserialize; use sqlx::PgPool; @@ -21,10 +19,17 @@ pub struct CalendarQuery { #[template(path = "index.html")] struct CalendarTemplate { user: User, + user_can_create_availabillity: bool, date: NaiveDate, selected_area: Option, areas: Vec, - events_and_assignments: Vec<(Event, Vec, Option, Option, Vec)>, + events_and_assignments: Vec<( + Event, + Vec, + Option, + Option, + Vec, + )>, availabillities: Vec, } @@ -59,6 +64,18 @@ async fn get( ) .await?; + let availabilities_from_user: Vec<&Availabillity> = availabillities + .iter() + .filter(|a| a.user_id == user.id) + .collect(); + let only_one_availability_is_wholeday = availabilities_from_user.len() == 1 + && availabilities_from_user[0].start_time.is_none() + && availabilities_from_user[0].end_time.is_none(); + + let user_can_create_availabillity = availabilities_from_user.len() == 0 + || !(only_one_availability_is_wholeday + || calc_free_slots_cor(&availabilities_from_user).len() == 0); + let mut events_and_assignments = Vec::new(); for e in Event::read_all_by_date_and_area_including_location( pool.get_ref(), @@ -120,12 +137,13 @@ async fn get( .clone(), ) }), - assigned_vehicle + assigned_vehicle, )); } let template = CalendarTemplate { user: user.into_inner(), + user_can_create_availabillity, date, selected_area, areas, diff --git a/web/src/endpoints/availability/get_update.rs b/web/src/endpoints/availability/get_update.rs index 669e5786..dd191c41 100644 --- a/web/src/endpoints/availability/get_update.rs +++ b/web/src/endpoints/availability/get_update.rs @@ -4,7 +4,10 @@ use serde::Deserialize; use sqlx::PgPool; use crate::{ - endpoints::{availability::NewOrEditAvailabilityTemplate, IdPath}, + endpoints::{ + availability::{calc_free_slots_cor, NewOrEditAvailabilityTemplate}, + IdPath, + }, models::{Availabillity, User}, utils::ApplicationError, }; @@ -40,6 +43,29 @@ pub async fn get( let has_time = availabillity.start_time.is_some() && availabillity.end_time.is_some(); + let suggestions = if has_time { + let availabillities = Availabillity::read_by_date_and_area_including_user( + pool.get_ref(), + availabillity.date, + user.area_id, + ) + .await?; + + let availabilities_from_user: Vec<&Availabillity> = availabillities + .iter() + .filter(|a| a.user_id == user.id) + .collect(); + + calc_free_slots_cor(&availabilities_from_user) + .into_iter() + .filter(|(a, b)| { + *b == availabillity.start_time.unwrap() || *a == availabillity.end_time.unwrap() + }) + .collect() + } else { + Vec::new() + }; + let template = NewOrEditAvailabilityTemplate { user: user.into_inner(), date: availabillity.date, @@ -48,6 +74,7 @@ pub async fn get( start_time: start_time.as_deref(), end_time: end_time.as_deref(), comment: availabillity.comment.as_deref(), + slot_suggestions: suggestions, }; Ok(HttpResponse::Ok().body(template.render()?)) diff --git a/web/src/endpoints/availability/mod.rs b/web/src/endpoints/availability/mod.rs index d96667be..5affa8ee 100644 --- a/web/src/endpoints/availability/mod.rs +++ b/web/src/endpoints/availability/mod.rs @@ -1,8 +1,8 @@ +use chrono::{NaiveDate, NaiveTime}; use rinja::Template; -use chrono::NaiveDate; use crate::filters; -use crate::models::{Role, User}; +use crate::models::{Availabillity, Role, User}; pub mod delete; pub mod get_new; @@ -21,4 +21,72 @@ struct NewOrEditAvailabilityTemplate<'a> { start_time: Option<&'a str>, end_time: Option<&'a str>, comment: Option<&'a str>, + slot_suggestions: Vec<(NaiveTime, NaiveTime)>, +} + +fn calc_free_slots_cor(availabilities: &Vec<&Availabillity>) -> Vec<(NaiveTime, NaiveTime)> { + let mut times = Vec::new(); + + for a in availabilities { + let Some(start_time) = a.start_time else { + continue; + }; + + let Some(end_time) = a.end_time else { + continue; + }; + + times.push((start_time, end_time)); + } + + if times.len() == 0 { + return Vec::new(); + } + println!("zeiten {times:?}"); + + times.sort(); + + println!("zeiten sort {times:?}"); + + let mut changed = true; + while changed { + changed = false; + + for i in 0..(times.len() - 1) { + let b = times[i + 1].clone(); + let a = times.get_mut(i).unwrap(); + + if a.1 == b.0 { + a.1 = b.1; + times.remove(i + 1); + changed = true; + break; + } + } + } + + let start = NaiveTime::parse_from_str("00:00", "%R").unwrap(); + let end = NaiveTime::parse_from_str("23:59", "%R").unwrap(); + println!("zeiten unified {times:?}"); + + // now times contains unified list of existing availabilities -> now calculate the "inverse" + + let mut available_slots = Vec::new(); + if times.first().unwrap().0 != start { + available_slots.push((start, times.first().unwrap().0)); + } + + let mut iterator = times.iter().peekable(); + while let Some(a) = iterator.next() { + if let Some(b) = iterator.peek() { + available_slots.push((a.1, b.0)); + } + } + + if times.last().unwrap().1 != end { + available_slots.push((times.last().unwrap().1, end)); + } + + println!("available {available_slots:?}"); + available_slots } diff --git a/web/src/models/availabillity.rs b/web/src/models/availabillity.rs index 97d60198..d28bafdd 100644 --- a/web/src/models/availabillity.rs +++ b/web/src/models/availabillity.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - use chrono::{NaiveDate, NaiveTime}; use sqlx::{query, PgPool}; @@ -30,18 +28,6 @@ pub enum AvailabillityAssignmentState { AssignedWachhabender(i32), } -impl Display for AvailabillityAssignmentState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AvailabillityAssignmentState::Unassigned => write!(f, "nicht zugewiesen"), - AvailabillityAssignmentState::Conflicting => write!(f, "bereits anders zugewiesen"), - AvailabillityAssignmentState::AssignedPosten(_) => write!(f, "zugewiesen als Posten"), - AvailabillityAssignmentState::AssignedFührungsassistent(_) => write!(f, "zugewiesen als Führungsassistent"), - AvailabillityAssignmentState::AssignedWachhabender(_) => write!(f, "zugewiesen als Wachhabender"), - } - } -} - impl Availabillity { pub async fn create( pool: &PgPool, diff --git a/web/templates/availability/new_or_edit.html b/web/templates/availability/new_or_edit.html index e7d34f76..ccef9a96 100644 --- a/web/templates/availability/new_or_edit.html +++ b/web/templates/availability/new_or_edit.html @@ -52,6 +52,12 @@
+

noch mögliche Zeiträume:

+
+ {% for (s, e) in slot_suggestions %} + {{ s }} - {{ e }} + {% endfor %} +
{% if selected_area.is_none() || selected_area.unwrap() == user.area_id %} {% endif %}