things3_cloud/ui/views/
projects.rs1use crate::common::ICONS;
2use crate::ids::ThingsId;
3use crate::store::Task;
4use crate::ui::components::empty_text::EmptyText;
5use crate::ui::components::id::Id;
6use crate::ui::components::tasks::{TaskList, TaskOptions};
7use iocraft::prelude::*;
8
9#[derive(Clone)]
10pub struct ProjectsAreaGroup {
11 pub area_uuid: ThingsId,
12 pub area_title: String,
13 pub projects: Vec<Task>,
14}
15
16#[derive(Default, Props)]
17pub struct ProjectsViewProps {
18 pub projects_count: usize,
19 pub no_area_projects: Vec<Task>,
20 pub area_groups: Vec<ProjectsAreaGroup>,
21 pub detailed: bool,
22 pub id_prefix_len: usize,
23}
24
25#[component]
26pub fn ProjectsView<'a>(props: &'a ProjectsViewProps) -> impl Into<AnyElement<'a>> {
27 if props.projects_count == 0 {
28 return element! { EmptyText(content: "No active projects.") }.into_any();
29 }
30
31 let options = TaskOptions {
32 detailed: props.detailed,
33 show_project: false,
34 show_area: false,
35 show_today_markers: true,
36 show_staged_today_marker: false,
37 };
38
39 let free_projects = if !props.no_area_projects.is_empty() {
40 element! {
41 View(flex_direction: FlexDirection::Column, padding_left: 2) {
42 TaskList(
43 items: props.no_area_projects.iter().collect::<Vec<_>>(),
44 id_prefix_len: props.id_prefix_len,
45 options,
46 )
47 }
48 }
49 .into_any()
50 } else {
51 element!(Fragment).into_any()
52 };
53
54 let project_areas = props.area_groups.iter().map(|group| {
55 element! {
56 ProjectsAreaSection(group, id_prefix_len: props.id_prefix_len, options)
57 }
58 });
59
60 element! {
61 View(flex_direction: FlexDirection::Column) {
62 Text(
63 content: format!("● Projects ({})", props.projects_count),
64 color: Color::Green,
65 weight: Weight::Bold,
66 wrap: TextWrap::NoWrap,
67 )
68 Text(content: "", wrap: TextWrap::NoWrap)
69 #(free_projects)
70 #(project_areas)
71 }
72 }
73 .into_any()
74}
75
76#[derive(Default, Props)]
77struct ProjectsAreaSectionProps<'a> {
78 pub group: Option<&'a ProjectsAreaGroup>,
79 pub id_prefix_len: usize,
80 pub options: TaskOptions,
81}
82
83#[component]
84fn ProjectsAreaSection<'a>(props: &ProjectsAreaSectionProps<'a>) -> impl Into<AnyElement<'a>> {
85 let Some(group) = props.group else {
86 return element!(Fragment).into_any();
87 };
88
89 element! {
90 View(flex_direction: FlexDirection::Column, padding_left: 2) {
91 Text(content: "", wrap: TextWrap::NoWrap)
92 View(flex_direction: FlexDirection::Row, gap: 1) {
93 Id(id: &group.area_uuid, length: props.id_prefix_len)
94 Text(content: ICONS.area, color: Color::DarkGrey)
95 Text(content: &group.area_title, wrap: TextWrap::NoWrap, weight: Weight::Bold)
96 }
97 View(flex_direction: FlexDirection::Column, padding_left: 2) {
98 TaskList(
99 items: group.projects.iter().collect::<Vec<_>>(),
100 id_prefix_len: props.id_prefix_len,
101 options: props.options,
102 )
103 }
104 }
105 }
106 .into_any()
107}