mod requests; use std::collections::{HashMap, HashSet}; use gloo::console::log; use requests::{fetch_slots, fetch_teachers, fetch_unavailable, send_appointments}; use terminwahl_typen::{ AppointmentSlot, AppointmentSlots, IdType, Nutzer, PlannedAppointment, RequestState, SlotId, Teacher, Teachers, }; use web_sys::HtmlInputElement; use yew::prelude::*; // Define the possible messages which can be sent to the component pub enum Msg { UpdateName(String), UpdateSchüler(String), UpdateEmail(String), DataEntered(Nutzer), GetTeachers, ReceivedTeachers(Teachers), GetSlots, ReceivedSlots(AppointmentSlots), Selected(PlannedAppointment), TooMany, SendToServer, AppointmentsSent(RequestState), ReceivedUnavailable(HashSet), } pub struct App { nutzer: Option, tmp_nutzer: Nutzer, teachers: Option, slots: Option, appointments: HashMap, unavailable: Option>, successfully_saved: Option, } impl From> for Msg { fn from(value: Result) -> Self { match value { Ok(m) => m, Err(m) => m, } } } impl Component for App { type Message = Msg; type Properties = (); fn create(ctx: &Context) -> Self { let app = Self { appointments: HashMap::new(), slots: None, unavailable: None, teachers: None, nutzer: None, tmp_nutzer: Nutzer { name: "".into(), schüler: "".into(), email: "".into(), }, successfully_saved: None, }; ctx.link().send_message(Msg::GetTeachers); ctx.link().send_message(Msg::GetSlots); app } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { match msg { Msg::Selected(planned_appointment) => { let slot_id = SlotId::new(planned_appointment.teacher_id, planned_appointment.slot_id); if self.appointments.contains_key(&slot_id) { self.appointments.remove(&slot_id); } else { self.appointments.insert( SlotId::new(planned_appointment.teacher_id, planned_appointment.slot_id), planned_appointment, ); }; true } Msg::TooMany => todo!(), Msg::GetTeachers => { ctx.link().send_future(fetch_teachers()); false } Msg::ReceivedTeachers(teachers) => { self.teachers = Some(teachers); true } Msg::GetSlots => { ctx.link().send_future(fetch_slots()); ctx.link().send_future(fetch_unavailable()); false } Msg::ReceivedSlots(slots) => { self.slots = Some(slots); true } Msg::DataEntered(n) => { self.nutzer = Some(n); true } Msg::UpdateName(s) => { log!("update name to {}", &s); self.tmp_nutzer.name = s; true } Msg::UpdateSchüler(s) => { self.tmp_nutzer.schüler = s; true } Msg::UpdateEmail(s) => { self.tmp_nutzer.email = s; true } Msg::SendToServer => { if (1..=3).contains(&self.appointments.len()) { let values = self.appointments.clone().into_values(); let appointments: Vec = values.collect(); ctx.link().send_future(send_appointments( appointments, self.nutzer .as_ref() .expect("This should always exist") .clone(), )); true } else { true } } Msg::AppointmentsSent(state) => { self.successfully_saved = Some(state); true } Msg::ReceivedUnavailable(r) => { self.unavailable = Some(r); true } } } fn view(&self, ctx: &Context) -> Html { html! {<>

{"Am 28.02.23"}

{ 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) } else { self.view_auswahl_termine(ctx) } }
} } } impl App { fn view_eingabe_daten(&self, ctx: &Context) -> Html { html! {

{"Anmeldung zum Elternsprechtag!"}

{"Bitte geben Sie unbedingt eine gültige E-Mail-Adresse an, da die Termine erst nach Bestätigung über den per E-Mail zugesandten Link gebucht werden."}

().map(|input|Msg::UpdateName(input.value())) })}/>

().map(|input|Msg::UpdateSchüler(input.value())) })}/>

().map(|input|Msg::UpdateEmail(input.value())) })}/>

{"Datenschutzerklärung:"}

