Funktionierende zu und absage.
This commit is contained in:
parent
87c0a2d541
commit
43d08a014b
@ -8,14 +8,11 @@ use log::debug;
|
|||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use terminwahl_typen::{
|
use terminwahl_typen::{AppointmentSlot, Nutzer, PlannedAppointment, RequestState, Teacher};
|
||||||
AppointmentSlot, IdType, Nutzer, PlannedAppointment, RequestState, Teacher,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::db::{
|
use crate::db::{
|
||||||
self,
|
self,
|
||||||
read::{get_slot_by_id, get_teacher_by_id},
|
read::{get_slot_by_id, get_teacher_by_id},
|
||||||
write::confirm_appointments,
|
|
||||||
Pool,
|
Pool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,7 +23,7 @@ pub struct FullAppointment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FullAppointment {
|
impl FullAppointment {
|
||||||
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)
|
||||||
.await
|
.await
|
||||||
|
@ -76,7 +76,7 @@ pub async fn get_unavailable(db: &Pool) -> Result<HashSet<SlotId>, sqlx::Error>
|
|||||||
SlotId,
|
SlotId,
|
||||||
r#"
|
r#"
|
||||||
SELECT teacher_id, slot_id
|
SELECT teacher_id, slot_id
|
||||||
FROM `appointments` WHERE datetime(expires) > datetime('now');"#,
|
FROM `appointments` WHERE datetime(expires) > datetime('now') OR expires is NULL;"#,
|
||||||
)
|
)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use chrono::{Duration, Local};
|
use chrono::{Duration, Local};
|
||||||
use sqlx::query;
|
use futures::future;
|
||||||
|
use sqlx::{query, query_as};
|
||||||
use terminwahl_typen::{IdType, Nutzer, PlannedAppointment};
|
use terminwahl_typen::{IdType, Nutzer, PlannedAppointment};
|
||||||
|
|
||||||
|
use crate::api::write::FullAppointment;
|
||||||
|
|
||||||
use super::Pool;
|
use super::Pool;
|
||||||
|
|
||||||
pub async fn save_appointments(
|
pub async fn save_appointments(
|
||||||
@ -51,12 +54,69 @@ pub async fn save_nutzer(pool: &Pool, nutzer: &Nutzer) -> Result<IdType, sqlx::E
|
|||||||
Ok(db_nutzer.id)
|
Ok(db_nutzer.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn confirm_appointments(pool: &Pool, validation_key: &str) -> Result<(), sqlx::Error> {
|
pub async fn confirm_appointments(
|
||||||
|
pool: &Pool,
|
||||||
|
validation_key: &str,
|
||||||
|
) -> Result<Vec<FullAppointment>, 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
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
let appointments = query_as!(
|
||||||
|
PlannedAppointment,
|
||||||
|
"Select teacher_id, slot_id from appointments WHERE validation_key = ? and expires is NULL",
|
||||||
|
validation_key
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let full_appointments = future::try_join_all(appointments.into_iter().map(
|
||||||
|
|PlannedAppointment {
|
||||||
|
teacher_id,
|
||||||
|
slot_id,
|
||||||
|
}| { FullAppointment::get(pool, teacher_id, slot_id) },
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.expect("Failed to get full list of appointments");
|
||||||
|
|
||||||
|
Ok(full_appointments)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cancel_appointment(
|
||||||
|
pool: &Pool,
|
||||||
|
teacher_id: IdType,
|
||||||
|
slot_id: IdType,
|
||||||
|
validation_key: &str,
|
||||||
|
) -> Result<Vec<FullAppointment>, sqlx::Error> {
|
||||||
|
let _ = query!(
|
||||||
|
"DELETE FROM appointments WHERE teacher_id = ? AND slot_id = ? AND validation_key = ?",
|
||||||
|
teacher_id,
|
||||||
|
slot_id,
|
||||||
|
validation_key
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Fetch the remaining appointments
|
||||||
|
let appointments = query_as!(
|
||||||
|
PlannedAppointment,
|
||||||
|
"Select teacher_id, slot_id from appointments WHERE validation_key = ? and expires is NULL",
|
||||||
|
validation_key
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Fetch the teacher names and times
|
||||||
|
let full_appointments = future::try_join_all(appointments.into_iter().map(
|
||||||
|
|PlannedAppointment {
|
||||||
|
teacher_id,
|
||||||
|
slot_id,
|
||||||
|
}| { FullAppointment::get(pool, teacher_id, slot_id) },
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.expect("Failed to get full list of appointments");
|
||||||
|
|
||||||
|
Ok(full_appointments)
|
||||||
}
|
}
|
||||||
|
@ -2,3 +2,19 @@ pub mod api;
|
|||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod handlebars_helper;
|
pub mod handlebars_helper;
|
||||||
pub mod views;
|
pub mod views;
|
||||||
|
|
||||||
|
pub struct CssPath {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CssPath {
|
||||||
|
fn default() -> Self {
|
||||||
|
let css_path = glob::glob("terminwahl_front/dist/my_bulma_colors*.css")
|
||||||
|
.expect("Failed to find css file")
|
||||||
|
.next()
|
||||||
|
.expect("Failed to find file")
|
||||||
|
.expect("Failed to find file");
|
||||||
|
let css_file = css_path.file_name().unwrap().to_str().unwrap().to_owned();
|
||||||
|
Self { path: css_file }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,7 +11,7 @@ use handlebars::Handlebars;
|
|||||||
use lettre::{transport::smtp::authentication::Credentials, SmtpTransport};
|
use lettre::{transport::smtp::authentication::Credentials, SmtpTransport};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use std::env;
|
use std::env;
|
||||||
use terminwahl_back::{api, db, handlebars_helper::TimeOfDate, views};
|
use terminwahl_back::{api, db, handlebars_helper::TimeOfDate, views, CssPath};
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
@ -58,6 +58,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.app_data(web::Data::new(pool.clone()))
|
.app_data(web::Data::new(pool.clone()))
|
||||||
.app_data(web::Data::new(smtp_pool.clone()))
|
.app_data(web::Data::new(smtp_pool.clone()))
|
||||||
.app_data(web::Data::new(handlebars.clone()))
|
.app_data(web::Data::new(handlebars.clone()))
|
||||||
|
.app_data(web::Data::new(CssPath::default()))
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
.wrap(session_store)
|
.wrap(session_store)
|
||||||
.wrap(error_handlers)
|
.wrap(error_handlers)
|
||||||
@ -80,6 +81,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
web::resource("/confirm/{validation_key}")
|
web::resource("/confirm/{validation_key}")
|
||||||
.route(web::get().to(views::confirm_validation_key)),
|
.route(web::get().to(views::confirm_validation_key)),
|
||||||
)
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/cancel/{teacher_id}/{slot_id}/{validation_key}")
|
||||||
|
.route(web::get().to(views::delete_appointment)),
|
||||||
|
)
|
||||||
.service(Files::new("/", "./terminwahl_front/dist/").index_file("index.html"))
|
.service(Files::new("/", "./terminwahl_front/dist/").index_file("index.html"))
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", 8080))?
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
@ -1,29 +1,54 @@
|
|||||||
use actix_web::{error, web, HttpResponse};
|
use actix_web::{error, web, HttpResponse};
|
||||||
use glob::glob;
|
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use lettre::SmtpTransport;
|
use lettre::SmtpTransport;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use terminwahl_typen::RequestState;
|
use terminwahl_typen::IdType;
|
||||||
|
|
||||||
use crate::db::{write::confirm_appointments, Pool};
|
use crate::{
|
||||||
|
db::{
|
||||||
|
write::{cancel_appointment, confirm_appointments},
|
||||||
|
Pool,
|
||||||
|
},
|
||||||
|
CssPath,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn confirm_validation_key(
|
pub async fn confirm_validation_key(
|
||||||
pool: web::Data<Pool>,
|
pool: web::Data<Pool>,
|
||||||
mailer: web::Data<SmtpTransport>,
|
_mailer: web::Data<SmtpTransport>,
|
||||||
|
css: web::Data<CssPath>,
|
||||||
handlebars: web::Data<Handlebars<'_>>,
|
handlebars: web::Data<Handlebars<'_>>,
|
||||||
validation_key: web::Path<String>,
|
validation_key: web::Path<String>,
|
||||||
) -> Result<HttpResponse, error::Error> {
|
) -> Result<HttpResponse, error::Error> {
|
||||||
let css_path = glob("terminwahl_front/dist/my_bulma_colors*.css")
|
|
||||||
.expect("Failed to find css file")
|
|
||||||
.next()
|
|
||||||
.expect("Failed to find file")
|
|
||||||
.expect("Failed to find file");
|
|
||||||
let css_file = css_path.file_name().unwrap().to_str();
|
|
||||||
let data = json!({
|
|
||||||
"css_file" : css_file,
|
|
||||||
});
|
|
||||||
match confirm_appointments(&pool, &validation_key).await {
|
match confirm_appointments(&pool, &validation_key).await {
|
||||||
Ok(_) => Ok(HttpResponse::Ok().body(handlebars.render("confirmed.html", &data).unwrap())),
|
Ok(appointments) => Ok({
|
||||||
|
let data = json!({
|
||||||
|
"css_file" : css.path,
|
||||||
|
"appointments": appointments,
|
||||||
|
"validation_key": validation_key.into_inner(),
|
||||||
|
});
|
||||||
|
HttpResponse::Ok().body(handlebars.render("confirmed.html", &data).unwrap())
|
||||||
|
}),
|
||||||
|
Err(e) => Err(error::ErrorBadRequest(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_appointment(
|
||||||
|
pool: web::Data<Pool>,
|
||||||
|
_mailer: web::Data<SmtpTransport>,
|
||||||
|
handlebars: web::Data<Handlebars<'_>>,
|
||||||
|
css: web::Data<CssPath>,
|
||||||
|
path: web::Path<(IdType, IdType, String)>,
|
||||||
|
) -> Result<HttpResponse, error::Error> {
|
||||||
|
let (teacher_id, slot_id, validation_key) = path.into_inner();
|
||||||
|
match cancel_appointment(&pool, teacher_id, slot_id, &validation_key).await {
|
||||||
|
Ok(appointments) => {
|
||||||
|
let data = json!({
|
||||||
|
"css_file" : css.path,
|
||||||
|
"appointments": appointments,
|
||||||
|
"validation_key": validation_key,
|
||||||
|
});
|
||||||
|
Ok(HttpResponse::Ok().body(handlebars.render("confirmed.html", &data).unwrap()))
|
||||||
|
}
|
||||||
Err(e) => Err(error::ErrorBadRequest(e)),
|
Err(e) => Err(error::ErrorBadRequest(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,25 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h1 class="title is-1">Bestätigt</h1>
|
<h1 class="title is-1">Bestätigt</h1>
|
||||||
|
<p>Die unten stehenden Treffen sind bestätigt.</p>
|
||||||
|
<p> Sollten Sie eines oder mehrere Treffen doch nicht wahrnehmen können,
|
||||||
|
sagen Sie diese bitte möglichst früh ab, sodass Sie von anderen wiederum gebucht werden können.</p>
|
||||||
|
<div class="mt-6">
|
||||||
|
{{#each appointments as |appoint|}}
|
||||||
|
<div class="columns is-centered"><div class="column is-6">
|
||||||
|
<div class="box"><div class="columns is-vcentered">
|
||||||
|
<div class="column">
|
||||||
|
<b>{{appoint.teacher.ansprache}} {{appoint.teacher.last_name}}: {{time_of appoint.slot.start_time}} - {{time_of appoint.slot.end_time}}</b>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<a class="button is-small" href="/cancel/{{appoint.teacher.id}}/{{appoint.slot.id}}/{{ ../validation_key }}">
|
||||||
|
|
||||||
|
<span class="fas fa-ban fa-fw"></span>
|
||||||
|
<span class="ml-2">Dieses Treffen Absagen</span>
|
||||||
|
</a></div></div></div></div></div>
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,21 +1,21 @@
|
|||||||
Sehr geehrte/r {{ nutzer.name }},
|
Sehr geehrte/r {{ nutzer.name }},
|
||||||
|
|
||||||
bitte bestätigen Sie durch klick auf den folgenden Link ihre Anmeldung für die Termine
|
bitte bestätigen Sie Ihre Anmeldung zu folgenden Terminen durch Anklicken des unten stehenden Links:
|
||||||
|
|
||||||
Alle Treffen bestätigen:
|
|
||||||
https://elternsprechtag.uhle.cloud/confirm/{{ validation_key }}
|
|
||||||
|
|
||||||
Wenn Sie ein Treffen absagen wollen klicken Sie trotzdem auf den obigen Bestätigungslink und zusätzlich auf den passenden Absage-Link unten.
|
|
||||||
Auch bestätigte Treffen können abgesagt werden.
|
|
||||||
|
|
||||||
Liste der Treffen - mit Absage-Links, sollten Sie zu einem Treffen doch nicht können.
|
|
||||||
|
|
||||||
{{#each appointments as |appoint|}}
|
{{#each appointments as |appoint|}}
|
||||||
{{appoint.teacher.ansprache}} {{appoint.teacher.last_name}}: {{time_of appoint.slot.start_time}} - {{time_of appoint.slot.end_time}}
|
{{appoint.teacher.ansprache}} {{appoint.teacher.last_name}}: {{time_of appoint.slot.start_time}} - {{time_of appoint.slot.end_time}}
|
||||||
Dieses Treffen Absagen:
|
|
||||||
https://elternsprechtag.uhle.cloud/cancel/{{appoint.teacher.id}}/{{appoint.slot.id}}/{{ ../validation_key }}
|
|
||||||
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
|
Alle Termine bestätigen:
|
||||||
|
https://elternsprechtag.uhle.cloud/confirm/{{ validation_key }}
|
||||||
|
|
||||||
|
Wenn Sie einen Termin absagen möchten, klicken Sie bitte trotzdem auf den Bestätigungslink oben.
|
||||||
|
Dort können Sie dann einzelne oder alle Termine stornieren.
|
||||||
|
Dies funktioniert auch, wenn Sie die Termine bereits bestätigt haben.
|
||||||
|
|
||||||
|
Wenn Sie länger als 3 Stunden gewartet haben, werden die Termine wieder freigegeben.
|
||||||
|
Neue Termine müssen Sie wieder über die ursprüngliche Seite buchen.
|
||||||
|
|
||||||
Vielen Dank für Ihre Anmeldung!
|
Vielen Dank für Ihre Anmeldung!
|
||||||
Das Oberstufenkollegium
|
Das Oberstufenkollegium
|
@ -161,7 +161,7 @@ impl Component for App {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
{
|
{
|
||||||
if self.nutzer.is_none(){
|
if let Some(_saved) = self.successfully_saved.as_ref(){self.view_dank_dialog(ctx)} else if self.nutzer.is_none(){
|
||||||
self.view_eingabe_daten(ctx)
|
self.view_eingabe_daten(ctx)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -186,8 +186,8 @@ impl App {
|
|||||||
<img src="/logoheader.png" />
|
<img src="/logoheader.png" />
|
||||||
</figure>
|
</figure>
|
||||||
<div class="box mt-3 is-light">
|
<div class="box mt-3 is-light">
|
||||||
<p>{"Anmeldung zum Elternsprechtag!"}</p><p>{"Bitte geben Sie unbedingt eine valide Emailadresse an,
|
<p>{"Anmeldung zum Elternsprechtag!"}</p><p>{"Bitte geben Sie unbedingt eine gültige E-Mail-Adresse an,
|
||||||
da die Termine erst nach Bestätigung über den per Email zugesandten Link gebucht sind."}</p>
|
da die Termine erst nach Bestätigung über den per E-Mail zugesandten Link gebucht werden."}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{"Voller Name"}</label>
|
<label class="label">{"Voller Name"}</label>
|
||||||
@ -227,9 +227,9 @@ impl App {
|
|||||||
</div>
|
</div>
|
||||||
<div class="box mt-3 is-light">
|
<div class="box mt-3 is-light">
|
||||||
<h4 class="title is-4">{"Datenschutzerklärung:"}</h4>
|
<h4 class="title is-4">{"Datenschutzerklärung:"}</h4>
|
||||||
<p>{"Mit dem klick auf Weiter bestätigen Sie, dass das Lehrerkollegium die hier
|
<p>{"Mit dem Klick auf Weiter bestätigen Sie, dass das Lehrerkollegium die hier
|
||||||
und im folgenden eingegebenen Daten zur Organisation des Elternsprechtags speichert, verarbeitet und verwendet.
|
und im Folgenden eingegebenen Daten zur Organisation des Elternsprechtages speichert, verarbeitet und nutzt.
|
||||||
Die Daten werden nur für diesen Zweck verwendet."}</p>
|
Die Daten werden ausschließlich zu diesem Zweck verwendet."}</p>
|
||||||
<div class="has-text-right mt-6 mr-6">
|
<div class="has-text-right mt-6 mr-6">
|
||||||
{
|
{
|
||||||
if self.tmp_nutzer.validate() {
|
if self.tmp_nutzer.validate() {
|
||||||
@ -278,6 +278,37 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn view_dank_dialog(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {<>
|
||||||
|
<div id="app" class="row columns is-multiline">
|
||||||
|
<div class="column is-12">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<div class="card-header-title is-centered">
|
||||||
|
{"Erfolgreich abgeschickt, aber noch nicht fertig!"}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
<p>{"Sie haben die Termine erfolgreich vorgemerkt. Um sie endgültig zu buchen müssen Sie:"}</p>
|
||||||
|
<p><ol>
|
||||||
|
<li>{"In Ihren E-Mails die E-Mail mit dem Betreff \"Elternsprechtag: Bestätigen Sie Ihre Termine\" suchen."}</li>
|
||||||
|
<li>{"Innerhalb von 3 Stunden auf den darin enthaltenen "}<b>{"Link klicken."}</b></li>
|
||||||
|
</ol></p>
|
||||||
|
|
||||||
|
<p>{"Sie können auch mehrmals auf den Link klicken, den Sie erhalten haben. Sie können dort Ihre Termine einsehen und gegebenenfalls absagen.
|
||||||
|
Wenn Sie einen Termin absagen, tun Sie dies bitte so früh wie möglich, damit der Termin wieder für andere frei wird."}</p>
|
||||||
|
<p>{"Wenn Sie einen anderen Termin buchen möchten, müssen Sie sich erneut über diese Seite anmelden, Sie erhalten dann einen zweiten Link.
|
||||||
|
Achtung: Die Termine, die Sie zuerst gebucht haben, werden dadurch "}<b>{"nicht"}</b>{" gelöscht."}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn anleitung(&self) -> Html {
|
fn anleitung(&self) -> Html {
|
||||||
html!( <div class="column is-12">
|
html!( <div class="column is-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -289,16 +320,16 @@ impl App {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ul>
|
<ul>
|
||||||
<li>{"Termine sind erst gebucht, wenn Sie:"}
|
<li>{"Die Termine sind erst gebucht, wenn Sie:"}
|
||||||
<ul><li>{"ganz unten auf Absenden geklickt haben"}</li>
|
<ul><li>{"ganz unten auf Absenden geklickt haben"}</li>
|
||||||
<li>{"Sie die Buchung innerhalb von 3 Stunden über den link in der email bestätigen."}</li>
|
<li>{"Sie die Buchung innerhalb von 3 Stunden über den Link in der E-Mail bestätigen."}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>{"Maximal 3 Termine pro Elternhaus, da die Anzahl der Termine begrenzt ist."}</li>
|
<li>{"Maximal 3 Termine pro Elternhaus, da die Anzahl der Termine begrenzt ist."}</li>
|
||||||
<li>{"Buchen Sie die Termine so, dass sie zwischen zwei Terminen mindestens einen Slot Pause haben für Raumsuche usw."}</li>
|
<li>{"Buchen Sie die Termine so, dass Sie zwischen zwei Terminen eine Pause für die Raumsuche haben."}</li>
|
||||||
<li>{"Sprechen Sie vor allem mit den LehrerInnen, wo ihre Kinder Probleme haben."}</li>
|
<li>{"Sprechen Sie vor allem mit den LehrerInnen, deren Fächer Ihren Kindern Probleme bereiten."}</li>
|
||||||
<li>{"Sollten Sie dringenden Gesprächsbedarf haben, aber alle Termine sind voll, melden Sie sich wie gehabt bei den entsprechenden LehrerInnen"}</li>
|
<li>{"Sollten Sie dringenden Gesprächsbedarf haben, aber alle Termine sind voll, melden Sie sich wie gewohnt bei den entsprechenden LehrerInnen"}</li>
|
||||||
<li>{"Sollten Sie ihren Termin nicht wahrnehmen können, sagen Sie ihn möglichst früh ab, dass er erneut belegt werden kann."}</li>
|
<li>{"Wenn Sie Ihren Termin nicht wahrnehmen können, sagen Sie ihn so früh wie möglich ab, damit er neu vergeben werden kann."}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user