Compare commits

..

3 Commits

17 changed files with 532 additions and 156 deletions

View File

@ -0,0 +1,114 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n event.id AS eventId,\n event.startTimestamp,\n event.endTimestamp,\n event.name,\n event.locationId,\n event.voluntaryWachhabender,\n event.voluntaryFuehrungsassistent,\n event.amountOfPosten,\n event.clothing,\n event.canceled,\n event.note,\n location.id,\n location.name AS locationName,\n location.areaId AS locationAreaId,\n clothing.id AS clothingId,\n clothing.name AS clothingName\n FROM event\n JOIN location ON event.locationId = location.id\n JOIN clothing ON event.clothing = clothing.id\n WHERE starttimestamp::date >= $1\n AND starttimestamp::date <= $2\n AND location.areaId = $3;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "starttimestamp",
"type_info": "Timestamptz"
},
{
"ordinal": 2,
"name": "endtimestamp",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "locationid",
"type_info": "Int4"
},
{
"ordinal": 5,
"name": "voluntarywachhabender",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "voluntaryfuehrungsassistent",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "amountofposten",
"type_info": "Int2"
},
{
"ordinal": 8,
"name": "clothing",
"type_info": "Int4"
},
{
"ordinal": 9,
"name": "canceled",
"type_info": "Bool"
},
{
"ordinal": 10,
"name": "note",
"type_info": "Text"
},
{
"ordinal": 11,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 12,
"name": "locationname",
"type_info": "Text"
},
{
"ordinal": 13,
"name": "locationareaid",
"type_info": "Int4"
},
{
"ordinal": 14,
"name": "clothingid",
"type_info": "Int4"
},
{
"ordinal": 15,
"name": "clothingname",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Date",
"Date",
"Int4"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
false,
false,
false
]
},
"hash": "10b4b80f351b66ac5e778a3031288ac5dc66efd0a66b38b7e30f4c954df91bdf"
}

View File

@ -0,0 +1,48 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n availability.id,\n availability.userId,\n availability.startTimestamp,\n availability.endTimestamp,\n availability.comment\n FROM availability\n WHERE availability.userId = $1\n AND availability.starttimestamp::date >= $2\n AND availability.endtimestamp::date <= $3;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "starttimestamp",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "endtimestamp",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "comment",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Int4",
"Date",
"Date"
]
},
"nullable": [
false,
false,
false,
false,
true
]
},
"hash": "f60053118df6a791d31fa258ee3737881f8f97ca41cbebd92eb22c967292d2ee"
}

View File

