//! Project detail screen — item list within a project. use ratatui::Frame; use ratatui::layout::{Constraint, Layout}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Paragraph, Row}; use crate::api::Project; use crate::format; use super::App; use super::widgets; pub fn render(frame: &mut Frame, app: &App, project: &Project) { let area = frame.area(); let title = Line::from(vec![ Span::styled(" Makenot.work ", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" ── "), Span::styled(&project.title, Style::default().add_modifier(Modifier::BOLD)), Span::raw(" "), ]); let block = Block::default() .title(title) .borders(Borders::ALL) .border_style(Style::default().fg(Color::Gray)); let inner = block.inner(area); frame.render_widget(block, area); let chunks = Layout::vertical([ Constraint::Length(1), // spacer Constraint::Length(1), // project info line Constraint::Length(1), // spacer Constraint::Length(1), // section header Constraint::Min(3), // item list Constraint::Length(1), // keybindings ]) .split(inner); // Project info let visibility = if project.is_public { "public" } else { "draft" }; let info = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled(&project.slug, Style::default().fg(Color::DarkGray)), Span::raw(" "), Span::raw(format::format_project_type(&project.project_type)), Span::raw(" "), Span::raw(visibility), Span::raw(" "), Span::raw(format::format_cents(project.revenue_cents)), Span::styled(" revenue", Style::default().fg(Color::DarkGray)), ])); frame.render_widget(info, chunks[1]); // Section header let header = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled("Items", Style::default().add_modifier(Modifier::BOLD)), if app.items.is_empty() { Span::raw("") } else { Span::raw(format!(" ({})", app.items.len())) }, ])); frame.render_widget(header, chunks[3]); // Item list if app.loading { let loading = Paragraph::new(" Loading..."); frame.render_widget(loading, chunks[4]); } else if app.items.is_empty() { let empty = Paragraph::new(" No items in this project."); frame.render_widget(empty, chunks[4]); } else { render_item_table(frame, app, chunks[4]); } // Keybindings let mut key_spans = vec![ Span::raw(" "), Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Nav "), ]; if !app.items.is_empty() { key_spans.extend([ Span::styled("[Enter]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Open "), Span::styled("[p]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Pub/Unpub "), Span::styled("[d]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Delete "), ]); } key_spans.extend([ Span::styled("[b]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Blog "), Span::styled("[r]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Refresh "), Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Back"), ]); let keys = Paragraph::new(Line::from(key_spans)) .style(Style::default().fg(Color::DarkGray)); frame.render_widget(keys, chunks[5]); } fn render_item_table(frame: &mut Frame, app: &App, area: ratatui::layout::Rect) { let rows: Vec = app .items .iter() .enumerate() .map(|(i, item)| { let visibility = if item.is_public { "public" } else { "draft" }; Row::new(vec![ format!(" {}", item.title), format::format_item_type(&item.item_type).to_string(), format::format_price(item.price_cents), visibility.to_string(), ]) .style(widgets::selected_style(i, Some(app.selected_index))) }) .collect(); let widths = [ Constraint::Min(20), Constraint::Length(12), Constraint::Length(10), Constraint::Length(8), ]; widgets::render_table(frame, area, &[" Title", "Type", "Price", "Status"], &widths, rows); }