use crate::components::schedule::{Record, ScheduleRow, ScheduleState};
use crate::state::app_state::HomeOrAway;
use crate::ui::styling::{border_style, dim_style, header_style, selected_style};
use tui::prelude::*;
use tui::widgets::{Block, BorderType, Borders, Cell, Padding, Row, Table};
const HEADER: &[&str; 8] = &["away", "", "", "home", "", "", "time", "status"];
pub struct ScheduleWidget {
pub tz_abbreviation: String,
}
impl ScheduleRow {
pub const ABBREVIATION_WIDTH: u16 = 70;
pub const ABBREVIATION_COL_WIDTH: u16 = 5;
pub const NORMAL_COL_WIDTH: u16 = 11;
fn format_record(record: Option<Record>) -> String {
record
.map(|r| r.to_display_string())
.unwrap_or(Record::default_display_string())
}
fn default_score(score: Option<u8>) -> String {
let s = score
.map(|s| s.to_string())
.unwrap_or_else(|| "-".to_string());
format!("{s:<3}")
}
fn get_styles(&self, team: HomeOrAway) -> (Style, Style) {
let winning_team = self.winning_team();
let lose_style = dim_style();
match winning_team {
Some(winner) if winner == team => (Style::default(), Style::default()),
None => (Style::default(), Style::default()),
_ => (lose_style, lose_style),
}
}
fn format(&self, width: u16) -> Vec<Span<'_>> {
let (away_team_style, away_score_style) = self.get_styles(HomeOrAway::Away);
let (home_team_style, home_score_style) = self.get_styles(HomeOrAway::Home);
let away_record = Self::format_record(self.away_record);
let home_record = Self::format_record(self.home_record);
let (away_team, home_team) = if width < Self::ABBREVIATION_WIDTH {
(self.away_team.abbreviation, self.home_team.abbreviation)
} else {
(self.away_team.team_name, self.home_team.team_name)
};
vec![
Span::styled(away_team, away_team_style),
Span::styled(away_record, away_team_style),
Span::styled(Self::default_score(self.away_score), away_score_style),
Span::styled(home_team, home_team_style),
Span::styled(home_record, home_team_style),
Span::styled(Self::default_score(self.home_score), home_score_style),
Span::raw(self.start_time.to_string()),
Span::raw(self.game_status.to_string()),
]
}
}
impl StatefulWidget for ScheduleWidget {
type State = ScheduleState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let header_cells = HEADER.iter().enumerate().map(|(i, h)| {
if i == 6 {
Cell::from(format!("{} [{}]", *h, self.tz_abbreviation))
} else {
Cell::from(*h)
}
});
let header = Row::new(header_cells).height(1).style(header_style());
let rows = state
.schedule
.iter()
.map(|r| Row::new(r.format(area.width)));
let name_constraint = if area.width < ScheduleRow::ABBREVIATION_WIDTH {
Constraint::Length(ScheduleRow::ABBREVIATION_COL_WIDTH)
} else {
let max_name_len = state
.schedule
.iter()
.map(|r| r.home_team.team_name.len().max(r.away_team.team_name.len()))
.max()
.unwrap_or(ScheduleRow::NORMAL_COL_WIDTH as usize);
Constraint::Length(max_name_len.max(ScheduleRow::NORMAL_COL_WIDTH as usize) as u16)
};
let widths = [
name_constraint, Constraint::Length(6), Constraint::Length(3), name_constraint, Constraint::Length(6), Constraint::Length(3), Constraint::Length(12), Constraint::Fill(1), ];
let t = Table::new(rows, widths)
.header(header)
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(border_style())
.padding(Padding::new(1, 1, 0, 0))
.title(Span::styled(
state.date_selector.format_date_border_title(),
selected_style(),
)),
)
.row_highlight_style(selected_style());
StatefulWidget::render(t, area, buf, &mut state.state);
}
}