things3_cloud/commands/
project.rs1use std::{collections::BTreeMap, sync::Arc};
2
3use anyhow::Result;
4use clap::Args;
5use iocraft::prelude::*;
6
7use crate::{
8 app::Cli,
9 commands::Command,
10 ui::{
11 render_element_to_string,
12 views::project::{ProjectHeadingGroup, ProjectView},
13 },
14};
15
16#[derive(Debug, Args)]
17#[command(about = "Show all tasks in a project")]
18pub struct ProjectArgs {
19 pub project_id: String,
21 #[arg(long)]
23 pub detailed: bool,
24}
25
26impl Command for ProjectArgs {
27 fn run_with_ctx(
28 &self,
29 cli: &Cli,
30 out: &mut dyn std::io::Write,
31 ctx: &mut dyn crate::cmd_ctx::CmdCtx,
32 ) -> Result<()> {
33 let store = Arc::new(cli.load_store()?);
34 let today = ctx.today();
35 let (task_opt, err, ambiguous) = store.resolve_mark_identifier(&self.project_id);
36 let Some(project) = task_opt else {
37 eprintln!("{err}");
38 for match_task in ambiguous {
39 eprintln!(" {}", match_task.title);
40 }
41 return Ok(());
42 };
43
44 if !project.is_project() {
45 eprintln!("Not a project: {}", project.title);
46 return Ok(());
47 }
48
49 let children = store
50 .tasks(None, Some(false), None)
51 .into_iter()
52 .filter(|t| store.effective_project_uuid(t).as_ref() == Some(&project.uuid))
53 .collect::<Vec<_>>();
54
55 let headings = store
56 .tasks_by_uuid
57 .values()
58 .filter(|t| t.is_heading() && !t.trashed && t.project.as_ref() == Some(&project.uuid))
59 .cloned()
60 .map(|h| (h.uuid.clone(), h))
61 .collect::<BTreeMap<_, _>>();
62
63 let mut ungrouped = Vec::new();
64 let mut by_heading: BTreeMap<_, Vec<_>> = BTreeMap::new();
65 for t in children.clone() {
66 if let Some(heading_uuid) = &t.action_group
67 && headings.contains_key(heading_uuid)
68 {
69 by_heading.entry(heading_uuid.clone()).or_default().push(t);
70 continue;
71 }
72 ungrouped.push(t);
73 }
74
75 let mut sorted_heading_uuids = by_heading.keys().cloned().collect::<Vec<_>>();
76 sorted_heading_uuids.sort_by_key(|u| headings.get(u).map(|h| h.index).unwrap_or(0));
77 ungrouped.sort_by_key(|t| t.index);
78 for items in by_heading.values_mut() {
79 items.sort_by_key(|t| t.index);
80 }
81
82 let heading_groups = sorted_heading_uuids
83 .iter()
84 .filter_map(|heading_uuid| {
85 let heading = headings.get(heading_uuid)?;
86 let tasks = by_heading.get(heading_uuid)?;
87 Some(ProjectHeadingGroup {
88 title: heading.title.clone(),
89 items: tasks.iter().collect::<Vec<_>>(),
90 })
91 })
92 .collect::<Vec<_>>();
93
94 let mut ui = element! {
95 ContextProvider(value: Context::owned(store.clone())) {
96 ContextProvider(value: Context::owned(today)) {
97 ProjectView(
98 project: &project,
99 ungrouped: ungrouped.iter().collect::<Vec<_>>(),
100 heading_groups,
101 detailed: self.detailed,
102 no_color: cli.no_color,
103 )
104 }
105 }
106 };
107 let rendered = render_element_to_string(&mut ui, cli.no_color);
108 writeln!(out, "{}", rendered)?;
109
110 Ok(())
111 }
112}