Compare commits

..

No commits in common. "38a30e1898b38d04c4670040ad5d852d8cdbe555" and "b42540ac2f6f3aab0c175aeaed0f1b8adfdfb8aa" have entirely different histories.

17 changed files with 156 additions and 532 deletions

View File

@ -1,114 +0,0 @@
{
"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

@ -1,48 +0,0 @@
{
"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,46 +341,6 @@ impl Availability {
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(
pool: &PgPool,
start: &NaiveDateTime,

View File

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

View File

@ -95,73 +95,6 @@ impl Event {
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>> {
let record = query!(
r#"

View File

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

View File

@ -1,158 +0,0 @@
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,23 +1,43 @@
use actix_web::{web, Responder};
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use chrono::{Months, Utc};
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::{Assignment, Availability, Event, Function, Role, User};
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 = "overview.html")]
struct OverviewTemplate {
#[template(path = "index.html")]
struct CalendarTemplate {
user: User,
events: Vec<Event>,
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>,
}
@ -25,24 +45,112 @@ struct OverviewTemplate {
async fn get(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
query: web::Query<CalendarQuery>,
) -> Result<impl Responder, ApplicationError> {
let today = Utc::now().date_naive();
let next_month = today.checked_add_months(Months::new(1)).unwrap();
let date = match query.date {
Some(given_date) => given_date,
None => Utc::now().date_naive(),
};
let events = Event::read_all_by_daterange_and_area_including_location(
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(),
(&today, &next_month),
user.area_id,
date,
query.area.unwrap_or(user.area_id),
)
.await?;
let availabilities =
Availability::read_by_user_and_daterange(pool.get_ref(), user.id, (&today, &next_month))
.await?;
let availabilities_from_user =
Availability::read_by_user_and_date(pool.get_ref(), user.id, &date).await?;
//println!("{availabilities_from_user:#?}");
let template = OverviewTemplate {
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(),
events,
user_can_create_availability,
date,
selected_area,
areas,
events_and_assignments,
availabilities,
};

View File

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

View File

@ -1,5 +1,4 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use maud::html;
use sqlx::PgPool;
use crate::{
@ -23,7 +22,7 @@ pub async fn post(
let context = AvailabilityContext {
pool: pool.get_ref(),
user_id: user.id,
availability: None,
availability_to_get_edited: None,
};
let mut changeset = AvailabilityChangeset {
@ -32,13 +31,7 @@ pub async fn post(
};
if let Err(e) = changeset.validate_with_context(&context).await {
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()));
return Ok(HttpResponse::BadRequest().body(e.to_string()));
};
if let Some(a) =

View File

@ -1,5 +1,4 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use maud::html;
use sqlx::PgPool;
use crate::{
@ -32,7 +31,7 @@ pub async fn post(
let context = AvailabilityContext {
pool: pool.get_ref(),
user_id: user.id,
availability: Some(availability.id),
availability_to_get_edited: Some(availability.id),
};
let mut changeset = AvailabilityChangeset {
@ -41,13 +40,7 @@ pub async fn post(
};
if let Err(e) = changeset.validate_with_context(&context).await {
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()));
return Ok(HttpResponse::BadRequest().body(e.to_string()));
};
if let Some(a) = Availability::find_adjacent_by_time_for_user(

View File

@ -56,11 +56,10 @@ pub fn init(cfg: &mut ServiceConfig) {
cfg.service(availability::delete::delete);
cfg.service(availability::get_new::get);
cfg.service(availability::get_calendar::get);
cfg.service(availability::get_overview::get);
cfg.service(availability::get_update::get);
cfg.service(availability::post_new::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_uncancel);

View File

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

View File

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

View File

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

View File

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