feat: only allow one availability for a time
This commit is contained in:
parent
2cae816741
commit
864141121a
@ -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<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<AvailabilityNewQuery>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
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()?))
|
||||
|
@ -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<i32>,
|
||||
areas: Vec<Area>,
|
||||
events_and_assignments: Vec<(Event, Vec<String>, Option<String>, Option<String>, Vec<Vehicle>)>,
|
||||
events_and_assignments: Vec<(
|
||||
Event,
|
||||
Vec<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Vec<Vehicle>,
|
||||
)>,
|
||||
availabillities: Vec<Availabillity>,
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
@ -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()?))
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -52,6 +52,12 @@
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="from" name="from" value='{{ start_time.unwrap_or("00:00") }}' {{
|
||||
whole_day|cond_show("disabled") }} {{ whole_day|invert|ref|cond_show("required") }}>
|
||||
<p class="help">noch mögliche Zeiträume:</p>
|
||||
<div class="tags help">
|
||||
{% for (s, e) in slot_suggestions %}
|
||||
<span class="tag">{{ s }} - {{ e }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="till" name="till" value='{{ end_time.unwrap_or("23:59") }}' {{
|
||||
|
@ -183,12 +183,14 @@
|
||||
</div>
|
||||
{% if selected_area.is_none() || selected_area.unwrap() == user.area_id %}
|
||||
<div class="level-right">
|
||||
<a class="button is-link is-light" hx-boost="true" href="/availabillity/new?date={{ date }}">
|
||||
{% let btn_disabled = !user_can_create_availabillity %}
|
||||
<button class="button is-link is-light" hx-get="/availabillity/new?date={{ date }}" {{
|
||||
btn_disabled|cond_show("disabled") }} hx-target="closest body">
|
||||
<svg class="icon">
|
||||
<use href="/static/feather-sprite.svg#plus-circle" />
|
||||
</svg>
|
||||
<span>Neue Verfügbarkeit für diesen Tag</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user