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::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::{END_OF_DAY, START_OF_DAY};
#[derive(Deserialize)]
struct AvailabilityNewQuery {
@ -27,20 +28,25 @@ pub async fn get(
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 user_can_create_availabillity =
!(only_one_availability_exists_and_is_whole_day(&availabilities_from_user)
|| free_slots.len() == 0);
let user_can_create_availabillity = availabilities_from_user.len() == 0
|| !only_one_availability_exists_and_is_whole_day(&availabilities_from_user)
|| free_slots.len() > 0;
if !user_can_create_availabillity {
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 {
user: user.into_inner(),
date: query.date,
whole_day_selected: query.whole_day.unwrap_or(true),
time_selection,
id: None,
time: None,
comment: None,
slot_suggestions: free_slots,
};

View File

@ -71,10 +71,15 @@ async fn get(
let availabilities_from_user =
Availability::read_by_user_and_date(pool.get_ref(), user.id, &date).await?;
println!("{availabilities_from_user:#?}");
let user_can_create_availabillity =
!(only_one_availability_exists_and_is_whole_day(&availabilities_from_user)
|| find_free_time_slots(&availabilities_from_user).len() == 0);
let 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;
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();
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);
}
let whole_day_selected = query.whole_day.unwrap_or(availability.time == AvailabilityTime::WholeDay);
let suggestions = if let AvailabilityTime::Temporarily(start, end) = availability.time {
let availabilities = Availability::read_by_user_and_date(
pool.get_ref(),
@ -51,14 +49,20 @@ pub async fn get(
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 {
user: user.into_inner(),
date: availability.date,
id: Some(path.id),
time: Some(availability.time),
time_selection,
comment: availability.comment.as_deref(),
slot_suggestions: suggestions,
whole_day_selected
};
Ok(HttpResponse::Ok().body(template.render()?))

View File

@ -2,7 +2,7 @@ use chrono::{NaiveDate, NaiveTime};
use rinja::Template;
use crate::filters;
use crate::models::{AvailabilityTime, Role, User};
use crate::models::{Availability, AvailabilityChangeset, AvailabilityTime, Role, User};
pub mod delete;
pub mod get_new;
@ -17,8 +17,30 @@ struct NewOrEditAvailabilityTemplate<'a> {
user: User,
date: NaiveDate,
id: Option<i32>,
time: Option<AvailabilityTime>,
whole_day_selected: bool,
time_selection: AvailabilityTime,
comment: Option<&'a str>,
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 chrono::{NaiveDate, NaiveTime};
use garde::Validate;
use serde::Deserialize;
use sqlx::PgPool;
use crate::{
models::{Availability, User},
endpoints::availability::find_adjacend_availability,
models::{Availability, AvailabilityChangeset, AvailabilityContext, AvailabilityTime, User},
utils::{self, ApplicationError},
};
@ -22,16 +24,43 @@ pub async fn post(
pool: web::Data<PgPool>,
form: web::Form<AvailabillityForm>,
) -> Result<impl Responder, ApplicationError> {
// TODO: create and validate changeset
//Availability::create(
// pool.get_ref(),
// user.id,
// form.date,
// form.from,
// form.till,
// form.comment.clone(),
//)
//.await?;
let existing_availabilities =
Availability::read_by_user_and_date(pool.get_ref(), user.id, &form.date).await?;
let context = AvailabilityContext {
existing_availabilities: existing_availabilities.clone(),
};
let time = if form.from.is_some() && form.till.is_some() {
AvailabilityTime::Temporarily(form.from.unwrap(), form.till.unwrap())
} else {
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);
Ok(HttpResponse::Found()

View File

@ -3,7 +3,7 @@ use garde::Validate;
use sqlx::PgPool;
use crate::{
endpoints::{availability::post_new::AvailabillityForm, IdPath},
endpoints::{availability::{find_adjacend_availability, post_new::AvailabillityForm}, IdPath},
models::{Availability, AvailabilityChangeset, AvailabilityContext, AvailabilityTime, User},
utils::{self, ApplicationError},
};
@ -44,22 +44,7 @@ pub async fn post(
return Ok(HttpResponse::BadRequest().body(e.to_string()));
};
let mut adjacent = None;
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 {
if let Some(a) = find_adjacend_availability(&changeset, Some(availability.id), &existing_availabilities) {
let (changeset_start, changeset_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"));
}
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(
"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 {
return Vec::new();
}
//println!("zeiten {times:?}");
println!("zeiten {times:?}");
times.sort();

View File

@ -12,6 +12,7 @@
{% endif %}
<input type="hidden" name="date" value="{{ date }}">
{% let whole_day_selected = time_selection == AvailabilityTime::WholeDay %}
<div class="field is-horizontal">
<div class="field-label">
@ -46,25 +47,21 @@
<label class="label">Von - Bis</label>
</div>
<div class="field-body">
{% let times = ("00:00".to_string(), "23:59".to_string()) -%}
{% if let Some(times) = time -%}
{% if let AvailabilityTime::Temporarily(start, end) = times -%}
{% let times = (start.to_string(), end.to_string()) -%}
{% endif -%}
{% endif -%}
{% let times = time_selection.time_tuple() -%}
<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") }}>
{% if slot_suggestions.len() > 0 %}
<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>
{% endif %}
</div>
<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") }}>
</div>
</div>