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 #[arg(long, default_value = "0")]
71 page: u32,
72 },
73}
74
75const VIEW_FIELDS: &[&str] = &["id", "name", "type"];
76
77pub async fn execute(command: ViewCommands, cli: &Cli) -> Result<(), CliError> {
78 let token = resolve_token(cli)?;
79 let client = ClickUpClient::new(&token, cli.timeout)?;
80 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
81
82 match command {
83 ViewCommands::List {
84 workspace_level: workspace,
85 space,
86 folder,
87 list,
88 } => {
89 let url = if workspace {
90 let ws_id = resolve_workspace(cli)?;
91 format!("/v2/team/{}/view", ws_id)
92 } else if let Some(id) = space {
93 format!("/v2/space/{}/view", id)
94 } else if let Some(id) = folder {
95 format!("/v2/folder/{}/view", id)
96 } else if let Some(id) = list {
97 format!("/v2/list/{}/view", id)
98 } else {
99 return Err(CliError::ClientError {
100 message: "Specify a scope: --workspace, --space ID, --folder ID, or --list ID"
101 .into(),
102 status: 0,
103 });
104 };
105 let resp = client.get(&url).await?;
106 let views = resp
107 .get("views")
108 .and_then(|v| v.as_array())
109 .cloned()
110 .unwrap_or_default();
111 output.print_items(&views, VIEW_FIELDS, "id");
112 Ok(())
113 }
114 ViewCommands::Get { id } => {
115 let resp = client.get(&format!("/v2/view/{}", id)).await?;
116 let view = resp.get("view").cloned().unwrap_or(resp);
117 output.print_single(&view, VIEW_FIELDS, "id");
118 Ok(())
119 }
120 ViewCommands::Create {
121 name,
122 view_type,
123 workspace_level: workspace,
124 space,
125 folder,
126 list,
127 } => {
128 let url = if workspace {
129 let ws_id = resolve_workspace(cli)?;
130 format!("/v2/team/{}/view", ws_id)
131 } else if let Some(id) = space {
132 format!("/v2/space/{}/view", id)
133 } else if let Some(id) = folder {
134 format!("/v2/folder/{}/view", id)
135 } else if let Some(id) = list {
136 format!("/v2/list/{}/view", id)
137 } else {
138 return Err(CliError::ClientError {
139 message: "Specify a scope: --workspace, --space ID, --folder ID, or --list ID"
140 .into(),
141 status: 0,
142 });
143 };
144 let body = default_view_body(&name, &view_type);
145 let resp = client.post(&url, &body).await?;
146 let view = resp.get("view").cloned().unwrap_or(resp);
147 output.print_single(&view, VIEW_FIELDS, "id");
148 Ok(())
149 }
150 ViewCommands::Update { id, name } => {
151 let mut body = serde_json::Map::new();
152 if let Some(n) = name {
153 body.insert("name".into(), serde_json::Value::String(n));
154 }
155 let resp = client
156 .put(
157 &format!("/v2/view/{}", id),
158 &serde_json::Value::Object(body),
159 )
160 .await?;
161 let view = resp.get("view").cloned().unwrap_or(resp);
162 output.print_single(&view, VIEW_FIELDS, "id");
163 Ok(())
164 }
165 ViewCommands::Delete { id } => {
166 client.delete(&format!("/v2/view/{}", id)).await?;
167 output.print_message(&format!("View {} deleted", id));
168 Ok(())
169 }
170 ViewCommands::Tasks { id, page } => {
171 let resp = client
172 .get(&format!("/v2/view/{}/task?page={}", id, page))
173 .await?;
174 let tasks = resp
175 .get("tasks")
176 .and_then(|t| t.as_array())
177 .cloned()
178 .unwrap_or_default();
179 output.print_items(&tasks, &["id", "name", "status", "assignees"], "id");
180 Ok(())
181 }
182 }
183}
184
185pub(crate) fn default_view_body(name: &str, view_type: &str) -> serde_json::Value {
193 serde_json::json!({
194 "name": name,
195 "type": view_type,
196 "grouping": {
197 "field": null,
198 "dir": 1,
199 "collapsed": [],
200 "ignore": false
201 },
202 "divide": {
203 "field": null,
204 "dir": null,
205 "collapsed": []
206 },
207 "sorting": {"fields": []},
208 "filters": {
209 "op": "AND",
210 "fields": [],
211 "search": "",
212 "show_closed": false
213 },
214 "columns": {"fields": []},
215 "team_sidebar": {
216 "assignees": [],
217 "assigned_comments": false,
218 "unassigned_tasks": false
219 },
220 "settings": {
221 "show_task_locations": false,
222 "show_subtasks": 3,
223 "show_subtask_parent_names": false,
224 "show_closed_subtasks": false,
225 "show_assignees": true,
226 "show_images": true,
227 "collapse_empty_columns": null,
228 "me_comments": true,
229 "me_subtasks": true,
230 "me_checklists": true
231 }
232 })
233}