diff options
author | Joris | 2022-03-12 13:27:29 +0100 |
---|---|---|
committer | Joris | 2022-03-12 13:36:09 +0100 |
commit | d584df359640176ec4bc06f59d1e8d42ab17a413 (patch) | |
tree | 6cfaf676fc2ecf4e61067aa376fb2bed0d984d79 /src/gui/form/mod.rs | |
parent | aad7b9601dfa05255d5c24f4a6377d9a25646d45 (diff) |
Update and delete recurring events
Diffstat (limited to 'src/gui/form/mod.rs')
-rw-r--r-- | src/gui/form/mod.rs | 238 |
1 files changed, 201 insertions, 37 deletions
diff --git a/src/gui/form/mod.rs b/src/gui/form/mod.rs index 57ccac7..68e6539 100644 --- a/src/gui/form/mod.rs +++ b/src/gui/form/mod.rs @@ -2,8 +2,13 @@ mod repetition; use gtk4 as gtk; +use anyhow::Result; +use chrono::{NaiveDate, NaiveTime}; use gtk::glib; use gtk::prelude::*; +use rusqlite::Connection; +use thiserror::Error; +use uuid::Uuid; use crate::{ db, @@ -11,12 +16,70 @@ use crate::{ model::{event, event::Event}, }; -pub async fn show(app: &App, event: Event, is_new: bool) { +pub async fn repetition_dialog(app: &App, date: NaiveDate, event: Event) { let dialog = gtk::Dialog::builder() .transient_for(&*app.window) .modal(true) - .title(if is_new { "Ajouter" } else { "Modifier" }) - .css_classes(vec!["g-Form".to_string()]) + .title("Modifier") + .css_classes(vec!["g-Dialog".to_string()]) + .build(); + + let content_area = dialog.content_area(); + + let lines = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .build(); + content_area.append(&lines); + + let button = gtk::Button::builder() + .label("Cette occurence") + .margin_bottom(10) + .build(); + lines.append(&button); + let tx = app.tx.clone(); + button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| { + update::send(tx.clone(), Msg::ShowUpdateRepetitionForm { date, event: event.clone() }); + dialog.close() + })); + + let button = gtk::Button::builder() + .label("Toutes les occurences") + .build(); + lines.append(&button); + let tx = app.tx.clone(); + button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| { + update::send(tx.clone(), Msg::ShowUpdateForm { event: event.clone() }); + dialog.close() + })); + + dialog.run_future().await; +} + +#[derive(Clone)] +pub enum Target { + New { date: NaiveDate }, + Update { event: Event }, + UpdateRepetition { event: Event, date: NaiveDate }, +} + +pub async fn show(app: &App, target: Target) { + let event = match target { + Target::New { .. } => None, + Target::Update { ref event } => Some(event.clone()), + Target::UpdateRepetition { ref event, .. } => Some(event.clone()), + }; + + let title = if event.is_some() { + "Modifier" + } else { + "Ajouter" + }; + + let dialog = gtk::Dialog::builder() + .transient_for(&*app.window) + .modal(true) + .title(title) + .css_classes(vec!["g-Dialog".to_string()]) .build(); let content_area = dialog.content_area(); @@ -29,7 +92,6 @@ pub async fn show(app: &App, event: Event, is_new: bool) { let columns = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .build(); - columns.add_css_class("g-Form__Columns"); lines.append(&columns); // First column @@ -37,79 +99,145 @@ pub async fn show(app: &App, event: Event, is_new: bool) { let column1 = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); - column1.add_css_class("g-Form__Inputs"); columns.append(&column1); - let name = entry(&event.name); + let name = event.as_ref().map(|e| entry(&e.name)).unwrap_or_default(); column1.append(&label("Événement")); column1.append(&name); - let date = entry(&event.date.format(event::DATE_FORMAT).to_string()); + let date = match target { + Target::New { date } => date, + Target::Update { ref event } => event.date, + Target::UpdateRepetition { date, .. } => date, + }; + let date = entry(&date.format(event::DATE_FORMAT).to_string()); column1.append(&label("Jour")); column1.append(&date); - let start = entry( - &event - .start - .map(event::pprint_time) - .unwrap_or_else(|| "".to_string()), - ); + let start = event + .as_ref() + .map(|e| time_entry(e.start)) + .unwrap_or_else(|| entry("")); column1.append(&label("Début")); column1.append(&start); - let end = entry( - &event - .end - .map(event::pprint_time) - .unwrap_or_else(|| "".to_string()), - ); + let end = event + .as_ref() + .map(|e| time_entry(e.end)) + .unwrap_or_else(|| entry("")); column1.append(&label("Fin")); column1.append(&end); // Second column - let repetition_model = repetition::view(&event); + let repetition = match target { + Target::Update { ref event } => event.repetition.as_ref(), + _ => None, + }; + let repetition_model = repetition::view(repetition); columns.append(&repetition_model.view); // Buttons + let button_title = match target { + Target::New { .. } => "Créer", + Target::Update { .. } => "Modifier", + Target::UpdateRepetition { .. } => "Modifier l’occurence", + }; + let button = gtk::Button::builder() - .label(if is_new { "Créer" } else { "Modifier" }) + .label(button_title) .margin_bottom(10) .build(); lines.append(&button); let conn = app.conn.clone(); let tx = app.tx.clone(); - button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| { + button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event => move |_| { match repetition::validate(&repetition_model) { Ok(repetition) => { - match event::validate(event.id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition) { + let id = match &target { + Target::Update {event} => event.id, + _ => Uuid::new_v4(), + }; + match event::validate(id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition) { Some(new) => { - match if is_new { db::insert(&conn, &new) } else { db::update(&conn, &new) } { - Ok(_) => { - let msg = if is_new { Msg::AddEvent { new } } else { Msg::UpdateEvent { old: event.clone(), new } }; - update::send(tx.clone(), msg); - dialog.close() - }, - Err(err) => eprintln!("Error when upserting event: {err}") + match &target { + Target::New {..} => { + match db::insert(&conn, &new) { + Ok(_) => { + update::send(tx.clone(), Msg::AddEvent { new }); + dialog.close() + }, + Err(err) => eprintln!("Error when inserting event: {}", err) + } + } + Target::Update {event} => { + match db::update(&conn, &new) { + Ok(_) => { + update::send(tx.clone(), Msg::UpdateEvent { old: event.clone(), new }); + dialog.close() + }, + Err(err) => eprintln!("Error when updating event: {}", err) + } + } + Target::UpdateRepetition { event, date } => { + // TODO: improve intermediate error state + match delete_repetition_occurence(&conn, event, *date) { + Ok(occurence) => { + match db::insert(&conn, &new) { + Ok(_) => { + update::send(tx.clone(), Msg::UpdateEventOccurence { + event: event.clone(), + occurence, + date: *date, + new + }) + } + Err(err) => eprintln!("Error when updating repetition: {}", err) + }; + dialog.close() + }, + Err(err) => eprintln!("Error when updating repetition: {}", err) + } + } } } - None => eprintln!("Event is not valid: {event:?}") + None => eprintln!("Event is not valid.") } }, - Err(message) => eprintln!("{message}") + Err(message) => eprintln!("{}", message) } })); - if !is_new { - let button = gtk::Button::builder().label("Supprimer").build(); + if let Some(event) = event { + let label = match target { + Target::Update { .. } => "Supprimer", + _ => "Supprimer l’occurence", + }; + let button = gtk::Button::builder().label(label).build(); lines.append(&button); let conn = app.conn.clone(); let tx = app.tx.clone(); button.connect_clicked(glib::clone!(@weak dialog => move |_| { - if db::delete(&conn, &event.id).is_ok() { - update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() }); - dialog.close() + match target { + Target::UpdateRepetition { date, .. } => { + match delete_repetition_occurence(&conn, &event, date) { + Ok(occurence) => { + update::send(tx.clone(), Msg::DeleteOccurence { event: event.clone(), date, occurence }); + dialog.close() + } + Err(err) => { + eprintln!("{:?}", err); + } + } + } + _ => { + let operation = db::delete(&conn, &event.id); + if operation.is_ok() { + update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() }); + dialog.close() + } + } } })); } @@ -117,6 +245,42 @@ pub async fn show(app: &App, event: Event, is_new: bool) { dialog.run_future().await; } +#[derive(Error, Debug)] +enum DeleteError { + #[error("Repetition not found")] + RepetitionNotFound, + #[error("Occurence not found")] + OccurenceNotFound, +} + +fn delete_repetition_occurence( + conn: &Connection, + event: &Event, + occurence_date: NaiveDate, +) -> Result<usize> { + if let Some(ref repetition) = event.repetition { + if let Some(occurence) = repetition.occurence_index(event.date, occurence_date) { + let mut event = event.clone(); + let mut repetition = repetition.clone(); + repetition.removed_occurences.insert(occurence); + event.repetition = Some(repetition); + db::update(conn, &event).map(|_| occurence) + } else { + Err(anyhow::Error::new(DeleteError::OccurenceNotFound)) + } + } else { + Err(anyhow::Error::new(DeleteError::RepetitionNotFound)) + } +} + +fn time_entry(time: Option<NaiveTime>) -> gtk::Entry { + entry( + &time + .map(event::pprint_time) + .unwrap_or_else(|| "".to_string()), + ) +} + fn entry(text: &str) -> gtk::Entry { gtk::Entry::builder().text(text).margin_bottom(10).build() } |