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