use std::sync::Arc; use iocraft::prelude::*; use crate::{ common::{one_line, shown_title}, ids::ThingsId, store::{Task, ThingsStore}, ui::components::{ deadline_badge::DeadlineBadge, details_container::DetailsContainer, header::Header, progress_badge::ProgressBadge, tags_badge::TagsBadge, tasks::{TaskList, TaskOptions}, }, }; pub struct ProjectHeadingGroup<'a> { pub uuid: ThingsId, pub title: String, pub items: Vec<&'a Task>, } #[derive(Default, Props)] pub struct ProjectViewProps<'a> { pub project: Option<&'a Task>, pub ungrouped: Vec<&'a Task>, pub heading_groups: Vec>, pub detailed: bool, } #[component] pub fn ProjectView<'a>(hooks: Hooks, props: &'a ProjectViewProps<'a>) -> impl Into> { let store = hooks.use_context::>().clone(); let Some(project) = props.project else { return element! { Text(content: "") }.into_any(); }; let mut all_uuids = props .ungrouped .iter() .map(|t| t.uuid.clone()) .collect::>(); // a heading carries its id as a row does // reorder takes it for group in &props.heading_groups { all_uuids.push(group.uuid.clone()); all_uuids.extend(group.items.iter().map(|t| t.uuid.clone())); } let id_prefix_len = store.unique_prefix_length(&all_uuids); let options = TaskOptions { detailed: props.detailed, show_project: false, show_area: false, show_today_markers: true, show_staged_today_marker: false, }; let note_lines = project .notes .as_deref() .unwrap_or("") .lines() .map(|line| { element! { Text(content: one_line(line), wrap: TextWrap::NoWrap, color: Color::DarkGrey) } .into_any() }) .collect::>(); element! { View(flex_direction: FlexDirection::Column) { View(flex_direction: FlexDirection::Row, gap: 1) { ProgressBadge( project: project, title: Some(shown_title(&project.title)), show_count: true, color: Color::Magenta, weight: Weight::Bold, ) DeadlineBadge(deadline: project.deadline) TagsBadge(tags: project.tags.clone()) } #(if !note_lines.is_empty() { Some(element! { View(flex_direction: FlexDirection::Column, padding_left: 2) { DetailsContainer { #(note_lines) } } }) } else { None }) #(if props.ungrouped.is_empty() && props.heading_groups.is_empty() { Some(element! { Text(content: " No tasks.", wrap: TextWrap::NoWrap, color: Color::DarkGrey) }) } else { None }) #(if !props.ungrouped.is_empty() { Some(element! { View(flex_direction: FlexDirection::Column) { Text(content: "", wrap: TextWrap::NoWrap) View(flex_direction: FlexDirection::Column, padding_left: 2) { TaskList(items: props.ungrouped.clone(), id_prefix_len, options) } } }) } else { None }) #(props.heading_groups.iter().map(|group| element! { View(flex_direction: FlexDirection::Column) { Text(content: "", wrap: TextWrap::NoWrap) View(padding_left: 2) { Header(uuid: &group.uuid, title: group.title.as_str(), id_prefix_len) } View(flex_direction: FlexDirection::Column, padding_left: 4) { TaskList(items: group.items.clone(), id_prefix_len, options) } } })) } } .into_any() }