feat: crud for availabillity
This commit is contained in:
parent
8840f0ab48
commit
b736b04ced
28
src/calendar/delete_availabillity.rs
Normal file
28
src/calendar/delete_availabillity.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::availabillity::Availabillity;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AvailabillityPath {
|
||||
pub id: i32
|
||||
}
|
||||
|
||||
#[actix_web::delete("/availabillity/delete/{id}")]
|
||||
pub async fn delete_availabillity(user: Identity, pool: web::Data<PgPool>, path: web::Path<AvailabillityPath>) -> impl Responder {
|
||||
if let Ok(current_user_id) = user.id() {
|
||||
let current_user_id: i32 = current_user_id.parse().unwrap();
|
||||
|
||||
if let Ok(availabillity_in_db) = Availabillity::read_by_id(pool.get_ref(), path.id).await {
|
||||
if availabillity_in_db.user_id == current_user_id {
|
||||
if let Ok(_) = Availabillity::delete(pool.get_ref(), availabillity_in_db.id).await {
|
||||
return HttpResponse::NoContent().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HttpResponse::BadRequest().finish();
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
pub mod routes;
|
||||
mod get_availabillity_new;
|
||||
mod post_availabillity;
|
||||
mod delete_availabillity;
|
||||
mod update_availabillity;
|
||||
|
||||
pub use routes::init;
|
||||
|
@ -8,10 +8,10 @@ use crate::models::{availabillity::Availabillity, user::User};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AvailabillityForm {
|
||||
date: NaiveDate,
|
||||
start_time: Option<NaiveTime>,
|
||||
end_time: Option<NaiveTime>,
|
||||
comment: Option<String>
|
||||
pub date: NaiveDate,
|
||||
pub from: Option<NaiveTime>,
|
||||
pub till: Option<NaiveTime>,
|
||||
pub comment: Option<String>
|
||||
}
|
||||
|
||||
#[actix_web::post("/availabillity/new")]
|
||||
@ -24,7 +24,7 @@ pub async fn post_availabillity(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Ok(_) = Availabillity::create(pool.get_ref(), current_user.id, form.date, form.start_time, form.end_time, form.comment.clone()).await {
|
||||
if let Ok(_) = Availabillity::create(pool.get_ref(), current_user.id, form.date, form.from, form.till, form.comment.clone()).await {
|
||||
HttpResponse::Found()
|
||||
.insert_header((LOCATION, "/"))
|
||||
.finish()
|
||||
|
@ -1,20 +1,23 @@
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use chrono::{NaiveDate, Utc};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::{
|
||||
area::Area, availabillity::Availabillity, event::Event, role::Role, user::User,
|
||||
area::Area, availabillity::Availabillity, event::Event, function, role::Role, user::User
|
||||
};
|
||||
|
||||
use super::{get_availabillity_new::get_availabillity_new, post_availabillity::post_availabillity};
|
||||
use super::{delete_availabillity::delete_availabillity, get_availabillity_new::get_availabillity_new, post_availabillity::post_availabillity, update_availabillity::{get_update_availabillity, post_update_availabillity}};
|
||||
|
||||
pub fn init(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(get_index);
|
||||
cfg.service(get_availabillity_new);
|
||||
cfg.service(post_availabillity);
|
||||
cfg.service(delete_availabillity);
|
||||
cfg.service(get_update_availabillity);
|
||||
cfg.service(post_update_availabillity);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -26,12 +29,15 @@ pub struct CalendarQuery {
|
||||
#[template(path = "index.html")]
|
||||
struct CalendarTemplate {
|
||||
user_role: Role,
|
||||
current_user_id: i32,
|
||||
date: NaiveDate,
|
||||
area: Area,
|
||||
events: Vec<Event>,
|
||||
availabillities: Vec<Availabillity>,
|
||||
}
|
||||
|
||||
type Function = function::Function;
|
||||
|
||||
#[actix_web::get("/")]
|
||||
async fn get_index(
|
||||
user: Identity,
|
||||
@ -49,16 +55,13 @@ async fn get_index(
|
||||
.await
|
||||
.unwrap();
|
||||
let events = Event::read_by_date(pool.get_ref(), date).await.unwrap();
|
||||
let mut availabillities = Availabillity::read_by_date(pool.get_ref(), date)
|
||||
let availabillities = Availabillity::read_by_date_including_user(pool.get_ref(), date)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for avl in availabillities.iter_mut() {
|
||||
avl.load_user(pool.get_ref()).await.unwrap()
|
||||
}
|
||||
|
||||
let template = CalendarTemplate {
|
||||
user_role: current_user.role,
|
||||
current_user_id: current_user.id,
|
||||
date,
|
||||
area,
|
||||
events,
|
||||
|
110
src/calendar/update_availabillity.rs
Normal file
110
src/calendar/update_availabillity.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use askama_actix::TemplateToResponse;
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{calendar::post_availabillity::AvailabillityForm, models::{availabillity::Availabillity, role::Role, user::User}};
|
||||
|
||||
use super::delete_availabillity::AvailabillityPath;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "availabillity_edit.html")]
|
||||
pub struct AvailabillityEditTemplate {
|
||||
user_role: Role,
|
||||
date: NaiveDate,
|
||||
id: i32,
|
||||
start_time: String,
|
||||
end_time: String,
|
||||
has_time: bool,
|
||||
comment: String,
|
||||
}
|
||||
|
||||
#[actix_web::get("/availabillity/edit/{id}")]
|
||||
pub async fn get_update_availabillity(
|
||||
user: Identity,
|
||||
pool: web::Data<PgPool>,
|
||||
path: web::Path<AvailabillityPath>,
|
||||
) -> impl Responder {
|
||||
let current_user = User::read_by_id(pool.as_ref(), user.id().unwrap().parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Ok(availabillity) = Availabillity::read_by_id(pool.get_ref(), path.id).await {
|
||||
if availabillity.user_id == user.id().unwrap().parse::<i32>().unwrap() {
|
||||
let start_time = availabillity
|
||||
.start_time
|
||||
.unwrap_or(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
|
||||
.format("%R")
|
||||
.to_string();
|
||||
|
||||
let end_time = availabillity
|
||||
.end_time
|
||||
.unwrap_or(NaiveTime::from_hms_opt(23, 59, 0).unwrap())
|
||||
.format("%R")
|
||||
.to_string();
|
||||
|
||||
let has_time = availabillity.start_time.is_some() && availabillity.end_time.is_some();
|
||||
|
||||
let comment = availabillity.comment.unwrap_or(String::new());
|
||||
|
||||
let template = AvailabillityEditTemplate {
|
||||
user_role: current_user.role,
|
||||
date: availabillity.date,
|
||||
id: path.id,
|
||||
start_time,
|
||||
end_time,
|
||||
has_time,
|
||||
comment
|
||||
};
|
||||
|
||||
return template.to_response();
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::BadRequest().body("Availabillity with this id doesn't exist.")
|
||||
}
|
||||
|
||||
#[actix_web::post("/availabillity/edit/{id}")]
|
||||
pub async fn post_update_availabillity(
|
||||
user: Identity,
|
||||
pool: web::Data<PgPool>,
|
||||
path: web::Path<AvailabillityPath>,
|
||||
form: web::Form<AvailabillityForm>
|
||||
) -> impl Responder {
|
||||
let current_user = User::read_by_id(pool.as_ref(), user.id().unwrap().parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Ok(mut availabillity) = Availabillity::read_by_id(pool.get_ref(), path.id).await {
|
||||
if availabillity.user_id == user.id().unwrap().parse::<i32>().unwrap() {
|
||||
let mut has_changed = false;
|
||||
|
||||
if availabillity.start_time != form.from {
|
||||
availabillity.start_time = form.from;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::BadRequest().body("Fehler beim erstellen")
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
use sqlx::{query, PgPool};
|
||||
|
||||
use super::user::User;
|
||||
use super::{function::Function, role::Role, user::User};
|
||||
|
||||
pub struct Availabillity {
|
||||
pub id: i32,
|
||||
@ -56,10 +56,106 @@ impl Availabillity {
|
||||
Ok(availabillities)
|
||||
}
|
||||
|
||||
pub async fn load_user(&mut self, pool: &PgPool) -> anyhow::Result<()> {
|
||||
let user = User::read_by_id(pool, self.user_id).await?;
|
||||
self.user = Some(user);
|
||||
pub async fn read_by_date_including_user(
|
||||
pool: &PgPool,
|
||||
date: NaiveDate,
|
||||
) -> anyhow::Result<Vec<Availabillity>> {
|
||||
let records = query!(
|
||||
r##"
|
||||
SELECT
|
||||
availabillity.id,
|
||||
availabillity.userId,
|
||||
availabillity.date,
|
||||
availabillity.startTime,
|
||||
availabillity.endTime,
|
||||
availabillity.comment,
|
||||
user_.name,
|
||||
user_.email,
|
||||
user_.password,
|
||||
user_.salt,
|
||||
user_.role AS "role: Role",
|
||||
user_.function AS "function: Function",
|
||||
user_.areaId,
|
||||
user_.locked,
|
||||
user_.lastLogin,
|
||||
user_.receiveNotifications
|
||||
FROM availabillity
|
||||
JOIN user_ ON availabillity.userId = user_.id
|
||||
WHERE availabillity.date = $1;
|
||||
"##,
|
||||
date
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let availabillities = records
|
||||
.iter()
|
||||
.map(|r| Availabillity {
|
||||
id: r.id,
|
||||
user_id: r.userid,
|
||||
user: Some(User {
|
||||
id: r.userid,
|
||||
name: r.name.clone(),
|
||||
email: r.email.clone(),
|
||||
password: r.password.clone(),
|
||||
salt: r.salt.clone(),
|
||||
role: r.role.clone(),
|
||||
function: r.function.clone(),
|
||||
area_id: r.areaid,
|
||||
locked: r.locked,
|
||||
last_login: r.lastlogin,
|
||||
receive_notifications: r.receivenotifications,
|
||||
}),
|
||||
date: r.date,
|
||||
start_time: r.starttime,
|
||||
end_time: r.endtime,
|
||||
comment: r.comment.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(availabillities)
|
||||
}
|
||||
|
||||
pub async fn read_by_id(pool: &PgPool, id: i32) -> anyhow::Result<Availabillity> {
|
||||
let record = query!("SELECT * FROM availabillity WHERE id = $1", id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
let availabillity = Availabillity {
|
||||
id: record.id,
|
||||
user_id: record.userid,
|
||||
user: None,
|
||||
date: record.date,
|
||||
start_time: record.starttime,
|
||||
end_time: record.endtime,
|
||||
comment: record.comment.clone(),
|
||||
};
|
||||
|
||||
Ok(availabillity)
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
pool: &PgPool,
|
||||
id: i32,
|
||||
updated_availabillity: &Availabillity,
|
||||
) -> anyhow::Result<()> {
|
||||
query!(
|
||||
"UPDATE availabillity SET startTime = $1, endTime = $2, comment = $3 WHERE id = $4",
|
||||
updated_availabillity.start_time,
|
||||
updated_availabillity.end_time,
|
||||
updated_availabillity.comment,
|
||||
id
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(pool: &PgPool, id: i32) -> anyhow::Result<()> {
|
||||
query!("DELETE FROM availabillity WHERE id = $1", id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
117
templates/availabillity_edit.html
Normal file
117
templates/availabillity_edit.html
Normal file
@ -0,0 +1,117 @@
|
||||
{% extends "nav.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<form method="post" action="/availabillity/edit/{{ id }}">
|
||||
<h1 class="title">Bearbeite Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
|
||||
|
||||
<input type="hidden" name="date" value="{{ date }}">
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Dauer</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="radio">
|
||||
{% if has_time %}
|
||||
<input type="radio" id="wholeDay" name="hasTime">
|
||||
{% else %}
|
||||
<input type="radio" id="wholeDay" name="hasTime" checked>
|
||||
{% endif %}
|
||||
ganztägig
|
||||
<label class="radio">
|
||||
{% if has_time %}
|
||||
<input type="radio" id="partDay" name="hasTime" checked>
|
||||
{% else %}
|
||||
<input type="radio" id="partDay" name="hasTime">
|
||||
{% endif %}
|
||||
zeitweise
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Von - Bis</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="from" name="from" value="{{ start_time }}" disabled>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="till" name="till" value="{{ end_time }}" disabled>
|
||||
</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 }}</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">
|
||||
<input class="button is-link" type="submit" value="Speichern">
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-link is-light" href="/?date={{ date }}">Zurück</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const wholeDay = document.getElementById("wholeDay");
|
||||
const partDay = document.getElementById("partDay");
|
||||
|
||||
const from = document.getElementById("from");
|
||||
const till = document.getElementById("till");
|
||||
|
||||
let lastFrom = null;
|
||||
let lastTill = null;
|
||||
|
||||
wholeDay.addEventListener("click", (event) => {
|
||||
from.disabled = true
|
||||
till.disabled = true
|
||||
|
||||
lastFrom = from.value;
|
||||
lastTill = till.value;
|
||||
|
||||
from.value = null;
|
||||
till.value = null;
|
||||
});
|
||||
|
||||
partDay.addEventListener("click", (event) => {
|
||||
from.disabled = false
|
||||
till.disabled = false
|
||||
|
||||
if (lastFrom != null) {
|
||||
from.value = lastFrom;
|
||||
}
|
||||
|
||||
if (lastTill != null) {
|
||||
till.value = lastTill;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -33,10 +33,10 @@
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="from" value="00:00" disabled>
|
||||
<input class="input" type="time" id="from" name="from" value="00:00" disabled>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input class="input" type="time" id="till" value="23:59" disabled>
|
||||
<input class="input" type="time" id="till" name="till" value="23:59" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -48,7 +48,7 @@
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" placeholder="nur Posten, nur Wachhabender, etc.."></textarea>
|
||||
<textarea class="textarea" name="comment" placeholder="nur Posten, nur Wachhabender, etc.."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,19 +62,74 @@
|
||||
<h5 class="title is-5">keine Verfügbarkeiten eingetragen</h5>
|
||||
</div>
|
||||
{% else %}
|
||||
{% for availabillity in availabillities %}
|
||||
{% let user = availabillity.user.as_ref().unwrap() %}
|
||||
|
||||
<div class="box">
|
||||
<p>{{ user.name }}</p>
|
||||
<p>{{ user.function }}</p>
|
||||
{% if availabillity.start_time.is_some() && availabillity.end_time.is_some() %}
|
||||
<p>{{ availabillity.start_time.as_ref().unwrap() }}</p>
|
||||
{% endif %}
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Funktion</th>
|
||||
<th>Zeitraum</th>
|
||||
<th>Kommentar</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for availabillity in availabillities %}
|
||||
{% let user = availabillity.user.as_ref().unwrap() %}
|
||||
<tr id="availabillity-{{ availabillity.id }}">
|
||||
<td>{{ user.name }}</td>
|
||||
<td>
|
||||
{% match user.function %}
|
||||
{% when Function::Posten %}
|
||||
<span class="tag is-info is-light">Posten</span>
|
||||
{% when Function::Wachhabender %}
|
||||
<span class="tag is-info">Wachhabender</span>
|
||||
{% else %}
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>
|
||||
{% if availabillity.start_time.is_some() && availabillity.end_time.is_some() %}
|
||||
{{ availabillity.start_time.as_ref().unwrap().format("%R") }} bis {{ availabillity.end_time.as_ref().unwrap().format("%R") }}
|
||||
{% else %}
|
||||
ganztägig
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ availabillity.comment.as_deref().unwrap_or("") }}
|
||||
</td>
|
||||
<td>
|
||||
{% if availabillity.user_id == current_user_id %}
|
||||
<div class="buttons is-right">
|
||||
<a class="button is-link" href="/availabillity/edit/{{ availabillity.id }}">Bearbeiten</a>
|
||||
<button class="button is-danger" name="delete-availabillity">Löschen</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.getElementsByName("delete-availabillity")
|
||||
.forEach(ele => ele.addEventListener("click", (event) => {
|
||||
const id = event.target.closest("tr").id.split('-')[1];
|
||||
event.target.classList.add("is-loading");
|
||||
|
||||
fetch(`/availabillity/delete/${id}`, { method: "DELETE"})
|
||||
.then(response => {
|
||||
if (response.status == 204) {
|
||||
document.getElementById(`availabillity-${id}`).remove()
|
||||
} else {
|
||||
event.target.classList.remove("is-loading");
|
||||
console.log("Fehler beim Löschen.")
|
||||
}
|
||||
});
|
||||
}));
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
Loading…
x
Reference in New Issue
Block a user