Skip to main content

things3_cloud/ui/views/
area.rs

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