@ -341,6 +341,46 @@ impl Availability {
Ok(availabilities) Ok(availabilities)
} }
pub async fn read_by_user_and_daterange(
pool: &PgPool,
user_id: i32,
date_range: (&NaiveDate, &NaiveDate),
) -> Result<Vec<Availability>> {
let records = query!(
r##"
SELECT
availability.id,
availability.userId,
availability.startTimestamp,
availability.endTimestamp,
availability.comment
FROM availability
WHERE availability.userId = $1
AND availability.starttimestamp::date >= $2
AND availability.endtimestamp::date <= $3;
"##,
user_id,
date_range.0,
date_range.1
)
.fetch_all(pool)
.await?;
let availabilities = records
.iter()
.map(|r| Availability {
id: r.id,
user_id: r.userid,
user: None,
start: r.starttimestamp.naive_utc(),
end: r.endtimestamp.naive_utc(),
comment: r.comment.clone(),
})
.collect();
Ok(availabilities)
}
pub async fn find_adjacent_by_time_for_user( pub async fn find_adjacent_by_time_for_user(
pool: &PgPool, pool: &PgPool,
start: &NaiveDateTime, start: &NaiveDateTime,

View File

@ -2,9 +2,11 @@ use chrono::{Days, NaiveDateTime};
use sqlx::PgPool; use sqlx::PgPool;
use super::Availability; use super::Availability;
use crate::{validation::{ use crate::{
start_date_time_lies_before_end_date_time, AsyncValidate, AsyncValidateError END_OF_DAY, START_OF_DAY,
}, END_OF_DAY, START_OF_DAY}; models::Assignment,
validation::{AsyncValidate, AsyncValidateError, start_date_time_lies_before_end_date_time},
};
pub struct AvailabilityChangeset { pub struct AvailabilityChangeset {
pub time: (NaiveDateTime, NaiveDateTime), pub time: (NaiveDateTime, NaiveDateTime),
@ -14,7 +16,7 @@ pub struct AvailabilityChangeset {
pub struct AvailabilityContext<'a> { pub struct AvailabilityContext<'a> {
pub pool: &'a PgPool, pub pool: &'a PgPool,
pub user_id: i32, pub user_id: i32,
pub availability_to_get_edited: Option<i32>, pub availability: Option<i32>,
} }
impl<'a> AsyncValidate<'a> for AvailabilityChangeset { impl<'a> AsyncValidate<'a> for AvailabilityChangeset {
@ -28,48 +30,69 @@ impl<'a> AsyncValidate<'a> for AvailabilityChangeset {
Availability::read_by_user_and_date(context.pool, context.user_id, &self.time.0.date()) Availability::read_by_user_and_date(context.pool, context.user_id, &self.time.0.date())
.await?; .await?;
if let Some(existing) = context.availability_to_get_edited { start_date_time_lies_before_end_date_time(&self.time.0, &self.time.1)?;
if let Some(availability) = context.availability {
existing_availabilities = existing_availabilities existing_availabilities = existing_availabilities
.into_iter() .into_iter()
.filter(|a| a.id != existing) .filter(|a| a.id != availability)
.collect(); .collect();
time_is_not_already_assigned(&self.time, availability, context.pool).await?;
} }
time_is_not_already_made_available(&self.time, &existing_availabilities)?; if !existing_availabilities.is_empty() {
start_date_time_lies_before_end_date_time(&self.time.0, &self.time.1)?; time_is_not_already_made_available(&self.time, &existing_availabilities)?;
}
Ok(()) Ok(())
} }
} }
fn time_is_not_already_made_available( fn time_is_not_already_made_available(
value: &(NaiveDateTime, NaiveDateTime), (start, end): &(NaiveDateTime, NaiveDateTime),
existing_availabilities: &Vec<Availability>, existing_availabilities: &Vec<Availability>,
) -> Result<(), AsyncValidateError> { ) -> Result<(), AsyncValidateError> {
if existing_availabilities.is_empty() {
return Ok(());
}
let free_slots = find_free_date_time_slots(existing_availabilities); let free_slots = find_free_date_time_slots(existing_availabilities);
if free_slots.is_empty() { if free_slots.is_empty() {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"cant create a availability as every time slot is already filled", "Verfügbarkeit kann nicht erstellt werden, da bereits alle Zeiträume verfügbar gemacht wurden.",
)); ));
} }
let free_block_found_for_start = free_slots.iter().any(|s| s.0 <= value.0 && s.1 >= value.0); let free_block_found_for_start = free_slots.iter().any(|s| s.0 <= *start && s.1 >= *start);
let free_block_found_for_end = free_slots.iter().any(|s| s.0 <= value.1 && s.1 >= value.1); let free_block_found_for_end = free_slots.iter().any(|s| s.0 <= *end && s.1 >= *end);
let is_already_present_as_is = existing_availabilities
.iter()
.any(|a| a.start == *start && a.end == a.end);
if !free_block_found_for_start || !free_block_found_for_end { if !free_block_found_for_start || !free_block_found_for_end || is_already_present_as_is {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"cant create availability as there exists already a availability with the desired time", "Verfügbarkeit kann nicht erstellt werden, da eine vorhandene Verfügbarkeit überschnitten würde.",
)); ));
} }
Ok(()) Ok(())
} }
async fn time_is_not_already_assigned(
(start, end): &(NaiveDateTime, NaiveDateTime),
availability: i32,
pool: &PgPool,
) -> Result<(), AsyncValidateError> {
let existing_assignments = Assignment::read_all_by_availability(pool, availability).await?;
for a in existing_assignments {
if a.start < *start || a.end > *end {
return Err(AsyncValidateError::new(
"Verfügbarkeitszeit kann nicht verkleinert werden, da bereits eine Planung für diese Zeit existiert.",
));
}
}
Ok(())
}
pub fn find_free_date_time_slots( pub fn find_free_date_time_slots(
availabilities: &[Availability], availabilities: &[Availability],
) -> Vec<(NaiveDateTime, NaiveDateTime)> { ) -> Vec<(NaiveDateTime, NaiveDateTime)> {

View File

@ -95,6 +95,73 @@ impl Event {
Ok(events) Ok(events)
} }
pub async fn read_all_by_daterange_and_area_including_location(
pool: &PgPool,
date_range: (&NaiveDate, &NaiveDate),
area_id: i32,
) -> Result<Vec<Event>> {
let records = query!(
r#"
SELECT
event.id AS eventId,
event.startTimestamp,
event.endTimestamp,
event.name,
event.locationId,
event.voluntaryWachhabender,
event.voluntaryFuehrungsassistent,
event.amountOfPosten,
event.clothing,
event.canceled,
event.note,
location.id,
location.name AS locationName,
location.areaId AS locationAreaId,
clothing.id AS clothingId,
clothing.name AS clothingName
FROM event
JOIN location ON event.locationId = location.id
JOIN clothing ON event.clothing = clothing.id
WHERE starttimestamp::date >= $1
AND starttimestamp::date <= $2
AND location.areaId = $3;
"#,
date_range.0,
date_range.1,
area_id
)
.fetch_all(pool)
.await?;
let events = records
.into_iter()
.map(|record| Event {
id: record.eventid,
start: record.starttimestamp.naive_utc(),
end: record.endtimestamp.naive_utc(),
name: record.name.to_string(),
location_id: record.locationid,
location: Some(Location {
id: record.locationid,
name: record.locationname.to_string(),
area_id: record.locationareaid,
area: None,
}),
voluntary_wachhabender: record.voluntarywachhabender,
voluntary_fuehrungsassistent: record.voluntaryfuehrungsassistent,
amount_of_posten: record.amountofposten,
clothing: Clothing {
id: record.clothingid,
name: record.clothingname,
},
canceled: record.canceled,
note: record.note,
})
.collect();
Ok(events)
}
pub async fn read_by_id_including_location(pool: &PgPool, id: i32) -> Result<Option<Event>> { pub async fn read_by_id_including_location(pool: &PgPool, id: i32) -> Result<Option<Event>> {
let record = query!( let record = query!(
r#" r#"

View File

@ -5,8 +5,8 @@ mod validation_trait;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
pub use email::email_is_valid; pub use email::email_is_valid;
pub use error::AsyncValidateError; pub use error::AsyncValidateError;
pub use validation_trait::AsyncValidate;
use sqlx::PgPool; use sqlx::PgPool;
pub use validation_trait::AsyncValidate;
pub struct DbContext<'a> { pub struct DbContext<'a> {
pub pool: &'a PgPool, pub pool: &'a PgPool,
@ -24,7 +24,7 @@ pub fn start_date_time_lies_before_end_date_time(
) -> Result<(), AsyncValidateError> { ) -> Result<(), AsyncValidateError> {
if start >= end { if start >= end {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"endtime can't lie before starttime", "Ende kann nicht vor dem Beginn liegen.",
)); ));
} }

View File

@ -0,0 +1,158 @@
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use chrono::{NaiveDate, Utc};
use serde::Deserialize;
use sqlx::PgPool;
use crate::{
filters,
utils::{
event_planning_template::generate_vehicles_assigned_and_available,
ApplicationError,
DateTimeFormat::{DayMonthYearHourMinute, HourMinute, WeekdayDayMonthYear},
TemplateResponse,
},
};
use brass_db::models::{
find_free_date_time_slots, Area, Assignment, Availability, Event, Function, Role, User, Vehicle,
};
#[derive(Deserialize)]
pub struct CalendarQuery {
date: Option<NaiveDate>,
area: Option<i32>,
}
#[derive(Template)]
#[template(path = "calendar.html")]
struct CalendarTemplate {
user: User,
user_can_create_availability: bool,
date: NaiveDate,
selected_area: Option<i32>,
areas: Vec<Area>,
events_and_assignments: Vec<(
Event,
Vec<String>,
Option<String>,
Option<String>,
Vec<Vehicle>,
)>,
availabilities: Vec<Availability>,
}
#[actix_web::get("/calendar")]
async fn get(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
query: web::Query<CalendarQuery>,
) -> Result<impl Responder, ApplicationError> {
let date = match query.date {
Some(given_date) => given_date,
None => Utc::now().date_naive(),
};
let areas = Area::read_all(pool.get_ref()).await?;
let selected_area = match query.area {
Some(id) => {
if !areas.iter().any(|a| a.id == id) {
return Ok(HttpResponse::BadRequest().finish());
}
Some(id)
}
None => None,
};
let availabilities = Availability::read_by_date_and_area_including_user(
pool.get_ref(),
date,
query.area.unwrap_or(user.area_id),
)
.await?;
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_availability = availabilities_from_user.is_empty()
|| !find_free_date_time_slots(&availabilities_from_user).is_empty();
//println!("{} || {} || {} = {user_can_create_availability}", availabilities_from_user.is_empty(),
// !only_one_availability_exists_and_is_whole_day(&availabilities_from_user),
// !find_free_time_slots(&availabilities_from_user).is_empty());
let mut events_and_assignments = Vec::new();
for e in Event::read_all_by_date_and_area_including_location(
pool.get_ref(),
date,
query.area.unwrap_or(user.area_id),
)
.await?
.into_iter()
{
let assignments = Assignment::read_all_by_event(pool.get_ref(), e.id).await?;
let (posten, rest): (Vec<Assignment>, Vec<Assignment>) = assignments
.into_iter()
.partition(|a| a.function == Function::Posten);
let (wachhabender, fuehrungsassistent): (Vec<Assignment>, Vec<Assignment>) = rest
.into_iter()
.partition(|a| a.function == Function::Wachhabender);
let (assigned_vehicle, _) = generate_vehicles_assigned_and_available(&pool, &e).await?;
events_and_assignments.push((
e,
posten
.into_iter()
.map(|p| {
availabilities
.iter()
.find(|a| a.id == p.availability_id)
.unwrap()
.user
.as_ref()
.unwrap()
.name
.clone()
})
.collect(),
fuehrungsassistent.first().map(|fa| {
availabilities
.iter()
.find(|a| a.id == fa.availability_id)
.unwrap()
.user
.as_ref()
.unwrap()
.name
.clone()
}),
wachhabender.first().map(|wh| {
availabilities
.iter()
.find(|a| a.id == wh.availability_id)
.unwrap()
.user
.as_ref()
.unwrap()
.name
.clone()
}),
assigned_vehicle,
));
}
let template = CalendarTemplate {
user: user.into_inner(),
user_can_create_availability,
date,
selected_area,
areas,
events_and_assignments,
availabilities,
};
Ok(template.to_response()?)
}

View File

@ -1,43 +1,23 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, Responder};
use askama::Template; use askama::Template;
use chrono::{NaiveDate, Utc}; use chrono::{Months, Utc};
use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
filters, filters,
utils::{ utils::{
event_planning_template::generate_vehicles_assigned_and_available,
ApplicationError, ApplicationError,
DateTimeFormat::{DayMonthYearHourMinute, HourMinute, WeekdayDayMonthYear}, DateTimeFormat::{DayMonthYearHourMinute, HourMinute, WeekdayDayMonthYear},
TemplateResponse, TemplateResponse,
}, },
}; };
use brass_db::models::{ use brass_db::models::{Assignment, Availability, Event, Function, Role, User};
find_free_date_time_slots, Area, Assignment, Availability, Event, Function, Role, User, Vehicle,
};
#[derive(Deserialize)]
pub struct CalendarQuery {
date: Option<NaiveDate>,
area: Option<i32>,
}
#[derive(Template)] #[derive(Template)]
#[template(path = "index.html")] #[template(path = "overview.html")]
struct CalendarTemplate { struct OverviewTemplate {
user: User, user: User,
user_can_create_availability: bool, events: Vec<Event>,
date: NaiveDate,
selected_area: Option<i32>,
areas: Vec<Area>,
events_and_assignments: Vec<(
Event,
Vec<String>,
Option<String>,
Option<String>,
Vec<Vehicle>,
)>,
availabilities: Vec<Availability>, availabilities: Vec<Availability>,
} }
@ -45,112 +25,24 @@ struct CalendarTemplate {
async fn get( async fn get(
user: web::ReqData<User>, user: web::ReqData<User>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
query: web::Query<CalendarQuery>,
) -> Result<impl Responder, ApplicationError> { ) -> Result<impl Responder, ApplicationError> {
let date = match query.date { let today = Utc::now().date_naive();
Some(given_date) => given_date, let next_month = today.checked_add_months(Months::new(1)).unwrap();
None => Utc::now().date_naive(),
};
let areas = Area::read_all(pool.get_ref()).await?; let events = Event::read_all_by_daterange_and_area_including_location(
let selected_area = match query.area {
Some(id) => {
if !areas.iter().any(|a| a.id == id) {
return Ok(HttpResponse::BadRequest().finish());
}
Some(id)
}
None => None,
};
let availabilities = Availability::read_by_date_and_area_including_user(
pool.get_ref(), pool.get_ref(),
date, (&today, &next_month),
query.area.unwrap_or(user.area_id), user.area_id,
) )
.await?; .await?;
let availabilities_from_user = let availabilities =
Availability::read_by_user_and_date(pool.get_ref(), user.id, &date).await?; Availability::read_by_user_and_daterange(pool.get_ref(), user.id, (&today, &next_month))
//println!("{availabilities_from_user:#?}"); .await?;
let user_can_create_availability = availabilities_from_user.is_empty() let template = OverviewTemplate {
|| !find_free_date_time_slots(&availabilities_from_user).is_empty();
//println!("{} || {} || {} = {user_can_create_availability}", availabilities_from_user.is_empty(),
// !only_one_availability_exists_and_is_whole_day(&availabilities_from_user),
// !find_free_time_slots(&availabilities_from_user).is_empty());
let mut events_and_assignments = Vec::new();
for e in Event::read_all_by_date_and_area_including_location(
pool.get_ref(),
date,
query.area.unwrap_or(user.area_id),
)
.await?
.into_iter()
{
let assignments = Assignment::read_all_by_event(pool.get_ref(), e.id).await?;
let (posten, rest): (Vec<Assignment>, Vec<Assignment>) = assignments
.into_iter()
.partition(|a| a.function == Function::Posten);
let (wachhabender, fuehrungsassistent): (Vec<Assignment>, Vec<Assignment>) = rest
.into_iter()
.partition(|a| a.function == Function::Wachhabender);
let (assigned_vehicle, _) = generate_vehicles_assigned_and_available(&pool, &e).await?;
events_and_assignments.push((
e,
posten
.into_iter()
.map(|p| {
availabilities
.iter()
.find(|a| a.id == p.availability_id)
.unwrap()
.user
.as_ref()
.unwrap()
.name
.clone()
})
.collect(),
fuehrungsassistent.first().map(|fa| {
availabilities
.iter()
.find(|a| a.id == fa.availability_id)
.unwrap()
.user
.as_ref()
.unwrap()
.name
.clone()
}),
wachhabender.first().map(|wh| {
availabilities
.iter()
.find(|a| a.id == wh.availability_id)
.unwrap()
.user
.as_ref()
.unwrap()
.name
.clone()
}),
assigned_vehicle,
));
}
let template = CalendarTemplate {
user: user.into_inner(), user: user.into_inner(),
user_can_create_availability, events,
date,
selected_area,
areas,
events_and_assignments,
availabilities, availabilities,
}; };

View File

@ -9,6 +9,7 @@ use crate::{
use brass_db::models::{Role, User}; use brass_db::models::{Role, User};
pub mod delete; pub mod delete;
pub mod get_calendar;
pub mod get_new; pub mod get_new;
pub mod get_overview; pub mod get_overview;
pub mod get_update; pub mod get_update;

View File

@ -1,4 +1,5 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder}; use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use maud::html;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
@ -22,7 +23,7 @@ pub async fn post(
let context = AvailabilityContext { let context = AvailabilityContext {
pool: pool.get_ref(), pool: pool.get_ref(),
user_id: user.id, user_id: user.id,
availability_to_get_edited: None, availability: None,
}; };
let mut changeset = AvailabilityChangeset { let mut changeset = AvailabilityChangeset {
@ -31,7 +32,13 @@ pub async fn post(
}; };
if let Err(e) = changeset.validate_with_context(&context).await { if let Err(e) = changeset.validate_with_context(&context).await {
return Ok(HttpResponse::BadRequest().body(e.to_string())); let error_message = html! {
svg class="icon is-small" {
use href="/static/feather-sprite.svg#alert-triangle" {}
}
" " (e)
};
return Ok(HttpResponse::UnprocessableEntity().body(error_message.into_string()));
}; };
if let Some(a) = if let Some(a) =

View File

@ -1,4 +1,5 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder}; use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use maud::html;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
@ -31,7 +32,7 @@ pub async fn post(
let context = AvailabilityContext { let context = AvailabilityContext {
pool: pool.get_ref(), pool: pool.get_ref(),
user_id: user.id, user_id: user.id,
availability_to_get_edited: Some(availability.id), availability: Some(availability.id),
}; };
let mut changeset = AvailabilityChangeset { let mut changeset = AvailabilityChangeset {
@ -40,7 +41,13 @@ pub async fn post(
}; };
if let Err(e) = changeset.validate_with_context(&context).await { if let Err(e) = changeset.validate_with_context(&context).await {
return Ok(HttpResponse::BadRequest().body(e.to_string())); let error_message = html! {
svg class="icon is-small" {
use href="/static/feather-sprite.svg#alert-triangle" {}
}
" " (e)
};
return Ok(HttpResponse::UnprocessableEntity().body(error_message.into_string()));
}; };
if let Some(a) = Availability::find_adjacent_by_time_for_user( if let Some(a) = Availability::find_adjacent_by_time_for_user(

View File

@ -56,10 +56,11 @@ pub fn init(cfg: &mut ServiceConfig) {
cfg.service(availability::delete::delete); cfg.service(availability::delete::delete);
cfg.service(availability::get_new::get); cfg.service(availability::get_new::get);
cfg.service(availability::get_overview::get); cfg.service(availability::get_calendar::get);
cfg.service(availability::get_update::get); cfg.service(availability::get_update::get);
cfg.service(availability::post_new::post); cfg.service(availability::post_new::post);
cfg.service(availability::post_update::post); cfg.service(availability::post_update::post);
cfg.service(availability::get_overview::get);
cfg.service(events::put_cancelation::put_cancel); cfg.service(events::put_cancelation::put_cancel);
cfg.service(events::put_cancelation::put_uncancel); cfg.service(events::put_cancelation::put_uncancel);

View File

@ -4,7 +4,8 @@
<section class="section"> <section class="section">
<div class="container"> <div class="container">
{% let is_edit = id.is_some() %} {% let is_edit = id.is_some() %}
<form method="post" action="/availability/{% if is_edit %}edit/{{ id.unwrap() }}{% else %}new{% endif %}"> <form hx-post="/availability/{% if is_edit %}edit/{{ id.unwrap() }}{% else %}new{% endif %}" hx-target="body"
hx-target-422="#error">
<h1 class="title">{% if is_edit %}Bearbeite{% else %}Neue{% endif %} Vefügbarkeit für {{ <h1 class="title">{% if is_edit %}Bearbeite{% else %}Neue{% endif %} Vefügbarkeit für {{
date|fmt_date(WeekdayDayMonthYear) }}</h1> date|fmt_date(WeekdayDayMonthYear) }}</h1>
@ -18,7 +19,7 @@
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
<input class="input" type="time" name="starttime" required {{ start|insert_time_value|safe }} <input class="input" type="time" name="starttime" required {{ start|insert_time_value|safe }}
_="on change put the value of me into #st"> _="on change put the value of me into #st then put '' into #error">
{% if slot_suggestions.len() > 0 %} {% 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">
@ -32,7 +33,7 @@
</div> </div>
<div class="field"> <div class="field">
<input class="input" type="time" name="endtime" required {{ end|insert_time_value|safe }} <input class="input" type="time" name="endtime" required {{ end|insert_time_value|safe }}
_='on change put the value of me into #et _='on change put the value of me into #et then put "" into #error
then if (value of the previous <input/>) is greater than (value of me) then if (value of the previous <input/>) is greater than (value of me)
then set the value of #enddate to "{{ datetomorrow }}" then set the value of #enddate to "{{ datetomorrow }}"
then put "{{ datetomorrow|fmt_date(WeekdayDayMonth) }}" into #ed then put "{{ datetomorrow|fmt_date(WeekdayDayMonth) }}" into #ed
@ -53,13 +54,15 @@
<label class="radio"> <label class="radio">
<input type="radio" name="isovernight" {{ is_overnight|invert|ref|cond_show("checked")|safe }} <input type="radio" name="isovernight" {{ is_overnight|invert|ref|cond_show("checked")|safe }}
_='on click set the value of #enddate to "{{ date }}" _='on click set the value of #enddate to "{{ date }}"
then put "{{ date|fmt_date(WeekdayDayMonth) }}" into #ed'> then put "{{ date|fmt_date(WeekdayDayMonth) }}" into #ed
then put "" into #error'>
am selben Tag am selben Tag
</label> </label>
<label class="radio ml-3"> <label class="radio ml-3">
<input type="radio" id="radionextday" name="isovernight" {{ is_overnight|cond_show("checked")|safe }} <input type="radio" id="radionextday" name="isovernight" {{ is_overnight|cond_show("checked")|safe }}
_='on click set the value of #enddate to "{{ datetomorrow }}" _='on click set the value of #enddate to "{{ datetomorrow }}"
then put "{{ datetomorrow|fmt_date(WeekdayDayMonth) }}" into #ed'> then put "{{ datetomorrow|fmt_date(WeekdayDayMonth) }}" into #ed
then put "" into #error'>
am Tag darauf am Tag darauf
</label> </label>
</div> </div>
@ -90,6 +93,7 @@
<span id="et">{{ end_time|fmt_time(HourMinute) }}</span> <span id="et">{{ end_time|fmt_time(HourMinute) }}</span>
Uhr Uhr
</p> </p>
<p id="error" class="help is-danger"></p>
</div> </div>
</div> </div>
</div> </div>
@ -99,7 +103,7 @@
<div class="field-body"> <div class="field-body">
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
<button class="button is-success"> <button class="button is-success" _="on click put '' into #error">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" /> <use href="/static/feather-sprite.svg#check-circle" />
</svg> </svg>

View File

@ -18,6 +18,10 @@
<div class="navbar-menu" id="navMenu"> <div class="navbar-menu" id="navMenu">
<div hx-boost="true" class="navbar-start"> <div hx-boost="true" class="navbar-start">
<a href="/" class="navbar-item"> <a href="/" class="navbar-item">
Übersicht
</a>
<a href="/calendar" class="navbar-item">
Kalender Kalender
</a> </a>

View File

@ -0,0 +1,9 @@
{% extends "nav.html" %}
{% block content %}
<section id="progress" class="section">
<div class="container">
Übersicht
</div>
</section>
{% endblock %}

View File

@ -3,7 +3,8 @@
{% block content %} {% block content %}
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<form hx-post="/users/{% if let Some(id) = id %}edit/{{ id }}{% else %}new{% endif %}" hx-target-422="find p"> <form hx-post="/users/{% if let Some(id) = id %}edit/{{ id }}{% else %}new{% endif %}" hx-target="body"
hx-target-422="find p">
<h1 class="title"> <h1 class="title">
{% if let Some(name) = name %}Nutzer '{{ name }}' bearbeiten{% else %}Neuen Nutzer anlegen{% endif %} {% if let Some(name) = name %}Nutzer '{{ name }}' bearbeiten{% else %}Neuen Nutzer anlegen{% endif %}
</h1> </h1>