jobber 1.1.2-alpha

Minimalistic console work time tracker
use crate::{
    commands::{Config, is_available, not_available},
    database::Result,
    entities::{Client, Limit, format_client_id, format_duration, format_opt},
    jobber::{JobberGet, JobberIter, JobberWork},
    views::ApplyTableStyle,
};
use tabled::{
    Table, Tabled,
    settings::{Alignment, object::Columns},
};

#[derive(Tabled)]
pub(crate) struct ClientView {
    #[tabled(skip)]
    pub(crate) order: String,
    #[tabled(rename = "ID")]
    pub(crate) id: String,
    #[tabled(rename = "Name")]
    pub(crate) name: String,
    #[tabled(rename = "Street")]
    pub(crate) street: String,
    #[tabled(rename = "ZIP")]
    pub(crate) zip: String,
    #[tabled(rename = "City")]
    pub(crate) city: String,
    #[tabled(rename = "Country")]
    pub(crate) country: String,
    #[tabled(rename = "VAT")]
    pub(crate) vat: String,
    #[tabled(rename = "Rate")]
    pub(crate) rate: String,
    #[tabled(rename = "Interval")]
    pub(crate) interval: String,
    #[tabled(rename = "Billing")]
    pub(crate) billing: String,
    #[tabled(rename = "Work")]
    pub(crate) work: String,
}

impl ClientView {
    pub(crate) fn new(
        id: &str,
        config: &Config,
        database: &(impl JobberGet + JobberIter + JobberWork),
        ansi: bool,
    ) -> Result<Self> {
        Self::from(id, database.get_client(id)?, config, database, ansi)
    }
    pub(crate) fn from(
        id: &str,
        client: &Client,
        config: &Config,
        database: &(impl JobberGet + JobberIter + JobberWork),
        ansi: bool,
    ) -> Result<Self> {
        let scheme = &client.scheme;
        Ok(Self {
            order: id.to_string(),
            id: format_client_id(id, database, config, ansi)?,
            name: client.address.name.clone(),
            street: client.address.street.clone(),
            zip: client.address.zip.clone(),
            city: client.address.city.clone(),
            country: client
                .address
                .country
                .clone()
                .unwrap_or(not_available(ansi)),
            vat: client.vat.clone().unwrap_or(not_available(ansi)),
            rate: format_opt(&scheme.rate, ansi),
            billing: format_opt(&scheme.billing_period, ansi),
            interval: scheme.format_duration(
                scheme.min_work_interval.duration(),
                Limit::None,
                ansi,
            ),
            work: format_duration(database.duration_by_client(id), Limit::None, ansi),
        })
    }
}

impl std::fmt::Display for ClientView {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "         Client ID: {}", self.id)?;
        writeln!(f, "              Name: {}", self.name)?;
        writeln!(f, "              Rate: {}", self.rate)?;
        writeln!(f, "Min. Work Interval: {}", self.interval)?;
        writeln!(f, "    Billing Period: {}", self.billing)?;
        if is_available(&self.work) {
            write!(f, "              Work: {}", self.work)?;
        }
        Ok(())
    }
}

pub(crate) fn create_client_table(
    rows: impl IntoIterator<Item = ClientView>,
    config: &Config,
) -> Table {
    let mut table = Table::new(rows);
    table.modify(Columns::first(), Alignment::right());
    table.modify(Columns::one(2), Alignment::right());
    table.modify(Columns::one(3), Alignment::right());
    table.modify(Columns::one(4), Alignment::center());
    table.modify(Columns::one(5), Alignment::right());
    table.style(&config.table_style);
    table
}