ratatui_form/block/
date_range.rs1use crossterm::event::KeyEvent;
4use ratatui::buffer::Buffer;
5use ratatui::layout::Rect;
6use serde_json::Value;
7
8use crate::block::Block;
9use crate::field::{Field, TextInput};
10use crate::style::FormStyle;
11use crate::validation::ValidationError;
12use crate::validation::rules::Pattern;
13
14pub struct DateRangeBlock {
16 prefix: String,
17 title: Option<String>,
18 required: bool,
19}
20
21impl DateRangeBlock {
22 pub fn new(prefix: impl Into<String>) -> Self {
24 Self {
25 prefix: prefix.into(),
26 title: None,
27 required: false,
28 }
29 }
30
31 pub fn title(mut self, title: impl Into<String>) -> Self {
33 self.title = Some(title.into());
34 self
35 }
36
37 pub fn required(mut self) -> Self {
39 self.required = true;
40 self
41 }
42
43 fn field_id(&self, name: &str) -> String {
44 format!("{}_{}", self.prefix, name)
45 }
46}
47
48impl Block for DateRangeBlock {
49 fn prefix(&self) -> &str {
50 &self.prefix
51 }
52
53 fn title(&self) -> Option<&str> {
54 self.title.as_deref()
55 }
56
57 fn fields(&self) -> Vec<Box<dyn Field>> {
58 let mut fields: Vec<Box<dyn Field>> = Vec::new();
59
60 let mut start_date = TextInput::new(self.field_id("start"), "Start Date")
62 .placeholder("YYYY-MM-DD")
63 .validator(Box::new(Pattern::date()));
64 if self.required {
65 start_date = start_date.required();
66 }
67 fields.push(Box::new(start_date));
68
69 let mut end_date = TextInput::new(self.field_id("end"), "End Date")
71 .placeholder("YYYY-MM-DD")
72 .validator(Box::new(Pattern::date()));
73 if self.required {
74 end_date = end_date.required();
75 }
76 fields.push(Box::new(end_date));
77
78 fields
79 }
80}
81
82#[allow(dead_code)]
85pub struct DateRangeField {
86 start_field: TextInput,
87 end_field: TextInput,
88 prefix: String,
89 current_focus: usize, }
91
92#[allow(dead_code)]
93impl DateRangeField {
94 pub fn new(prefix: impl Into<String>, required: bool) -> Self {
96 let prefix = prefix.into();
97
98 let mut start_field = TextInput::new(format!("{}_start", prefix), "Start Date")
99 .placeholder("YYYY-MM-DD")
100 .validator(Box::new(Pattern::date()));
101 if required {
102 start_field = start_field.required();
103 }
104
105 let mut end_field = TextInput::new(format!("{}_end", prefix), "End Date")
106 .placeholder("YYYY-MM-DD")
107 .validator(Box::new(Pattern::date()));
108 if required {
109 end_field = end_field.required();
110 }
111
112 Self {
113 start_field,
114 end_field,
115 prefix,
116 current_focus: 0,
117 }
118 }
119
120 fn validate_range(&self) -> Result<(), ValidationError> {
121 let start_value = match self.start_field.value() {
122 Value::String(s) if !s.is_empty() => s,
123 _ => return Ok(()),
124 };
125
126 let end_value = match self.end_field.value() {
127 Value::String(s) if !s.is_empty() => s,
128 _ => return Ok(()),
129 };
130
131 if end_value < start_value {
133 Err(ValidationError {
134 field_id: format!("{}_end", self.prefix),
135 message: "End date must be on or after start date".to_string(),
136 })
137 } else {
138 Ok(())
139 }
140 }
141}
142
143impl Field for DateRangeField {
144 fn id(&self) -> &str {
145 &self.prefix
146 }
147
148 fn label(&self) -> &str {
149 "Date Range"
150 }
151
152 fn render(&self, area: Rect, buf: &mut Buffer, focused: bool, style: &FormStyle) {
153 if area.height < 2 {
154 return;
155 }
156
157 let start_area = Rect {
158 x: area.x,
159 y: area.y,
160 width: area.width,
161 height: 1,
162 };
163
164 let end_area = Rect {
165 x: area.x,
166 y: area.y + 1,
167 width: area.width,
168 height: 1,
169 };
170
171 self.start_field.render(start_area, buf, focused && self.current_focus == 0, style);
172 self.end_field.render(end_area, buf, focused && self.current_focus == 1, style);
173 }
174
175 fn handle_input(&mut self, event: &KeyEvent) -> bool {
176 if self.current_focus == 0 {
177 self.start_field.handle_input(event)
178 } else {
179 self.end_field.handle_input(event)
180 }
181 }
182
183 fn value(&self) -> Value {
184 serde_json::json!({
185 "start": self.start_field.value(),
186 "end": self.end_field.value()
187 })
188 }
189
190 fn validate(&self) -> Result<(), Vec<ValidationError>> {
191 let mut errors = Vec::new();
192
193 if let Err(mut e) = self.start_field.validate() {
194 errors.append(&mut e);
195 }
196
197 if let Err(mut e) = self.end_field.validate() {
198 errors.append(&mut e);
199 }
200
201 if let Err(e) = self.validate_range() {
202 errors.push(e);
203 }
204
205 if errors.is_empty() {
206 Ok(())
207 } else {
208 Err(errors)
209 }
210 }
211
212 fn height(&self) -> u16 {
213 2
214 }
215}