diff --git a/src/client/colors.rs b/src/client/colors.rs new file mode 100644 index 0000000..ec40f30 --- /dev/null +++ b/src/client/colors.rs @@ -0,0 +1,20 @@ +// src/client/colors.rs +use ratatui::style::Color; + +// Pastel Gray Theme +pub struct PastelGrayTheme; + +impl PastelGrayTheme { + // Background colors + pub const BG: Color = Color::Rgb(245, 245, 245); // Light gray + pub const BG_DARK: Color = Color::Rgb(220, 220, 220); // Slightly darker gray + + // Text colors + pub const FG: Color = Color::Rgb(64, 64, 64); // Dark gray + pub const FG_LIGHT: Color = Color::Rgb(128, 128, 128); // Medium gray + + // Accent colors + pub const ACCENT: Color = Color::Rgb(173, 216, 230); // Pastel blue + pub const WARNING: Color = Color::Rgb(255, 182, 193); // Pastel pink + pub const HIGHLIGHT: Color = Color::Rgb(152, 251, 152); // Pastel green +} diff --git a/src/client/components/command_line.rs b/src/client/components/command_line.rs new file mode 100644 index 0000000..0accaea --- /dev/null +++ b/src/client/components/command_line.rs @@ -0,0 +1,23 @@ +// src/client/components/command_line.rs +use ratatui::{ + widgets::{Block, Paragraph}, + style::Style, + layout::Rect, + Frame, +}; +use crate::client::colors::PastelGrayTheme; + +pub fn render_command_line(f: &mut Frame, area: Rect, input: &str, active: bool) { + let prompt = if active { ":" } else { "Press ':' for commands" }; + let style = if active { + Style::default().fg(DoomColors::CYAN) + } else { + Style::default().fg(DoomColors::HL) + }; + + let paragraph = Paragraph::new(format!("{}{}", prompt, input)) + .block(Block::default().style(Style::default().bg(DoomColors::BG))) + .style(style); + + f.render_widget(paragraph, area); +} diff --git a/src/client/components/form.rs b/src/client/components/form.rs new file mode 100644 index 0000000..0f8f195 --- /dev/null +++ b/src/client/components/form.rs @@ -0,0 +1,57 @@ +// src/client/components/form.rs +use ratatui::{ + widgets::{Paragraph, Block, Borders}, + layout::{Layout, Constraint, Direction, Rect}, + style::Style, + text::Line, + Frame, +}; +use crate::client::colors::PastelGrayTheme; + +pub fn render_form( + f: &mut Frame, + area: Rect, + fields: &[&str], + current_field: &usize, + inputs: &[&String], +) { + let form_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![Constraint::Length(3); 8]) + .margin(1) + .split(area); + + let form_blocks = form_chunks.iter().enumerate().map(|(_i, chunk)| { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(*chunk); + + vec![chunks[0], chunks[1]] + }).flatten().collect::>(); + + for (i, field) in fields.iter().enumerate() { + let input = inputs[i].clone(); + let is_active = i == *current_field; + + let block = Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(if is_active { + PastelGrayTheme::ACCENT + } else { + PastelGrayTheme::FG_LIGHT + })) + .title(Line::from(field.to_string())) + .style(Style::default().bg(PastelGrayTheme::BG).fg(PastelGrayTheme::FG)); + + let paragraph = Paragraph::new(input.as_str()) + .block(block) + .style(if is_active { + Style::default().fg(PastelGrayTheme::HIGHLIGHT) + } else { + Style::default().fg(PastelGrayTheme::FG) + }); + + f.render_widget(paragraph, form_blocks[i]); + } +} diff --git a/src/client/components/mod.rs b/src/client/components/mod.rs new file mode 100644 index 0000000..d6b6735 --- /dev/null +++ b/src/client/components/mod.rs @@ -0,0 +1,4 @@ +// src/client/components/mod.rs +pub mod form; +pub mod preview_card; +pub mod command_line; diff --git a/src/client/components/preview_card.rs b/src/client/components/preview_card.rs new file mode 100644 index 0000000..a6709c6 --- /dev/null +++ b/src/client/components/preview_card.rs @@ -0,0 +1,31 @@ +// src/client/components/preview_card.rs +use ratatui::{ + widgets::{Block, List, ListItem}, + layout::Rect, + style::Style, + text::Text, + Frame, +}; +use crate::client::colors::PastelGrayTheme; + +pub fn render_preview_card(f: &mut Frame, area: Rect, fields: &[&String]) { + let card = Block::default() + .borders(ratatui::widgets::Borders::ALL) + .border_style(Style::default().fg(DoomColors::HL)) + .title(" Preview Card ") + .style(Style::default().bg(DoomColors::BG)); + + let _inner_area = card.inner(area); // Prefix with underscore to indicate intentional unused + + let items = vec![ + ListItem::new(Text::from(format!("Firma: {}", fields[0]))), + ListItem::new(Text::from(format!("Ulica: {}", fields[1]))), + // ... other fields ... + ]; + + let list = List::new(items) + .block(card) + .style(Style::default().bg(DoomColors::BG).fg(DoomColors::FG)); + + f.render_widget(list, area); +} diff --git a/src/client/mod.rs b/src/client/mod.rs index 07e74b6..a186a9b 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,177 +1,6 @@ // src/client/mod.rs -use ratatui::{ - backend::CrosstermBackend, - widgets::{Block, Borders, Paragraph}, - layout::{Layout, Constraint, Direction}, - Terminal, - style::{Style, Color}, - Frame, -}; -use crossterm::{ - event::{self, Event, KeyCode}, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; -use std::io; -use crate::proto::multieko2::{ - AdresarRequest, - adresar_client::AdresarClient, -}; +mod ui; +mod colors; +mod components; -pub async fn run_client() -> Result<(), Box> { - // Setup terminal - enable_raw_mode()?; - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen)?; - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - - let mut client = AdresarClient::connect("http://[::1]:50051").await?; - - let mut firma = String::new(); - let mut kz = String::new(); - let mut drc = String::new(); - let mut ulica = String::new(); - let mut psc = String::new(); - let mut mesto = String::new(); - let mut stat = String::new(); - let mut banka = String::new(); - let mut ucet = String::new(); - let mut skladm = String::new(); - let mut ico = String::new(); - let mut kontakt = String::new(); - let mut telefon = String::new(); - let mut skladu = String::new(); - let mut fax = String::new(); - - let mut current_field = 0; - let fields = vec!["Firma", "KZ", "DRC", "Ulica", "PSC", "Mesto", "Stat", "Banka", "Ucet", "Skladm", "ICO", "Kontakt", "Telefon", "Skladu", "Fax"]; - - loop { - terminal.draw(|f| { - ui(f, &fields, &mut current_field, &[ - &firma, &kz, &drc, &ulica, &psc, &mesto, &stat, &banka, &ucet, &skladm, &ico, &kontakt, &telefon, &skladu, &fax, - ]); - })?; - - if let Event::Key(key) = event::read()? { - match key.code { - KeyCode::Char(c) => { - match current_field { - 0 => firma.push(c), - 1 => kz.push(c), - 2 => drc.push(c), - 3 => ulica.push(c), - 4 => psc.push(c), - 5 => mesto.push(c), - 6 => stat.push(c), - 7 => banka.push(c), - 8 => ucet.push(c), - 9 => skladm.push(c), - 10 => ico.push(c), - 11 => kontakt.push(c), - 12 => telefon.push(c), - 13 => skladu.push(c), - 14 => fax.push(c), - _ => (), - } - } - KeyCode::Backspace => { - match current_field { - 0 => firma.pop(), - 1 => kz.pop(), - 2 => drc.pop(), - 3 => ulica.pop(), - 4 => psc.pop(), - 5 => mesto.pop(), - 6 => stat.pop(), - 7 => banka.pop(), - 8 => ucet.pop(), - 9 => skladm.pop(), - 10 => ico.pop(), - 11 => kontakt.pop(), - 12 => telefon.pop(), - 13 => skladu.pop(), - 14 => fax.pop(), - _ => None, - }; - } - KeyCode::Down => { - if current_field < fields.len() - 1 { - current_field += 1; - } - } - KeyCode::Up => { - if current_field > 0 { - current_field -= 1; - } - } - KeyCode::Enter => { - if current_field == fields.len() - 1 { - break; - } else { - current_field += 1; - } - } - _ => {} - } - } - } - - // Cleanup terminal - disable_raw_mode()?; - execute!(terminal.backend_mut(), LeaveAlternateScreen)?; - - let request = tonic::Request::new(AdresarRequest { - firma, - kz, - drc, - ulica, - psc, - mesto, - stat, - banka, - ucet, - skladm, - ico, - kontakt, - telefon, - skladu, - fax, - }); - - let response = client.create_adresar(request).await?; - println!("Adresar created: {:?}", response.into_inner()); - - Ok(()) -} - -fn ui( - f: &mut Frame, - fields: &[&str], - current_field: &mut usize, - inputs: &[&String], -) { - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints::<&[Constraint]>( - &fields - .iter() - .map(|_| Constraint::Length(3)) - .collect::>(), - ) - .split(f.area()); - - for (i, field) in fields.iter().enumerate() { - let input = inputs[i].clone(); - let paragraph = Paragraph::new(input) - .block(Block::default().borders(Borders::ALL).title(*field)) - .style(if i == *current_field { - Style::default().fg(Color::Yellow) - } else { - Style::default() - }); - - f.render_widget(paragraph, chunks[i]); - } -} +pub use ui::run_client; diff --git a/src/client/ui.rs b/src/client/ui.rs new file mode 100644 index 0000000..424779e --- /dev/null +++ b/src/client/ui.rs @@ -0,0 +1,207 @@ +// src/client/ui.rs +use crossterm::{ + event::{self, Event, KeyCode, KeyModifiers}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::{ + backend::CrosstermBackend, + layout::{Constraint, Direction, Layout}, + Terminal, +}; +use std::io; +use crate::proto::multieko2::{AdresarRequest, adresar_client::AdresarClient}; +use crate::client::colors::DoomColors; +use crate::client::components::{form::render_form, preview_card::render_preview_card, command_line::render_command_line}; + +pub async fn run_client() -> Result<(), Box> { + // Setup terminal + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let mut client = AdresarClient::connect("http://[::1]:50051").await?; + + // Initialize fields + let mut firma = String::new(); + let mut kz = String::new(); + let mut drc = String::new(); + let mut ulica = String::new(); + let mut psc = String::new(); + let mut mesto = String::new(); + let mut stat = String::new(); + let mut banka = String::new(); + let mut ucet = String::new(); + let mut skladm = String::new(); + let mut ico = String::new(); + let mut kontakt = String::new(); + let mut telefon = String::new(); + let mut skladu = String::new(); + let mut fax = String::new(); + + let mut current_field: usize = 0; + let fields = vec![ + "Firma", "KZ", "DRC", "Ulica", "PSC", "Mesto", "Stat", "Banka", + "Ucet", "Skladm", "ICO", "Kontakt", "Telefon", "Skladu", "Fax", + ]; + let mut command_mode = false; + let mut command_input = String::new(); + + loop { + terminal.draw(|f| { + let root = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(10), Constraint::Length(1)]) + .split(f.area()); + + // Main content area + let main_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) + .split(root[0]); + + // Left panel - Form + render_form( + f, + main_chunks[0], + &fields, + &mut current_field, + &[ + &firma, &kz, &drc, &ulica, &psc, &mesto, &stat, &banka, + &ucet, &skladm, &ico, &kontakt, &telefon, &skladu, &fax, + ], + ); + + // Right panel - Preview Card + render_preview_card( + f, + main_chunks[1], + &[ + &firma, &ulica, &mesto, &psc, &ico, &kontakt, &telefon, + ], + ); + + // Command line + render_command_line(f, root[1], &command_input, command_mode); + })?; + + if let Event::Key(key) = event::read()? { + if command_mode { + match key.code { + KeyCode::Enter => { + if command_input == "w" { + break; + } + command_mode = false; + command_input.clear(); + } + KeyCode::Char(c) => command_input.push(c), + KeyCode::Backspace => { + command_input.pop(); + } + KeyCode::Esc => { + command_mode = false; + command_input.clear(); + } + _ => {} + } + continue; + } + + match key.code { + KeyCode::Char(':') => { + command_mode = true; + command_input.clear(); + } + KeyCode::Tab => { + if key.modifiers.contains(KeyModifiers::SHIFT) { + current_field = current_field.saturating_sub(1); + } else { + current_field = (current_field + 1) % fields.len(); + } + } + KeyCode::BackTab => current_field = current_field.saturating_sub(1), + KeyCode::Down => current_field = (current_field + 1) % fields.len(), + KeyCode::Up => current_field = current_field.saturating_sub(1), + KeyCode::Char(c) => { + match current_field { + 0 => firma.push(c), + 1 => kz.push(c), + 2 => drc.push(c), + 3 => ulica.push(c), + 4 => psc.push(c), + 5 => mesto.push(c), + 6 => stat.push(c), + 7 => banka.push(c), + 8 => ucet.push(c), + 9 => skladm.push(c), + 10 => ico.push(c), + 11 => kontakt.push(c), + 12 => telefon.push(c), + 13 => skladu.push(c), + 14 => fax.push(c), + _ => (), + } + } + KeyCode::Backspace => { + match current_field { + 0 => firma.pop(), + 1 => kz.pop(), + 2 => drc.pop(), + 3 => ulica.pop(), + 4 => psc.pop(), + 5 => mesto.pop(), + 6 => stat.pop(), + 7 => banka.pop(), + 8 => ucet.pop(), + 9 => skladm.pop(), + 10 => ico.pop(), + 11 => kontakt.pop(), + 12 => telefon.pop(), + 13 => skladu.pop(), + 14 => fax.pop(), + _ => None, + }; + } + KeyCode::Enter => { + if current_field == fields.len() - 1 { + break; + } else { + current_field += 1; + } + } + _ => {} + } + } + } + + // Cleanup terminal + disable_raw_mode()?; + execute!(terminal.backend_mut(), LeaveAlternateScreen)?; + + // Create and send request + let request = tonic::Request::new(AdresarRequest { + firma, + kz, + drc, + ulica, + psc, + mesto, + stat, + banka, + ucet, + skladm, + ico, + kontakt, + telefon, + skladu, + fax, + }); + + let response = client.create_adresar(request).await?; + println!("Adresar created: {:?}", response.into_inner()); + + Ok(()) +}