Skip to main content

things3_cloud/ui/components/
project_item.rs

1use crate::store::Task;
2use crate::ui::components::details_container::DetailsContainer;
3use crate::ui::components::id::Id;
4use crate::ui::components::progress_badge::ProgressBadge;
5use crate::ui::components::task_line::TaskLine;
6use crate::ui::components::tasks::TaskOptions;
7use iocraft::prelude::*;
8
9#[derive(Default, Props)]
10pub struct ProjectItemProps<'a> {
11    pub project: Option<&'a Task>,
12    pub options: TaskOptions,
13    pub id_prefix_len: usize,
14}
15
16#[component]
17pub fn ProjectItem<'a>(props: &ProjectItemProps<'a>) -> impl Into<AnyElement<'a>> {
18    let Some(project) = props.project else {
19        return element!(Fragment).into_any();
20    };
21
22    let details = if props.options.detailed {
23        element!(ProjectDetails(project: project)).into_any()
24    } else {
25        element!(Fragment).into_any()
26    };
27
28    element! {
29        View(flex_direction: FlexDirection::Row, gap: 1) {
30            Id(id: &project.uuid, length: props.id_prefix_len)
31            View(flex_direction: FlexDirection::Column) {
32                ProjectText(project: project, options: props.options)
33                #(details)
34            }
35        }
36    }
37    .into_any()
38}
39
40#[derive(Default, Props)]
41struct ProjectTextProps<'a> {
42    pub project: Option<&'a Task>,
43    pub options: TaskOptions,
44}
45
46#[component]
47fn ProjectText<'a>(props: &ProjectTextProps<'a>) -> impl Into<AnyElement<'a>> {
48    let Some(project) = props.project else {
49        return element!(Fragment).into_any();
50    };
51
52    element! {
53        View(flex_direction: FlexDirection::Row, gap: 1) {
54            ProgressBadge(project)
55            TaskLine(
56                task: project,
57                show_today_markers: props.options.show_today_markers,
58                show_staged_today_marker: props.options.show_staged_today_marker,
59                show_tags: true,
60                show_project: false,
61                show_area: props.options.show_area,
62            )
63        }
64    }
65    .into_any()
66}
67
68#[derive(Default, Props)]
69struct ProjectDetailsProps<'a> {
70    pub project: Option<&'a Task>,
71}
72
73#[component]
74fn ProjectDetails<'a>(props: &ProjectDetailsProps<'a>) -> impl Into<AnyElement<'a>> {
75    let Some(project) = props.project else {
76        return element!(Fragment).into_any();
77    };
78
79    let note_text = project.notes.as_deref().unwrap_or("").trim();
80    if note_text.is_empty() {
81        return element!(Fragment).into_any();
82    }
83
84    element! {
85        DetailsContainer {
86            Text(content: note_text, wrap: TextWrap::NoWrap, color: Color::DarkGrey)
87        }
88    }
89    .into_any()
90}