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