feat: combine availability finished

This commit is contained in:
Max Hohlfeld 2025-01-29 13:45:29 +01:00
parent 6995599a81
commit 85fdd41c6b
8 changed files with 103 additions and 55 deletions

View File

@ -6,9 +6,10 @@ use sqlx::PgPool;
use crate::endpoints::availability::NewOrEditAvailabilityTemplate; use crate::endpoints::availability::NewOrEditAvailabilityTemplate;
use crate::models::{ use crate::models::{
find_free_time_slots, only_one_availability_exists_and_is_whole_day, Availability, User, find_free_time_slots, only_one_availability_exists_and_is_whole_day, Availability, AvailabilityTime, User
}; };
use crate::utils::ApplicationError; use crate::utils::ApplicationError;
use crate::{END_OF_DAY, START_OF_DAY};
#[derive(Deserialize)] #[derive(Deserialize)]
struct AvailabilityNewQuery { struct AvailabilityNewQuery {
@ -27,20 +28,25 @@ pub async fn get(
Availability::read_by_user_and_date(pool.get_ref(), user.id, &query.date).await?; Availability::read_by_user_and_date(pool.get_ref(), user.id, &query.date).await?;
let free_slots = find_free_time_slots(&availabilities_from_user); let free_slots = find_free_time_slots(&availabilities_from_user);
let user_can_create_availabillity = let user_can_create_availabillity = availabilities_from_user.len() == 0
!(only_one_availability_exists_and_is_whole_day(&availabilities_from_user) || !only_one_availability_exists_and_is_whole_day(&availabilities_from_user)
|| free_slots.len() == 0); || free_slots.len() > 0;
if !user_can_create_availabillity { if !user_can_create_availabillity {
return Ok(HttpResponse::BadRequest().finish()); return Ok(HttpResponse::BadRequest().finish());
} }
let time_selection = if query.whole_day.unwrap_or(true) {
AvailabilityTime::WholeDay
} else {
AvailabilityTime::Temporarily(START_OF_DAY, END_OF_DAY)
};
let template = NewOrEditAvailabilityTemplate { let template = NewOrEditAvailabilityTemplate {
user: user.into_inner(), user: user.into_inner(),
date: query.date, date: query.date,
whole_day_selected: query.whole_day.unwrap_or(true), time_selection,
id: None, id: None,
time: None,
comment: None, comment: None,
slot_suggestions: free_slots, slot_suggestions: free_slots,
}; };

View File

@ -71,10 +71,15 @@ async fn get(
let availabilities_from_user = let availabilities_from_user =
Availability::read_by_user_and_date(pool.get_ref(), user.id, &date).await?; Availability::read_by_user_and_date(pool.get_ref(), user.id, &date).await?;
println!("{availabilities_from_user:#?}");
let user_can_create_availabillity = let user_can_create_availabillity = availabilities_from_user.len() == 0
!(only_one_availability_exists_and_is_whole_day(&availabilities_from_user) || !only_one_availability_exists_and_is_whole_day(&availabilities_from_user)
|| find_free_time_slots(&availabilities_from_user).len() == 0); || find_free_time_slots(&availabilities_from_user).len() > 0;
println!("{} || {} || {} = {user_can_create_availabillity}", availabilities_from_user.len() == 0,
!only_one_availability_exists_and_is_whole_day(&availabilities_from_user),
find_free_time_slots(&availabilities_from_user).len() > 0);
let mut events_and_assignments = Vec::new(); let mut events_and_assignments = Vec::new();
for e in Event::read_all_by_date_and_area_including_location( for e in Event::read_all_by_date_and_area_including_location(

View File

@ -33,8 +33,6 @@ pub async fn get(
return Err(ApplicationError::Unauthorized); return Err(ApplicationError::Unauthorized);
} }
let whole_day_selected = query.whole_day.unwrap_or(availability.time == AvailabilityTime::WholeDay);
let suggestions = if let AvailabilityTime::Temporarily(start, end) = availability.time { let suggestions = if let AvailabilityTime::Temporarily(start, end) = availability.time {
let availabilities = Availability::read_by_user_and_date( let availabilities = Availability::read_by_user_and_date(
pool.get_ref(), pool.get_ref(),
@ -51,14 +49,20 @@ pub async fn get(
Vec::new() Vec::new()
}; };
let time_selection = if query.whole_day.unwrap_or(availability.time == AvailabilityTime::WholeDay) {
AvailabilityTime::WholeDay
} else {
availability.time.clone()
};
println!("{:?}", availability.time);
let template = NewOrEditAvailabilityTemplate { let template = NewOrEditAvailabilityTemplate {
user: user.into_inner(), user: user.into_inner(),
date: availability.date, date: availability.date,
id: Some(path.id), id: Some(path.id),
time: Some(availability.time), time_selection,
comment: availability.comment.as_deref(), comment: availability.comment.as_deref(),
slot_suggestions: suggestions, slot_suggestions: suggestions,
whole_day_selected
}; };
Ok(HttpResponse::Ok().body(template.render()?)) Ok(HttpResponse::Ok().body(template.render()?))

View File

@ -2,7 +2,7 @@ use chrono::{NaiveDate, NaiveTime};
use rinja::Template; use rinja::Template;
use crate::filters; use crate::filters;
use crate::models::{AvailabilityTime, Role, User}; use crate::models::{Availability, AvailabilityChangeset, AvailabilityTime, Role, User};
pub mod delete; pub mod delete;
pub mod get_new; pub mod get_new;
@ -17,8 +17,30 @@ struct NewOrEditAvailabilityTemplate<'a> {
user: User, user: User,
date: NaiveDate, date: NaiveDate,
id: Option<i32>, id: Option<i32>,
time: Option<AvailabilityTime>, time_selection: AvailabilityTime,
whole_day_selected: bool,
comment: Option<&'a str>, comment: Option<&'a str>,
slot_suggestions: Vec<(NaiveTime, NaiveTime)>, slot_suggestions: Vec<(NaiveTime, NaiveTime)>,
} }
fn find_adjacend_availability<'a>(
changeset: &AvailabilityChangeset,
availability_id_to_be_updated: Option<i32>,
existing_availabilities: &'a [Availability],
) -> Option<&'a Availability> {
let AvailabilityTime::Temporarily(changeset_start, changeset_end) = changeset.time else {
return None;
};
for a in existing_availabilities
.iter()
.filter(|a| availability_id_to_be_updated.is_none_or(|id| a.id != id))
{
if let AvailabilityTime::Temporarily(start, end) = a.time {
if start == changeset_end || end == changeset_start {
return Some(a);
}
}
}
None
}

View File

@ -1,10 +1,12 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder}; use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use chrono::{NaiveDate, NaiveTime}; use chrono::{NaiveDate, NaiveTime};
use garde::Validate;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
models::{Availability, User}, endpoints::availability::find_adjacend_availability,
models::{Availability, AvailabilityChangeset, AvailabilityContext, AvailabilityTime, User},
utils::{self, ApplicationError}, utils::{self, ApplicationError},
}; };
@ -22,16 +24,43 @@ pub async fn post(
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
form: web::Form<AvailabillityForm>, form: web::Form<AvailabillityForm>,
) -> Result<impl Responder, ApplicationError> { ) -> Result<impl Responder, ApplicationError> {
// TODO: create and validate changeset let existing_availabilities =
//Availability::create( Availability::read_by_user_and_date(pool.get_ref(), user.id, &form.date).await?;
// pool.get_ref(), let context = AvailabilityContext {
// user.id, existing_availabilities: existing_availabilities.clone(),
// form.date, };
// form.from,
// form.till, let time = if form.from.is_some() && form.till.is_some() {
// form.comment.clone(), AvailabilityTime::Temporarily(form.from.unwrap(), form.till.unwrap())
//) } else {
//.await?; AvailabilityTime::WholeDay
};
let mut changeset = AvailabilityChangeset {
time,
comment: form.comment.clone(),
};
if let Err(e) = changeset.validate_with(&context) {
return Ok(HttpResponse::BadRequest().body(e.to_string()));
};
if let Some(a) = find_adjacend_availability(&changeset, None, &existing_availabilities) {
let (changeset_start, changeset_end) = changeset.time.time_tuple_opt().unwrap();
let (adjacent_start, adjacent_end) = changeset.time.time_tuple_opt().unwrap();
if adjacent_end == changeset_start {
changeset.time = AvailabilityTime::Temporarily(adjacent_start, changeset_end);
}
if adjacent_start == changeset_end {
changeset.time = AvailabilityTime::Temporarily(changeset_start, adjacent_end);
}
Availability::update(pool.get_ref(), a.id, changeset).await?;
} else {
Availability::create(pool.get_ref(), user.id, form.date, changeset).await?;
}
let url = utils::get_return_url_for_date(&form.date); let url = utils::get_return_url_for_date(&form.date);
Ok(HttpResponse::Found() Ok(HttpResponse::Found()

View File

@ -3,7 +3,7 @@ use garde::Validate;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
endpoints::{availability::post_new::AvailabillityForm, IdPath}, endpoints::{availability::{find_adjacend_availability, post_new::AvailabillityForm}, IdPath},
models::{Availability, AvailabilityChangeset, AvailabilityContext, AvailabilityTime, User}, models::{Availability, AvailabilityChangeset, AvailabilityContext, AvailabilityTime, User},
utils::{self, ApplicationError}, utils::{self, ApplicationError},
}; };
@ -44,22 +44,7 @@ pub async fn post(
return Ok(HttpResponse::BadRequest().body(e.to_string())); return Ok(HttpResponse::BadRequest().body(e.to_string()));
}; };
let mut adjacent = None; if let Some(a) = find_adjacend_availability(&changeset, Some(availability.id), &existing_availabilities) {
if let AvailabilityTime::Temporarily(changeset_start, changeset_end) = changeset.time {
for a in existing_availabilities
.iter()
.filter(|a| a.id != availability.id)
{
if let AvailabilityTime::Temporarily(start, end) = a.time {
if start == changeset_end || end == changeset_start {
adjacent = Some(a);
break;
}
}
}
}
if let Some(a) = adjacent {
let (changeset_start, changeset_end) = changeset.time.time_tuple_opt().unwrap(); let (changeset_start, changeset_end) = changeset.time.time_tuple_opt().unwrap();
let (adjacent_start, adjacent_end) = changeset.time.time_tuple_opt().unwrap(); let (adjacent_start, adjacent_end) = changeset.time.time_tuple_opt().unwrap();

View File

@ -34,7 +34,7 @@ fn time_is_not_already_made_available(
return Err(garde::Error::new("cant create a availability while an other availability for the whole day is already present")); return Err(garde::Error::new("cant create a availability while an other availability for the whole day is already present"));
} }
if find_free_time_slots(&context.existing_availabilities).len() == 0 { if context.existing_availabilities.len() > 0 && find_free_time_slots(&context.existing_availabilities).len() == 0 {
return Err(garde::Error::new( return Err(garde::Error::new(
"cant create a availability as every time slot is already filled", "cant create a availability as every time slot is already filled",
)); ));
@ -71,7 +71,7 @@ pub fn find_free_time_slots(availabilities: &[Availability]) -> Vec<(NaiveTime,
if times.len() == 0 { if times.len() == 0 {
return Vec::new(); return Vec::new();
} }
//println!("zeiten {times:?}"); println!("zeiten {times:?}");
times.sort(); times.sort();

View File

@ -12,6 +12,7 @@
{% endif %} {% endif %}
<input type="hidden" name="date" value="{{ date }}"> <input type="hidden" name="date" value="{{ date }}">
{% let whole_day_selected = time_selection == AvailabilityTime::WholeDay %}
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"> <div class="field-label">
@ -46,25 +47,21 @@
<label class="label">Von - Bis</label> <label class="label">Von - Bis</label>
</div> </div>
<div class="field-body"> <div class="field-body">
{% let times = ("00:00".to_string(), "23:59".to_string()) -%} {% let times = time_selection.time_tuple() -%}
{% if let Some(times) = time -%}
{% if let AvailabilityTime::Temporarily(start, end) = times -%}
{% let times = (start.to_string(), end.to_string()) -%}
{% endif -%}
{% endif -%}
<div class="field"> <div class="field">
<input class="input" type="time" id="from" name="from" value='{{ times.0 }}' {{ <input class="input" type="time" id="from" name="from" value='{{ times.0.format("%R") }}' {{
whole_day_selected|cond_show("disabled") }} {{ whole_day_selected|invert|ref|cond_show("required") }}> whole_day_selected|cond_show("disabled") }} {{ whole_day_selected|invert|ref|cond_show("required") }}>
{% if slot_suggestions.len() > 0 %}
<p class="help">noch mögliche Zeiträume:</p> <p class="help">noch mögliche Zeiträume:</p>
<div class="tags help"> <div class="tags help">
{% for (s, e) in slot_suggestions %} {% for (s, e) in slot_suggestions %}
<span class="tag">{{ s }} - {{ e }}</span> <span class="tag">{{ s }} - {{ e }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
</div> </div>
<div class="field"> <div class="field">
<input class="input" type="time" id="till" name="till" value='{{ times.1 }}' {{ <input class="input" type="time" id="till" name="till" value='{{ times.1.format("%R") }}' {{
whole_day_selected|cond_show("disabled") }} {{ whole_day_selected|invert|ref|cond_show("required") }}> whole_day_selected|cond_show("disabled") }} {{ whole_day_selected|invert|ref|cond_show("required") }}>
</div> </div>
</div> </div>