use comfy_table::{presets::UTF8_FULL, Table, ContentArrangement, Cell, Color};
use crate::types::{
Device, DeviceBrand, DeviceComparison, DeviceRecommendOutput,
};
pub fn render_device_list(devices: &[&Device], brands: &[DeviceBrand]) {
if devices.is_empty() {
eprintln!("No devices found.");
return;
}
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_header(vec!["ID", "Name", "Brand", "Category", "Price", "Rx"]);
for d in devices {
let brand_name = brands
.iter()
.find(|b| b.id == d.brand_id)
.map(|b| b.name.as_str())
.unwrap_or(&d.brand_id);
let price = d
.price_usd
.map(|p| format!("${p}"))
.unwrap_or_else(|| d.price_range.clone());
let rx = if d.prescription_required { "Yes" } else { "No" };
table.add_row(vec![&d.id, &d.name, brand_name, &d.category, &price, rx]);
}
println!("{table}");
}
pub fn render_device_detail(device: &Device, brand_name: &str) {
println!();
println!(" {} ({})", device.name, device.id);
println!(" Brand: {brand_name}");
println!(" Category: {}", device.category);
println!(
" Price: {}",
device
.price_usd
.map(|p| format!("${p} ({})", device.price_range))
.unwrap_or_else(|| device.price_range.clone())
);
println!(
" Prescription: {}",
if device.prescription_required {
"Required"
} else {
"No"
}
);
println!(
" Connectivity: {}",
device.connectivity.join(", ")
);
println!(" Platforms: {}", device.platforms.join(", "));
println!(" Description: {}", device.description);
if !device.tracks.is_empty() {
println!();
println!(" Tracked Metrics:");
for t in &device.tracks {
let note = if t.notes.is_empty() {
String::new()
} else {
format!(" — {}", t.notes)
};
println!(" - {} [{}]{note}", t.metric, t.accuracy);
}
}
if !device.use_cases.is_empty() {
println!();
println!(" Use Cases: {}", device.use_cases.join(", "));
}
if !device.integrations.is_empty() {
println!(
" Integrations: {}",
device.integrations.join(", ")
);
}
if !device.notes.is_empty() {
println!(" Notes: {}", device.notes);
}
println!();
}
pub fn render_brands(brands: &[DeviceBrand]) {
if brands.is_empty() {
eprintln!("No brands found.");
return;
}
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_header(vec!["ID", "Name", "Country", "Website", "Verified"]);
for b in brands {
let verified = if b.verified { "Yes" } else { "No" };
table.add_row(vec![&b.id, &b.name, &b.country, &b.website, verified]);
}
println!("{table}");
}
pub fn render_recommendations(output: &DeviceRecommendOutput) {
if output.recommendations.is_empty() {
eprintln!("No device recommendations for this assessment.");
return;
}
println!();
println!(
" Device Recommendations ({} found)",
output.total_recommendations
);
println!();
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_header(vec!["#", "Priority", "Device", "Category", "Price", "Rationale"]);
for (i, rec) in output.recommendations.iter().enumerate() {
let priority_cell = match rec.priority.as_str() {
"red_flag_monitor" => Cell::new(&rec.priority).fg(Color::Red),
"pattern_monitor" => Cell::new(&rec.priority).fg(Color::Yellow),
_ => Cell::new(&rec.priority).fg(Color::Cyan),
};
let price = rec
.device
.price_usd
.map(|p| format!("${p}"))
.unwrap_or_else(|| rec.device.price_range.clone());
table.add_row(vec![
Cell::new(i + 1),
priority_cell,
Cell::new(&rec.device.name),
Cell::new(&rec.device.category),
Cell::new(&price),
Cell::new(&rec.rationale),
]);
}
println!("{table}");
}
pub fn render_comparison(comparison: &DeviceComparison) {
if comparison.devices.is_empty() {
eprintln!("No devices to compare.");
return;
}
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic);
let mut header = vec!["Attribute".to_string()];
for d in &comparison.devices {
header.push(d.name.clone());
}
table.set_header(header);
for row in &comparison.comparison_matrix {
let mut cells: Vec<String> = vec![row.attribute.clone()];
cells.extend(row.values.clone());
table.add_row(cells);
}
println!("{table}");
}