Skip to main content

things3_cloud/ui/views/
project.rs

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