Add printable export
This commit is contained in:
parent
3b78ef37dc
commit
d5194e82f8
2
.env
2
.env
@ -1,5 +1,5 @@
|
|||||||
DATABASE_URL="sqlite://terminwahl_back/db.sqlite"
|
DATABASE_URL="sqlite://terminwahl_back/db.sqlite"
|
||||||
RUST_LOG="debug"
|
RUST_LOG="debug,sqlx=error"
|
||||||
SMTP_USER="SMTP_USERNAME"
|
SMTP_USER="SMTP_USERNAME"
|
||||||
SMTP_PASSWORD="SMTP_PASSWORD"
|
SMTP_PASSWORD="SMTP_PASSWORD"
|
||||||
PORT="8087"
|
PORT="8087"
|
||||||
|
@ -19,12 +19,12 @@ use crate::db::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct FullAppointment {
|
pub struct AppointmentTeacherSlot {
|
||||||
pub teacher: Teacher,
|
pub teacher: Teacher,
|
||||||
pub slot: AppointmentSlot,
|
pub slot: AppointmentSlot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FullAppointment {
|
impl AppointmentTeacherSlot {
|
||||||
pub async fn get(pool: &Pool, teacher_id: i64, slot_id: i64) -> Result<Self, Box<dyn Error>> {
|
pub async fn get(pool: &Pool, teacher_id: i64, slot_id: i64) -> Result<Self, Box<dyn Error>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
teacher: get_teacher_by_id(pool, teacher_id)
|
teacher: get_teacher_by_id(pool, teacher_id)
|
||||||
@ -62,7 +62,7 @@ pub async fn save_appointments_json(
|
|||||||
|PlannedAppointment {
|
|PlannedAppointment {
|
||||||
teacher_id,
|
teacher_id,
|
||||||
slot_id,
|
slot_id,
|
||||||
}| { FullAppointment::get(&pool, teacher_id, slot_id) },
|
}| { AppointmentTeacherSlot::get(&pool, teacher_id, slot_id) },
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get full list of appointments");
|
.expect("Failed to get full list of appointments");
|
||||||
@ -80,7 +80,7 @@ pub async fn save_appointments_json(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_confirmation_request<'a>(
|
pub async fn send_confirmation_request<'a>(
|
||||||
appointments: &Vec<FullAppointment>,
|
appointments: &Vec<AppointmentTeacherSlot>,
|
||||||
nutzer: &Nutzer,
|
nutzer: &Nutzer,
|
||||||
validation_key: &str,
|
validation_key: &str,
|
||||||
handlebars: &Handlebars<'a>,
|
handlebars: &Handlebars<'a>,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::query_as;
|
use sqlx::query_as;
|
||||||
|
|
||||||
use terminwahl_typen::{
|
use terminwahl_typen::{
|
||||||
AppointmentSlot, AppointmentSlots, IdType, SlotId, Subject, Subjects, Teacher, Teachers,
|
AppointmentSlot, AppointmentSlots, IdType, Nutzer, SlotId, Subject, Subjects, Teacher, Teachers,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Pool;
|
use super::Pool;
|
||||||
@ -85,3 +87,74 @@ pub async fn get_unavailable(db: &Pool) -> Result<HashSet<SlotId>, sqlx::Error>
|
|||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct TeacherWithAppointments {
|
||||||
|
teacher: Teacher,
|
||||||
|
appointments: Vec<AssignedAppointment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct FullAppointment {
|
||||||
|
id: IdType,
|
||||||
|
teacher_id: IdType,
|
||||||
|
slot_id: IdType,
|
||||||
|
nutzer_id: IdType,
|
||||||
|
expires: Option<NaiveDateTime>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct AssignedAppointment {
|
||||||
|
appointment: FullAppointment,
|
||||||
|
slot: AppointmentSlot,
|
||||||
|
nutzer: Nutzer,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_teachers(db: &Pool) -> Result<Vec<TeacherWithAppointments>, sqlx::Error> {
|
||||||
|
let teachers = get_teachers(db).await?;
|
||||||
|
let mut response = Vec::new();
|
||||||
|
|
||||||
|
for teacher in teachers.into_iter() {
|
||||||
|
let teacher_id: i64 = teacher.id;
|
||||||
|
let mut appointments: Vec<FullAppointment> = query_as!(
|
||||||
|
FullAppointment,
|
||||||
|
r#"
|
||||||
|
SELECT id,teacher_id,slot_id,nutzer_id,expires
|
||||||
|
FROM `appointments` WHERE teacher_id = ?"#,
|
||||||
|
teacher_id
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await?;
|
||||||
|
appointments.sort_by(|a, b| a.slot_id.cmp(&b.slot_id));
|
||||||
|
let mut assigned_appointments: Vec<AssignedAppointment> = Vec::new();
|
||||||
|
for appointment in appointments.into_iter() {
|
||||||
|
let slot = query_as!(
|
||||||
|
AppointmentSlot,
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM `appointment_slots` WHERE id = ? "#,
|
||||||
|
appointment.slot_id
|
||||||
|
)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?;
|
||||||
|
let nutzer = query_as!(
|
||||||
|
Nutzer,
|
||||||
|
r#"
|
||||||
|
SELECT name, schueler as schüler,email
|
||||||
|
FROM `nutzer` WHERE id = ? "#,
|
||||||
|
appointment.nutzer_id
|
||||||
|
)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?;
|
||||||
|
assigned_appointments.push(AssignedAppointment {
|
||||||
|
appointment,
|
||||||
|
slot,
|
||||||
|
nutzer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
response.push(TeacherWithAppointments {
|
||||||
|
teacher,
|
||||||
|
appointments: assigned_appointments,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ use futures::future;
|
|||||||
use sqlx::{query, query_as};
|
use sqlx::{query, query_as};
|
||||||
use terminwahl_typen::{IdType, Nutzer, PlannedAppointment};
|
use terminwahl_typen::{IdType, Nutzer, PlannedAppointment};
|
||||||
|
|
||||||
use crate::api::write::FullAppointment;
|
use crate::api::write::AppointmentTeacherSlot;
|
||||||
|
|
||||||
use super::Pool;
|
use super::Pool;
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ pub async fn save_nutzer(pool: &Pool, nutzer: &Nutzer) -> Result<IdType, sqlx::E
|
|||||||
pub async fn confirm_appointments(
|
pub async fn confirm_appointments(
|
||||||
pool: &Pool,
|
pool: &Pool,
|
||||||
validation_key: &str,
|
validation_key: &str,
|
||||||
) -> Result<Vec<FullAppointment>, sqlx::Error> {
|
) -> Result<Vec<AppointmentTeacherSlot>, sqlx::Error> {
|
||||||
let _ = query!(
|
let _ = query!(
|
||||||
"UPDATE appointments SET expires = NULL WHERE validation_key = ?",
|
"UPDATE appointments SET expires = NULL WHERE validation_key = ?",
|
||||||
validation_key
|
validation_key
|
||||||
@ -76,7 +76,7 @@ pub async fn confirm_appointments(
|
|||||||
|PlannedAppointment {
|
|PlannedAppointment {
|
||||||
teacher_id,
|
teacher_id,
|
||||||
slot_id,
|
slot_id,
|
||||||
}| { FullAppointment::get(pool, teacher_id, slot_id) },
|
}| { AppointmentTeacherSlot::get(pool, teacher_id, slot_id) },
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get full list of appointments");
|
.expect("Failed to get full list of appointments");
|
||||||
@ -89,7 +89,7 @@ pub async fn cancel_appointment(
|
|||||||
teacher_id: IdType,
|
teacher_id: IdType,
|
||||||
slot_id: IdType,
|
slot_id: IdType,
|
||||||
validation_key: &str,
|
validation_key: &str,
|
||||||
) -> Result<Vec<FullAppointment>, sqlx::Error> {
|
) -> Result<Vec<AppointmentTeacherSlot>, sqlx::Error> {
|
||||||
let _ = query!(
|
let _ = query!(
|
||||||
"DELETE FROM appointments WHERE teacher_id = ? AND slot_id = ? AND validation_key = ?",
|
"DELETE FROM appointments WHERE teacher_id = ? AND slot_id = ? AND validation_key = ?",
|
||||||
teacher_id,
|
teacher_id,
|
||||||
@ -113,7 +113,7 @@ pub async fn cancel_appointment(
|
|||||||
|PlannedAppointment {
|
|PlannedAppointment {
|
||||||
teacher_id,
|
teacher_id,
|
||||||
slot_id,
|
slot_id,
|
||||||
}| { FullAppointment::get(pool, teacher_id, slot_id) },
|
}| { AppointmentTeacherSlot::get(pool, teacher_id, slot_id) },
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get full list of appointments");
|
.expect("Failed to get full list of appointments");
|
||||||
|
@ -92,6 +92,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
web::resource("/cancel/{teacher_id}/{slot_id}/{validation_key}")
|
web::resource("/cancel/{teacher_id}/{slot_id}/{validation_key}")
|
||||||
.route(web::get().to(views::delete_appointment)),
|
.route(web::get().to(views::delete_appointment)),
|
||||||
)
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/export/all/{password}")
|
||||||
|
.route(web::get().to(views::export_appointments)),
|
||||||
|
)
|
||||||
.service(Files::new("/", wasm_statics.clone()).index_file("index.html"))
|
.service(Files::new("/", wasm_statics.clone()).index_file("index.html"))
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", port))?
|
.bind(("127.0.0.1", port))?
|
||||||
|
@ -6,6 +6,7 @@ use terminwahl_typen::IdType;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{
|
db::{
|
||||||
|
read::get_all_teachers,
|
||||||
write::{cancel_appointment, confirm_appointments},
|
write::{cancel_appointment, confirm_appointments},
|
||||||
Pool,
|
Pool,
|
||||||
},
|
},
|
||||||
@ -52,3 +53,30 @@ pub async fn delete_appointment(
|
|||||||
Err(e) => Err(error::ErrorBadRequest(e)),
|
Err(e) => Err(error::ErrorBadRequest(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn export_appointments(
|
||||||
|
pool: web::Data<Pool>,
|
||||||
|
_mailer: web::Data<AsyncSmtpTransport<Tokio1Executor>>,
|
||||||
|
handlebars: web::Data<Handlebars<'_>>,
|
||||||
|
css: web::Data<CssPath>,
|
||||||
|
path: web::Path<String>,
|
||||||
|
) -> Result<HttpResponse, error::Error> {
|
||||||
|
let password = path.into_inner();
|
||||||
|
dbg!(&password);
|
||||||
|
if password == "AllExport1517" {
|
||||||
|
match get_all_teachers(&pool).await {
|
||||||
|
Ok(teachers) => {
|
||||||
|
dbg!(&teachers);
|
||||||
|
let data = json!({
|
||||||
|
"css_file" : css.path,
|
||||||
|
"teachers": teachers,
|
||||||
|
});
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.body(handlebars.render("lehrer_einzeln.html", &data).unwrap()))
|
||||||
|
}
|
||||||
|
Err(e) => Err(error::ErrorBadRequest(e)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(error::ErrorBadRequest("Wrong Password"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
76
terminwahl_back/templates/lehrer_einzeln.html.hbs
Normal file
76
terminwahl_back/templates/lehrer_einzeln.html.hbs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Elternsprechtagtermine</title>
|
||||||
|
<link rel="stylesheet" href="/{{css_file}}" />
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content="Termine,Waldorfschule,Lehrersprechtag,Lehrerinnensprechtag"
|
||||||
|
/>
|
||||||
|
<meta name="author" content="Franz Dietrich" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Termine buchen für den Lehrersprechtag der Waldorfschule Uhlandshöhe"
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
@media print {
|
||||||
|
.card {
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
||||||
|
.columns {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns is-multiline is-centered">
|
||||||
|
|
||||||
|
{{#each teachers as |lehrer|}}
|
||||||
|
<div
|
||||||
|
class="column is-12-tablet is-4-desktop"
|
||||||
|
>
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
{{lehrer.teacher.ansprache}}
|
||||||
|
{{lehrer.teacher.last_name}}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
<table class="table is-striped is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Zeit</th>
|
||||||
|
<th>Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each lehrer.appointments as |appointment|}}
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{{time_of appointment.slot.start_time}}
|
||||||
|
–
|
||||||
|
{{time_of appointment.slot.end_time}}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
{{appointment.nutzer.name}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
pub type IdType = i64;
|
pub type IdType = i64;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct Teacher {
|
pub struct Teacher {
|
||||||
pub id: IdType,
|
pub id: IdType,
|
||||||
pub ansprache: String,
|
pub ansprache: String,
|
||||||
@ -15,14 +15,14 @@ pub struct Teacher {
|
|||||||
|
|
||||||
pub type Teachers = Vec<Teacher>;
|
pub type Teachers = Vec<Teacher>;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct Subject {
|
pub struct Subject {
|
||||||
pub id: IdType,
|
pub id: IdType,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
pub type Subjects = Vec<Subject>;
|
pub type Subjects = Vec<Subject>;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct Appointment {
|
pub struct Appointment {
|
||||||
pub id: IdType,
|
pub id: IdType,
|
||||||
pub teacher_id: IdType,
|
pub teacher_id: IdType,
|
||||||
@ -36,14 +36,14 @@ pub struct PlannedAppointment {
|
|||||||
|
|
||||||
pub type AppointmentSlots = HashMap<IdType, AppointmentSlot>;
|
pub type AppointmentSlots = HashMap<IdType, AppointmentSlot>;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct AppointmentSlot {
|
pub struct AppointmentSlot {
|
||||||
pub id: IdType,
|
pub id: IdType,
|
||||||
pub start_time: NaiveDateTime,
|
pub start_time: NaiveDateTime,
|
||||||
pub end_time: NaiveDateTime,
|
pub end_time: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct OccupiedSlot {
|
pub struct OccupiedSlot {
|
||||||
pub id: IdType,
|
pub id: IdType,
|
||||||
pub teacher_id: IdType,
|
pub teacher_id: IdType,
|
||||||
@ -65,7 +65,7 @@ impl SlotId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub enum RequestState {
|
pub enum RequestState {
|
||||||
Success,
|
Success,
|
||||||
Message(String),
|
Message(String),
|
||||||
|
Loading…
Reference in New Issue
Block a user