Skip to main content

things3_cloud/ui/views/
area.rs

1use crate::common::ICONS;
2use crate::store::{Area, Task, ThingsStore};
3use crate::ui::components::tags_badge::TagsBadge;
4use crate::ui::components::tasks::{TaskList, TaskOptions};
5use iocraft::prelude::*;
6use std::sync::Arc;
7
8const LIST_INDENT: u32 = 2;
9
10#[derive(Default, Props)]
11pub struct AreaViewProps<'a> {
12    pub area: Option<&'a Area>,
13    pub tasks: Vec<&'a Task>,
14    pub projects: Vec<&'a Task>,
15    pub detailed: bool,
16}
17
18#[component]
19pub fn AreaView<'a>(hooks: Hooks, props: &AreaViewProps<'a>) -> impl Into<AnyElement<'a>> {
20    let store = hooks.use_context::<Arc<ThingsStore>>().clone();
21    let Some(area) = props.area else {
22        return element! { Text(content: "") }.into_any();
23    };
24
25    let project_count = props.projects.len();
26    let task_count = props.tasks.len();
27
28    let mut parts = Vec::new();
29    if project_count > 0 {
30        parts.push(format!(
31            "{} project{}",
32            project_count,
33            if project_count == 1 { "" } else { "s" }
34        ));
35    }
36    if task_count > 0 {
37        parts.push(format!(
38            "{} task{}",
39            task_count,
40            if task_count == 1 { "" } else { "s" }
41        ));
42    }
43    let count_str = if parts.is_empty() {
44        String::new()
45    } else {
46        format!("  ({})", parts.join(", "))
47    };
48
49    let mut item_uuids = props
50        .projects
51        .iter()
52        .map(|p| p.uuid.clone())
53        .collect::<Vec<_>>();
54    item_uuids.extend(props.tasks.iter().map(|t| t.uuid.clone()));
55    let id_prefix_len = store.unique_prefix_length(&item_uuids);
56
57    let task_options = TaskOptions {
58        detailed: props.detailed,
59        show_project: false,
60        show_area: false,
61        show_today_markers: true,
62        show_staged_today_marker: false,
63    };
64    let project_options = TaskOptions {
65        detailed: props.detailed,
66        show_project: false,
67        show_area: false,
68        show_today_markers: false,
69        show_staged_today_marker: false,
70    };
71
72    element! {
73        View(flex_direction: FlexDirection::Column) {
74            View(flex_direction: FlexDirection::Row, gap: 1) {
75                Text(
76                    content: format!("{} {}{}", ICONS.area, area.title, count_str),
77                    wrap: TextWrap::NoWrap,
78                    color: Color::Magenta,
79                    weight: Weight::Bold,
80                )
81                TagsBadge(tags: area.tags.clone())
82            }
83
84            #(if !props.tasks.is_empty() {
85                Some(element! {
86                    View(flex_direction: FlexDirection::Column) {
87                        Text(content: "", wrap: TextWrap::NoWrap)
88                        View(flex_direction: FlexDirection::Column, padding_left: LIST_INDENT) {
89                            TaskList(items: props.tasks.clone(), id_prefix_len, options: task_options)
90                        }
91                    }
92                })
93            } else { None })
94
95            #(if !props.projects.is_empty() {
96                Some(element! {
97                    View(flex_direction: FlexDirection::Column) {
98                        Text(content: "", wrap: TextWrap::NoWrap)
99                        View(flex_direction: FlexDirection::Column, padding_left: LIST_INDENT) {
100                            TaskList(items: props.projects.clone(), id_prefix_len, options: project_options)
101                        }
102                    }
103                })
104            } else { None })
105        }
106    }
107    .into_any()
108}