diff options
author | Joris | 2022-04-24 16:31:49 +0200 |
---|---|---|
committer | Joris | 2022-04-24 16:31:49 +0200 |
commit | 47fe90ee23d8ab04645ef3c7a17459ed40c5b765 (patch) | |
tree | d1aa7d3840a3bd825dcae0a0398fec7ca310f45c | |
parent | 6d271b09303d924381cc65e7c0b5eb56833780ed (diff) |
Allow to attach categories to events
-rw-r--r-- | README.md | 12 | ||||
-rw-r--r-- | src/cli/mod.rs | 4 | ||||
-rw-r--r-- | src/db/categories.rs | 25 | ||||
-rw-r--r-- | src/db/event-color.rs | 7 | ||||
-rw-r--r-- | src/db/event_color.rs | 14 | ||||
-rw-r--r-- | src/db/events.rs | 139 | ||||
-rw-r--r-- | src/db/migrations/2-categories.sql | 10 | ||||
-rw-r--r-- | src/db/migrations/3-event-color.sql | 5 | ||||
-rw-r--r-- | src/db/mod.rs | 137 | ||||
-rw-r--r-- | src/gui/app.rs | 40 | ||||
-rw-r--r-- | src/gui/calendar.rs | 56 | ||||
-rw-r--r-- | src/gui/form/mod.rs | 54 | ||||
-rw-r--r-- | src/gui/style.css | 12 | ||||
-rw-r--r-- | src/gui/update.rs | 22 | ||||
-rw-r--r-- | src/model/category.rs | 8 | ||||
-rw-r--r-- | src/model/event.rs | 3 | ||||
-rw-r--r-- | src/model/mod.rs | 1 |
17 files changed, 356 insertions, 193 deletions
@@ -38,14 +38,16 @@ cargo run -- --list-today # TODO -## Optimizations +## Categorize events -- Optimize refresh. +1. Add category form +2. Show categories +3. Update category +4. Delete category -## Categorize events +## Optimizations -1. CRUD for list of types (name + color). -2. Show / hide depending on the type. +- Optimize refresh. ## Multi day events diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 81c5895..6ce50af 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -6,8 +6,8 @@ use crate::{db, model::event}; pub fn today(conn: &Connection) -> Result<String> { let today = Local::today().naive_local(); - let mut events = db::list_non_recurring_between(conn, today, today)?; - let recurring_events = db::list_recurring(conn)?; + let mut events = db::events::list_non_recurring_between(conn, today, today)?; + let recurring_events = db::events::list_recurring(conn)?; let repetitions = event::repetitions_between(&recurring_events, today, today); for repetition in repetitions.values().flatten() { events.push(repetition.clone()); diff --git a/src/db/categories.rs b/src/db/categories.rs new file mode 100644 index 0000000..ebefb6d --- /dev/null +++ b/src/db/categories.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use rusqlite::Connection; +use uuid::Uuid; + +use crate::model::category::Category; + +pub fn list(conn: &Connection) -> Result<Vec<Category>> { + let mut stmt = conn.prepare("SELECT id, name, color FROM categories")?; + + let iter = stmt.query_map([], |row| { + Ok(read_category(row.get(0)?, row.get(1)?, row.get(2)?)) + })?; + + let mut res = vec![]; + for category in iter { + res.push(category??) + } + Ok(res) +} + +fn read_category(id: String, name: String, color: String) -> Result<Category> { + let id = Uuid::parse_str(&id)?; + + Ok(Category { id, name, color }) +} diff --git a/src/db/event-color.rs b/src/db/event-color.rs new file mode 100644 index 0000000..18612e4 --- /dev/null +++ b/src/db/event-color.rs @@ -0,0 +1,7 @@ + + +pub fn get_default_color(conn: &Connection) -> Result<String> { +} + +// pub fn set_default_color(conon: &Connection, color: &str) -> Result<()> { +// } diff --git a/src/db/event_color.rs b/src/db/event_color.rs new file mode 100644 index 0000000..33d350b --- /dev/null +++ b/src/db/event_color.rs @@ -0,0 +1,14 @@ +use anyhow::Result; +use rusqlite::Connection; + +pub fn get_default_color(conn: &Connection) -> Result<String> { + let mut stmt = conn.prepare("SELECT * FROM event_color LIMIT 1")?; + + let iter = stmt.query_map([], |row| row.get(0))?; + + let mut res = vec![]; + for color in iter { + res.push(color?) + } + Ok(res.first().unwrap_or(&"blue".to_string()).clone()) +} diff --git a/src/db/events.rs b/src/db/events.rs new file mode 100644 index 0000000..86206bb --- /dev/null +++ b/src/db/events.rs @@ -0,0 +1,139 @@ +use anyhow::Result; +use chrono::{NaiveDate, NaiveTime}; +use rusqlite::{params, Connection}; +use uuid::Uuid; + +use crate::model::event::Event; + +pub fn insert(conn: &Connection, event: &Event) -> Result<()> { + let repetition = match &event.repetition { + Some(r) => Some(serde_json::to_string(&r)?), + None => None, + }; + + let category = event.category.map(|id| id.to_hyphenated().to_string()); + + conn.execute( + "INSERT INTO events (id, date, start, end, name, repetition, category, created, updated) VALUES (?, ?, ?, ?, ?, ?, ?, datetime(), datetime())", + params![event.id.to_hyphenated().to_string(), event.date, event.start, event.end, event.name, repetition, category] + )?; + + Ok(()) +} + +pub fn update(conn: &Connection, event: &Event) -> Result<()> { + let repetition = match &event.repetition { + Some(r) => Some(serde_json::to_string(&r)?), + None => None, + }; + + let category = event.category.map(|id| id.to_hyphenated().to_string()); + + conn.execute( + "UPDATE events SET date = ?, start = ?, end = ?, name = ?, repetition = ?, category = ?, updated = datetime() WHERE id = ?", + params![event.date, event.start, event.end, event.name, repetition, category, event.id.to_hyphenated().to_string()] + )?; + + Ok(()) +} + +pub fn delete(conn: &Connection, id: &Uuid) -> Result<()> { + conn.execute( + "DELETE FROM events WHERE id = ?", + params![id.to_hyphenated().to_string()], + )?; + + Ok(()) +} + +pub fn list_recurring(conn: &Connection) -> Result<Vec<Event>> { + let mut stmt = conn.prepare( + " + SELECT id, date, start, end, name, repetition, category + FROM events + WHERE repetition IS NOT NULL", + )?; + + let iter = stmt.query_map([], |row| { + Ok(read_event( + row.get(0)?, + row.get(1)?, + row.get(2)?, + row.get(3)?, + row.get(4)?, + row.get(5)?, + row.get(6)?, + )) + })?; + + let mut res = vec![]; + for event in iter { + res.push(event??) + } + Ok(res) +} + +pub fn list_non_recurring_between( + conn: &Connection, + start: NaiveDate, + end: NaiveDate, +) -> Result<Vec<Event>> { + let mut stmt = conn.prepare( + " + SELECT id, date, start, end, name, category + FROM events + WHERE + repetition IS NULL + AND date >= ? + AND date <= ? + ", + )?; + + let iter = stmt.query_map([start, end], |row| { + Ok(read_event( + row.get(0)?, + row.get(1)?, + row.get(2)?, + row.get(3)?, + row.get(4)?, + None, + row.get(5)?, + )) + })?; + + let mut res = vec![]; + for event in iter { + res.push(event??) + } + Ok(res) +} + +fn read_event( + id: String, + date: NaiveDate, + start: Option<NaiveTime>, + end: Option<NaiveTime>, + name: String, + repetition: Option<String>, + category: Option<String>, +) -> Result<Event> { + let id = Uuid::parse_str(&id)?; + let repetition = match repetition { + Some(r) => Some(serde_json::from_str(&r)?), + None => None, + }; + let category = match category { + Some(c) => Some(Uuid::parse_str(&c)?), + None => None, + }; + + Ok(Event { + id, + date, + start, + end, + name, + repetition, + category, + }) +} diff --git a/src/db/migrations/2-categories.sql b/src/db/migrations/2-categories.sql index 27e9699..0b373d0 100644 --- a/src/db/migrations/2-categories.sql +++ b/src/db/migrations/2-categories.sql @@ -1,13 +1,11 @@ CREATE TABLE IF NOT EXISTS "categories" ( "id" TEXT PRIMARY KEY, /* UUID */ - "name" TEXT NOT NULL, - "color" TEXT NOT NULL, /* HEXA */ + "name" TEXT NOT NULL UNIQUE, + "color" TEXT NOT NULL, /* COLOR */ "created" TEXT NOT NULL, /* DATETIME */ "updated" TEXT NOT NULL /* DATETIME */ ); -INSERT INTO - "categories" ("id", "name", "color", "created", "updated") - VALUES ("b2253284-1572-43d5-96ec-bf6ad448f46a", "Perso", "#a8c2e0", datetime(), datetime()); - ALTER TABLE "events" ADD COLUMN "category" TEXT REFERENCES "categories" ("id"); /* UUID */ + +PRAGMA foreign_keys = ON diff --git a/src/db/migrations/3-event-color.sql b/src/db/migrations/3-event-color.sql new file mode 100644 index 0000000..ec589ea --- /dev/null +++ b/src/db/migrations/3-event-color.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS "event_color" ( + "color" TEXT NOT NULL /* COLOR */ +); + +INSERT INTO "event_color" ("color") VALUES ("#a8c2e0"); diff --git a/src/db/mod.rs b/src/db/mod.rs index 101c874..20e7f81 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,143 +1,18 @@ +pub mod categories; +pub mod event_color; +pub mod events; + use anyhow::Result; -use chrono::{NaiveDate, NaiveTime}; -use rusqlite::{params, Connection}; +use rusqlite::Connection; use rusqlite_migration::{Migrations, M}; -use uuid::Uuid; - -use crate::model::event::Event; pub fn init(db_path: &str) -> Result<Connection> { let mut conn = Connection::open(db_path)?; let migrations = Migrations::new(vec![ M::up(include_str!("migrations/1-init.sql")), M::up(include_str!("migrations/2-categories.sql")), + M::up(include_str!("migrations/3-event-color.sql")), ]); migrations.to_latest(&mut conn)?; Ok(conn) } - -pub fn insert(conn: &Connection, event: &Event) -> Result<()> { - let repetition = match &event.repetition { - Some(r) => Some(serde_json::to_string(&r)?), - None => None, - }; - - conn.execute( - "INSERT INTO events (id, date, start, end, name, repetition, created, updated) VALUES (?, ?, ?, ?, ?, ?, datetime(), datetime())", - params![event.id.to_hyphenated().to_string(), event.date, event.start, event.end, event.name, repetition] - )?; - - Ok(()) -} - -pub fn update(conn: &Connection, event: &Event) -> Result<()> { - let repetition = match &event.repetition { - Some(r) => Some(serde_json::to_string(&r)?), - None => None, - }; - - conn.execute( - "UPDATE events SET date = ?, start = ?, end = ?, name = ?, repetition = ?, updated = datetime() WHERE id = ?", - params![event.date, event.start, event.end, event.name, repetition, event.id.to_hyphenated().to_string()] - )?; - - Ok(()) -} - -pub fn delete(conn: &Connection, id: &Uuid) -> Result<()> { - conn.execute( - "DELETE FROM events WHERE id = ?", - params![id.to_hyphenated().to_string()], - )?; - - Ok(()) -} - -pub fn list_recurring(conn: &Connection) -> Result<Vec<Event>> { - let mut stmt = conn.prepare( - " - SELECT id, date, start, end, name, repetition - FROM events - WHERE repetition IS NOT NULL", - )?; - - let iter = stmt.query_map([], |row| { - Ok(read_recurring_event( - row.get(0)?, - row.get(1)?, - row.get(2)?, - row.get(3)?, - row.get(4)?, - row.get(5)?, - )) - })?; - - let mut res = vec![]; - for event in iter { - res.push(event??) - } - Ok(res) -} - -fn read_recurring_event( - uuid: String, - date: NaiveDate, - start: Option<NaiveTime>, - end: Option<NaiveTime>, - name: String, - repetition: Option<String>, -) -> Result<Event> { - let id = Uuid::parse_str(&uuid)?; - let repetition = match repetition { - Some(r) => Some(serde_json::from_str(&r)?), - None => None, - }; - - Ok(Event { - id, - date, - start, - end, - name, - repetition, - }) -} - -pub fn list_non_recurring_between( - conn: &Connection, - start: NaiveDate, - end: NaiveDate, -) -> Result<Vec<Event>> { - let mut stmt = conn.prepare( - " - SELECT id, date, start, end, name - FROM events - WHERE - repetition IS NULL - AND date >= ? - AND date <= ? - ", - )?; - - let iter = stmt.query_map([start, end], |row| { - let uuid: String = row.get(0)?; - let date = row.get(1)?; - let start = row.get(2)?; - let end = row.get(3)?; - let name = row.get(4)?; - Ok(Uuid::parse_str(&uuid).map(|id| Event { - id, - date, - start, - end, - name, - repetition: None, - })) - })?; - - let mut res = vec![]; - for event in iter { - res.push(event??) - } - Ok(res) -} diff --git a/src/gui/app.rs b/src/gui/app.rs index 8cd7096..4ed864b 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -10,18 +10,23 @@ use std::rc::Rc; use crate::gui::calendar; use crate::gui::update::Msg; -use crate::{db, model::event::Event}; +use crate::{db, model::category::Category, model::event::Event}; pub struct App { pub conn: Rc<Connection>, pub window: Rc<gtk::ApplicationWindow>, - pub grid: gtk::Grid, + pub tx: Sender<Msg>, + // Calendar + pub calendar: gtk::Grid, pub events: Vec<Event>, // TODO: use Hashmap to have fast access to events by id ? pub recurring_events: Vec<Event>, // TODO: use Hashmap to have fast access to events by id ? pub today: NaiveDate, pub start_date: NaiveDate, pub end_date: NaiveDate, - pub tx: Sender<Msg>, + // Categories + // pub categories: gtk::Box, + pub categories: Vec<Category>, + pub default_color: String, } impl App { @@ -29,7 +34,7 @@ impl App { let window = Rc::new( gtk::ApplicationWindow::builder() .application(app) - .title("Calendar") + .title("Calendrier") .default_width(800) .default_height(600) .visible(true) @@ -41,19 +46,32 @@ impl App { NaiveDate::from_isoywd(today.year(), today.iso_week().week(), Weekday::Mon); let end_date = start_date + Duration::days(7 * 4 - 1); - let events = db::list_non_recurring_between(&conn, start_date, end_date)?; - let recurring_events = db::list_recurring(&conn)?; + let events = db::events::list_non_recurring_between(&conn, start_date, end_date)?; + let recurring_events = db::events::list_recurring(&conn)?; + let categories = db::categories::list(&conn)?; + let default_color = db::event_color::get_default_color(&conn)?; - let grid = calendar::create( + let calendar = calendar::create( tx.clone(), today, start_date, end_date, &events, &recurring_events, + &categories, + &default_color, ); - window.set_child(Some(&grid)); + // let categories = gtk::Box::builder() + // .orientation(gtk::Orientation::Vertical) + // .build(); + + // let notebook = gtk::Notebook::builder().build(); + // notebook.append_page(&calendar, Some(>k::Label::new(Some("Calendrier")))); + // notebook.append_page(&categories, Some(>k::Label::new(Some("Catégories")))); + // window.set_child(Some(¬ebook)); + + window.set_child(Some(&calendar)); window.connect_close_request(move |window| { if let Some(application) = window.application() { @@ -65,13 +83,15 @@ impl App { Ok(Self { conn, window, - grid, + tx, + calendar, events, recurring_events, today, start_date, end_date, - tx, + categories, + default_color, }) } } diff --git a/src/gui/calendar.rs b/src/gui/calendar.rs index 547c087..f5f9c10 100644 --- a/src/gui/calendar.rs +++ b/src/gui/calendar.rs @@ -6,7 +6,7 @@ use gtk::glib; use gtk::prelude::*; use std::collections::HashMap; -use crate::{gui::update, gui::update::Msg, gui::App, model::event, model::event::Event}; +use crate::{gui::update, gui::update::Msg, gui::App, model::event, model::event::Event, model::category::Category}; static DAYS: [&str; 7] = ["LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM"]; static MONTHES: [&str; 12] = [ @@ -20,6 +20,8 @@ pub fn create( end_date: NaiveDate, events: &[Event], recurring_events: &[Event], + categories: &[Category], + default_color: &str, ) -> gtk::Grid { let grid = gtk::Grid::builder().build(); @@ -28,7 +30,7 @@ pub fn create( } let repetitions = event::repetitions_between(recurring_events, start_date, end_date); - attach_days(tx.clone(), &grid, start_date, today, events, &repetitions); + attach_days(tx.clone(), &grid, start_date, today, events, &repetitions, categories, default_color); let event_controller_key = gtk::EventControllerKey::new(); event_controller_key.connect_key_released(glib::clone!(@strong tx => move |_, _, keycode, _| { @@ -50,12 +52,14 @@ fn attach_days( today: NaiveDate, events: &[Event], repetitions: &HashMap<NaiveDate, Vec<Event>>, + categories: &[Category], + default_color: &str, ) { let mut d = start_date; for row in 1..5 { for col in 0..7 { grid.attach( - &day_entry(tx.clone(), d, today, events, repetitions), + &day_entry(tx.clone(), d, today, events, repetitions, categories, default_color), col, row, 1, @@ -66,14 +70,21 @@ fn attach_days( } } -pub fn refresh_date(app: &App, date: NaiveDate, repetitions: &HashMap<NaiveDate, Vec<Event>>) { +pub fn refresh_date( + app: &App, + date: NaiveDate, + repetitions: &HashMap<NaiveDate, + Vec<Event>>, + categories: &[Category], + default_color: &str +) { let d = date.signed_duration_since(app.start_date).num_days(); let col = (d % 7) as i32; let row = 1 + (d / 7) as i32; - app.grid.attach( - &day_entry(app.tx.clone(), date, app.today, &app.events, repetitions), + app.calendar.attach( + &day_entry(app.tx.clone(), date, app.today, &app.events, repetitions, categories, default_color), col, row, 1, @@ -101,6 +112,8 @@ pub fn day_entry( today: NaiveDate, events: &[Event], repetitions: &HashMap<NaiveDate, Vec<Event>>, + categories: &[Category], + default_color: &str, ) -> gtk::ScrolledWindow { let vbox = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) @@ -131,7 +144,7 @@ pub fn day_entry( events.sort_by_key(|e| e.start); if !events.is_empty() { - vbox.append(&day_events(date, tx, events)); + vbox.append(&day_events(date, tx, events, categories, default_color)); } gtk::ScrolledWindow::builder() @@ -164,7 +177,13 @@ fn day_label(today: NaiveDate, date: NaiveDate) -> gtk::Label { label } -fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box { +fn day_events( + date: NaiveDate, + tx: Sender<Msg>, + events: Vec<&Event>, + categories: &[Category], + default_color: &str, +) -> gtk::Box { let vbox = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); @@ -175,6 +194,25 @@ fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box .hexpand(true) .build(); + let background_color = categories.iter().find(|c| event.category == Some(c.id)).map(|c| c.color.clone()).unwrap_or_else(|| default_color.to_string()); + + let provider = gtk::CssProvider::new(); + provider.load_from_data(format!(" + .event {{ + background-color: {}; + color: white; + border-radius: 4px; + padding: 4px; + margin: 4px; + }} + + .event:hover {{ + filter: brightness(120%); + }} + ", background_color).as_bytes()); + hbox.style_context().add_provider(&provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + hbox.style_context().add_class("event"); + let gesture = gtk::GestureClick::new(); gesture.connect_pressed( glib::clone!(@strong event, @strong tx => move |gesture, n, _, _| { @@ -190,8 +228,6 @@ fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box ); hbox.add_controller(&gesture); - hbox.add_css_class("g-Calendar__DayEvent"); - let event_txt = &event.pprint(); let label = gtk::Label::builder() .label(event_txt) diff --git a/src/gui/form/mod.rs b/src/gui/form/mod.rs index bb43ef5..cc77a19 100644 --- a/src/gui/form/mod.rs +++ b/src/gui/form/mod.rs @@ -15,7 +15,7 @@ use uuid::Uuid; use crate::{ db, gui::{update, update::Msg, App}, - model::{event, event::Event, repetition::Repetition}, + model::{category::Category, event, event::Event, repetition::Repetition}, }; pub async fn repetition_dialog(app: &App, date: NaiveDate, event: &Event) { @@ -148,6 +148,19 @@ pub async fn show(app: &App, target: Target) { column1.append(&utils::label("Fin")); column1.append(&end); + let dropdown_categories = get_dropdown_categories(&app.categories); + let category_dropdown = gtk::DropDown::from_strings( + &dropdown_categories + .iter() + .map(|s| s.as_str()) + .collect::<Vec<&str>>(), + ); + category_dropdown.set_margin_bottom(10); + let selected = get_selected_category(&event, &app.categories).unwrap_or_else(|| "".to_string()); + category_dropdown.set_selected(dropdown_categories.iter().position(|d| d == &selected).unwrap_or(0) as u32); + column1.append(&utils::label("Catégorie")); + column1.append(&category_dropdown); + // Second column let repetition = match target { @@ -174,7 +187,8 @@ pub async fn show(app: &App, target: Target) { lines.append(&button); let conn = app.conn.clone(); let tx = app.tx.clone(); - button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event => move |_| { + let categories = app.categories.clone(); + button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event, @strong categories => move |_| { let removed_occurences = match &target { Target::Update { event, .. } => { event.repetition.as_ref().map(|r| r.removed_occurences.clone()).unwrap_or_default() @@ -187,11 +201,15 @@ pub async fn show(app: &App, target: 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) { + + // Find category id from selected id + let category = categories.iter().find(|c| c.name == dropdown_categories[category_dropdown.selected() as usize]).map(|c| c.id); + + match event::validate(id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition, category) { Some(new) => { match &target { Target::New {..} => { - match db::insert(&conn, &new) { + match db::events::insert(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::AddEvent { new }); dialog.close() @@ -200,7 +218,7 @@ pub async fn show(app: &App, target: Target) { } } Target::Update {event} => { - match db::update(&conn, &new) { + match db::events::update(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::UpdateEvent { old: event.clone(), new }); dialog.close() @@ -212,7 +230,7 @@ pub async fn show(app: &App, target: Target) { // TODO: improve intermediate error state match delete_repetition_occurence(&conn, event, *date) { Ok(occurence) => { - match db::insert(&conn, &new) { + match db::events::insert(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::UpdateEventOccurence { event: event.clone(), @@ -231,7 +249,7 @@ pub async fn show(app: &App, target: Target) { Target::UpdateFromOccurence { date, event } => { match update_repetition_until(&conn, *date - Duration::days(1), event) { Ok(updated) => { - match db::insert(&conn, &new) { + match db::events::insert(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::UpdateRepeatedFrom { old: event.clone(), @@ -288,7 +306,7 @@ pub async fn show(app: &App, target: Target) { } } _ => { - let operation = db::delete(&conn, &event.id); + let operation = db::events::delete(&conn, &event.id); if operation.is_ok() { update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() }); dialog.close() @@ -301,6 +319,22 @@ pub async fn show(app: &App, target: Target) { dialog.run_future().await; } +fn get_dropdown_categories(categories: &[Category]) -> Vec<String> { + let mut xs = categories + .iter() + .map(|c| c.name.clone()) + .collect::<Vec<String>>(); + xs.push("".to_string()); + xs.sort(); + xs +} + +fn get_selected_category(event: &Option<Event>, categories: &[Category]) -> Option<String> { + let id = event.as_ref()?.category?; + let category = categories.iter().find(|c| c.id == id)?; + Some(category.name.clone()) +} + #[derive(Error, Debug)] enum DeleteError { #[error("Repetition not found")] @@ -320,7 +354,7 @@ fn delete_repetition_occurence( let mut repetition = repetition.clone(); repetition.removed_occurences.insert(occurence); event.repetition = Some(repetition); - db::update(conn, &event).map(|_| occurence) + db::events::update(conn, &event).map(|_| occurence) } else { Err(anyhow::Error::new(DeleteError::OccurenceNotFound)) } @@ -336,6 +370,6 @@ fn update_repetition_until(conn: &Connection, date: NaiveDate, event: &Event) -> removed_occurences: r.removed_occurences.clone(), until: Some(date), }); - db::update(conn, &with_repetition_until)?; + db::events::update(conn, &with_repetition_until)?; Ok(with_repetition_until) } diff --git a/src/gui/style.css b/src/gui/style.css index 59ae30d..1b592d3 100644 --- a/src/gui/style.css +++ b/src/gui/style.css @@ -26,18 +26,6 @@ font-weight: bold; } -.g-Calendar__DayEvent { - background-color: #A8C2E0; - color: white; - border-radius: 4px; - padding: 4px; - margin: 4px; -} - -.g-Calendar__DayEvent:hover { - background-color: pink; -} - .g-Dialog { background-color: white; color: black; diff --git a/src/gui/update.rs b/src/gui/update.rs index bd4e7a9..7b3625c 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -166,24 +166,32 @@ pub async fn event_handler(rx: Receiver<Msg>, mut app: App) { refresh(&app, &HashSet::from([date])) } Msg::SelectPreviousWeek => { - app.grid.remove_row(4); - app.grid.insert_row(1); + app.calendar.remove_row(4); + app.calendar.insert_row(1); app.start_date -= Duration::days(7); app.end_date -= Duration::days(7); - match db::list_non_recurring_between(&app.conn, app.start_date, app.end_date) { + match db::events::list_non_recurring_between( + &app.conn, + app.start_date, + app.end_date, + ) { Ok(events) => app.events = events, Err(err) => eprintln!("{}", err), }; refresh(&app, &HashSet::from_iter(week_from(app.start_date))); } Msg::SelectNextWeek => { - app.grid.remove_row(1); - app.grid.insert_row(4); + app.calendar.remove_row(1); + app.calendar.insert_row(4); app.start_date += Duration::days(7); app.end_date += Duration::days(7); - match db::list_non_recurring_between(&app.conn, app.start_date, app.end_date) { + match db::events::list_non_recurring_between( + &app.conn, + app.start_date, + app.end_date, + ) { Ok(events) => app.events = events, Err(err) => eprintln!("{}", err), }; @@ -275,7 +283,7 @@ fn refresh(app: &App, dates: &HashSet<NaiveDate>) { for date in dates { if date >= &app.start_date && date <= &app.end_date { - calendar::refresh_date(app, *date, &repetitions) + calendar::refresh_date(app, *date, &repetitions, &app.categories, &app.default_color) } } } diff --git a/src/model/category.rs b/src/model/category.rs new file mode 100644 index 0000000..2f65671 --- /dev/null +++ b/src/model/category.rs @@ -0,0 +1,8 @@ +use uuid::Uuid; + +#[derive(Debug, Clone)] +pub struct Category { + pub id: Uuid, + pub name: String, + pub color: String, +} diff --git a/src/model/event.rs b/src/model/event.rs index e556f6e..6cc16a2 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -16,6 +16,7 @@ pub struct Event { pub end: Option<NaiveTime>, pub name: String, pub repetition: Option<Repetition>, + pub category: Option<Uuid>, } impl Event { @@ -62,6 +63,7 @@ pub fn validate( start: String, end: String, repetition: Option<Repetition>, + category: Option<Uuid>, ) -> Option<Event> { let start = validation::time(start)?; let end = validation::time(end)?; @@ -78,5 +80,6 @@ pub fn validate( start, end, repetition, + category, }) } diff --git a/src/model/mod.rs b/src/model/mod.rs index 0aefbc6..2491bb4 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,3 +1,4 @@ +pub mod category; pub mod event; pub mod repetition; pub mod time; |