Skip to main content

things3_cloud/commands/
area.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4use clap::Args;
5use iocraft::prelude::*;
6
7use crate::{
8    app::Cli,
9    commands::{Command, detailed_json_conflict, write_json},
10    common::ICONS,
11    ui::{
12        render_element_to_string,
13        views::{area::AreaView, json::common::build_tasks_json},
14    },
15    wire::task::TaskStatus,
16};
17
18#[derive(Debug, Args)]
19#[command(about = "Show projects and tasks in an area")]
20pub struct AreaArgs {
21    /// Area UUID (or unique UUID prefix)
22    pub area_id: String,
23    /// Show notes beneath each task/project
24    #[arg(long, short = 'd')]
25    pub detailed: bool,
26    /// Include completed and canceled items
27    #[arg(long, short = 'a')]
28    pub all: bool,
29}
30
31impl Command for AreaArgs {
32    fn run_with_ctx(
33        &self,
34        cli: &Cli,
35        out: &mut dyn std::io::Write,
36        ctx: &mut dyn crate::cmd_ctx::CmdCtx,
37    ) -> Result<()> {
38        let store = Arc::new(cli.load_store()?);
39        let today = ctx.today();
40        let (area_opt, err, ambiguous) = store.resolve_area_identifier(&self.area_id);
41        let Some(area) = area_opt else {
42            eprintln!("{err}");
43            for match_area in ambiguous {
44                eprintln!(
45                    "  {} {}  ({})",
46                    ICONS.area, match_area.title, match_area.uuid
47                );
48            }
49            return Ok(());
50        };
51
52        let status_filter = if self.all {
53            None
54        } else {
55            Some(TaskStatus::Incomplete)
56        };
57        let mut projects = store
58            .projects(status_filter)
59            .into_iter()
60            .filter(|p| p.area.as_ref() == Some(&area.uuid))
61            .collect::<Vec<_>>();
62        projects.sort_by_key(|p| p.index);
63
64        let mut loose_tasks = store
65            .tasks(status_filter, Some(false), None)
66            .into_iter()
67            .filter(|t| {
68                t.area.as_ref() == Some(&area.uuid)
69                    && !t.is_project()
70                    && store.effective_project_uuid(t).is_none()
71            })
72            .collect::<Vec<_>>();
73        loose_tasks.sort_by_key(|t| t.index);
74
75        let json = cli.json;
76        if json {
77            if detailed_json_conflict(json, self.detailed) {
78                return Ok(());
79            }
80
81            let mut items = projects;
82            items.extend(loose_tasks);
83            items.sort_by(|a, b| {
84                let a_proj = if a.is_project() { 0 } else { 1 };
85                let b_proj = if b.is_project() { 0 } else { 1 };
86                (a_proj, a.index, &a.uuid).cmp(&(b_proj, b.index, &b.uuid))
87            });
88            write_json(out, &build_tasks_json(&items, &store, &today))?;
89            return Ok(());
90        }
91
92        let mut ui = element! {
93            ContextProvider(value: Context::owned(store.clone())) {
94                ContextProvider(value: Context::owned(today)) {
95                    AreaView(
96                        area: &area,
97                        tasks: loose_tasks.iter().collect::<Vec<_>>(),
98                        projects: projects.iter().collect::<Vec<_>>(),
99                        detailed: self.detailed,
100                    )
101                }
102            }
103        };
104        let rendered = render_element_to_string(&mut ui, cli.no_color);
105        writeln!(out, "{}", rendered)?;
106
107        Ok(())
108    }
109}