things3_cloud/ui/components/
task_item.rs1use iocraft::prelude::*;
2
3use crate::{
4 common::ICONS,
5 store::Task,
6 ui::components::{
7 checklist::CheckList,
8 details_container::DetailsContainer,
9 id::Id,
10 task_line::TaskLine,
11 tasks::TaskOptions,
12 },
13};
14
15#[derive(Default, Props)]
16pub struct TaskItemProps<'a> {
17 pub task: Option<&'a Task>,
18 pub options: TaskOptions,
19 pub id_prefix_len: usize,
20}
21
22#[component]
23pub fn TaskItem<'a>(props: &TaskItemProps<'a>) -> impl Into<AnyElement<'a>> {
24 let Some(task) = props.task else {
25 return element!(Fragment).into_any();
26 };
27
28 let details = if props.options.detailed {
29 element! {
30 TaskDetails(task: task, id_prefix_len: props.id_prefix_len)
31 }
32 .into_any()
33 } else {
34 element!(Fragment).into_any()
35 };
36
37 element! {
38 View(flex_direction: FlexDirection::Row, gap: 1) {
39 Id(id: &task.uuid, length: props.id_prefix_len)
40 View(flex_direction: FlexDirection::Column) {
41 TaskText(task: task, options: props.options)
42 #(details)
43 }
44 }
45 }
46 .into_any()
47}
48
49#[derive(Default, Props)]
50struct TaskTextProps<'a> {
51 pub task: Option<&'a Task>,
52 pub options: TaskOptions,
53}
54
55#[component]
56fn TaskText<'a>(props: &TaskTextProps<'a>) -> impl Into<AnyElement<'a>> {
57 let Some(task) = props.task else {
58 return element!(Fragment).into_any();
59 };
60
61 element! {
62 View(flex_direction: FlexDirection::Row, gap: 1) {
63 Text(content: checkbox_str(task), color: Color::DarkGrey)
64 TaskLine(
65 task: task,
66 show_today_markers: props.options.show_today_markers,
67 show_staged_today_marker: props.options.show_staged_today_marker,
68 show_tags: true,
69 show_project: props.options.show_project,
70 show_area: props.options.show_area,
71 )
72 }
73 }
74 .into_any()
75}
76
77#[derive(Default, Props)]
78struct TaskDetailProps<'a> {
79 pub task: Option<&'a Task>,
80 pub id_prefix_len: usize,
81}
82
83#[component]
84fn TaskDetails<'a>(props: &TaskDetailProps<'a>) -> impl Into<AnyElement<'a>> {
85 let Some(task) = props.task else {
86 return element!(Fragment).into_any();
87 };
88
89 let show_ids = props.id_prefix_len > 0;
90 let note_text = task.notes.as_deref().unwrap_or("").trim();
91
92 let checklist_items = if task.checklist_items.is_empty() {
93 None
94 } else {
95 Some(task.checklist_items.as_slice())
96 };
97
98 if note_text.is_empty() && checklist_items.is_none() {
99 return element!(Fragment).into_any();
100 }
101
102 let note_text = if note_text.is_empty() {
103 element!(Fragment).into_any()
104 } else {
105 element!(Text(content: note_text, color: Color::DarkGrey)).into_any()
106 };
107
108 element! {
109 DetailsContainer {
110 View(flex_direction: FlexDirection::Column, gap: 1) {
111 #(note_text)
112 CheckList(items: checklist_items, show_ids, shift_left: true)
113 }
114 }
115
116 }
117 .into_any()
118}
119
120fn checkbox_str(task: &Task) -> &'static str {
121 if task.is_completed() {
122 ICONS.task_done
123 } else if task.is_canceled() {
124 ICONS.task_canceled
125 } else if task.in_someday() {
126 ICONS.task_someday
127 } else {
128 ICONS.task_open
129 }
130}