1mod validate;
9
10use cli_core::forms::{EditableTransaction, LogicalSplitInput};
11
12use crate::tabs::reports::ReportKind;
13use crate::widgets::{
14 AmountWidget, DateWidget, EditMode, Editor, SelectOption, SelectWidget, SplitRowPrefill,
15 SplitsWidget, Widget, WidgetKind,
16};
17
18#[cfg(test)]
19mod tests;
20
21pub use validate::validate;
22
23#[derive(Debug)]
25pub struct Field {
26 pub label: &'static str,
27 pub widget: Widget,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum FormKind {
33 ConfigSet,
34 ReportParams {
35 kind: ReportKind,
36 },
37 CommodityCreate,
38 CommodityConvert,
39 AccountCreate,
40 TransactionCreate,
41 TransactionEdit,
43 AccountTag,
45 TransactionTag,
47}
48
49#[derive(Debug)]
51pub struct Form {
52 pub fields: Vec<Field>,
53 pub focus: usize,
55 pub kind: FormKind,
56 pub entity_id: Option<String>,
58}
59
60#[derive(Debug)]
62pub enum FormSubmit {
63 ConfigSet {
64 key: String,
65 value: String,
66 },
67 Report {
68 kind: ReportKind,
69 from: String,
70 to: String,
71 chart: String,
72 },
73 CommodityCreate {
74 symbol: String,
75 name: String,
76 },
77 CommodityConvert {
78 amount_num_denom: String,
80 amount_str: String,
82 from: String,
83 from_label: String,
84 to: String,
85 to_label: String,
86 },
87 AccountCreate {
88 name: String,
89 parent: Option<String>,
90 },
91 TransactionCreate {
92 note: String,
93 date: String,
94 splits: Vec<LogicalSplitInput>,
95 },
96 TransactionEdit {
97 id: String,
98 note: String,
99 date: String,
100 splits: Vec<LogicalSplitInput>,
101 },
102 AccountTag {
103 account_id: String,
104 name: String,
105 value: String,
106 },
107 TransactionTag {
108 transaction_id: String,
109 name: String,
110 value: String,
111 },
112}
113
114impl Form {
115 #[must_use]
117 pub fn config_set(name: Editor, value: Editor) -> Self {
118 Self {
119 fields: vec![
120 Field {
121 label: "name",
122 widget: Widget::Text(name),
123 },
124 Field {
125 label: "value",
126 widget: Widget::Text(value),
127 },
128 ],
129 focus: 0,
130 kind: FormKind::ConfigSet,
131 entity_id: None,
132 }
133 }
134
135 #[must_use]
137 pub fn report_params(from: Editor, to: Editor, chart: Editor, kind: ReportKind) -> Self {
138 Self {
139 fields: vec![
140 Field {
141 label: "from",
142 widget: Widget::Text(from),
143 },
144 Field {
145 label: "to",
146 widget: Widget::Text(to),
147 },
148 Field {
149 label: "chart",
150 widget: Widget::Text(chart),
151 },
152 ],
153 focus: 0,
154 kind: FormKind::ReportParams { kind },
155 entity_id: None,
156 }
157 }
158
159 #[must_use]
161 pub fn commodity_create(mode: EditMode) -> Self {
162 Self {
163 fields: vec![
164 Field {
165 label: "Symbol",
166 widget: Widget::Text(Editor::new(mode)),
167 },
168 Field {
169 label: "Name",
170 widget: Widget::Text(Editor::new(mode)),
171 },
172 ],
173 focus: 0,
174 kind: FormKind::CommodityCreate,
175 entity_id: None,
176 }
177 }
178
179 #[must_use]
184 pub fn commodity_convert(mode: EditMode) -> Self {
185 Self {
186 fields: vec![
187 Field {
188 label: "Amount",
189 widget: Widget::Amount(AmountWidget::new(mode)),
190 },
191 Field {
192 label: "From",
193 widget: Widget::Select(SelectWidget::new()),
194 },
195 Field {
196 label: "To",
197 widget: Widget::Select(SelectWidget::new()),
198 },
199 ],
200 focus: 0,
201 kind: FormKind::CommodityConvert,
202 entity_id: None,
203 }
204 }
205
206 #[must_use]
211 pub fn account_create(mode: EditMode, parent_options: Vec<SelectOption>) -> Self {
212 let mut sw = SelectWidget::new();
213 sw.set_options(parent_options);
214 Self {
215 fields: vec![
216 Field {
217 label: "Name",
218 widget: Widget::Text(Editor::new(mode)),
219 },
220 Field {
221 label: "Parent",
222 widget: Widget::Select(sw),
223 },
224 ],
225 focus: 0,
226 kind: FormKind::AccountCreate,
227 entity_id: None,
228 }
229 }
230
231 #[must_use]
233 pub fn transaction_create(mode: EditMode) -> Self {
234 Self {
235 fields: vec![
236 Field {
237 label: "Date",
238 widget: Widget::Date(DateWidget::new(mode)),
239 },
240 Field {
241 label: "Note",
242 widget: Widget::Text(Editor::new(mode)),
243 },
244 Field {
245 label: "Splits",
246 widget: Widget::Splits(SplitsWidget::new(mode)),
247 },
248 ],
249 focus: 0,
250 kind: FormKind::TransactionCreate,
251 entity_id: None,
252 }
253 }
254
255 #[must_use]
262 pub fn transaction_edit(mode: EditMode, et: &EditableTransaction) -> Self {
263 let prefill_rows: Vec<SplitRowPrefill<'_>> = et
264 .rows
265 .iter()
266 .map(|r| SplitRowPrefill {
267 from: &r.from_account,
268 to: &r.to_account,
269 from_commodity: &r.from_commodity,
270 to_commodity: &r.to_commodity,
271 value: &r.value,
272 to_amount: r.to_amount.as_deref(),
273 })
274 .collect();
275 let mut sw = SplitsWidget::new(mode);
276 sw.apply_prefill(&prefill_rows);
277 Self {
278 fields: vec![
279 Field {
280 label: "Date",
281 widget: Widget::Date(DateWidget::with_value(mode, et.date.as_str())),
282 },
283 Field {
284 label: "Note",
285 widget: Widget::Text(Editor::with_buffer(mode, et.note.as_str())),
286 },
287 Field {
288 label: "Splits",
289 widget: Widget::Splits(sw),
290 },
291 ],
292 focus: 0,
293 kind: FormKind::TransactionEdit,
294 entity_id: Some(et.id.clone()),
295 }
296 }
297
298 #[must_use]
302 pub fn account_tag(mode: EditMode, account_id: String) -> Self {
303 Self {
304 fields: vec![
305 Field {
306 label: "Tag name",
307 widget: Widget::Text(Editor::new(mode)),
308 },
309 Field {
310 label: "Value",
311 widget: Widget::Text(Editor::new(mode)),
312 },
313 ],
314 focus: 0,
315 kind: FormKind::AccountTag,
316 entity_id: Some(account_id),
317 }
318 }
319
320 #[must_use]
324 pub fn transaction_tag(mode: EditMode, transaction_id: String) -> Self {
325 Self {
326 fields: vec![
327 Field {
328 label: "Tag name",
329 widget: Widget::Text(Editor::new(mode)),
330 },
331 Field {
332 label: "Value",
333 widget: Widget::Text(Editor::new(mode)),
334 },
335 ],
336 focus: 0,
337 kind: FormKind::TransactionTag,
338 entity_id: Some(transaction_id),
339 }
340 }
341
342 pub fn cycle(&mut self, forward: bool) {
344 if self.fields.is_empty() {
345 return;
346 }
347 let len = self.fields.len();
348 self.focus = if forward {
349 (self.focus + 1) % len
350 } else {
351 (self.focus + len - 1) % len
352 };
353 }
354
355 pub fn focused_field_mut(&mut self) -> Option<&mut Field> {
357 self.fields.get_mut(self.focus)
358 }
359
360 pub fn focused_editor_mut(&mut self) -> Option<&mut Editor> {
365 self.focused_field_mut()
366 .and_then(|f| f.widget.as_text_mut())
367 }
368
369 #[must_use]
371 pub fn focused_widget_kind(&self) -> Option<WidgetKind> {
372 self.fields.get(self.focus).map(|f| f.widget.kind())
373 }
374
375 #[must_use]
378 pub fn focused_splits_sub_kind(&self) -> Option<WidgetKind> {
379 match self.fields.get(self.focus)?.widget {
380 Widget::Splits(ref sw) => Some(sw.focused_subwidget_kind()),
381 _ => None,
382 }
383 }
384
385 #[must_use]
389 pub fn focused_splits_boundary(&self) -> Option<(bool, bool)> {
390 match self.fields.get(self.focus)?.widget {
391 Widget::Splits(ref sw) => Some((sw.at_first_cell(), sw.at_last_cell())),
392 _ => None,
393 }
394 }
395
396 pub(super) fn field_buffer(&self, i: usize) -> &str {
398 self.fields.get(i).map_or("", |f| f.widget.value())
399 }
400}