calendar. default nick

f7cb98f · gmorell · 2025-01-12 05:25

11 files +423 -97

Files changed

modified Cargo.lock
+13 −0
@@ -1703,6 +1703,15 @@ dependencies = [
1703 1703 "libm",
1704 1704 ]
1705 1705
1706 +[[package]]
1707 +name = "num_threads"
1708 +version = "0.1.7"
1709 +source = "registry+https://github.com/rust-lang/crates.io-index"
1710 +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
1711 +dependencies = [
1712 + "libc",
1713 +]
1714 +
1706 1715 [[package]]
1707 1716 name = "object"
1708 1717 version = "0.32.2"
@@ -1886,6 +1895,7 @@ dependencies = [
1886 1895 "ratatui",
1887 1896 "sea-orm",
1888 1897 "sea-orm-migration",
1898 + "time",
1889 1899 "tokio",
1890 1900 "tracing",
1891 1901 "tracing-error",
@@ -2047,6 +2057,7 @@ dependencies = [
2047 2057 "lru 0.12.1",
2048 2058 "paste",
2049 2059 "strum",
2060 + "time",
2050 2061 "unicode-segmentation",
2051 2062 "unicode-truncate",
2052 2063 "unicode-width 0.2.0",
@@ -2957,7 +2968,9 @@ checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
2957 2968 dependencies = [
2958 2969 "deranged",
2959 2970 "itoa",
2971 + "libc",
2960 2972 "num-conv",
2973 + "num_threads",
2961 2974 "powerfmt",
2962 2975 "serde",
2963 2976 "time-core",
modified Cargo.toml
+2 −1
@@ -9,7 +9,7 @@ edition = "2021"
9 9 clap = { version = "4.4.18", features = ["derive"] }
10 10 color-eyre = "0.6"
11 11 crossterm = "0.28.1"
12 −ratatui = "0.29.0"
12 +ratatui = { version = "0.29.0", features = ["widget-calendar"] }
13 13 sea-orm = { version = "1.1.2" , features = ["sqlx-sqlite", "runtime-tokio", "macros"]}
14 14 wealthy = "0.2.0"
15 15 futures = "0.3.30"
@@ -29,3 +29,4 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
29 29 directories = "5.0.1"
30 30 lazy_static = "1.5.0"
31 31 colored = { version = "2.2.0" }
32 +time = "0.3.37"
modified src/data.rs
+13 −6
@@ -13,7 +13,7 @@ use color_eyre::owo_colors::OwoColorize;
13 13 use log::warn;
14 14 use ratatui::style::palette::tailwind::PURPLE;
15 15 use sea_orm::ActiveValue::Set;
16 −use sea_orm::{ActiveModelTrait, DatabaseConnection, DbErr, EntityTrait};
16 +use sea_orm::{ActiveModelTrait, DatabaseConnection, DbErr, EntityTrait, NotSet};
17 17
18 18 fn bool_from_string(input: Option<String>) -> bool {
19 19 if input.is_none() {
@@ -118,6 +118,7 @@ impl ContactOps {
118 118 am.designator = Set(designator);
119 119 am.r#type = Set(typ);
120 120 am.value = Set(value);
121 + am.date_up = Set(Local::today().naive_local());
121 122 am.update(db).await?;
122 123 Ok(())
123 124 }
@@ -128,13 +129,11 @@ impl ContactOps {
128 129 pub struct PplOps {}
129 130
130 131 impl PplOps {
131 − pub async fn create_me(
132 − db: &DatabaseConnection,
133 − name: String,
134 − ) -> Result<ppl::Model, PplError> {
132 + pub async fn create_me(db: &DatabaseConnection, name: String) -> Result<ppl::Model, PplError> {
135 133 let p = ppl::ActiveModel {
136 134 id: Default::default(),
137 135 name: Set(name),
136 + nick: NotSet,
138 137 me: Set(true),
139 138 date_ins: Set(Local::today().naive_local()),
140 139 date_up: Set(Local::today().naive_local()),
@@ -156,6 +155,7 @@ impl PplOps {
156 155 id: Default::default(),
157 156 name: Set(name),
158 157 me: Set(false),
158 + nick: NotSet,
159 159 date_ins: Set(Local::today().naive_local()),
160 160 date_up: Set(Local::today().naive_local()),
161 161 };
@@ -190,6 +190,7 @@ impl PplOps {
190 190 None => {}
191 191 Some(i) => am.name = Set(i),
192 192 }
193 + am.date_up = Set(Local::today().naive_local());
193 194 am.update(db).await?;
194 195 Ok(())
195 196 }
@@ -247,7 +248,9 @@ impl RelationOps {
247 248 None,
248 249 option_naive_date_from_option_string(editable.second),
249 250 option_naive_date_from_option_string(editable.third),
250 − ).await.expect("huh");
251 + )
252 + .await
253 + .expect("huh");
251 254 }
252 255
253 256 pub async fn update(
@@ -286,6 +289,7 @@ impl RelationOps {
286 289 Some(date) => am.date_ended = Set(Option::from(date)),
287 290 }
288 291 am.r#type = Set(typ);
292 + am.date_up = Set(Local::today().naive_local());
289 293 am.update(db).await?;
290 294 Ok(())
291 295 }
@@ -362,6 +366,7 @@ impl SigDateOps {
362 366 am.event = Set(event);
363 367 am.date = Set(date.unwrap_or_default());
364 368 am.do_remind = Set(do_remind);
369 + am.date_up = Set(Local::today().naive_local());
365 370 am.update(db).await?;
366 371 Ok(())
367 372 }
@@ -431,6 +436,7 @@ impl TierOps {
431 436 am.name = Set(name);
432 437 am.color = Set(color);
433 438 am.symbol = Set(symbol);
439 + am.date_up = Set(Local::today().naive_local());
434 440 am.update(db).await?;
435 441 Ok(())
436 442 }
@@ -506,6 +512,7 @@ impl TraitOps {
506 512 Some(j) => am.value = Set(j),
507 513 }
508 514 am.hidden = Set(hidden);
515 + am.date_up = Set(Local::today().naive_local());
509 516 am.update(db).await?;
510 517 Ok(())
511 518 }
modified src/do_add.rs
+4 −26
@@ -115,16 +115,7 @@ pub async fn do_add(args: &AddArgs, db: DatabaseConnection) -> Result<(), PplErr
115 115 match &args.relation {
116 116 None => Ok(()),
117 117 Some(relation) => {
118 − RelationOps::create(
119 − &db,
120 − me.id,
121 − pp.id,
122 − relation.to_owned(),
123 − false,
124 − None,
125 − None,
126 − )
127 − .await
118 + RelationOps::create(&db, me.id, pp.id, relation.to_owned(), false, None, None).await
128 119 }
129 120 }?;
130 121
@@ -134,14 +125,8 @@ pub async fn do_add(args: &AddArgs, db: DatabaseConnection) -> Result<(), PplErr
134 125 let date = parse_date_string(met, Local::now(), Dialect::Us);
135 126 match date {
136 127 Ok(parsed) => {
137 − SigDateOps::create(
138 − &db,
139 − pp.id,
140 − parsed.date_naive(),
141 − "met".to_string(),
142 − false,
143 − )
144 − .await
128 + SigDateOps::create(&db, pp.id, parsed.date_naive(), "met".to_string(), false)
129 + .await
145 130 }
146 131 Err(e) => {
147 132 warn!("failed to parse metdate {:?}", e.red());
@@ -154,14 +139,7 @@ pub async fn do_add(args: &AddArgs, db: DatabaseConnection) -> Result<(), PplErr
154 139 match &args.from {
155 140 None => Ok(()),
156 141 Some(from) => {
157 − TraitOps::create(
158 − &db,
159 − pp.id,
160 − "from".to_string(),
161 − from.to_string(),
162 − false,
163 − )
164 − .await
142 + TraitOps::create(&db, pp.id, "from".to_string(), from.to_string(), false).await
165 143 }
166 144 }?;
167 145
modified src/do_init.rs
+4 −2
@@ -17,7 +17,7 @@ use ratatui::{
17 17 use sea_orm::sqlx::types::chrono;
18 18 use sea_orm::sqlx::types::chrono::Local;
19 19 use sea_orm::ActiveValue::Set;
20 −use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait};
20 +use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, NotSet};
21 21 use std::fmt;
22 22 use std::string::String;
23 23 use tui_input::backend::crossterm::EventHandler;
@@ -438,6 +438,7 @@ pub async fn run_init(
438 438 id: Default::default(),
439 439 name: Set(ofppl.clone()),
440 440 me: Set(false),
441 + nick: NotSet,
441 442 date_ins: Set(Local::today().naive_local()),
442 443 date_up: Set(Local::today().naive_local()),
443 444 };
@@ -491,6 +492,7 @@ pub async fn run_init(
491 492 id: Default::default(),
492 493 name: Set(ofppl.clone()),
493 494 me: Set(false),
495 + nick: NotSet,
494 496 date_ins: Set(Local::today().naive_local()),
495 497 date_up: Set(Local::today().naive_local()),
496 498 };
@@ -1020,7 +1022,7 @@ const TEXT_FG_COLOR: Color = SLATE.c200;
1020 1022 const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500;
1021 1023 const BLUEBLUE: Color = BLUE.c500;
1022 1024
1023 −fn lcolor(input: &String) -> Color {
1025 +pub fn lcolor(input: &String) -> Color {
1024 1026 match input.to_lowercase().as_str() {
1025 1027 "red" => RED.c500,
1026 1028 "gold" => AMBER.c500,
modified src/do_tui.rs
+383 −60
@@ -1,29 +1,35 @@
1 1 use crate::data::{
2 2 ContactOps, PplOps, RelationOps, SigDateOps, TierDefaultOps, TierOps, TraitDefaultOps, TraitOps,
3 3 };
4 +use crate::do_init::lcolor;
4 5 use crate::entities::ppl::Model;
6 +use crate::entities::prelude::{TierDefaults, Traits};
5 7 use crate::entities::{
6 8 contact, ppl, relation, sig_date, tier, tier_defaults, trait_defaults, traits,
7 9 };
8 10 use crate::PplError;
11 +use chrono::{Datelike, NaiveDate};
9 12 use color_eyre::owo_colors::OwoColorize;
10 13 use crossterm::event;
11 14 use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
12 15 use enum_iterator::{next, Sequence};
13 16 use ratatui::prelude::{Constraint, Direction, Layout, Line, Modifier, Span, Style, Stylize, Text};
14 17 use ratatui::style::palette::material::AMBER;
15 −use ratatui::style::palette::tailwind::{ORANGE, SLATE, WHITE};
18 +use ratatui::style::palette::tailwind::{GREEN, ORANGE, PINK, SLATE, TEAL, WHITE};
16 19 use ratatui::style::{Color, Styled};
20 +use ratatui::widgets::calendar::{CalendarEventStore, Monthly};
17 21 use ratatui::widgets::{Block, Borders, List, ListDirection, ListItem, ListState, Paragraph};
18 22 use ratatui::{DefaultTerminal, Frame};
19 23 use sea_orm::DatabaseConnection;
20 24 use std::ops::Index;
25 +use time::{Date, Month, OffsetDateTime};
21 26 use tui_input::backend::crossterm::EventHandler;
22 27 use tui_input::Input;
23 28
24 29 struct Tui {
25 30 current_tab: Tabs,
26 31 sigdate_list: Vec<sig_date::Model>,
32 + sigdate_cal_state: ListState,
27 33 trait_list: Vec<traits::Model>,
28 34 tier_list: Vec<tier::Model>,
29 35 rel_list: Vec<relation::Model>,
@@ -108,6 +114,7 @@ impl Default for Tui {
108 114 Tui {
109 115 current_tab: Tabs::Ppl,
110 116 sigdate_list: vec![],
117 + sigdate_cal_state: ListState::default(),
111 118 trait_list: vec![],
112 119 tier_list: vec![],
113 120 rel_list: vec![],
@@ -138,6 +145,21 @@ fn get_style(mine: u8, cursor: u8, total: u8) -> Color {
138 145 }
139 146 }
140 147
148 +fn get_cal<'a>(
149 + month: Month,
150 + year: i32,
151 + events: &CalendarEventStore,
152 +) -> Monthly<'a, &CalendarEventStore> {
153 + let default_style = Style::default()
154 + .add_modifier(Modifier::BOLD)
155 + .fg(AMBER.c500)
156 + .bg(SLATE.c800);
157 +
158 + Monthly::new(Date::from_calendar_date(year, month, 1).unwrap(), events)
159 + .show_month_header(Style::default())
160 + .default_style(default_style)
161 +}
162 +
141 163 pub async fn run_tui(
142 164 mut terminal: DefaultTerminal,
143 165 db: DatabaseConnection,
@@ -178,66 +200,68 @@ pub async fn run_tui(
178 200 true => {
179 201 app.ppl_field_idx += 1;
180 202 }
181 − false => {
182 − app.current_tab = next(&app.current_tab).unwrap_or(Tabs::Ppl)
183 − }
203 + false => app.current_tab = next(&app.current_tab).unwrap_or(Tabs::Ppl),
184 204 },
185 205 KeyCode::Esc => break Ok(()),
186 − KeyCode::Char('e') => if app.current_tab == Tabs::Ppl { match app.ppl_field_editing {
187 − true => {
188 − default_editing_handler(&mut app, &key_event);
189 − }
190 − false => {
191 − app.ppl_editing = !app.ppl_editing;
206 + KeyCode::Char('e') => {
207 + if app.current_tab == Tabs::Ppl {
208 + match app.ppl_field_editing {
209 + true => {
210 + default_editing_handler(&mut app, &key_event);
211 + }
212 + false => {
213 + app.ppl_editing = !app.ppl_editing;
214 + }
215 + }
192 216 }
193 − } },
217 + }
194 218 KeyCode::Char('f') => {
195 219 default_editing_handler(&mut app, &key_event);
196 220 }
197 − KeyCode::Enter => if app.current_tab == Tabs::Ppl { if app.ppl_editing && app.ppl_detail_state.selected().is_some() {
198 − let idx = &app.ppl_detail_state.selected().unwrap();
199 − let e = app.ppl_editables.get(*idx).unwrap();
200 − if !app.ppl_field_editing {
201 − // let k
202 − app.ppl_input_a = e.first.clone().into();
203 − app.ppl_input_b =
204 − e.second.clone().unwrap_or("".to_string()).into();
205 − app.ppl_input_c =
206 − e.third.clone().unwrap_or("".to_string()).into();
207 − } else {
208 − let new_editable = Editable {
209 − tgt: e.tgt.clone(),
210 − id: e.id,
211 − first: app.ppl_input_a.value().parse().unwrap(),
212 − second: Option::from(
213 − app.ppl_input_b.value().to_string(),
214 − ),
215 − third: Option::from(
216 − app.ppl_input_c.value().to_string(),
217 − ),
218 − };
221 + KeyCode::Enter => {
222 + if app.current_tab == Tabs::Ppl {
223 + if app.ppl_editing && app.ppl_detail_state.selected().is_some() {
224 + let idx = &app.ppl_detail_state.selected().unwrap();
225 + let e = app.ppl_editables.get(*idx).unwrap();
226 + if !app.ppl_field_editing {
227 + // let k
228 + app.ppl_input_a = e.first.clone().into();
229 + app.ppl_input_b =
230 + e.second.clone().unwrap_or("".to_string()).into();
231 + app.ppl_input_c =
232 + e.third.clone().unwrap_or("".to_string()).into();
233 + } else {
234 + let new_editable = Editable {
235 + tgt: e.tgt.clone(),
236 + id: e.id,
237 + first: app.ppl_input_a.value().parse().unwrap(),
238 + second: Option::from(app.ppl_input_b.value().to_string()),
239 + third: Option::from(app.ppl_input_c.value().to_string()),
240 + };
219 241
220 − match e.tgt {
221 − Editablez::Trait => {
222 − TraitOps::updatee(&db, new_editable).await;
223 − }
224 − Editablez::Tier => {
225 − TierOps::updatee(&db, new_editable).await;
226 − }
227 − Editablez::Contact => {
228 − ContactOps::updatee(&db, new_editable).await;
229 − }
230 − Editablez::Relation => {
231 − RelationOps::updatee(&db, new_editable).await;
242 + match e.tgt {
243 + Editablez::Trait => {
244 + TraitOps::updatee(&db, new_editable).await;
245 + }
246 + Editablez::Tier => {
247 + TierOps::updatee(&db, new_editable).await;
248 + }
249 + Editablez::Contact => {
250 + ContactOps::updatee(&db, new_editable).await;
251 + }
252 + Editablez::Relation => {
253 + RelationOps::updatee(&db, new_editable).await;
254 + }
255 + Editablez::SigDate => {
256 + SigDateOps::updatee(&db, new_editable).await;
257 + }
258 + };
259 + app.reload(&db).await?;
232 260 }
233 − Editablez::SigDate => {
234 − SigDateOps::updatee(&db, new_editable).await;
235 − }
236 − };
237 − app.reload(&db).await?;
261 + app.ppl_field_editing = !app.ppl_field_editing;
262 + }
238 263 }
239 − app.ppl_field_editing = !app.ppl_field_editing;
240 − } },
264 + }
241 265 _ => {
242 266 default_editing_handler(&mut app, &key_event);
243 267 }
@@ -320,7 +344,14 @@ fn render(f: &mut Frame, app: &mut Tui) {
320 344 let chunks = Layout::default()
321 345 .direction(Direction::Vertical)
322 346 .margin(2)
323 − .constraints([Constraint::Length(1), Constraint::Length(15)].as_ref())
347 + .constraints(
348 + [
349 + Constraint::Length(1),
350 + Constraint::Percentage(100),
351 + Constraint::Length(1),
352 + ]
353 + .as_ref(),
354 + )
324 355 .split(f.area());
325 356
326 357 let msgw = vec![
@@ -337,6 +368,45 @@ fn render(f: &mut Frame, app: &mut Tui) {
337 368
338 369 match app.current_tab {
339 370 Tabs::Ppl => {
371 + match app.ppl_editing {
372 + true => {
373 + let msgw = vec![
374 + Span::styled(
375 + "Enter",
376 + Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
377 + ),
378 + Span::raw(" to modify a ppl entry. "),
379 + Span::styled(
380 + "Shift+Enter",
381 + Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
382 + ),
383 + Span::raw(" to add a ppl entry"),
384 + ];
385 + let textw = Text::from(Line::from(msgw)).style(Style::default());
386 + let welcome = Paragraph::new(textw);
387 + f.render_widget(welcome, chunks[2]);
388 + }
389 + false => {
390 + let msgw = vec![
391 + Span::styled(
392 + "Tab",
393 + Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
394 + ),
395 + Span::raw(" to switch views"),
396 + Span::styled(
397 + "Enter",
398 + Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
399 + ),
400 + Span::raw(" to add ppl. "),
401 + Span::styled("E", Style::default().add_modifier(Modifier::BOLD).fg(WHITE)),
402 + Span::raw(" to edit the selected ppl. "),
403 + ];
404 + let textw = Text::from(Line::from(msgw)).style(Style::default());
405 + let welcome = Paragraph::new(textw);
406 + f.render_widget(welcome, chunks[2]);
407 + }
408 + }
409 +
340 410 let layout_ppl = Layout::default()
341 411 .direction(Direction::Horizontal)
342 412 .margin(0)
@@ -353,7 +423,22 @@ fn render(f: &mut Frame, app: &mut Tui) {
353 423 Constraint::Length(3),
354 424 ])
355 425 .split(layout_ppl[1]);
356 − let ppl = List::new(&app.ppl_list)
426 + let ppls: Vec<PplAndProps> = app
427 + .ppl_list
428 + .iter()
429 + .map(|p| PplAndProps {
430 + ppl: p.clone(),
431 + tiers: app
432 + .tier_list
433 + .iter()
434 + .filter(|t| t.ppl_id == p.id)
435 + .map(|t| t.to_owned())
436 + .collect::<Vec<tier::Model>>(),
437 + tier_defaults: app.def_tier_list.clone(),
438 + })
439 + .collect::<Vec<PplAndProps>>();
440 +
441 + let ppl = List::new(ppls)
357 442 .block(Block::bordered().title("ppl"))
358 443 .style(Style::new().white())
359 444 .highlight_style(Style::new().italic())
@@ -717,17 +802,32 @@ fn render(f: &mut Frame, app: &mut Tui) {
717 802 f.render_widget(input_c, layout_edit[3]);
718 803 }
719 804 }
720 −
805 + let title = match &curr.nick {
806 + None => {
807 + format!("Editing {}", curr.name.clone())
808 + }
809 + Some(n) => {
810 + format!("Editing {} ({})", curr.name.clone(), n)
811 + }
812 + };
721 813 let block = Block::new()
722 814 .borders(Borders::LEFT)
723 − .title(format!("Editing {}", curr.name.clone()))
815 + .title(title)
724 816 .style(Style::default().fg(ORANGE.c500));
725 817 f.render_widget(paragraph.clone().block(block), layout_edit[0])
726 818 }
727 819 false => {
820 + let title = match &curr.nick {
821 + None => {
822 + format!("Editing {}", curr.name.clone())
823 + }
824 + Some(n) => {
825 + format!("Editing {} ({})", curr.name.clone(), n)
826 + }
827 + };
728 828 let block = Block::new()
729 829 .borders(Borders::LEFT)
730 − .title(format!("Editing {}", curr.name.clone()))
830 + .title(title)
731 831 .style(Style::default().fg(AMBER.c500));
732 832 f.render_stateful_widget(
733 833 list_contact.block(block),
@@ -737,15 +837,123 @@ fn render(f: &mut Frame, app: &mut Tui) {
737 837 }
738 838 },
739 839 false => {
740 − let block = Block::new()
741 − .borders(Borders::ALL)
742 − .title(curr.name.clone().to_string());
840 + let title = match &curr.nick {
841 + None => {
842 + format!("{}", curr.name.clone())
843 + }
844 + Some(n) => {
845 + format!("{} ({})", curr.name.clone(), n)
846 + }
847 + };
848 + let block = Block::new().borders(Borders::ALL).title(title);
743 849 f.render_widget(paragraph.clone().block(block), layout_ppl[1]);
744 850 }
745 851 };
746 852 }
747 853 }
748 854 }
855 + Tabs::Calendar => {
856 + let layout_cal = Layout::default()
857 + .direction(Direction::Horizontal)
858 + .margin(0)
859 + .constraints([Constraint::Percentage(26), Constraint::Percentage(74)])
860 + .split(chunks[1]);
861 +
862 + let mut start = OffsetDateTime::now_local()
863 + .unwrap()
864 + .date()
865 + .replace_month(Month::January)
866 + .unwrap()
867 + .replace_day(1)
868 + .unwrap();
869 + &app.sigdate_list.sort_by(|a, b| a.date.cmp(&b.date));
870 + let as_upcoming = &app
871 + .sigdate_list
872 + .iter()
873 + .map(|mut d| {
874 + let mut n = d.clone();
875 + n.date = NaiveDate::from_ymd(start.year(), n.date.month(), n.date.day());
876 + let p = app
877 + .ppl_list
878 + .iter()
879 + .find(|p| p.id == n.ppl_id)
880 + .unwrap()
881 + .clone();
882 + let tr = app
883 + .trait_list
884 + .iter()
885 + .filter(|p| p.id == n.ppl_id)
886 + .map(|t| t.to_owned())
887 + .collect::<Vec<traits::Model>>();
888 + crate::do_tui::SigDateAndProps {
889 + date: n,
890 + ppl: p,
891 + traits: tr,
892 + trait_defaults: app.def_trait_list.clone(),
893 + }
894 + })
895 + .collect::<Vec<SigDateAndProps>>();
896 + let mut event_list = CalendarEventStore::today(
897 + Style::default()
898 + .add_modifier(Modifier::BOLD)
899 + .bg(Color::Blue),
900 + );
901 +
902 + for u in as_upcoming {
903 + event_list.add(
904 + // Date::from_calendar_date(start.year(), Month::try_from(4 as u8).expect("oops"), 15 as u8).expect("doubleoops"),
905 + Date::from_calendar_date(
906 + start.year(),
907 + Month::try_from(u.date.date.month() as u8).expect("oops"),
908 + u.date.date.day() as u8,
909 + )
910 + .expect("doubleoops"),
911 + Style::default()
912 + .add_modifier(Modifier::BOLD)
913 + .fg(GREEN.c300)
914 + .bg(PINK.c500),
915 + );
916 + }
917 +
918 + // let _ = as_upcoming.iter().map(|u| {
919 + // let _ = event_list.add(
920 + // Date::from_calendar_date(start.year(), Month::try_from(4 as u8).expect("oops"), 15 as u8).expect("doubleoops"),
921 + // // Date::from_calendar_date(start.year(), Month::try_from(u.date.month() as u8).expect("oops"), u.date.day() as u8).expect("doubleoops"),
922 + // Style::default()
923 + // .add_modifier(Modifier::BOLD)
924 + // .fg(GREEN.c300)
925 + // .bg(PINK.c500),
926 + // );
927 + // });
928 +
929 + let rows = Layout::vertical([Constraint::Ratio(1, 2); 2]).split(layout_cal[1]);
930 + let cols = rows.iter().flat_map(|row| {
931 + Layout::horizontal([Constraint::Ratio(1, 6); 6])
932 + .split(*row)
933 + .to_vec()
934 + });
935 + for col in cols {
936 + let cal = get_cal(start.month(), start.year(), &event_list);
937 + f.render_widget(cal, col);
938 + start = start.replace_month(start.month().next()).unwrap();
939 + }
940 +
941 + let dates = List::new(as_upcoming)
942 + .block(Block::bordered().title("upcoming"))
943 + .style(Style::new().white())
944 + .highlight_style(Style::new().italic())
945 + .highlight_symbol(">>")
946 + .repeat_highlight_symbol(true)
947 + .direction(ListDirection::TopToBottom);
948 +
949 + f.render_stateful_widget(dates, layout_cal[0], &mut app.sigdate_cal_state);
950 +
951 + // let layout_cal_detail = Layout::default()
952 + // .direction(Direction::Horizontal)
953 + // .margin(0)
954 + // .constraints([Constraint::Percentage(25), Constraint::Percentage(75)])
955 + // .split(chunks[1]);
956 + }
749 957 Tabs::TierSettings => {
750 958 let ppl = List::new(&app.def_tier_list)
751 959 .block(Block::bordered().title("tiers"))
@@ -772,8 +980,106 @@ fn render(f: &mut Frame, app: &mut Tui) {
772 980 }
773 981 }
774 982
983 +struct PplAndProps {
984 + ppl: ppl::Model,
985 + tier_defaults: Vec<tier_defaults::Model>,
986 + tiers: Vec<tier::Model>,
987 +}
988 +
989 +struct SigDateAndProps {
990 + date: sig_date::Model,
991 + ppl: ppl::Model,
992 + traits: Vec<traits::Model>,
993 + trait_defaults: Vec<trait_defaults::Model>,
994 +}
995 +
996 +impl From<PplAndProps> for ListItem<'_> {
997 + fn from(value: PplAndProps) -> Self {
998 + // value.date_up;
999 + let line = match value.ppl.me {
1000 + true => Line::styled(
1001 + format!(" 🌟 {}", value.ppl.nick.unwrap_or(value.ppl.name)),
1002 + WHITE,
1003 + ),
1004 + false => match value.tiers.len() {
1005 + 0 => Line::styled(
1006 + format!(" {}", value.ppl.nick.unwrap_or(value.ppl.name)),
1007 + WHITE,
1008 + ),
1009 + _ => {
1010 + let t = value.tiers.first().unwrap();
1011 + match value.tier_defaults.iter().find(|q| q.key == t.name) {
1012 + None => Line::styled(
1013 + format!(" {}", value.ppl.nick.unwrap_or(value.ppl.name)),
1014 + WHITE,
1015 + ),
1016 + Some(tierd) => match t.symbol.clone().unwrap_or("".to_string()).as_str() {
1017 + "" => Line::styled(
1018 + format!(
1019 + " {} {}",
1020 + tierd.symbol.clone().unwrap_or("".to_string()),
1021 + value.ppl.nick.unwrap_or(value.ppl.name)
1022 + ),
1023 + lcolor(
1024 + &t.color
1025 + .clone()
1026 + .unwrap_or(t.color.clone().unwrap_or("".to_string())),
1027 + ),
1028 + ),
1029 + otherwise => Line::styled(
1030 + format!(
1031 + " {} {}",
1032 + otherwise,
1033 + value.ppl.nick.unwrap_or(value.ppl.name)
1034 + ),
1035 + lcolor(
1036 + &t.color
1037 + .clone()
1038 + .unwrap_or(tierd.color.clone().unwrap_or("".to_string())),
1039 + ),
1040 + ),
1041 + },
1042 + }
1043 + }
1044 + },
1045 + };
1046 + ListItem::new(line)
1047 + }
1048 +}
1049 +
1050 +impl From<&SigDateAndProps> for ListItem<'_> {
1051 + fn from(value: &SigDateAndProps) -> Self {
1052 + let n = value.ppl.nick.clone().unwrap_or(value.ppl.name.clone());
1053 + let default = value
1054 + .trait_defaults
1055 + .iter()
1056 + .find(|q| q.key == value.date.event && q.is_date == true);
1057 + let line = match (value.date.do_remind, default) {
1058 + (true, Some(t)) => {
1059 + Line::styled(format!(" ✓ {} {} {}", t.symbol, n, value.date.date), WHITE)
1060 + }
1061 + (true, None) => Line::styled(
1062 + format!(" ✓ 📅 {} {} ({})", n, value.date.date, value.date.event),
1063 + WHITE,
1064 + ),
1065 + (false, Some(t)) => Line::styled(
1066 + format!(" ☐ {} {} {}", t.symbol, n, value.date.date),
1067 + SLATE.c500,
1068 + ),
1069 +
1070 + (false, None) => Line::styled(
1071 + format!(" ☐ 📅 {} {} ({})", n, value.date.date, value.date.event),
1072 + SLATE.c500,
1073 + ),
1074 + };
1075 +
1076 + ListItem::new(line)
1077 + }
1078 +}
1079 +
775 1080 impl From<&ppl::Model> for ListItem<'_> {
776 1081 fn from(value: &Model) -> Self {
1082 + value.date_up;
777 1083 let line = match value.me {
778 1084 true => Line::styled(format!(" 🌟 {}", value.name), WHITE),
779 1085 false => Line::styled(format!(" {}", value.name), WHITE),
@@ -802,3 +1108,20 @@ impl From<&trait_defaults::Model> for ListItem<'_> {
802 1108 ListItem::new(line)
803 1109 }
804 1110 }
1111 +
1112 +impl From<&sig_date::Model> for ListItem<'_> {
1113 + fn from(value: &sig_date::Model) -> Self {
1114 + let line = match value.do_remind {
1115 + true => Line::styled(
1116 + format!(" ✓ {} {} {}", value.ppl_id, value.event, value.date),
1117 + WHITE,
1118 + ),
1119 + false => Line::styled(
1120 + format!(" ☐ {} {} {}", value.ppl_id, value.event, value.date),
1121 + SLATE.c500,
1122 + ),
1123 + };
1124 +
1125 + ListItem::new(line)
1126 + }
1127 +}
modified src/entities/ppl.rs
+1 −0
@@ -9,6 +9,7 @@ pub struct Model {
9 9 pub id: i32,
10 10 pub name: String,
11 11 pub me: bool,
12 + pub nick: Option<String>,
12 13 pub date_ins: Date,
13 14 pub date_up: Date,
14 15 }
modified src/migrator/m20241219_000001_create_ppl.rs
+3 −2
@@ -25,6 +25,7 @@ impl MigrationTrait for Migration {
25 25 )
26 26 .col(ColumnDef::new(Ppl::Name).string().not_null())
27 27 .col(ColumnDef::new(Ppl::Me).boolean().not_null())
28 + .col(ColumnDef::new(Ppl::Nick).string().not_null())
28 29 .col(ColumnDef::new(Ppl::DateIns).date().not_null())
29 30 .col(ColumnDef::new(Ppl::DateUp).date().not_null())
30 31 .to_owned(),
@@ -45,8 +46,8 @@ pub enum Ppl {
45 46 Table,
46 47 Id,
47 48 Name,
48 − Me, // boolean for self to wish you a happy bday
49 −
49 + Me, // boolean for self to wish you a happy bday
50 + Nick, // default-alias
50 51 DateIns,
51 52 DateUp,
52 53 }

Parents: 19f8d9b