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"
|
||||
RUST_LOG="debug"
|
||||
RUST_LOG="debug,sqlx=error"
|
||||
SMTP_USER="SMTP_USERNAME"
|
||||
SMTP_PASSWORD="SMTP_PASSWORD"
|
||||
PORT="8087"
|
||||
|
@ -19,12 +19,12 @@ use crate::db::{
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FullAppointment {
|
||||
pub struct AppointmentTeacherSlot {
|
||||
pub teacher: Teacher,
|
||||
pub slot: AppointmentSlot,
|
||||
}
|
||||
|
||||
impl FullAppointment {
|
||||
impl AppointmentTeacherSlot {
|
||||
pub async fn get(pool: &Pool, teacher_id: i64, slot_id: i64) -> Result<Self, Box<dyn Error>> {
|
||||
Ok(Self {
|
||||
teacher: get_teacher_by_id(pool, teacher_id)
|
||||
@ -62,7 +62,7 @@ pub async fn save_appointments_json(
|
||||
|PlannedAppointment {
|
||||
teacher_id,
|
||||
slot_id,
|
||||
}| { FullAppointment::get(&pool, teacher_id, slot_id) },
|
||||
}| { AppointmentTeacherSlot::get(&pool, teacher_id, slot_id) },
|
||||
))
|
||||
.await
|
||||
.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>(
|
||||
appointments: &Vec<FullAppointment>,
|
||||
appointments: &Vec<AppointmentTeacherSlot>,
|
||||
nutzer: &Nutzer,
|
||||
validation_key: &str,
|
||||
handlebars: &Handlebars<'a>,
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::query_as;
|
||||
|
||||
use terminwahl_typen::{
|
||||
AppointmentSlot, AppointmentSlots, IdType, SlotId, Subject, Subjects, Teacher, Teachers,
|
||||
AppointmentSlot, AppointmentSlots, IdType, Nutzer, SlotId, Subject, Subjects, Teacher, Teachers,
|
||||
};
|
||||
|
||||
use super::Pool;
|
||||
@ -85,3 +87,74 @@ pub async fn get_unavailable(db: &Pool) -> Result<HashSet<SlotId>, sqlx::Error>
|
||||
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 terminwahl_typen::{IdType, Nutzer, PlannedAppointment};
|
||||
|
||||
use crate::api::write::FullAppointment;
|
||||
use crate::api::write::AppointmentTeacherSlot;
|
||||
|
||||
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(
|
||||
pool: &Pool,
|
||||
validation_key: &str,
|
||||
) -> Result<Vec<FullAppointment>, sqlx::Error> {
|
||||
) -> Result<Vec<AppointmentTeacherSlot>, sqlx::Error> {
|
||||
let _ = query!(
|
||||
"UPDATE appointments SET expires = NULL WHERE validation_key = ?",
|
||||
validation_key
|
||||
@ -76,7 +76,7 @@ pub async fn confirm_appointments(
|
||||
|PlannedAppointment {
|
||||
teacher_id,
|
||||
slot_id,
|
||||
}| { FullAppointment::get(pool, teacher_id, slot_id) },
|
||||
}| { AppointmentTeacherSlot::get(pool, teacher_id, slot_id) },
|
||||
))
|
||||
.await
|
||||
.expect("Failed to get full list of appointments");
|
||||
@ -89,7 +89,7 @@ pub async fn cancel_appointment(
|
||||
teacher_id: IdType,
|
||||
slot_id: IdType,
|
||||
validation_key: &str,
|
||||
) -> Result<Vec<FullAppointment>, sqlx::Error> {
|
||||
) -> Result<Vec<AppointmentTeacherSlot>, sqlx::Error> {
|
||||
let _ = query!(
|
||||
"DELETE FROM appointments WHERE teacher_id = ? AND slot_id = ? AND validation_key = ?",
|
||||
teacher_id,
|
||||
@ -113,7 +113,7 @@ pub async fn cancel_appointment(
|
||||
|PlannedAppointment {
|
||||
teacher_id,
|
||||
slot_id,
|
||||
}| { FullAppointment::get(pool, teacher_id, slot_id) },
|
||||
}| { AppointmentTeacherSlot::get(pool, teacher_id, slot_id) },
|
||||
))
|
||||
.await
|
||||
.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}")
|
||||
.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"))
|
||||
})
|
||||
.bind(("127.0.0.1", port))?
|
||||
|
@ -6,6 +6,7 @@ use terminwahl_typen::IdType;
|
||||
|
||||
use crate::{
|
||||
db::{
|
||||
read::get_all_teachers,
|
||||
write::{cancel_appointment, confirm_appointments},
|
||||
Pool,
|
||||
},
|
||||
@ -52,3 +53,30 @@ pub async fn delete_appointment(
|
||||
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;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Teacher {
|
||||
pub id: IdType,
|
||||
pub ansprache: String,
|
||||
@ -15,14 +15,14 @@ pub struct Teacher {
|
||||
|
||||
pub type Teachers = Vec<Teacher>;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Subject {
|
||||
pub id: IdType,
|
||||
pub name: String,
|
||||
}
|
||||
pub type Subjects = Vec<Subject>;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Appointment {
|
||||
pub id: IdType,
|
||||
pub teacher_id: IdType,
|
||||
@ -36,14 +36,14 @@ pub struct PlannedAppointment {
|
||||
|
||||
pub type AppointmentSlots = HashMap<IdType, AppointmentSlot>;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct AppointmentSlot {
|
||||
pub id: IdType,
|
||||
pub start_time: NaiveDateTime,
|
||||
pub end_time: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct OccupiedSlot {
|
||||
pub id: IdType,
|
||||
pub teacher_id: IdType,
|
||||
@ -65,7 +65,7 @@ impl SlotId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub enum RequestState {
|
||||
Success,
|
||||
Message(String),
|
||||
|
Loading…
Reference in New Issue
Block a user