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 {
|
||||
KeyCode::Char(c) => wizard.push_name_char(c),
|
||||
KeyCode::Backspace => wizard.pop_name_char(),
|
||||
|
||||
@ -5,7 +5,7 @@ use ratatui::{
|
||||
widgets::{Block, Borders, Clear, Widget},
|
||||
};
|
||||
|
||||
use crate::import::analyzer::FieldKind;
|
||||
use crate::import::analyzer::{DateComponent, FieldKind};
|
||||
use crate::import::wizard::{ImportWizard, WizardStep};
|
||||
|
||||
pub struct ImportWizardWidget<'a> {
|
||||
@ -29,10 +29,12 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
Clear.render(popup_area, buf);
|
||||
|
||||
let title = match self.wizard.step {
|
||||
WizardStep::Preview => " Import Wizard — Step 1: Preview ",
|
||||
WizardStep::SelectArrayPath => " Import Wizard — Step 2: Select Array ",
|
||||
WizardStep::ReviewProposals => " Import Wizard — Step 3: Review Fields ",
|
||||
WizardStep::NameModel => " Import Wizard — Step 4: Name Model ",
|
||||
WizardStep::Preview => " Import Wizard — Preview ",
|
||||
WizardStep::SelectArrayPath => " Import Wizard — Select Array ",
|
||||
WizardStep::ReviewProposals => " Import Wizard — Review Fields ",
|
||||
WizardStep::ConfigureDates => " Import Wizard — Date Components ",
|
||||
WizardStep::DefineFormulas => " Import Wizard — Formulas ",
|
||||
WizardStep::NameModel => " Import Wizard — Name Model ",
|
||||
WizardStep::Done => " Import Wizard — Done ",
|
||||
};
|
||||
|
||||
@ -158,6 +160,152 @@ impl<'a> Widget for ImportWizardWidget<'a> {
|
||||
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 => {
|
||||
buf.set_string(x, y, "Model name:", Style::default().fg(Color::Yellow));
|
||||
y += 1;
|
||||
|
||||
Reference in New Issue
Block a user