feat: wizard UI for date config and formula steps
Add key handling for ConfigureDates (space toggle components) and DefineFormulas (n new, d delete, text input mode) wizard steps. Render date component toggles, formula list with input area, and sample formulas derived from detected measures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -1200,6 +1200,41 @@ impl App {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
WizardStep::ConfigureDates => match key.code {
|
||||||
|
KeyCode::Up | KeyCode::Char('k') => wizard.move_cursor(-1),
|
||||||
|
KeyCode::Down | KeyCode::Char('j') => wizard.move_cursor(1),
|
||||||
|
KeyCode::Char(' ') => wizard.toggle_date_component(),
|
||||||
|
KeyCode::Enter => wizard.advance(),
|
||||||
|
KeyCode::Esc => {
|
||||||
|
self.mode = AppMode::Normal;
|
||||||
|
self.wizard = None;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
WizardStep::DefineFormulas => {
|
||||||
|
if wizard.formula_editing {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Enter => wizard.confirm_formula(),
|
||||||
|
KeyCode::Esc => wizard.cancel_formula_edit(),
|
||||||
|
KeyCode::Backspace => wizard.pop_formula_char(),
|
||||||
|
KeyCode::Char(c) => wizard.push_formula_char(c),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('n') => wizard.start_formula_edit(),
|
||||||
|
KeyCode::Char('d') => wizard.delete_formula(),
|
||||||
|
KeyCode::Up | KeyCode::Char('k') => wizard.move_cursor(-1),
|
||||||
|
KeyCode::Down | KeyCode::Char('j') => wizard.move_cursor(1),
|
||||||
|
KeyCode::Enter => wizard.advance(),
|
||||||
|
KeyCode::Esc => {
|
||||||
|
self.mode = AppMode::Normal;
|
||||||
|
self.wizard = None;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
WizardStep::NameModel => match key.code {
|
WizardStep::NameModel => match key.code {
|
||||||
KeyCode::Char(c) => wizard.push_name_char(c),
|
KeyCode::Char(c) => wizard.push_name_char(c),
|
||||||
KeyCode::Backspace => wizard.pop_name_char(),
|
KeyCode::Backspace => wizard.pop_name_char(),
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use ratatui::{
|
|||||||
widgets::{Block, Borders, Clear, Widget},
|
widgets::{Block, Borders, Clear, Widget},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::import::analyzer::FieldKind;
|
use crate::import::analyzer::{DateComponent, FieldKind};
|
||||||
use crate::import::wizard::{ImportWizard, WizardStep};
|
use crate::import::wizard::{ImportWizard, WizardStep};
|
||||||
|
|
||||||
pub struct ImportWizardWidget<'a> {
|
pub struct ImportWizardWidget<'a> {
|
||||||
@ -29,10 +29,12 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
|||||||
Clear.render(popup_area, buf);
|
Clear.render(popup_area, buf);
|
||||||
|
|
||||||
let title = match self.wizard.step {
|
let title = match self.wizard.step {
|
||||||
WizardStep::Preview => " Import Wizard — Step 1: Preview ",
|
WizardStep::Preview => " Import Wizard — Preview ",
|
||||||
WizardStep::SelectArrayPath => " Import Wizard — Step 2: Select Array ",
|
WizardStep::SelectArrayPath => " Import Wizard — Select Array ",
|
||||||
WizardStep::ReviewProposals => " Import Wizard — Step 3: Review Fields ",
|
WizardStep::ReviewProposals => " Import Wizard — Review Fields ",
|
||||||
WizardStep::NameModel => " Import Wizard — Step 4: Name Model ",
|
WizardStep::ConfigureDates => " Import Wizard — Date Components ",
|
||||||
|
WizardStep::DefineFormulas => " Import Wizard — Formulas ",
|
||||||
|
WizardStep::NameModel => " Import Wizard — Name Model ",
|
||||||
WizardStep::Done => " Import Wizard — Done ",
|
WizardStep::Done => " Import Wizard — Done ",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,6 +160,152 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
|||||||
Style::default().fg(Color::DarkGray),
|
Style::default().fg(Color::DarkGray),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
WizardStep::ConfigureDates => {
|
||||||
|
buf.set_string(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
"Select date components to extract (Space toggle):",
|
||||||
|
Style::default().fg(Color::Yellow),
|
||||||
|
);
|
||||||
|
y += 1;
|
||||||
|
|
||||||
|
let tc_proposals = self.wizard.time_category_proposals();
|
||||||
|
let mut item_idx = 0;
|
||||||
|
for proposal in &tc_proposals {
|
||||||
|
if y >= inner.y + inner.height - 2 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let fmt_str = proposal.date_format.as_deref().unwrap_or("?");
|
||||||
|
let header = format!(" {} (format: {})", proposal.field, fmt_str);
|
||||||
|
buf.set_string(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
truncate(&header, w),
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Magenta)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
|
y += 1;
|
||||||
|
|
||||||
|
for component in &[
|
||||||
|
DateComponent::Year,
|
||||||
|
DateComponent::Month,
|
||||||
|
DateComponent::Quarter,
|
||||||
|
] {
|
||||||
|
if y >= inner.y + inner.height - 2 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let enabled = proposal.date_components.contains(component);
|
||||||
|
let check = if enabled { "[\u{2713}]" } else { "[ ]" };
|
||||||
|
let label = match component {
|
||||||
|
DateComponent::Year => "Year",
|
||||||
|
DateComponent::Month => "Month",
|
||||||
|
DateComponent::Quarter => "Quarter",
|
||||||
|
};
|
||||||
|
let row = format!(" {} {}", check, label);
|
||||||
|
let is_sel = item_idx == self.wizard.cursor;
|
||||||
|
let style = if is_sel {
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Black)
|
||||||
|
.bg(Color::Cyan)
|
||||||
|
.add_modifier(Modifier::BOLD)
|
||||||
|
} else if enabled {
|
||||||
|
Style::default().fg(Color::Green)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::DarkGray)
|
||||||
|
};
|
||||||
|
buf.set_string(x, y, truncate(&row, w), style);
|
||||||
|
y += 1;
|
||||||
|
item_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let hint_y = inner.y + inner.height - 1;
|
||||||
|
buf.set_string(
|
||||||
|
x,
|
||||||
|
hint_y,
|
||||||
|
"Space: toggle Enter: next Esc: cancel",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
WizardStep::DefineFormulas => {
|
||||||
|
buf.set_string(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
"Define formulas (optional):",
|
||||||
|
Style::default().fg(Color::Yellow),
|
||||||
|
);
|
||||||
|
y += 1;
|
||||||
|
|
||||||
|
// Show existing formulas
|
||||||
|
if self.wizard.pipeline.formulas.is_empty() && !self.wizard.formula_editing {
|
||||||
|
buf.set_string(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
" (no formulas yet)",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
);
|
||||||
|
y += 1;
|
||||||
|
}
|
||||||
|
for (i, formula) in self.wizard.pipeline.formulas.iter().enumerate() {
|
||||||
|
if y >= inner.y + inner.height - 5 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let is_sel = i == self.wizard.cursor && !self.wizard.formula_editing;
|
||||||
|
let style = if is_sel {
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Black)
|
||||||
|
.bg(Color::Cyan)
|
||||||
|
.add_modifier(Modifier::BOLD)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::Green)
|
||||||
|
};
|
||||||
|
buf.set_string(x, y, truncate(&format!(" {}", formula), w), style);
|
||||||
|
y += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formula input area
|
||||||
|
if self.wizard.formula_editing {
|
||||||
|
y += 1;
|
||||||
|
buf.set_string(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
"Formula (e.g., Profit = Revenue - Cost):",
|
||||||
|
Style::default().fg(Color::Yellow),
|
||||||
|
);
|
||||||
|
y += 1;
|
||||||
|
let input = format!("> {}\u{2588}", self.wizard.formula_buffer);
|
||||||
|
buf.set_string(x, y, truncate(&input, w), Style::default().fg(Color::Green));
|
||||||
|
y += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample formulas
|
||||||
|
let samples = self.wizard.sample_formulas();
|
||||||
|
if !samples.is_empty() {
|
||||||
|
y += 1;
|
||||||
|
buf.set_string(x, y, "Examples:", Style::default().fg(Color::DarkGray));
|
||||||
|
y += 1;
|
||||||
|
for sample in &samples {
|
||||||
|
if y >= inner.y + inner.height - 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf.set_string(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
truncate(&format!(" {}", sample), w),
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
);
|
||||||
|
y += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hint_y = inner.y + inner.height - 1;
|
||||||
|
let hint = if self.wizard.formula_editing {
|
||||||
|
"Enter: add Esc: cancel"
|
||||||
|
} else {
|
||||||
|
"n: new formula d: delete Enter: next Esc: cancel"
|
||||||
|
};
|
||||||
|
buf.set_string(x, hint_y, hint, Style::default().fg(Color::DarkGray));
|
||||||
|
}
|
||||||
WizardStep::NameModel => {
|
WizardStep::NameModel => {
|
||||||
buf.set_string(x, y, "Model name:", Style::default().fg(Color::Yellow));
|
buf.set_string(x, y, "Model name:", Style::default().fg(Color::Yellow));
|
||||||
y += 1;
|
y += 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user