1use ratatui::widgets::ListState;
2
3#[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)]
4pub enum FilterOperator {
5 Eq,
6 NotEq,
7 Gt,
8 Lt,
9 GtEq,
10 LtEq,
11 Contains,
12 NotContains,
13}
14
15impl FilterOperator {
16 pub fn as_str(&self) -> &'static str {
17 match self {
18 FilterOperator::Eq => "=",
19 FilterOperator::NotEq => "!=",
20 FilterOperator::Gt => ">",
21 FilterOperator::Lt => "<",
22 FilterOperator::GtEq => ">=",
23 FilterOperator::LtEq => "<=",
24 FilterOperator::Contains => "contains",
25 FilterOperator::NotContains => "!contains",
26 }
27 }
28
29 pub fn iterator() -> impl Iterator<Item = FilterOperator> {
30 [
31 FilterOperator::Eq,
32 FilterOperator::NotEq,
33 FilterOperator::Gt,
34 FilterOperator::Lt,
35 FilterOperator::GtEq,
36 FilterOperator::LtEq,
37 FilterOperator::Contains,
38 FilterOperator::NotContains,
39 ]
40 .iter()
41 .copied()
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)]
46pub enum LogicalOperator {
47 And,
48 Or,
49}
50
51impl LogicalOperator {
52 pub fn as_str(&self) -> &'static str {
53 match self {
54 LogicalOperator::And => "AND",
55 LogicalOperator::Or => "OR",
56 }
57 }
58
59 pub fn iterator() -> impl Iterator<Item = LogicalOperator> {
60 [LogicalOperator::And, LogicalOperator::Or].iter().copied()
61 }
62}
63
64#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
65pub struct FilterStatement {
66 pub column: String,
67 pub operator: FilterOperator,
68 pub value: String,
69 pub logical_op: LogicalOperator,
70}
71
72#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
73pub enum FilterFocus {
74 #[default]
75 Column,
76 Operator,
77 Value,
78 Logical,
79 Add,
80 Statements,
81 Confirm,
82 Clear,
83}
84
85#[derive(Default)]
86pub struct FilterModal {
87 pub active: bool,
88 pub statements: Vec<FilterStatement>,
89 pub available_columns: Vec<String>,
90
91 pub new_column_idx: usize,
92 pub new_operator_idx: usize,
93 pub new_value: String,
94 pub new_logical_idx: usize,
95
96 pub focus: FilterFocus,
97 pub list_state: ListState,
98}
99
100impl FilterModal {
101 pub fn new() -> Self {
102 Self::default()
103 }
104
105 pub fn add_statement(&mut self) {
106 if self.available_columns.is_empty() {
107 return;
108 }
109 let op = FilterOperator::iterator()
110 .nth(self.new_operator_idx)
111 .unwrap();
112 let log = LogicalOperator::iterator()
113 .nth(self.new_logical_idx)
114 .unwrap();
115 let col = self.available_columns[self.new_column_idx].clone();
116
117 self.statements.push(FilterStatement {
118 column: col,
119 operator: op,
120 value: self.new_value.clone(),
121 logical_op: log,
122 });
123
124 self.new_value.clear();
125 self.focus = FilterFocus::Column;
126 }
127
128 pub fn next_body_focus(&mut self) -> bool {
131 match self.focus {
132 FilterFocus::Statements => return true,
133 FilterFocus::Column => self.focus = FilterFocus::Operator,
134 FilterFocus::Operator => self.focus = FilterFocus::Value,
135 FilterFocus::Value => self.focus = FilterFocus::Logical,
136 FilterFocus::Logical => self.focus = FilterFocus::Add,
137 FilterFocus::Add => self.focus = FilterFocus::Statements,
138 FilterFocus::Confirm | FilterFocus::Clear => {}
139 }
140 false
141 }
142
143 pub fn prev_body_focus(&mut self) -> bool {
145 match self.focus {
146 FilterFocus::Column => return true,
147 FilterFocus::Operator => self.focus = FilterFocus::Column,
148 FilterFocus::Value => self.focus = FilterFocus::Operator,
149 FilterFocus::Logical => self.focus = FilterFocus::Value,
150 FilterFocus::Add => self.focus = FilterFocus::Logical,
151 FilterFocus::Statements => self.focus = FilterFocus::Add,
152 FilterFocus::Confirm | FilterFocus::Clear => {}
153 }
154 false
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_filter_modal_new() {
164 let modal = FilterModal::new();
165 assert!(!modal.active);
166 assert!(modal.statements.is_empty());
167 assert!(modal.available_columns.is_empty());
168 assert_eq!(modal.new_column_idx, 0);
169 assert_eq!(modal.new_operator_idx, 0);
170 assert_eq!(modal.new_value, "");
171 assert_eq!(modal.new_logical_idx, 0);
172 assert_eq!(modal.focus, FilterFocus::Column);
173 }
174
175 #[test]
176 fn test_filter_modal_add_statement() {
177 let mut modal = FilterModal::new();
178 modal.available_columns = vec!["a".to_string(), "b".to_string()];
179 modal.new_column_idx = 1;
180 modal.new_operator_idx = 2; modal.new_value = "10".to_string();
182 modal.new_logical_idx = 1; modal.add_statement();
184
185 assert_eq!(modal.statements.len(), 1);
186 let statement = &modal.statements[0];
187 assert_eq!(statement.column, "b");
188 assert_eq!(statement.operator, FilterOperator::Gt);
189 assert_eq!(statement.value, "10");
190 assert_eq!(statement.logical_op, LogicalOperator::Or);
191
192 assert_eq!(modal.new_value, "");
193 assert_eq!(modal.focus, FilterFocus::Column);
194 }
195
196 #[test]
197 fn test_add_statement_no_columns() {
198 let mut modal = FilterModal::new();
199 modal.new_value = "test".to_string();
200 modal.add_statement();
201 assert!(modal.statements.is_empty());
202 }
203}