feat: WIP implement event planning
This commit is contained in:
parent
91046d0e1c
commit
95ca418875
@ -34,3 +34,6 @@ thiserror = "1.0.63"
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = "0.7.4"
|
built = "0.7.4"
|
||||||
static-files = "0.2.1"
|
static-files = "0.2.1"
|
||||||
|
|
||||||
|
[profile.dev.package.askama_derive]
|
||||||
|
opt-level = 3
|
||||||
|
6
build.rs
6
build.rs
@ -20,10 +20,6 @@ fn main() -> std::io::Result<()> {
|
|||||||
nm_path.join("feather-icons/dist/feather-sprite.svg"),
|
nm_path.join("feather-icons/dist/feather-sprite.svg"),
|
||||||
dist_path.join("feather-sprite.svg"),
|
dist_path.join("feather-sprite.svg"),
|
||||||
)?;
|
)?;
|
||||||
//copy(
|
|
||||||
// nm_path.join("bulma/css/bulma.min.css"),
|
|
||||||
// dist_path.join("bulma.min.css"),
|
|
||||||
//)?;
|
|
||||||
copy(
|
copy(
|
||||||
nm_path.join("htmx.org/dist/htmx.min.js"),
|
nm_path.join("htmx.org/dist/htmx.min.js"),
|
||||||
dist_path.join("htmx.min.js"),
|
dist_path.join("htmx.min.js"),
|
||||||
@ -33,7 +29,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
dist_path.join("response-targets.js"),
|
dist_path.join("response-targets.js"),
|
||||||
)?;
|
)?;
|
||||||
copy(
|
copy(
|
||||||
nm_path.join("sweetalert2/dist/sweetalert2.min.js"),
|
nm_path.join("sweetalert2-neutral/dist/sweetalert2.min.js"),
|
||||||
dist_path.join("sweetalert2.min.js"),
|
dist_path.join("sweetalert2.min.js"),
|
||||||
)?;
|
)?;
|
||||||
copy(
|
copy(
|
||||||
|
@ -24,23 +24,23 @@ pub struct NewAssignmentTemplate {
|
|||||||
|
|
||||||
#[actix_web::get("/assignments/new")]
|
#[actix_web::get("/assignments/new")]
|
||||||
pub async fn get(user: Identity, pool: web::Data<PgPool>, query: web::Query<EventQuery>) -> impl Responder {
|
pub async fn get(user: Identity, pool: web::Data<PgPool>, query: web::Query<EventQuery>) -> impl Responder {
|
||||||
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
|
//let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
|
||||||
.await
|
// .await
|
||||||
.unwrap();
|
// .unwrap();
|
||||||
|
//
|
||||||
if let Ok(event) = Event::read_by_id_including_location(pool.get_ref(), query.event).await {
|
//if let Ok(event) = Event::read_by_id_including_location(pool.get_ref(), query.event).await {
|
||||||
if current_user.role == Role::Admin || current_user.role == Role::AreaManager && event.location.as_ref().unwrap().area_id == current_user.area_id {
|
// if current_user.role == Role::Admin || current_user.role == Role::AreaManager && event.location.as_ref().unwrap().area_id == current_user.area_id {
|
||||||
let all = Availabillity::read_not_assigned_by_date_including_user(pool.get_ref(), event.date).await.unwrap();
|
// let all = Availabillity::read_not_assigned_by_date_including_user(pool.get_ref(), event.date).await.unwrap();
|
||||||
let available_posten = Availabillity::read_not_assigned_by_date_including_user(pool.get_ref(), event.date).await.unwrap();
|
// let available_posten = Availabillity::read_not_assigned_by_date_including_user(pool.get_ref(), event.date).await.unwrap();
|
||||||
let available_wachhabende = available_posten.iter().filter(|a| a.user.as_ref().unwrap().function == Function::Wachhabender).map(|avl| avl.clone()).collect();
|
// let available_wachhabende = available_posten.iter().filter(|a| a.user.as_ref().unwrap().function == Function::Wachhabender).map(|avl| avl.clone()).collect();
|
||||||
|
//
|
||||||
let template = NewAssignmentTemplate { user: current_user, event, available_wachhabende, available_posten, all };
|
// let template = NewAssignmentTemplate { user: current_user, event, available_wachhabende, available_posten, all };
|
||||||
|
//
|
||||||
return template.to_response();
|
// return template.to_response();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return HttpResponse::Unauthorized().finish();
|
// return HttpResponse::Unauthorized().finish();
|
||||||
}
|
//}
|
||||||
|
|
||||||
HttpResponse::BadRequest().body("Fehler beim Laden für Assignment")
|
HttpResponse::BadRequest().body("Fehler beim Laden für Assignment")
|
||||||
}
|
}
|
||||||
|
@ -13,47 +13,47 @@ pub struct NewAssignmentsForm {
|
|||||||
|
|
||||||
#[actix_web::post("/assignments/new")]
|
#[actix_web::post("/assignments/new")]
|
||||||
pub async fn post(pool: web::Data<PgPool>, form: web::Form<Vec<(String, String)>>) -> impl Responder {
|
pub async fn post(pool: web::Data<PgPool>, form: web::Form<Vec<(String, String)>>) -> impl Responder {
|
||||||
let event_id = form.iter().find(|x| x.0 == "event").unwrap().1.parse().unwrap();
|
//let event_id = form.iter().find(|x| x.0 == "event").unwrap().1.parse().unwrap();
|
||||||
let wachhabender = form.iter().find(|x| x.0 == "wachhabender");
|
//let wachhabender = form.iter().find(|x| x.0 == "wachhabender");
|
||||||
let posten: Vec<i32> = form.iter().filter(|x| x.0 == "posten").map(|x| x.1.parse().unwrap()).collect();
|
//let posten: Vec<i32> = form.iter().filter(|x| x.0 == "posten").map(|x| x.1.parse().unwrap()).collect();
|
||||||
|
//
|
||||||
let event = Event::read_by_id_including_location(&pool, event_id).await.unwrap(); // TODO: Check if location is needed
|
//let event = Event::read_by_id_including_location(&pool, event_id).await.unwrap(); // TODO: Check if location is needed
|
||||||
|
//
|
||||||
if event.voluntary_wachhabender && wachhabender.is_some() && posten.contains(&wachhabender.unwrap().1.parse().unwrap()) {
|
//if event.voluntary_wachhabender && wachhabender.is_some() && posten.contains(&wachhabender.unwrap().1.parse().unwrap()) {
|
||||||
return HttpResponse::BadRequest().body("Wachhabender kann nicht zugleich Posten sein!");
|
// return HttpResponse::BadRequest().body("Wachhabender kann nicht zugleich Posten sein!");
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
let mut joined_ids = posten.clone();
|
//let mut joined_ids = posten.clone();
|
||||||
if let Some((_,id)) = wachhabender {
|
//if let Some((_,id)) = wachhabender {
|
||||||
joined_ids.push(id.parse().unwrap());
|
// joined_ids.push(id.parse().unwrap());
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
for availabillity_id in joined_ids {
|
//for availabillity_id in joined_ids {
|
||||||
let assignments = Assignment::read_by_availabillity(pool.get_ref(), availabillity_id).await.unwrap();
|
// let assignments = Assignment::read_by_availabillity(pool.get_ref(), availabillity_id).await.unwrap();
|
||||||
|
//
|
||||||
let mut can_be_used = true;
|
// let mut can_be_used = true;
|
||||||
|
//
|
||||||
|
//
|
||||||
for assignment in assignments {
|
// for assignment in assignments {
|
||||||
if event.start_time >= assignment.start_time && event.start_time <= assignment.end_time {
|
// if event.start_time >= assignment.start_time && event.start_time <= assignment.end_time {
|
||||||
} else {
|
// } else {
|
||||||
can_be_used = false;
|
// can_be_used = false;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if !can_be_used {
|
// if !can_be_used {
|
||||||
return HttpResponse::BadRequest().body("availabillity time slot bereits genutzt!");
|
// return HttpResponse::BadRequest().body("availabillity time slot bereits genutzt!");
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
if let Some((_,id)) = wachhabender {
|
//if let Some((_,id)) = wachhabender {
|
||||||
Assignment::create(pool.get_ref(), event.id, id.parse().unwrap(), Function::Wachhabender, event.start_time, event.end_time).await.unwrap();
|
// Assignment::create(pool.get_ref(), event.id, id.parse().unwrap(), Function::Wachhabender, event.start_time, event.end_time).await.unwrap();
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
for id in posten {
|
//for id in posten {
|
||||||
Assignment::create(pool.get_ref(), event.id, id, Function::Posten, event.start_time, event.end_time).await.unwrap();
|
// Assignment::create(pool.get_ref(), event.id, id, Function::Posten, event.start_time, event.end_time).await.unwrap();
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
HttpResponse::Ok().finish()
|
HttpResponse::Ok().finish()
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::IdPath,
|
endpoints::IdPath,
|
||||||
models::{Availabillity, User},
|
models::{Availabillity, User}, utils::ApplicationError,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_web::delete("/availabillity/delete/{id}")]
|
#[actix_web::delete("/availabillity/delete/{id}")]
|
||||||
@ -11,14 +11,16 @@ pub async fn delete(
|
|||||||
user: web::ReqData<User>,
|
user: web::ReqData<User>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
path: web::Path<IdPath>,
|
path: web::Path<IdPath>,
|
||||||
) -> impl Responder {
|
) -> Result<impl Responder, ApplicationError> {
|
||||||
if let Ok(availabillity_in_db) = Availabillity::read_by_id(pool.get_ref(), path.id).await {
|
let Some(availabillity) = Availabillity::read_by_id(pool.get_ref(), path.id).await? else {
|
||||||
if availabillity_in_db.user_id == user.id {
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
if let Ok(_) = Availabillity::delete(pool.get_ref(), availabillity_in_db.id).await {
|
};
|
||||||
return HttpResponse::Ok().finish();
|
|
||||||
}
|
if availabillity.user_id == user.id {
|
||||||
}
|
return Err(ApplicationError::Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
return HttpResponse::BadRequest().finish();
|
Availabillity::delete(pool.get_ref(), availabillity.id).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ async fn get(
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let events = Event::read_by_date_and_area_including_location(
|
let events = Event::read_all_by_date_and_area_including_location(
|
||||||
pool.get_ref(),
|
pool.get_ref(),
|
||||||
date,
|
date,
|
||||||
query.area.unwrap_or(user.area_id),
|
query.area.unwrap_or(user.area_id),
|
||||||
|
@ -6,6 +6,7 @@ use sqlx::PgPool;
|
|||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{availability::NewOrEditAvailabilityTemplate, IdPath},
|
endpoints::{availability::NewOrEditAvailabilityTemplate, IdPath},
|
||||||
models::{Availabillity, User},
|
models::{Availabillity, User},
|
||||||
|
utils::ApplicationError,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -20,32 +21,34 @@ pub async fn get(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
path: web::Path<IdPath>,
|
path: web::Path<IdPath>,
|
||||||
query: web::Query<EditAvailabilityQuery>,
|
query: web::Query<EditAvailabilityQuery>,
|
||||||
) -> impl Responder {
|
) -> Result<impl Responder, ApplicationError> {
|
||||||
if let Ok(availabillity) = Availabillity::read_by_id(pool.get_ref(), path.id).await {
|
let Some(availabillity) = Availabillity::read_by_id(pool.get_ref(), path.id).await? else {
|
||||||
if availabillity.user_id == user.id {
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
let start_time = availabillity
|
};
|
||||||
.start_time
|
|
||||||
.and_then(|d| Some(d.format("%R").to_string()));
|
|
||||||
|
|
||||||
let end_time = availabillity
|
if availabillity.user_id == user.id {
|
||||||
.end_time
|
return Err(ApplicationError::Unauthorized);
|
||||||
.and_then(|d| Some(d.format("%R").to_string()));
|
|
||||||
|
|
||||||
let has_time = availabillity.start_time.is_some() && availabillity.end_time.is_some();
|
|
||||||
|
|
||||||
let template = NewOrEditAvailabilityTemplate {
|
|
||||||
user: user.into_inner(),
|
|
||||||
date: availabillity.date,
|
|
||||||
whole_day: query.whole_day.unwrap_or(!has_time),
|
|
||||||
id: Some(path.id),
|
|
||||||
start_time: start_time.as_deref(),
|
|
||||||
end_time: end_time.as_deref(),
|
|
||||||
comment: availabillity.comment.as_deref(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return template.to_response();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse::BadRequest().body("Availabillity with this id doesn't exist.")
|
let start_time = availabillity
|
||||||
|
.start_time
|
||||||
|
.and_then(|d| Some(d.format("%R").to_string()));
|
||||||
|
|
||||||
|
let end_time = availabillity
|
||||||
|
.end_time
|
||||||
|
.and_then(|d| Some(d.format("%R").to_string()));
|
||||||
|
|
||||||
|
let has_time = availabillity.start_time.is_some() && availabillity.end_time.is_some();
|
||||||
|
|
||||||
|
let template = NewOrEditAvailabilityTemplate {
|
||||||
|
user: user.into_inner(),
|
||||||
|
date: availabillity.date,
|
||||||
|
whole_day: query.whole_day.unwrap_or(!has_time),
|
||||||
|
id: Some(path.id),
|
||||||
|
start_time: start_time.as_deref(),
|
||||||
|
end_time: end_time.as_deref(),
|
||||||
|
comment: availabillity.comment.as_deref(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(template.to_response())
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,10 @@ use chrono::{NaiveDate, NaiveTime};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::models::{Availabillity, User};
|
use crate::{
|
||||||
|
models::{Availabillity, User},
|
||||||
|
utils::{self, ApplicationError},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct AvailabillityForm {
|
pub struct AvailabillityForm {
|
||||||
@ -18,8 +21,8 @@ pub async fn post(
|
|||||||
user: web::ReqData<User>,
|
user: web::ReqData<User>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
form: web::Form<AvailabillityForm>,
|
form: web::Form<AvailabillityForm>,
|
||||||
) -> impl Responder {
|
) -> Result<impl Responder, ApplicationError> {
|
||||||
if let Ok(_) = Availabillity::create(
|
Availabillity::create(
|
||||||
pool.get_ref(),
|
pool.get_ref(),
|
||||||
user.id,
|
user.id,
|
||||||
form.date,
|
form.date,
|
||||||
@ -27,12 +30,11 @@ pub async fn post(
|
|||||||
form.till,
|
form.till,
|
||||||
form.comment.clone(),
|
form.comment.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
{
|
|
||||||
HttpResponse::Found()
|
let url = utils::get_return_url_for_date(&form.date);
|
||||||
.insert_header((LOCATION, "/"))
|
Ok(HttpResponse::Found()
|
||||||
.finish()
|
.insert_header((LOCATION, url.clone()))
|
||||||
} else {
|
.insert_header(("HX-LOCATION", url))
|
||||||
HttpResponse::BadRequest().body("Fehler beim erstellen")
|
.finish())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use sqlx::PgPool;
|
|||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{availability::post_new::AvailabillityForm, IdPath},
|
endpoints::{availability::post_new::AvailabillityForm, IdPath},
|
||||||
models::{Availabillity, User},
|
models::{Availabillity, User},
|
||||||
|
utils::{self, ApplicationError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_web::post("/availabillity/edit/{id}")]
|
#[actix_web::post("/availabillity/edit/{id}")]
|
||||||
@ -12,42 +13,32 @@ pub async fn post(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
path: web::Path<IdPath>,
|
path: web::Path<IdPath>,
|
||||||
form: web::Form<AvailabillityForm>,
|
form: web::Form<AvailabillityForm>,
|
||||||
) -> impl Responder {
|
) -> Result<impl Responder, ApplicationError> {
|
||||||
if let Ok(mut availabillity) = Availabillity::read_by_id(pool.get_ref(), path.id).await {
|
let Some(availabillity) = Availabillity::read_by_id(pool.get_ref(), path.id).await? else {
|
||||||
if availabillity.user_id == user.id {
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
let mut has_changed = false;
|
};
|
||||||
|
|
||||||
if availabillity.start_time != form.from {
|
if availabillity.user_id != user.id {
|
||||||
availabillity.start_time = form.from;
|
return Err(ApplicationError::Unauthorized);
|
||||||
has_changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if availabillity.end_time != form.till {
|
|
||||||
availabillity.end_time = form.till;
|
|
||||||
has_changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if availabillity.comment != form.comment {
|
|
||||||
availabillity.comment = form.comment.clone();
|
|
||||||
has_changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_changed {
|
|
||||||
if let Ok(_) = Availabillity::update(pool.get_ref(), path.id, &availabillity).await
|
|
||||||
{
|
|
||||||
return HttpResponse::Found()
|
|
||||||
.insert_header((LOCATION, "/"))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !has_changed {
|
|
||||||
return HttpResponse::Found()
|
|
||||||
.insert_header((LOCATION, "/"))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse::BadRequest().body("Fehler beim erstellen")
|
if availabillity.start_time != form.from
|
||||||
|
|| availabillity.end_time != form.till
|
||||||
|
|| availabillity.comment != form.comment
|
||||||
|
{
|
||||||
|
Availabillity::update(
|
||||||
|
pool.get_ref(),
|
||||||
|
availabillity.id,
|
||||||
|
form.from,
|
||||||
|
form.till,
|
||||||
|
form.comment.as_ref(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = utils::get_return_url_for_date(&form.date);
|
||||||
|
Ok(HttpResponse::Found()
|
||||||
|
.insert_header((LOCATION, url.clone()))
|
||||||
|
.insert_header(("HX-LOCATION", url))
|
||||||
|
.finish())
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,44 @@
|
|||||||
use actix_identity::Identity;
|
use actix_web::{web, Responder};
|
||||||
use actix_web::{web, HttpResponse, Responder};
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use askama_actix::TemplateToResponse;
|
use askama_actix::TemplateToResponse;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::NaiveDateQuery, models::{Role, User, Location}};
|
use crate::{
|
||||||
|
endpoints::NaiveDateQuery,
|
||||||
|
models::{Location, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "events/new.html")]
|
#[template(path = "events/new.html")]
|
||||||
pub struct NewEventTemplate {
|
pub struct NewEventTemplate {
|
||||||
user: User,
|
user: User,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
locations: Vec<Location>
|
locations: Vec<Location>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::get("/events/new")]
|
#[actix_web::get("/events/new")]
|
||||||
pub async fn get(user: Identity, pool: web::Data<PgPool>, query: web::Query<NaiveDateQuery>) -> impl Responder {
|
pub async fn get(
|
||||||
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
|
user: web::ReqData<User>,
|
||||||
.await
|
pool: web::Data<PgPool>,
|
||||||
.unwrap();
|
query: web::Query<NaiveDateQuery>,
|
||||||
|
) -> Result<impl Responder, ApplicationError> {
|
||||||
if current_user.role != Role::Admin && current_user.role != Role::AreaManager {
|
if user.role != Role::Admin && user.role != Role::AreaManager {
|
||||||
return HttpResponse::Unauthorized().finish();
|
return Err(ApplicationError::Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
let locations;
|
let locations = if user.role == Role::Admin {
|
||||||
if current_user.role == Role::Admin {
|
Location::read_all_including_area(pool.get_ref()).await?
|
||||||
locations = Location::read_all_including_area(pool.get_ref()).await.unwrap();
|
|
||||||
} else {
|
} else {
|
||||||
locations = Location::read_by_area(pool.get_ref(), current_user.area_id).await.unwrap();
|
Location::read_by_area(pool.get_ref(), user.area_id).await?
|
||||||
}
|
};
|
||||||
|
|
||||||
let template = NewEventTemplate { user: current_user, date: query.date, locations };
|
let template = NewEventTemplate {
|
||||||
|
user: user.into_inner(),
|
||||||
|
date: query.date,
|
||||||
|
locations,
|
||||||
|
};
|
||||||
|
|
||||||
return template.to_response();
|
Ok(template.to_response())
|
||||||
}
|
}
|
||||||
|
101
src/endpoints/events/get_plan.rs
Normal file
101
src/endpoints/events/get_plan.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use askama::Template;
|
||||||
|
use askama_actix::TemplateToResponse;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
endpoints::{IdPath, NaiveDateQuery},
|
||||||
|
models::{Assignment, Availabillity, Event, Function, Location, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "events/plan.html")]
|
||||||
|
pub struct PlanEventTemplate {
|
||||||
|
user: User,
|
||||||
|
event: Event,
|
||||||
|
availabillities: Vec<(Availabillity, AssignmentState)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AssignmentState {
|
||||||
|
// availabillity is not assigned at all or at least not timely conflicting
|
||||||
|
Unassigned,
|
||||||
|
// availabillity is assigned for another event that is timely conflicting
|
||||||
|
Conflicting,
|
||||||
|
// availabillity is assigned to this event as Posten
|
||||||
|
AssignedPosten,
|
||||||
|
// availabillity is assigned to this event as Führungsassistent
|
||||||
|
AssignedFührungsassistent,
|
||||||
|
// availabillity is assigned to this event as Wachhabender
|
||||||
|
AssignedWachahabender,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::get("/events/{id}/plan")]
|
||||||
|
pub async fn get(
|
||||||
|
user: web::ReqData<User>,
|
||||||
|
pool: web::Data<PgPool>,
|
||||||
|
path: web::Path<IdPath>,
|
||||||
|
) -> Result<impl Responder, ApplicationError> {
|
||||||
|
if user.role != Role::Admin && user.role != Role::AreaManager {
|
||||||
|
return Err(ApplicationError::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(event) = Event::read_by_id_including_location(pool.get_ref(), path.id).await? else {
|
||||||
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
|
};
|
||||||
|
|
||||||
|
if user.role != Role::Admin && user.area_id != event.location.as_ref().unwrap().area_id {
|
||||||
|
return Err(ApplicationError::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
let availabillities_in_db = Availabillity::read_by_date_and_area_including_user(
|
||||||
|
pool.get_ref(),
|
||||||
|
event.date,
|
||||||
|
event.location.as_ref().unwrap().area_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut availabillities = Vec::new();
|
||||||
|
for availabillity in availabillities_in_db {
|
||||||
|
let assignments =
|
||||||
|
Assignment::read_all_by_availabillity(pool.get_ref(), availabillity.id).await?;
|
||||||
|
|
||||||
|
if let Some(assignment) = assignments
|
||||||
|
.iter()
|
||||||
|
.find(|assignment| assignment.event_id == event.id)
|
||||||
|
{
|
||||||
|
let state = match assignment.function {
|
||||||
|
Function::Posten => AssignmentState::AssignedPosten,
|
||||||
|
Function::Fuehrungsassistent => AssignmentState::AssignedFührungsassistent,
|
||||||
|
Function::Wachhabender => AssignmentState::AssignedWachahabender,
|
||||||
|
};
|
||||||
|
|
||||||
|
availabillities.push((availabillity, state));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_start_time_during_event =
|
||||||
|
|a: &Assignment| a.start_time >= event.start_time && a.start_time <= event.end_time;
|
||||||
|
let has_end_time_during_event =
|
||||||
|
|a: &Assignment| a.end_time >= event.start_time && a.end_time <= event.end_time;
|
||||||
|
|
||||||
|
if assignments
|
||||||
|
.iter()
|
||||||
|
.any(|a| has_start_time_during_event(a) || has_end_time_during_event(a))
|
||||||
|
{
|
||||||
|
availabillities.push((availabillity, AssignmentState::Conflicting));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
availabillities.push((availabillity, AssignmentState::Unassigned));
|
||||||
|
}
|
||||||
|
|
||||||
|
let template = PlanEventTemplate {
|
||||||
|
user: user.into_inner(),
|
||||||
|
event,
|
||||||
|
availabillities,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(template.to_response())
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
pub mod get_new;
|
pub mod get_new;
|
||||||
pub mod post_new;
|
pub mod post_new;
|
||||||
|
pub mod get_plan;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use actix_identity::Identity;
|
|
||||||
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 serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::models::{Event, Role, User};
|
use crate::{models::{Event, Role, User}, utils::{self, ApplicationError}};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct NewEventForm {
|
pub struct NewEventForm {
|
||||||
@ -16,25 +15,20 @@ pub struct NewEventForm {
|
|||||||
voluntarywachhabender: Option<bool>,
|
voluntarywachhabender: Option<bool>,
|
||||||
amount: i16,
|
amount: i16,
|
||||||
clothing: String,
|
clothing: String,
|
||||||
|
note: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::post("/events/new")]
|
#[actix_web::post("/events/new")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
user: Identity,
|
user: web::ReqData<User>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
form: web::Form<NewEventForm>,
|
form: web::Form<NewEventForm>,
|
||||||
) -> impl Responder {
|
) -> Result<impl Responder, ApplicationError> {
|
||||||
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
|
if user.role != Role::Admin && user.role != Role::AreaManager {
|
||||||
.await
|
return Err(ApplicationError::Unauthorized);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if current_user.role != Role::Admin && current_user.role != Role::AreaManager {
|
|
||||||
return HttpResponse::Unauthorized().finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let return_location = format!("/?data={}", form.date);
|
Event::create(
|
||||||
|
|
||||||
match Event::create(
|
|
||||||
pool.get_ref(),
|
pool.get_ref(),
|
||||||
&form.date,
|
&form.date,
|
||||||
&form.from,
|
&form.from,
|
||||||
@ -44,9 +38,14 @@ pub async fn post(
|
|||||||
form.voluntarywachhabender.is_some() && form.voluntarywachhabender.unwrap(),
|
form.voluntarywachhabender.is_some() && form.voluntarywachhabender.unwrap(),
|
||||||
form.amount,
|
form.amount,
|
||||||
&form.clothing,
|
&form.clothing,
|
||||||
|
form.note.as_ref()
|
||||||
)
|
)
|
||||||
.await {
|
.await?;
|
||||||
Ok(_) => HttpResponse::Found().insert_header((LOCATION, return_location)).finish(),
|
|
||||||
Err(_) => HttpResponse::BadRequest().body("Fehler beim Erstellen")
|
let url = utils::get_return_url_for_date(&form.date);
|
||||||
}
|
println!("redirecto to {url}");
|
||||||
|
Ok(HttpResponse::Found()
|
||||||
|
.insert_header((LOCATION, url.clone()))
|
||||||
|
.insert_header(("HX-LOCATION", url))
|
||||||
|
.finish())
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ pub fn init(cfg: &mut ServiceConfig) {
|
|||||||
|
|
||||||
cfg.service(events::get_new::get);
|
cfg.service(events::get_new::get);
|
||||||
cfg.service(events::post_new::post);
|
cfg.service(events::post_new::post);
|
||||||
|
cfg.service(events::get_plan::get);
|
||||||
|
|
||||||
cfg.service(assignment::get_new::get);
|
cfg.service(assignment::get_new::get);
|
||||||
cfg.service(assignment::post_new::post);
|
cfg.service(assignment::post_new::post);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use chrono::NaiveTime;
|
use chrono::NaiveTime;
|
||||||
use sqlx::{query, PgPool};
|
use sqlx::{query, PgPool};
|
||||||
|
|
||||||
use super::Function;
|
use super::{Function, Result};
|
||||||
|
|
||||||
pub struct Assignment {
|
pub struct Assignment {
|
||||||
pub event_id: i32,
|
pub event_id: i32,
|
||||||
@ -19,7 +19,7 @@ impl Assignment {
|
|||||||
function: Function,
|
function: Function,
|
||||||
start_time: NaiveTime,
|
start_time: NaiveTime,
|
||||||
end_time: NaiveTime,
|
end_time: NaiveTime,
|
||||||
) -> anyhow::Result<()> {
|
) -> Result<()> {
|
||||||
query!(
|
query!(
|
||||||
r##"
|
r##"
|
||||||
INSERT INTO assignment (eventId, availabillityId, function, startTime, endTime)
|
INSERT INTO assignment (eventId, availabillityId, function, startTime, endTime)
|
||||||
@ -37,13 +37,13 @@ impl Assignment {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_by_availabillity(
|
pub async fn read_all_by_availabillity(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
availabillity_id: i32,
|
availabillity_id: i32,
|
||||||
) -> anyhow::Result<Vec<Assignment>> {
|
) -> Result<Vec<Assignment>> {
|
||||||
let records = query!(
|
let records = query!(
|
||||||
r##"
|
r##"
|
||||||
SELECT
|
SELECT
|
||||||
assignment.eventId,
|
assignment.eventId,
|
||||||
assignment.availabillityId,
|
assignment.availabillityId,
|
||||||
assignment.function AS "function: Function",
|
assignment.function AS "function: Function",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use chrono::{NaiveDate, NaiveTime};
|
use chrono::{NaiveDate, NaiveTime};
|
||||||
use sqlx::{query, PgPool};
|
use sqlx::{query, PgPool};
|
||||||
|
|
||||||
use super::{function::Function, role::Role, user::User, Area};
|
use super::{Area, Function, Result, Role, User};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Availabillity {
|
pub struct Availabillity {
|
||||||
@ -22,22 +22,29 @@ impl Availabillity {
|
|||||||
start_time: Option<NaiveTime>,
|
start_time: Option<NaiveTime>,
|
||||||
end_time: Option<NaiveTime>,
|
end_time: Option<NaiveTime>,
|
||||||
comment: Option<String>,
|
comment: Option<String>,
|
||||||
) -> anyhow::Result<i32> {
|
) -> Result<()> {
|
||||||
let result = match (start_time, end_time, comment) {
|
query!(
|
||||||
(Some(start_time), Some(end_time), Some(comment)) => query!("INSERT INTO availabillity (userId, date, startTime, endTime, comment) VALUES ($1, $2, $3, $4, $5) RETURNING id;", user_id, date, start_time, end_time, comment).fetch_one(pool).await?.id,
|
r#"
|
||||||
(Some(start_time), Some(end_time), None) => query!("INSERT INTO availabillity (userId, date, startTime, endTime) VALUES ($1, $2, $3, $4) RETURNING id;", user_id, date, start_time, end_time).fetch_one(pool).await?.id,
|
INSERT INTO availabillity (userId, date, startTime, endTime, comment)
|
||||||
(None, None, Some(comment)) => query!("INSERT INTO availabillity (userId, date, comment) VALUES ($1, $2, $3) RETURNING id;", user_id, date, comment).fetch_one(pool).await?.id,
|
VALUES ($1, $2, $3, $4, $5);
|
||||||
(_, _, _) => query!("INSERT INTO availabillity (userId, date) VALUES ($1, $2) RETURNING id;", user_id, date).fetch_one(pool).await?.id
|
"#,
|
||||||
};
|
user_id,
|
||||||
|
date,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
comment
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(result)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_by_date_and_area_including_user(
|
pub async fn read_by_date_and_area_including_user(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
area_id: i32
|
area_id: i32,
|
||||||
) -> anyhow::Result<Vec<Availabillity>> {
|
) -> Result<Vec<Availabillity>> {
|
||||||
let records = query!(
|
let records = query!(
|
||||||
r##"
|
r##"
|
||||||
SELECT
|
SELECT
|
||||||
@ -100,7 +107,7 @@ impl Availabillity {
|
|||||||
pub async fn read_not_assigned_by_date_including_user(
|
pub async fn read_not_assigned_by_date_including_user(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
) -> anyhow::Result<Vec<Availabillity>> {
|
) -> Result<Vec<Availabillity>> {
|
||||||
let records = query!(
|
let records = query!(
|
||||||
r##"
|
r##"
|
||||||
SELECT
|
SELECT
|
||||||
@ -159,20 +166,22 @@ impl Availabillity {
|
|||||||
Ok(availabillities)
|
Ok(availabillities)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_by_id(pool: &PgPool, id: i32) -> anyhow::Result<Availabillity> {
|
pub async fn read_by_id(pool: &PgPool, id: i32) -> Result<Option<Availabillity>> {
|
||||||
let record = query!("SELECT * FROM availabillity WHERE id = $1", id)
|
let record = query!("SELECT * FROM availabillity WHERE id = $1", id)
|
||||||
.fetch_one(pool)
|
.fetch_optional(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let availabillity = Availabillity {
|
let availabillity = record.and_then(|record| {
|
||||||
id: record.id,
|
Some(Availabillity {
|
||||||
user_id: record.userid,
|
id: record.id,
|
||||||
user: None,
|
user_id: record.userid,
|
||||||
date: record.date,
|
user: None,
|
||||||
start_time: record.starttime,
|
date: record.date,
|
||||||
end_time: record.endtime,
|
start_time: record.starttime,
|
||||||
comment: record.comment.clone(),
|
end_time: record.endtime,
|
||||||
};
|
comment: record.comment.clone(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
Ok(availabillity)
|
Ok(availabillity)
|
||||||
}
|
}
|
||||||
@ -181,7 +190,7 @@ impl Availabillity {
|
|||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
date_range: (NaiveDate, NaiveDate),
|
date_range: (NaiveDate, NaiveDate),
|
||||||
area_id: i32,
|
area_id: i32,
|
||||||
) -> anyhow::Result<Vec<Availabillity>> {
|
) -> Result<Vec<Availabillity>> {
|
||||||
let records = query!(
|
let records = query!(
|
||||||
r##"
|
r##"
|
||||||
SELECT
|
SELECT
|
||||||
@ -251,13 +260,15 @@ impl Availabillity {
|
|||||||
pub async fn update(
|
pub async fn update(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
id: i32,
|
id: i32,
|
||||||
updated_availabillity: &Availabillity,
|
start_time: Option<NaiveTime>,
|
||||||
) -> anyhow::Result<()> {
|
end_time: Option<NaiveTime>,
|
||||||
|
comment: Option<&String>,
|
||||||
|
) -> Result<()> {
|
||||||
query!(
|
query!(
|
||||||
"UPDATE availabillity SET startTime = $1, endTime = $2, comment = $3 WHERE id = $4",
|
"UPDATE availabillity SET startTime = $1, endTime = $2, comment = $3 WHERE id = $4",
|
||||||
updated_availabillity.start_time,
|
start_time,
|
||||||
updated_availabillity.end_time,
|
end_time,
|
||||||
updated_availabillity.comment,
|
comment,
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
@ -266,7 +277,7 @@ impl Availabillity {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(pool: &PgPool, id: i32) -> anyhow::Result<()> {
|
pub async fn delete(pool: &PgPool, id: i32) -> Result<()> {
|
||||||
query!("DELETE FROM availabillity WHERE id = $1", id)
|
query!("DELETE FROM availabillity WHERE id = $1", id)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use chrono::{NaiveDate, NaiveTime};
|
use chrono::{NaiveDate, NaiveTime};
|
||||||
use sqlx::{query, PgPool};
|
use sqlx::{query, PgPool};
|
||||||
|
|
||||||
use super::Location;
|
use super::{Location, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Event {
|
pub struct Event {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub date: NaiveDate,
|
pub date: NaiveDate,
|
||||||
@ -15,6 +16,7 @@ pub struct Event {
|
|||||||
pub amount_of_posten: i16,
|
pub amount_of_posten: i16,
|
||||||
pub clothing: String,
|
pub clothing: String,
|
||||||
pub canceled: bool,
|
pub canceled: bool,
|
||||||
|
pub note: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
@ -28,17 +30,22 @@ impl Event {
|
|||||||
voluntary_wachhabender: bool,
|
voluntary_wachhabender: bool,
|
||||||
amount_of_posten: i16,
|
amount_of_posten: i16,
|
||||||
clothing: &String,
|
clothing: &String,
|
||||||
) -> anyhow::Result<i32> {
|
note: Option<&String>,
|
||||||
let result = query!("INSERT INTO event (date, startTime, endTime, name, locationId, voluntaryWachhabender, amountOfPosten, clothing) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id;", date, start_time, end_time, name, location_id, voluntary_wachhabender, amount_of_posten, clothing).fetch_one(pool).await?;
|
) -> Result<()> {
|
||||||
|
query!(r#"
|
||||||
|
INSERT INTO event (date, startTime, endTime, name, locationId, voluntaryWachhabender, amountOfPosten, clothing, note)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
|
||||||
|
"#,
|
||||||
|
date, start_time, end_time, name, location_id, voluntary_wachhabender, amount_of_posten, clothing, note).execute(pool).await?;
|
||||||
|
|
||||||
Ok(result.id)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_by_date_and_area_including_location(
|
pub async fn read_all_by_date_and_area_including_location(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
area_id: i32
|
area_id: i32,
|
||||||
) -> anyhow::Result<Vec<Event>> {
|
) -> Result<Vec<Event>> {
|
||||||
let records = query!(
|
let records = query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@ -52,6 +59,7 @@ impl Event {
|
|||||||
event.amountOfPosten,
|
event.amountOfPosten,
|
||||||
event.clothing,
|
event.clothing,
|
||||||
event.canceled,
|
event.canceled,
|
||||||
|
event.note,
|
||||||
location.id,
|
location.id,
|
||||||
location.name AS locationName,
|
location.name AS locationName,
|
||||||
location.areaId AS locationAreaId
|
location.areaId AS locationAreaId
|
||||||
@ -67,7 +75,7 @@ impl Event {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let events = records
|
let events = records
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|record| Event {
|
.map(|record| Event {
|
||||||
id: record.eventid,
|
id: record.eventid,
|
||||||
date: record.date,
|
date: record.date,
|
||||||
@ -85,13 +93,14 @@ impl Event {
|
|||||||
amount_of_posten: record.amountofposten,
|
amount_of_posten: record.amountofposten,
|
||||||
clothing: record.clothing.to_string(),
|
clothing: record.clothing.to_string(),
|
||||||
canceled: record.canceled,
|
canceled: record.canceled,
|
||||||
|
note: record.note,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(events)
|
Ok(events)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_by_id_including_location(pool: &PgPool, id: i32) -> anyhow::Result<Event> {
|
pub async fn read_by_id_including_location(pool: &PgPool, id: i32) -> Result<Option<Event>> {
|
||||||
let record = query!(
|
let record = query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@ -105,6 +114,7 @@ impl Event {
|
|||||||
event.amountOfPosten,
|
event.amountOfPosten,
|
||||||
event.clothing,
|
event.clothing,
|
||||||
event.canceled,
|
event.canceled,
|
||||||
|
event.note,
|
||||||
location.id,
|
location.id,
|
||||||
location.name AS locationName,
|
location.name AS locationName,
|
||||||
location.areaId AS locationAreaId
|
location.areaId AS locationAreaId
|
||||||
@ -114,27 +124,30 @@ impl Event {
|
|||||||
"#,
|
"#,
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_optional(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let event = Event {
|
let event = record.and_then(|record| {
|
||||||
id: record.eventid,
|
Some(Event {
|
||||||
date: record.date,
|
id: record.eventid,
|
||||||
start_time: record.starttime,
|
date: record.date,
|
||||||
end_time: record.endtime,
|
start_time: record.starttime,
|
||||||
name: record.name.to_string(),
|
end_time: record.endtime,
|
||||||
location_id: record.locationid,
|
name: record.name.to_string(),
|
||||||
location: Some(Location {
|
location_id: record.locationid,
|
||||||
id: record.locationid,
|
location: Some(Location {
|
||||||
name: record.locationname.to_string(),
|
id: record.locationid,
|
||||||
area_id: record.locationareaid,
|
name: record.locationname.to_string(),
|
||||||
area: None,
|
area_id: record.locationareaid,
|
||||||
}),
|
area: None,
|
||||||
voluntary_wachhabender: record.voluntarywachhabender,
|
}),
|
||||||
amount_of_posten: record.amountofposten,
|
voluntary_wachhabender: record.voluntarywachhabender,
|
||||||
clothing: record.clothing.to_string(),
|
amount_of_posten: record.amountofposten,
|
||||||
canceled: record.canceled,
|
clothing: record.clothing.to_string(),
|
||||||
};
|
canceled: record.canceled,
|
||||||
|
note: record.note,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
Ok(event)
|
Ok(event)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,10 @@ use sqlx::{query, PgPool};
|
|||||||
|
|
||||||
use super::Area;
|
use super::Area;
|
||||||
|
|
||||||
pub struct Location {
|
use super::Result;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Location {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub area_id: i32,
|
pub area_id: i32,
|
||||||
@ -10,19 +13,19 @@ pub struct Location {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Location {
|
impl Location {
|
||||||
pub async fn create(pool: &PgPool, name: &str, area_id: i32) -> anyhow::Result<i32> {
|
pub async fn create(pool: &PgPool, name: &str, area_id: i32) -> Result<()> {
|
||||||
let result = query!(
|
query!(
|
||||||
"INSERT INTO location (name, areaId) VALUES ($1, $2) RETURNING id;",
|
"INSERT INTO location (name, areaId) VALUES ($1, $2);",
|
||||||
name,
|
name,
|
||||||
area_id
|
area_id
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(result.id)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_by_area(pool: &PgPool, area_id: i32) -> anyhow::Result<Vec<Location>> {
|
pub async fn read_by_area(pool: &PgPool, area_id: i32) -> Result<Vec<Location>> {
|
||||||
let records = query!("SELECT * FROM location WHERE areaId = $1;", area_id)
|
let records = query!("SELECT * FROM location WHERE areaId = $1;", area_id)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await?;
|
.await?;
|
||||||
@ -40,7 +43,7 @@ impl Location {
|
|||||||
Ok(locations)
|
Ok(locations)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_all(pool: &PgPool) -> anyhow::Result<Vec<Location>> {
|
pub async fn read_all(pool: &PgPool) -> Result<Vec<Location>> {
|
||||||
let records = query!("SELECT * FROM location").fetch_all(pool).await?;
|
let records = query!("SELECT * FROM location").fetch_all(pool).await?;
|
||||||
|
|
||||||
let locations = records
|
let locations = records
|
||||||
@ -56,7 +59,7 @@ impl Location {
|
|||||||
Ok(locations)
|
Ok(locations)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_all_including_area(pool: &PgPool) -> anyhow::Result<Vec<Location>> {
|
pub async fn read_all_including_area(pool: &PgPool) -> Result<Vec<Location>> {
|
||||||
let records = query!("SELECT location.id AS locationId, location.name, location.areaId, area.id, area.name AS areaName FROM location JOIN area ON location.areaId = area.id;")
|
let records = query!("SELECT location.id AS locationId, location.name, location.areaId, area.id, area.name AS areaName FROM location JOIN area ON location.areaId = area.id;")
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await?;
|
.await?;
|
||||||
@ -77,7 +80,7 @@ impl Location {
|
|||||||
Ok(locations)
|
Ok(locations)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_by_id(pool: &PgPool, id: i32) -> super::Result<Option<Location>> {
|
pub async fn read_by_id(pool: &PgPool, id: i32) -> Result<Option<Location>> {
|
||||||
let record = query!("SELECT * FROM location WHERE id = $1;", id)
|
let record = query!("SELECT * FROM location WHERE id = $1;", id)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
.await?;
|
.await?;
|
||||||
@ -94,7 +97,7 @@ impl Location {
|
|||||||
Ok(location)
|
Ok(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(pool: &PgPool, id: i32, name: &str, area_id: i32) -> super::Result<()> {
|
pub async fn update(pool: &PgPool, id: i32, name: &str, area_id: i32) -> Result<()> {
|
||||||
query!(
|
query!(
|
||||||
"UPDATE location SET name = $1, areaid = $2 WHERE id = $3;",
|
"UPDATE location SET name = $1, areaid = $2 WHERE id = $3;",
|
||||||
name,
|
name,
|
||||||
@ -107,7 +110,7 @@ impl Location {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(pool: &PgPool, id: i32) -> super::Result<()> {
|
pub async fn delete(pool: &PgPool, id: i32) -> Result<()> {
|
||||||
query!("DELETE FROM location WHERE id = $1;", id)
|
query!("DELETE FROM location WHERE id = $1;", id)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -5,3 +5,13 @@ pub mod token_generation;
|
|||||||
mod application_error;
|
mod application_error;
|
||||||
|
|
||||||
pub use application_error::ApplicationError;
|
pub use application_error::ApplicationError;
|
||||||
|
use chrono::{NaiveDate, Utc};
|
||||||
|
|
||||||
|
pub fn get_return_url_for_date(date: &NaiveDate) -> String {
|
||||||
|
let today = Utc::now().date_naive();
|
||||||
|
if date == &today {
|
||||||
|
return String::from("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("/?date={}", date)
|
||||||
|
}
|
||||||
|
1394
static/package-lock.json
generated
1394
static/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bulma": "^1.0.1",
|
"bulma": "^1.0.2",
|
||||||
"feather-icons": "^4.29.2",
|
"feather-icons": "^4.29.2",
|
||||||
"htmx.org": "^1.9.12",
|
"htmx.org": "^1.9.12",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"sweetalert2": "^11.12.4"
|
"sweetalert2-neutral": "^11.14.1-neutral-fix6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-bulma": "sass --load-path=node_modules --no-source-map style.scss dist/style.css"
|
"build-bulma": "sass --load-path=node_modules --no-source-map style.scss dist/style.css"
|
||||||
|
@ -3,7 +3,7 @@ $crimson: #00d1b2; //#B80F0A;
|
|||||||
|
|
||||||
// Override global Sass variables from the /utilities folder
|
// Override global Sass variables from the /utilities folder
|
||||||
@use "bulma/sass/utilities" with ($family-primary: '"Nunito", sans-serif',
|
@use "bulma/sass/utilities" with ($family-primary: '"Nunito", sans-serif',
|
||||||
$primary: $crimson,
|
$primary: $crimson,
|
||||||
);
|
);
|
||||||
// $grey-dark: $brown,
|
// $grey-dark: $brown,
|
||||||
// $grey-light: $beige-light,
|
// $grey-light: $beige-light,
|
||||||
@ -34,6 +34,8 @@ $crimson: #00d1b2; //#B80F0A;
|
|||||||
@forward "bulma/sass/layout/section";
|
@forward "bulma/sass/layout/section";
|
||||||
@forward "bulma/sass/layout/hero";
|
@forward "bulma/sass/layout/hero";
|
||||||
|
|
||||||
|
@forward "bulma/sass/grid";
|
||||||
|
|
||||||
@forward "bulma/sass/helpers/spacing";
|
@forward "bulma/sass/helpers/spacing";
|
||||||
@forward "bulma/sass/helpers/flexbox";
|
@forward "bulma/sass/helpers/flexbox";
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ $crimson: #00d1b2; //#B80F0A;
|
|||||||
@forward "bulma/sass/themes";
|
@forward "bulma/sass/themes";
|
||||||
|
|
||||||
// TODO: bulma theme for sweetalert looks and feels outdated
|
// TODO: bulma theme for sweetalert looks and feels outdated
|
||||||
@use "sweetalert2/src/sweetalert2.scss" with ($swal2-confirm-button-background-color: $crimson);
|
@use "sweetalert2-neutral/src/sweetalert2.scss" with ($swal2-confirm-button-background-color: $crimson);
|
||||||
|
|
||||||
[class*=" icon"],
|
[class*=" icon"],
|
||||||
[class^=icon] {
|
[class^=icon] {
|
||||||
|
@ -96,6 +96,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label">
|
||||||
|
<label class="label">Anmerkung</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" name="note" placeholder="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field is-horizontal">
|
<div class="field is-horizontal">
|
||||||
<div class="field-label"></div>
|
<div class="field-label"></div>
|
||||||
<div class="field-body">
|
<div class="field-body">
|
||||||
|
64
templates/events/plan.html
Normal file
64
templates/events/plan.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{% extends "nav.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">Eventplanung</h1>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h5 class="title is-5">Allgemeines</h5>
|
||||||
|
|
||||||
|
<div class="fixed-grid has-1-cols-mobile">
|
||||||
|
<div class="grid content">
|
||||||
|
<div class="cell is-col-span-2">
|
||||||
|
<p><b>Name:</b> {{ event.name }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<p><b>Datum:</b> {{ event.date.format("%d.%m.%Y") }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<p><b>Uhrzeit:</b> {{ event.start_time.format("%R") }} Uhr - {{ event.end_time.format("%R") }} Uhr</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell is-col-span-2">
|
||||||
|
<p><b>Veranstaltungsort:</b> {{ event.location.as_ref().unwrap().name }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<p><b>Wachhabender:</b> {% if event.voluntary_wachhabender %}FF{% else %}BF{% endif %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<p><b>Führungsassistent:</b> {% if event.voluntary_wachhabender %}FF{% else %}BF{% endif %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell is-col-span-2">
|
||||||
|
<p><b>Anzahl der Posten:</b> {{ event.amount_of_posten }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell is-col-span-2">
|
||||||
|
<p><b>Anzugsordnung:</b> {{ event.clothing }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell is-col-span-2">
|
||||||
|
<p><b>Anmerkungen:</b> {{ event.note.as_ref().unwrap_or(String::new()|as_ref) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h5 class="title is-5">Einteilung Personal</h5>
|
||||||
|
<!--TODO: next: Use availabillities-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h5 class="title is-5">Einteilung Fahrzeuge</h5>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
@ -56,7 +56,7 @@
|
|||||||
Events am {{ date.format("%d.%m.%Y") }}
|
Events am {{ date.format("%d.%m.%Y") }}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{% if (user.role == Role::Admin || user.role == Role::AreaManager) && (selected_area.is_none() ||
|
{% if user.role == Role::Admin || user.role == Role::AreaManager && (selected_area.is_none() ||
|
||||||
selected_area.unwrap() == user.area_id) %}
|
selected_area.unwrap() == user.area_id) %}
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
<a class="button is-link is-light" hx-boost="true" href="/events/new?date={{ date }}">
|
<a class="button is-link is-light" hx-boost="true" href="/events/new?date={{ date }}">
|
||||||
@ -81,7 +81,7 @@
|
|||||||
<h5 class="title is-5 level-left">{{ event.name }}</h5>
|
<h5 class="title is-5 level-left">{{ event.name }}</h5>
|
||||||
<span class="level-right">
|
<span class="level-right">
|
||||||
{% if user.role == Role::AreaManager || user.role == Role::Admin %}
|
{% if user.role == Role::AreaManager || user.role == Role::Admin %}
|
||||||
<a href="/assignments/new?event={{ event.id }}" hx-boost="true"
|
<a href="/events/{{ event.id }}/plan" hx-boost="true"
|
||||||
class="button is-primary level-item">Planen</a>
|
class="button is-primary level-item">Planen</a>
|
||||||
<a href="" class="button is-primary-light level-item">bearbeiten</a>
|
<a href="" class="button is-primary-light level-item">bearbeiten</a>
|
||||||
<a href="" class="button is-warning level-item">als abgesagt markieren</a>
|
<a href="" class="button is-warning level-item">als abgesagt markieren</a>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user