Skip to main content

things3_cloud/ui/views/
project.rs

1use crate::store::{Task, ThingsStore};
2use crate::ui::components::deadline_badge::DeadlineBadge;
3use crate::ui::components::details_container::DetailsContainer;
4use crate::ui::components::progress_badge::ProgressBadge;
5use crate::ui::components::tags_badge::TagsBadge;
6use crate::ui::components::tasks::{TaskList, TaskOptions};
7use iocraft::prelude::*;
8use std::sync::Arc;
9
10#[derive(Clone)]
11pub struct ProjectHeadingGroup<'a> {
12    pub title: String,
13    pub items: Vec<&'a Task>,
14}
15
16#[derive(Default, Props)]
17pub struct ProjectViewProps<'a> {
18    pub project: Option<&'a Task>,
19    pub ungrouped: Vec<&'a Task>,
20    pub heading_groups: Vec<ProjectHeadingGroup<'a>>,
21    pub detailed: bool,
22    pub no_color: bool,
23}
24
25#[component]
26pub fn ProjectView<'a>(hooks: Hooks, props: &ProjectViewProps<'a>) -> impl Into<AnyElement<'a>> {
27    let store = hooks.use_context::<Arc<ThingsStore>>().clone();
28    let Some(project) = props.project else {
29        return element! { Text(content: "") }.into_any();
30    };
31    let _ = props.no_color;
32
33    let mut all_uuids = props
34        .ungrouped
35        .iter()
36        .map(|t| t.uuid.clone())
37        .collect::<Vec<_>>();
38    for group in &props.heading_groups {
39        all_uuids.extend(group.items.iter().map(|t| t.uuid.clone()));
40    }
41    let id_prefix_len = store.unique_prefix_length(&all_uuids);
42
43    let options = TaskOptions {
44        detailed: props.detailed,
45        show_project: false,
46        show_area: false,
47        show_today_markers: true,
48        show_staged_today_marker: false,
49    };
50
51    let note_lines = project
52        .notes
53        .as_deref()
54        .unwrap_or("")
55        .lines()
56        .map(|line| {
57            element! {
58                Text(content: line, wrap: TextWrap::NoWrap, color: Color::DarkGrey)
59            }
60            .into_any()
61        })
62        .collect::<Vec<_>>();
63
64    element! {
65        View(flex_direction: FlexDirection::Column) {
66            View(flex_direction: FlexDirection::Row, gap: 1) {
67                ProgressBadge(
68                    project: project,
69                    title: Some(project.title.clone()),
70                    show_count: true,
71                    color: Color::Magenta,
72                    weight: Weight::Bold,
73                )
74                DeadlineBadge(deadline: project.deadline)
75                TagsBadge(tags: project.tags.clone())
76            }
77
78            #(if !note_lines.is_empty() {
79                Some(element! {
80                    View(flex_direction: FlexDirection::Column, padding_left: 2) {
81                        DetailsContainer {
82                            #(note_lines)
83                        }
84                    }
85                })
86            } else { None })
87
88            #(if props.ungrouped.is_empty() && props.heading_groups.is_empty() {
89                Some(element! {
90                    Text(content: "  No tasks.", wrap: TextWrap::NoWrap, color: Color::DarkGrey)
91                })
92            } else { None })
93
94            #(if !props.ungrouped.is_empty() {
95                Some(element! {
96                    View(flex_direction: FlexDirection::Column) {
97                        Text(content: "", wrap: TextWrap::NoWrap)
98                        View(flex_direction: FlexDirection::Column, padding_left: 2) {
99                            TaskList(items: props.ungrouped.clone(), id_prefix_len, options)
100                        }
101                    }
102                })
103            } else { None })
104
105            #(props.heading_groups.iter().map(|group| element! {
106                View(flex_direction: FlexDirection::Column) {
107                    Text(content: "", wrap: TextWrap::NoWrap)
108                    Text(content: format!("  {}", group.title), wrap: TextWrap::NoWrap, weight: Weight::Bold)
109                    View(flex_direction: FlexDirection::Column, padding_left: 4) {
110                        TaskList(items: group.items.clone(), id_prefix_len, options)
111                    }
112                }
113            }))
114        }
115    }
116    .into_any()
117}