Zusätzliche modale anzeigen, sowie erste Zuweisung

This commit is contained in:
Dietrich 2023-01-06 17:26:34 +01:00
parent 4d46e985a5
commit 8a8030dc2b
Signed by: dietrich
GPG Key ID: F0CE5A20AB5C4B27
2 changed files with 210 additions and 56 deletions

View File

@ -16,7 +16,7 @@ pub struct GemüseAnfrage {
pub user: Benutzer, pub user: Benutzer,
} }
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Hash, Eq)] #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Hash, Eq, Copy)]
pub struct VpWunsch { pub struct VpWunsch {
pub id: i32, pub id: i32,
pub anfrage_id: i32, pub anfrage_id: i32,

View File

@ -39,6 +39,10 @@ pub enum ZuordnungMessage {
ShowDialog(Dialog), ShowDialog(Dialog),
/// A message indicating that the current dialog should be hidden. /// A message indicating that the current dialog should be hidden.
HideDialog, HideDialog,
AssignAll {
verteilpunkt: Verteilpunkt,
mitgliedschaften: Vec<VpWunsch>,
},
} }
pub struct Dialog { pub struct Dialog {
@ -47,7 +51,9 @@ pub struct Dialog {
} }
pub enum DialogContent { pub enum DialogContent {
Wunschliste(Vec<VpWunsch>), Wunschliste(Verteilpunkt, Vec<VpWunsch>),
Vorjahr(Vec<VpZuordnung>),
Geplant(HashMap<i32, GeplanteVpZuordnung>),
} }
#[derive(Properties, PartialEq, Default)] #[derive(Properties, PartialEq, Default)]
@ -66,7 +72,7 @@ pub struct ZuordnungApp {
/// The assignments made for the previous year. /// The assignments made for the previous year.
zuordnung_vorjahr: Option<JahresVpZuordnung>, zuordnung_vorjahr: Option<JahresVpZuordnung>,
/// The planned assignments for the current year. /// The planned assignments for the current year.
geplante_zuordnung: HashMap<i32, GeplanteVpZuordnung>, geplante_zuordnung: HashMap<i32, HashMap<i32, GeplanteVpZuordnung>>,
/// The currently displayed dialog. /// The currently displayed dialog.
dialog: Option<Dialog>, dialog: Option<Dialog>,
} }
@ -137,6 +143,12 @@ impl Component for ZuordnungApp {
NetworkObjekte::Verteilpunkt(v) => { NetworkObjekte::Verteilpunkt(v) => {
log!("Verteilpunkte geparsed"); log!("Verteilpunkte geparsed");
self.verteilpunkte = v; self.verteilpunkte = v;
for vp in self.verteilpunkte.iter() {
log!(format!(
"{:?}",
self.geplante_zuordnung.insert(*vp.0, HashMap::new())
));
}
true true
} }
NetworkObjekte::Wunsch(v) => { NetworkObjekte::Wunsch(v) => {
@ -163,6 +175,26 @@ impl Component for ZuordnungApp {
self.dialog = None; self.dialog = None;
true true
} }
ZuordnungMessage::AssignAll {
verteilpunkt,
mitgliedschaften,
} => {
log!(format!("{:?}", mitgliedschaften));
if let Some(liste) = self.geplante_zuordnung.get_mut(&verteilpunkt.id) {
for m in mitgliedschaften {
liste.insert(
m.anfrage_id,
GeplanteVpZuordnung {
mitgliedschaft_id: m.anfrage_id,
user_mitgliedsnummer: m.user_id,
verteilpunkt_id: m.verteilpunkt_id,
},
);
}
};
log!(format!("{:?}", self.geplante_zuordnung));
true
}
} }
} }
@ -185,8 +217,8 @@ impl Component for ZuordnungApp {
.map(|(id, v)| html! { .map(|(id, v)| html! {
<tr> <tr>
<td>{self.verteilpunkt_name(v)}</td> <td>{self.verteilpunkt_name(v)}</td>
<td>{self.aktuelle_mitglieder(v, &self.zuordnung_vorjahr)}</td> <td>{self.aktuelle_mitglieder(v, ctx)}</td>
<td>{self.zukünftige_mitglieder(v)}</td> <td>{self.zukünftige_mitglieder(v, ctx)}</td>
{self.count_wünsche(*id, ctx)} {self.count_wünsche(*id, ctx)}
</tr> </tr>
}) })
@ -198,6 +230,9 @@ impl Component for ZuordnungApp {
} }
} }
impl ZuordnungApp { impl ZuordnungApp {
/**
Erstelle Die Tabellenzellen für die 1. 2. und n. Wünsche.
*/
fn count_wünsche(&self, verteilpunkt_id: i32, ctx: &yew::Context<ZuordnungApp>) -> Html { fn count_wünsche(&self, verteilpunkt_id: i32, ctx: &yew::Context<ZuordnungApp>) -> Html {
// Erst wenn die Wünsche geladen sind wird etwas angezeigt. // Erst wenn die Wünsche geladen sind wird etwas angezeigt.
if let Some(wünsche) = &self.wünsche { if let Some(wünsche) = &self.wünsche {
@ -206,6 +241,7 @@ impl ZuordnungApp {
.wünsche .wünsche
.iter() .iter()
.filter(|(_, wunsch)| wunsch.verteilpunkt_id == verteilpunkt_id); .filter(|(_, wunsch)| wunsch.verteilpunkt_id == verteilpunkt_id);
// A mapping from priority values to lists of wishes. // A mapping from priority values to lists of wishes.
let counts: HashMap<i32, Vec<VpWunsch>> = wünsche let counts: HashMap<i32, Vec<VpWunsch>> = wünsche
.prioritäten .prioritäten
@ -214,11 +250,19 @@ impl ZuordnungApp {
.collect(); .collect();
let counts: HashMap<i32, Vec<VpWunsch>> = let counts: HashMap<i32, Vec<VpWunsch>> =
filtered.fold(counts, |mut counts, (_, wunsch)| { filtered.fold(counts, |mut counts, (_, wunsch)| {
if self
.geplante_zuordnung
.iter()
.any(|(_, v)| v.contains_key(&wunsch.anfrage_id))
{
counts
} else {
counts counts
.entry(wunsch.prioritaet) .entry(wunsch.prioritaet)
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(wunsch.clone()); .push(*wunsch);
counts counts
}
}); });
// sortiere nach Priorität // sortiere nach Priorität
let sorted_and_grouped_by_priority = counts.into_iter().sorted_by_key(|x| x.0); let sorted_and_grouped_by_priority = counts.into_iter().sorted_by_key(|x| x.0);
@ -230,14 +274,12 @@ impl ZuordnungApp {
let verteilpunkte = self.verteilpunkte.clone(); let verteilpunkte = self.verteilpunkte.clone();
let onclick = ctx.link().callback(move |_| { let onclick = ctx.link().callback(move |_| {
ZuordnungMessage::ShowDialog(Dialog {
title: {
let vp = verteilpunkte let vp = verteilpunkte
.get(&verteilpunkt_id) .get(&verteilpunkt_id)
.expect("should allways be there"); .expect("should allways be there");
format!("Wünsche für {}", vp.name) ZuordnungMessage::ShowDialog(Dialog {
}, title: { format!("Wünsche für {}", vp.name) },
content: DialogContent::Wunschliste(wunsch.clone()), content: DialogContent::Wunschliste(vp.clone(), wunsch.clone()),
}) })
}); });
html! {<td><b {onclick}>{length}</b></td>} html! {<td><b {onclick}>{length}</b></td>}
@ -294,53 +336,95 @@ impl ZuordnungApp {
</div> </div>
} }
} }
/**
fn aktuelle_mitglieder(&self, v: &Verteilpunkt, z: &Option<JahresVpZuordnung>) -> Html { Count the number of items in the zuordnungen map with the matching verteilpunkt_id
// Count the number of items in the zuordnungen map with the matching verteilpunkt_id */
fn count_zuordnungen(v: &Verteilpunkt, zuordnungen: &HashMap<i32, VpZuordnung>) -> usize { fn count_zuordnungen(
v: &Verteilpunkt,
zuordnungen: &HashMap<i32, VpZuordnung>,
) -> Vec<VpZuordnung> {
zuordnungen zuordnungen
.iter() .iter()
.filter(|(_, zuo)| v.id == zuo.verteilpunkt_id) .filter_map(|(_, zuo)| {
.count() if v.id == zuo.verteilpunkt_id {
Some(zuo.clone())
} else {
None
}
})
.collect()
} }
match z { fn aktuelle_mitglieder(
&self,
verteilpunkt: &Verteilpunkt,
ctx: &yew::Context<ZuordnungApp>,
) -> Html {
match &self.zuordnung_vorjahr {
Some(z) => { Some(z) => {
// Render the count and the kapazitaet as "X/Y" // Render the count and the kapazitaet as "X/Y"
let count = count_zuordnungen(v, &z.zuordnungen); let liste = Self::count_zuordnungen(verteilpunkt, &z.zuordnungen);
html! {<>{count}{"/"} {v.kapazitaet}</>} let count = liste.len();
let title = format!("Aktuelle Mitglieder am VP {}", verteilpunkt.name);
let onclick = ctx.link().callback(move |_| {
ZuordnungMessage::ShowDialog(Dialog {
title: title.clone(),
content: DialogContent::Vorjahr(liste.clone()),
})
});
html! {<span {onclick}>{count}{"/"} {verteilpunkt.kapazitaet}</span>}
} }
None => html!(), None => html!(),
} }
} }
/**
fn zukünftige_mitglieder(&self, verteilpunkt: &Verteilpunkt) -> Html { Count the number of items in the zuordnungen map with the matching verteilpunkt_id
*/
fn count_geplante_zuordnungen(
v: &Verteilpunkt,
zuordnungen: &HashMap<i32, HashMap<i32, GeplanteVpZuordnung>>,
) -> HashMap<i32, GeplanteVpZuordnung> {
zuordnungen.get(&v.id).cloned().unwrap_or_default()
}
/**
Zeige die Anzahl und Kappazität der zukünftigen Mitglieder an diesem Verteilpunkt.
*/
fn zukünftige_mitglieder(
&self,
verteilpunkt: &Verteilpunkt,
ctx: &yew::Context<ZuordnungApp>,
) -> Html {
// Count the number of items in the z map with the matching verteilpunkt_id // Count the number of items in the z map with the matching verteilpunkt_id
let count = &self let liste = Self::count_geplante_zuordnungen(verteilpunkt, &self.geplante_zuordnung);
.geplante_zuordnung let count = liste.len();
.iter() let title = format!("Geplante Mitglieder am VP {}", verteilpunkt.name);
.filter(|(_, zuo)| verteilpunkt.id == zuo.verteilpunkt_id) let onclick = ctx.link().callback(move |_| {
.count(); ZuordnungMessage::ShowDialog(Dialog {
title: title.clone(),
content: DialogContent::Geplant(liste.clone()),
})
});
// Render the count and the kapazitaet as "X/Y" // Render the count and the kapazitaet as "X/Y"
html! {<>{count}{"/"} {verteilpunkt.kapazitaet}</>} html! {<div {onclick}>{count}{"/"} {verteilpunkt.kapazitaet}</div>}
} }
fn show_dialog(&self, ctx: &yew::Context<Self>) -> Html { fn show_dialog(&self, ctx: &yew::Context<Self>) -> Html {
match &self.dialog { match &self.dialog {
Some(dialog) => { Some(dialog) => {
// Create a callback that calls the HideDialog function when triggered
let onclick = ctx.link().callback(|_| ZuordnungMessage::HideDialog);
html! { html! {
// Render the dialog container with the onclick callback attached // Render the dialog container with the onclick callback attached
<div class="modal_wasm" {onclick}> <div class="modal show" tabindex="-1" role="dialog" style="display:block;" onclick={ctx.link().callback(|_| ZuordnungMessage::HideDialog)}>
<div class="container"> <div class="modal-dialog" role="document">
<div class="body"> <div class="modal-content">
// Render the dialog title <div class="modal-header">
<h3>{&dialog.title}</h3> <h5 class="modal-title">{&dialog.title}</h5>
// Render the dialog content <button type="button" class="btn btn-link link-secondary" data-dismiss="modal" aria-label="Close" onclick={ctx.link().callback(|_| ZuordnungMessage::HideDialog)}>
{self.show_dialog_content(&dialog.content)} <span aria-hidden="true"><i class="bi bi-x-circle-fill"></i></span>
</button>
</div>
{self.show_dialog_content(&dialog.content, ctx)}
</div> </div>
</div> </div>
</div> </div>
@ -350,13 +434,14 @@ impl ZuordnungApp {
} }
} }
fn show_dialog_content(&self, content: &DialogContent) -> Html { fn show_dialog_content(&self, content: &DialogContent, ctx: &yew::Context<Self>) -> Html {
match content { // Generate the lines and the buttons at the bottom for the different dialogs
DialogContent::Wunschliste(content) => { let (lines, buttons) = match content {
DialogContent::Wunschliste(vp, content) => {
let mut htmlnodes = Vec::new(); let mut htmlnodes = Vec::new();
// Iterate over each wish in the list // Iterate over each wish in the list
for wunsch in content { for wunsch in content.clone() {
// Find the user associated with this wish // Find the user associated with this wish
let user = match &self.user { let user = match &self.user {
Some(users) => match users.get(&wunsch.user_id) { Some(users) => match users.get(&wunsch.user_id) {
@ -368,11 +453,80 @@ impl ZuordnungApp {
// Append the user's first and last name to the HTML // Append the user's first and last name to the HTML
htmlnodes htmlnodes
.push(html! {<tr><td>{&user.vorname}</td><td>{&user.nachname}</td></tr>}); .push(html! {<tr><td scope="col">{&user.vorname}</td><td scope="col">{&user.nachname}</td></tr>});
}
let lines = html! {{ htmlnodes.into_iter().collect::<Html>() }};
let vp = vp.clone();
let content = content.clone();
let onclick = ctx.link().callback(move |_| {
let verteilpunkt = vp.clone();
let mitgliedschaften = content.clone();
ZuordnungMessage::AssignAll {
verteilpunkt,
mitgliedschaften,
}
});
let buttons = html! {<>
<button type="button" class="btn btn-primary" {onclick}>{"Alle zuweisen"}</button>
</>};
(lines, buttons)
}
DialogContent::Vorjahr(content) => {
let mut htmlnodes = Vec::new();
// Iterate over each wish in the list
for mitgliedschaft in content {
// Find the user associated with this wish
let user = match &self.user {
Some(users) => match users.get(&mitgliedschaft.user_mitgliedsnummer) {
Some(user) => user,
None => continue, // Skip this wish if the user is not found
},
None => continue, // Skip this wish if self.user is not set
};
// Append the user's first and last name to the HTML
htmlnodes
.push(html! {<tr><td scope="col">{&user.vorname}</td><td scope="col">{&user.nachname}</td></tr>});
} }
html! {{ htmlnodes.into_iter().collect::<Html>() }} (html! {{ htmlnodes.into_iter().collect::<Html>() }}, html!())
} }
DialogContent::Geplant(content) => {
let mut htmlnodes = Vec::new();
// Iterate over each wish in the list
for mitgliedschaft in content.values() {
// Find the user associated with this wish
let user = match &self.user {
Some(users) => match users.get(&mitgliedschaft.user_mitgliedsnummer) {
Some(user) => user,
None => continue, // Skip this wish if the user is not found
},
None => continue, // Skip this wish if self.user is not set
};
// Append the user's first and last name to the HTML
htmlnodes
.push(html! {<tr><td scope="col">{&user.vorname}</td><td scope="col">{&user.nachname}</td></tr>});
}
(html! {{ htmlnodes.into_iter().collect::<Html>() }}, html!())
}
};
html! {
<>
<div class="modal-body">
<div class="table-responsive">
<table class="table table-hover table-striped align-middle">
<thead><tr><th scope="col">{"Vorname"}</th><th scope="col">{"Nachname"}</th></tr></thead>
<tbody>{lines}</tbody></table>
</div>
</div>
<div class="modal-footer">
{buttons}
</div>
</>
} }
} }
} }