feat: redesign availability input
This commit is contained in:
parent
8b61bb37a8
commit
a6b12d9bf2
@ -1,12 +1,10 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use chrono::NaiveDate;
|
||||
use chrono::{Days, NaiveDate, NaiveTime};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::endpoints::availability::NewOrEditAvailabilityTemplate;
|
||||
use crate::models::{
|
||||
find_free_date_time_slots, Availability, User,
|
||||
};
|
||||
use crate::models::{find_free_date_time_slots, Availability, User};
|
||||
use crate::utils::{ApplicationError, TemplateResponse};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -24,8 +22,8 @@ pub async fn get(
|
||||
Availability::read_by_user_and_date(pool.get_ref(), user.id, &query.date).await?;
|
||||
let slot_suggestions = find_free_date_time_slots(&availabilities_from_user);
|
||||
|
||||
let user_can_create_availabillity = availabilities_from_user.is_empty()
|
||||
|| !slot_suggestions.is_empty();
|
||||
let user_can_create_availabillity =
|
||||
availabilities_from_user.is_empty() || !slot_suggestions.is_empty();
|
||||
|
||||
if !user_can_create_availabillity {
|
||||
return Ok(HttpResponse::BadRequest().finish());
|
||||
@ -34,11 +32,13 @@ pub async fn get(
|
||||
let template = NewOrEditAvailabilityTemplate {
|
||||
user: user.into_inner(),
|
||||
date: query.date,
|
||||
enddate: None,
|
||||
id: None,
|
||||
start: None,
|
||||
end: None,
|
||||
start: Some(NaiveTime::from_hms_opt(10, 0, 0).unwrap()),
|
||||
end: Some(NaiveTime::from_hms_opt(20, 0, 0).unwrap()),
|
||||
comment: None,
|
||||
slot_suggestions,
|
||||
datetomorrow: query.date.checked_add_days(Days::new(1)).unwrap(),
|
||||
};
|
||||
|
||||
Ok(template.to_response()?)
|
||||
|
@ -1,4 +1,5 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use chrono::Days;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
@ -33,11 +34,17 @@ pub async fn get(
|
||||
let template = NewOrEditAvailabilityTemplate {
|
||||
user: user.into_inner(),
|
||||
date: availability.start.date(),
|
||||
enddate: Some(availability.end.date()),
|
||||
id: Some(path.id),
|
||||
start: Some(availability.start.time()),
|
||||
end: Some(availability.end),
|
||||
end: Some(availability.end.time()),
|
||||
comment: availability.comment.as_deref(),
|
||||
slot_suggestions
|
||||
slot_suggestions,
|
||||
datetomorrow: availability
|
||||
.start
|
||||
.date()
|
||||
.checked_add_days(Days::new(1))
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
Ok(template.to_response()?)
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::filters;
|
||||
use askama::Template;
|
||||
use chrono::{Days, NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::models::{Availability, AvailabilityChangeset, Role, User};
|
||||
|
||||
@ -15,11 +17,22 @@ pub mod post_update;
|
||||
struct NewOrEditAvailabilityTemplate<'a> {
|
||||
user: User,
|
||||
date: NaiveDate,
|
||||
enddate: Option<NaiveDate>,
|
||||
id: Option<i32>,
|
||||
start: Option<NaiveTime>,
|
||||
end: Option<NaiveDateTime>,
|
||||
end: Option<NaiveTime>,
|
||||
comment: Option<&'a str>,
|
||||
slot_suggestions: Vec<(NaiveDateTime, NaiveDateTime)>,
|
||||
datetomorrow: NaiveDate
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AvailabillityForm {
|
||||
pub startdate: NaiveDate,
|
||||
pub enddate: NaiveDate,
|
||||
pub starttime: NaiveTime,
|
||||
pub endtime: NaiveTime,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
fn find_adjacend_availability<'a>(
|
||||
|
@ -1,23 +1,13 @@
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use garde::Validate;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
endpoints::availability::find_adjacend_availability,
|
||||
endpoints::availability::{find_adjacend_availability, AvailabillityForm},
|
||||
models::{Availability, AvailabilityChangeset, AvailabilityContext, User},
|
||||
utils::{self, ApplicationError},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AvailabillityForm {
|
||||
pub date: NaiveDate,
|
||||
pub from: NaiveTime,
|
||||
pub till: NaiveDateTime,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
#[actix_web::post("/availabillity/new")]
|
||||
pub async fn post(
|
||||
user: web::ReqData<User>,
|
||||
@ -25,13 +15,16 @@ pub async fn post(
|
||||
form: web::Form<AvailabillityForm>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
let existing_availabilities =
|
||||
Availability::read_by_user_and_date(pool.get_ref(), user.id, &form.date).await?;
|
||||
Availability::read_by_user_and_date(pool.get_ref(), user.id, &form.startdate).await?;
|
||||
let context = AvailabilityContext {
|
||||
existing_availabilities: existing_availabilities.clone(),
|
||||
};
|
||||
|
||||
let start = form.startdate.and_time(form.starttime);
|
||||
let end = form.enddate.and_time(form.endtime);
|
||||
|
||||
let mut changeset = AvailabilityChangeset {
|
||||
time: (form.date.and_time(form.from), form.till),
|
||||
time: (start, end),
|
||||
comment: form.comment.clone(),
|
||||
};
|
||||
|
||||
@ -55,7 +48,7 @@ pub async fn post(
|
||||
Availability::create(pool.get_ref(), user.id, changeset).await?;
|
||||
}
|
||||
|
||||
let url = utils::get_return_url_for_date(&form.date);
|
||||
let url = utils::get_return_url_for_date(&form.startdate);
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header((LOCATION, url.clone()))
|
||||
.insert_header(("HX-LOCATION", url))
|
||||
|
@ -4,7 +4,7 @@ use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
endpoints::{
|
||||
availability::{find_adjacend_availability, post_new::AvailabillityForm},
|
||||
availability::{find_adjacend_availability, AvailabillityForm},
|
||||
IdPath,
|
||||
},
|
||||
models::{Availability, AvailabilityChangeset, AvailabilityContext, User},
|
||||
@ -37,8 +37,11 @@ pub async fn post(
|
||||
existing_availabilities: existing_availabilities.clone(),
|
||||
};
|
||||
|
||||
let start = form.startdate.and_time(form.starttime);
|
||||
let end = form.enddate.and_time(form.endtime);
|
||||
|
||||
let mut changeset = AvailabilityChangeset {
|
||||
time: (form.date.and_time(form.from), form.till),
|
||||
time: (start, end),
|
||||
comment: form.comment.clone(),
|
||||
};
|
||||
|
||||
@ -65,7 +68,7 @@ pub async fn post(
|
||||
Availability::update(pool.get_ref(), availability.id, changeset).await?;
|
||||
}
|
||||
|
||||
let url = utils::get_return_url_for_date(&form.date);
|
||||
let url = utils::get_return_url_for_date(&form.startdate);
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header((LOCATION, url.clone()))
|
||||
.insert_header(("HX-LOCATION", url))
|
||||
|
@ -1,4 +1,6 @@
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use std::fmt::Display;
|
||||
|
||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use maud::html;
|
||||
|
||||
use crate::models::UserFunction;
|
||||
@ -21,7 +23,10 @@ pub fn cond_show(show: &bool, text: &str) -> askama::Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_value(option: &Option<String>) -> askama::Result<String> {
|
||||
pub fn insert_value<T>(option: &Option<T>) -> askama::Result<String>
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
if let Some(val) = option {
|
||||
let s = format!(r#"value="{val}""#);
|
||||
return Ok(s);
|
||||
@ -30,6 +35,15 @@ pub fn insert_value(option: &Option<String>) -> askama::Result<String> {
|
||||
Ok(String::new())
|
||||
}
|
||||
|
||||
pub fn insert_time_value(option: &Option<NaiveTime>) -> askama::Result<String> {
|
||||
if let Some(val) = option {
|
||||
let s = val.format(r#"value="%H:%M""#).to_string();
|
||||
return Ok(s);
|
||||
}
|
||||
|
||||
Ok(String::new())
|
||||
}
|
||||
|
||||
pub fn is_some_and_eq<T>(option: &Option<T>, other: &T) -> askama::Result<bool>
|
||||
where
|
||||
T: Eq,
|
||||
@ -71,6 +85,22 @@ pub fn date_d(v: &NaiveDate) -> askama::Result<String> {
|
||||
Ok(v.format("%d.%m.%Y").to_string())
|
||||
}
|
||||
|
||||
pub fn date_c(v: &NaiveDate) -> askama::Result<String> {
|
||||
Ok(v.format("%d.%m").to_string())
|
||||
}
|
||||
|
||||
pub fn time(v: &NaiveTime) -> askama::Result<String> {
|
||||
Ok(v.format("%H:%M").to_string())
|
||||
}
|
||||
|
||||
pub fn time_opt(v: &Option<NaiveTime>, default: &str) -> askama::Result<String> {
|
||||
if let Some(t) = v {
|
||||
return time(t);
|
||||
}
|
||||
|
||||
Ok(default.to_string())
|
||||
}
|
||||
|
||||
pub fn dt_t(v: &NaiveDateTime) -> askama::Result<String> {
|
||||
Ok(v.format("%R").to_string())
|
||||
}
|
||||
|
@ -3,87 +3,109 @@
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{% if id.is_some() %}
|
||||
<form method="post" action="/availabillity/edit/{{ id.unwrap() }}">
|
||||
<h1 class="title">Bearbeite Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
|
||||
{% else %}
|
||||
<form method="post" action="/availabillity/new">
|
||||
<h1 class="title">Neue Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
|
||||
{% endif %}
|
||||
{% let is_edit = id.is_some() %}
|
||||
<form method="post" action="/availabillity/{% if is_edit %}edit/{{ id.unwrap() }}{% else %}new{% endif %}">
|
||||
<h1 class="title">{% if is_edit %}Bearbeite{% else %}Neue{% endif %} Vefügbarkeit für den {{ date|date_d }}</h1>
|
||||
|
||||
<input type="hidden" name="date" value="{{ date }}">
|
||||
{% let time = "%R" %}
|
||||
<input type="hidden" name="startdate" value="{{ date }}">
|
||||
<input type="hidden" name="enddate" value="{{ date }}" id="enddate">
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Dauer Von - Bis</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="from" name="from" required {% if let Some(start)=start
|
||||
%}value="{{start}}" {% endif %}>
|
||||
{% 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.format(time) }} - {{ e.format(time) }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Dauer Von - Bis</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<input class="input" type="time" name="starttime" required {{ start|insert_time_value|safe }}
|
||||
_="on change put the value of me into #st">
|
||||
{% 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|dt_t }} - {{ *e|dt_t }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<input class="input" type="datetime-local" id="till" name="till" required min="{{ date }}T00:00"
|
||||
max="{{ date.checked_add_days(Days::new(1)).unwrap() }}T23:59" {% if let Some(end)=end %}value="{{end}}"
|
||||
{% endif %}>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<input class="input" type="time" name="endtime" required {{ end|insert_time_value|safe }}
|
||||
_='on change put the value of me into #et then if (value of the previous <input/>) is greater than (value of me) then set the value of #enddate to "{{ datetomorrow }}" then add @disabled to #nextbtn then remove @disabled from #samebtn then put "{{ datetomorrow|date_c }}" into #ed end' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Verfügbarkeitsende</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
{% let is_overnight = enddate.is_some() && enddate.as_ref().unwrap() == datetomorrow|ref %}
|
||||
<button id="samebtn" class="button is-small is-success is-light" type="button"
|
||||
{{ is_overnight|invert|ref|cond_show("disabled")|safe }}
|
||||
_='on click set the value of #enddate to "{{ date }}" then toggle @disabled on me then toggle @disabled on #nextbtn then put "{{ date|date_c }}" into #ed'>am
|
||||
selben Tag</button>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<button id="nextbtn" class="button is-small is-info is-light" type="button"
|
||||
{{ is_overnight|cond_show("disabled")|safe }}
|
||||
_='on click set the value of #enddate to "{{ datetomorrow }}" then toggle @disabled on me then toggle @disabled on #samebtn then put "{{ datetomorrow|date_c }}" into #ed'>am
|
||||
Tag darauf</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Kommentar</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" name="comment" placeholder="nur Posten, nur Wachhabender, etc..">{{
|
||||
comment.unwrap_or("") }}</textarea>
|
||||
</div>
|
||||
<p class="help is-info">
|
||||
verfügbar von {{ date|date_c }} <span id="st">{{ start|time_opt("10:00")|safe }}</span> Uhr
|
||||
bis <span id="ed">{{ enddate.as_ref().unwrap_or(date)|date_c }}</span>
|
||||
<span id="et">{{ end|time_opt("20:00")|safe }}</span> Uhr
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label"></div>
|
||||
<div class="field-body">
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-success">
|
||||
<svg class="icon">
|
||||
<use href="/static/feather-sprite.svg#check-circle" />
|
||||
</svg>
|
||||
<span>
|
||||
{% if id.is_some() %}
|
||||
Speichern
|
||||
{% else %}
|
||||
Erstellen
|
||||
{% endif %}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-link is-light" hx-boost="true" href="/?date={{ date }}">
|
||||
<svg class="icon">
|
||||
<use href="/static/feather-sprite.svg#arrow-left" />
|
||||
</svg>
|
||||
<span>Zurück</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Kommentar</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" name="comment" placeholder="nur Posten, nur Wachhabender, etc..">{{
|
||||
comment.unwrap_or("") }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label"></div>
|
||||
<div class="field-body">
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-success">
|
||||
<svg class="icon">
|
||||
<use href="/static/feather-sprite.svg#check-circle" />
|
||||
</svg>
|
||||
<span>
|
||||
{% if id.is_some() %}
|
||||
Speichern
|
||||
{% else %}
|
||||
Erstellen
|
||||
{% endif %}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-link is-light" hx-boost="true" href="/?date={{ date }}">
|
||||
<svg class="icon">
|
||||
<use href="/static/feather-sprite.svg#arrow-left" />
|
||||
</svg>
|
||||
<span>Zurück</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
Loading…
x
Reference in New Issue
Block a user