use crate::db::config::Config;
use crate::db::models::{NewTodoItem, TodoItem, UIItem, UIList};
use anyhow::Result;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{
Block, BorderType, Borders, HighlightSpacing, List, ListItem, Padding, StatefulWidget, Widget,
};
use sqlx::SqlitePool;
use std::str::FromStr;
pub struct ItemsComponent;
impl ItemsComponent {
fn style_item(ui_item: &UIItem) -> Span<'_> {
let name = ui_item.item.name.clone();
if ui_item.item.is_done {
Span::styled(name, Style::default().add_modifier(Modifier::CROSSED_OUT))
} else {
Span::from(name)
}
}
pub fn select_next_item(ui_list: &mut UIList) {
ui_list.item_state.select_next();
}
pub fn select_previous_item(ui_list: &mut UIList) {
ui_list.item_state.select_previous();
}
pub fn remove_item_selection(ui_list: &mut UIList) {
ui_list.item_state.select(None);
}
pub fn select_first_item(ui_list: &mut UIList) {
if ui_list.item_state.selected().is_none() {
ui_list.item_state.select_first();
}
}
pub async fn toggle_item_done(ui_list: &mut UIList, pool: &SqlitePool) -> Result<()> {
if let Some(j) = ui_list.item_state.selected() {
ui_list.items[j].item.toggle_done(pool).await?;
}
Ok(())
}
pub async fn create_item(ui_list: &mut UIList, name: String, pool: &SqlitePool) -> Result<()> {
let new_item = NewTodoItem {
name,
list_id: ui_list.list.id,
priority: None,
due_date: None,
};
TodoItem::create(pool, new_item).await?;
ui_list.update_items(pool).await?;
Ok(())
}
pub async fn update_item(ui_list: &mut UIList, name: String, pool: &SqlitePool) -> Result<()> {
if let Some(j) = ui_list.item_state.selected() {
let mut item = ui_list.items[j].item.clone();
item.update_name(pool, name).await?;
ui_list.update_items(pool).await?;
}
Ok(())
}
pub async fn delete_selected_item(ui_list: &mut UIList, pool: &SqlitePool) -> Result<()> {
if let Some(j) = ui_list.item_state.selected() {
let item = ui_list.items[j].item.clone();
item.delete(pool).await?;
ui_list.update_items(pool).await?;
if ui_list.items.is_empty() {
ui_list.item_state.select(None);
} else if j >= ui_list.items.len() {
ui_list.item_state.select(Some(ui_list.items.len() - 1));
}
}
Ok(())
}
pub async fn move_selected_item_up(ui_list: &mut UIList, pool: &SqlitePool) -> Result<()> {
if let Some(j) = ui_list.item_state.selected() {
let mut item = ui_list.items[j].item.clone();
item.move_up(pool).await?;
ui_list.update_items(pool).await?;
if j > 0 {
ui_list.item_state.select(Some(j - 1));
}
}
Ok(())
}
pub async fn move_selected_item_down(ui_list: &mut UIList, pool: &SqlitePool) -> Result<()> {
if let Some(j) = ui_list.item_state.selected() {
let mut item = ui_list.items[j].item.clone();
item.move_down(pool).await?;
ui_list.update_items(pool).await?;
if j + 1 < ui_list.items.len() {
ui_list.item_state.select(Some(j + 1));
}
}
Ok(())
}
pub fn render(
selected_list: Option<&mut UIList>,
area: Rect,
buf: &mut Buffer,
config: Config,
) {
let fg = config.foreground();
let hl = config.highlight();
let bg = config.background();
let list_command_hints = Line::from(vec![
Span::raw(" "),
Span::styled(" ↓↑ ", Style::default()),
Span::styled("[a]", Style::default().fg(Color::from_str(hl).unwrap())),
Span::styled("dd", Style::default().fg(Color::from_str(fg).unwrap())),
Span::styled(" [d]", Style::default().fg(Color::from_str(hl).unwrap())),
Span::styled("el", Style::default().fg(Color::from_str(fg).unwrap())),
Span::styled(" [m]", Style::default().fg(Color::from_str(hl).unwrap())),
Span::styled("odify ", Style::default().fg(Color::from_str(fg).unwrap())),
Span::raw(" "),
])
.left_aligned();
let quit_hint = Line::from(vec![
Span::raw(" "),
Span::styled("[q]", Style::default().fg(Color::from_str(hl).unwrap())),
Span::styled("uit ", Style::default().fg(Color::from_str(fg).unwrap())),
Span::raw(" "),
])
.right_aligned();
let block = Block::default()
.padding(Padding::new(2, 2, 1, 1))
.title_top(Line::raw(" I T E M S ").left_aligned())
.title_bottom(list_command_hints)
.title_bottom(quit_hint)
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_type(BorderType::Rounded);
if let Some(ui_list) = selected_list {
let items: Vec<ListItem> = ui_list
.items
.iter()
.map(|ui_item| ListItem::from(Self::style_item(ui_item)))
.collect();
let list: List = List::new(items)
.block(block)
.highlight_symbol(" ▸ ")
.highlight_style(
Style::default()
.bg(Color::from_str(fg).unwrap())
.fg(Color::from_str(bg).unwrap()),
)
.highlight_spacing(HighlightSpacing::Always);
StatefulWidget::render(list, area, buf, &mut ui_list.item_state);
} else {
block.render(area, buf);
}
}
}