1use crate::client::ClickUpClient;
2use crate::commands::auth::resolve_token;
3use crate::commands::workspace::resolve_workspace;
4use crate::error::CliError;
5use crate::output::OutputConfig;
6use crate::Cli;
7use clap::Subcommand;
8
9#[derive(Subcommand)]
10pub enum ViewCommands {
11 List {
13 #[arg(long = "workspace-level", conflicts_with_all = &["space", "folder", "list"])]
15 workspace_level: bool,
16 #[arg(long, conflicts_with_all = &["workspace", "folder", "list"])]
18 space: Option<String>,
19 #[arg(long, conflicts_with_all = &["workspace", "space", "list"])]
21 folder: Option<String>,
22 #[arg(long, conflicts_with_all = &["workspace", "space", "folder"])]
24 list: Option<String>,
25 },
26 Get {
28 id: String,
30 },
31 Create {
33 #[arg(long)]
35 name: String,
36 #[arg(long, name = "type")]
38 view_type: String,
39 #[arg(long = "workspace-level", conflicts_with_all = &["space", "folder", "list"])]
41 workspace_level: bool,
42 #[arg(long, conflicts_with_all = &["workspace", "folder", "list"])]
44 space: Option<String>,
45 #[arg(long, conflicts_with_all = &["workspace", "space", "list"])]
47 folder: Option<String>,
48 #[arg(long, conflicts_with_all = &["workspace", "space", "folder"])]
50 list: Option<String>,
51 },
52 Update {
54 id: String,
56 #[arg(long)]
58 name: Option<String>,
59 },
60 Delete {
62 id: String,
64 },
65 Tasks {
67 id: String,
69 },
70}
71
72const VIEW_FIELDS: &[&str] = &["id", "name", "type"];
73
74pub async fn execute(command: ViewCommands, cli: &Cli) -> Result<(), CliError> {
75 let token = resolve_token(cli)?;
76 let client = ClickUpClient::new(&token, cli.timeout)?;
77 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
78
79 match command {
80 ViewCommands::List {
81 workspace_level: workspace,
82 space,
83 folder,
84 list,
85 } => {
86 let url = if workspace {
87 let ws_id = resolve_workspace(cli)?;
88 format!("/v2/team/{}/view", ws_id)
89 } else if let Some(id) = space {
90 format!("/v2/space/{}/view", id)
91 } else if let Some(id) = folder {
92 format!("/v2/folder/{}/view", id)
93 } else if let Some(id) = list {
94 format!("/v2/list/{}/view", id)
95 } else {
96 return Err(CliError::ClientError {
97 message: "Specify a scope: --workspace, --space ID, --folder ID, or --list ID"
98 .into(),
99 status: 0,
100 });
101 };
102 let resp = client.get(&url).await?;
103 let views = resp
104 .get("views")
105 .and_then(|v| v.as_array())
106 .cloned()
107 .unwrap_or_default();
108 output.print_items(&views, VIEW_FIELDS, "id");
109 Ok(())
110 }
111 ViewCommands::Get { id } => {
112 let resp = client.get(&format!("/v2/view/{}", id)).await?;
113 let view = resp.get("view").cloned().unwrap_or(resp);
114 output.print_single(&view, VIEW_FIELDS, "id");
115 Ok(())
116 }
117 ViewCommands::Create {
118 name,
119 view_type,
120 workspace_level: workspace,
121 space,
122 folder,
123 list,
124 } => {
125 let url = if workspace {
126 let ws_id = resolve_workspace(cli)?;
127 format!("/v2/team/{}/view", ws_id)
128 } else if let Some(id) = space {
129 format!("/v2/space/{}/view", id)
130 } else if let Some(id) = folder {
131 format!("/v2/folder/{}/view", id)
132 } else if let Some(id) = list {
133 format!("/v2/list/{}/view", id)
134 } else {
135 return Err(CliError::ClientError {
136 message: "Specify a scope: --workspace, --space ID, --folder ID, or --list ID"
137 .into(),
138 status: 0,
139 });
140 };
141 let body = default_view_body(&name, &view_type);
142 let resp = client.post(&url, &body).await?;
143 let view = resp.get("view").cloned().unwrap_or(resp);
144 output.print_single(&view, VIEW_FIELDS, "id");
145 Ok(())
146 }
147 ViewCommands::Update { id, name } => {
148 let mut body = serde_json::Map::new();
149 if let Some(n) = name {
150 body.insert("name".into(), serde_json::Value::String(n));
151 }
152 let resp = client
153 .put(
154 &format!("/v2/view/{}", id),
155 &serde_json::Value::Object(body),
156 )
157 .await?;
158 let view = resp.get("view").cloned().unwrap_or(resp);
159 output.print_single(&view, VIEW_FIELDS, "id");
160 Ok(())
161 }
162 ViewCommands::Delete { id } => {
163 client.delete(&format!("/v2/view/{}", id)).await?;
164 output.print_message(&format!("View {} deleted", id));
165 Ok(())
166 }
167 ViewCommands::Tasks { id } => {
168 let tasks = crate::commands::pagination::walk_page(cli, &client, "tasks", |p| {
169 format!("/v2/view/{}/task?page={}", id, p)
170 })
171 .await?;
172 output.print_items(&tasks, &["id", "name", "status", "assignees"], "id");
173 Ok(())
174 }
175 }
176}
177
178pub(crate) fn default_view_body(name: &str, view_type: &str) -> serde_json::Value {
186 serde_json::json!({
187 "name": name,
188 "type": view_type,
189 "grouping": {
190 "field": null,
191 "dir": 1,
192 "collapsed": [],
193 "ignore": false
194 },
195 "divide": {
196 "field": null,
197 "dir": null,
198 "collapsed": []
199 },
200 "sorting": {"fields": []},
201 "filters": {
202 "op": "AND",
203 "fields": [],
204 "search": "",
205 "show_closed": false
206 },
207 "columns": {"fields": []},
208 "team_sidebar": {
209 "assignees": [],
210 "assigned_comments": false,
211 "unassigned_tasks": false
212 },
213 "settings": {
214 "show_task_locations": false,
215 "show_subtasks": 3,
216 "show_subtask_parent_names": false,
217 "show_closed_subtasks": false,
218 "show_assignees": true,
219 "show_images": true,
220 "collapse_empty_columns": null,
221 "me_comments": true,
222 "me_subtasks": true,
223 "me_checklists": true
224 }
225 })
226}