{"Mit dem Klick auf Weiter bestätigen Sie, dass das Lehrerkollegium die hier und im Folgenden eingegebenen Daten zur Organisation des Elternsprechtages speichert, verarbeitet und nutzt. Die Daten werden ausschließlich zu diesem Zweck verwendet."}

{ if self.tmp_nutzer.validate() { html!{{"Weiter"}} } else {html!{<>

{"Füllen Sie zunächst die Felder oben aus!"}

{"Weiter"}}}}
} } fn view_auswahl_termine(&self, ctx: &Context) -> Html { html! {<>
{self.anleitung()} {if let Some(ref teachers) = self.teachers {teachers.iter().map(|t|{self.view_teacher_card(ctx, t)}).collect()}else{html!{}}}
{ if self.appointments.is_empty(){ html!{<>

{"Bitte wählen Sie mindestens einen Block aus."}

} }else if self.appointments.len() > 3{ html!{<>

{"Bitte wählen Sie höchstens drei Blöcke."}

}}else{html!{<> } }}
} } fn view_dank_dialog(&self, _ctx: &Context) -> Html { html! {<>
{"Erfolgreich abgeschickt, aber noch nicht fertig!"}

{"Sie haben die Termine erfolgreich vorgemerkt. Um sie endgültig zu buchen müssen Sie:"}

  1. {"In Ihren E-Mails die E-Mail mit dem Betreff \"Elternsprechtag: Bestätigen Sie Ihre Termine\" suchen."}
  2. {"Innerhalb von 3 Stunden auf den darin enthaltenen "}{"Link klicken."}

{"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."}

{"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 "}{"nicht"}{" gelöscht."}

} } fn anleitung(&self) -> Html { html!(
{"Hinweise"}
  • {"Die Termine sind erst gebucht, wenn Sie:"}
    • {"ganz unten auf Absenden geklickt haben"}
    • {"Sie die Buchung innerhalb von 3 Stunden über den Link in der E-Mail bestätigen."}
  • {"Maximal 3 Termine pro Elternhaus, da die Anzahl der Termine begrenzt ist."}
  • {"Buchen Sie die Termine so, dass Sie zwischen zwei Terminen eine Pause für die Raumsuche haben."}
  • {"Sprechen Sie vor allem mit den LehrerInnen, deren Fächer Ihren Kindern Probleme bereiten."}
  • {"Sollten Sie dringenden Gesprächsbedarf haben, aber alle Termine sind voll, melden Sie sich wie gewohnt bei den entsprechenden LehrerInnen"}
  • {"Wenn Sie Ihren Termin nicht wahrnehmen können, sagen Sie ihn so früh wie möglich ab, damit er neu vergeben werden kann."}
) } fn view_teacher_card(&self, ctx: &Context, teacher: &Teacher) -> Html { html! {
{&teacher.ansprache}{" "}{&teacher.last_name}
{ if let Some(ref slots) = self.slots { let mut slots:Vec<&AppointmentSlot> = slots.values().collect(); slots.sort_by(|x,y|{x.start_time.cmp(&y.start_time)}); let slots = slots.iter().map(|t|{self.view_teacher_slot(ctx, t, teacher.id)}); let first = slots.clone().take(4); let rest = slots.skip(4); first.chain([html!{
{"Pause"}
}]).chain(rest).collect()}else{html!{}} }
} } fn view_teacher_slot( &self, ctx: &Context, slot: &AppointmentSlot, teacher_id: IdType, ) -> Html { let slot_id = slot.id; let onclick = ctx.link().callback(move |_| { Msg::Selected(PlannedAppointment { teacher_id, slot_id, }) }); let text = format!( "{} – {}", slot.start_time.time().format("%H:%M"), slot.end_time.time().format("%H:%M") ); let slot_id = SlotId::new(teacher_id, slot.id); let (color, disabled) = if self.appointments.contains_key(&slot_id) { ("is-primary", false) } else if let Some(unavailable) = &self.unavailable { if unavailable.contains(&slot_id) { ("is-danger", true) } else { ("", false) } } else { ("", false) }; html! {} } } fn main() { yew::Renderer::::new().render(); }