1use super::api::{Cart, Menu, Merchant, Quote, Tracked, money};
5
6fn opt<T: core::fmt::Display>(label: &str, v: Option<T>) -> String {
7 v.map_or_else(String::new, |v| format!(" · {label}{v}"))
8}
9
10pub fn merchants(ms: &[Merchant]) -> String {
12 if ms.is_empty() {
13 return "No restaurants found.".into();
14 }
15 ms.iter()
16 .map(|m| {
17 let mut s = format!("- {} (`{}`)", m.name, m.id);
18 if !m.cuisine.is_empty() {
19 s.push_str(&format!(" · {}", m.cuisine));
20 }
21 s.push_str(&opt("ETA ", m.eta_min.map(|e| format!("{e} min"))));
22 s.push_str(&opt("", m.distance_km.map(|d| format!("{d:.1} km"))));
23 s.push_str(&opt("★", m.rating));
24 if !m.promo.is_empty() {
25 s.push_str(&format!(" · {}", m.promo));
26 }
27 if !m.open {
28 s.push_str(" · closed");
29 }
30 s
31 })
32 .collect::<Vec<_>>()
33 .join("\n")
34}
35
36pub fn menu(m: &Menu, query: Option<&str>) -> String {
38 let q = query.map(str::to_lowercase).filter(|q| !q.is_empty());
39 let mut out = format!("{} (`{}`)\n", m.merchant.name, m.merchant.id);
40 let mut n = 0;
41 for i in &m.items {
42 if let Some(q) = &q
43 && ![&i.name, &i.category, &i.description]
44 .iter()
45 .any(|f| f.to_lowercase().contains(q))
46 {
47 continue;
48 }
49 n += 1;
50 out.push_str(&format!(
51 "- {} (`{}`) {}",
52 i.name,
53 i.id,
54 money(i.price, &m.currency)
55 ));
56 if !i.category.is_empty() {
57 out.push_str(&format!(" · {}", i.category));
58 }
59 if !i.available {
60 out.push_str(" · unavailable");
61 }
62 out.push('\n');
63 }
64 if n == 0 {
65 out.push_str("No matching items.\n");
66 }
67 out.trim_end().to_owned()
68}
69
70pub fn cart(c: &Cart) -> String {
72 if c.lines.is_empty() {
73 return "The cart is empty.".into();
74 }
75 let mut out = format!("Cart at {} (`{}`)\n", c.merchant_name, c.merchant_id);
76 for l in &c.lines {
77 out.push_str(&format!(
78 "- {}× {} (`{}`) {}",
79 l.quantity,
80 l.name,
81 l.item_id,
82 money(l.amount, &c.currency)
83 ));
84 if !l.note.is_empty() {
85 out.push_str(&format!(" ({})", l.note));
86 }
87 out.push('\n');
88 }
89 out.push_str(&format!("Subtotal {}", money(c.subtotal, &c.currency)));
90 out
91}
92
93pub fn quote(q: &Quote) -> String {
96 let c = q.currency.as_str();
97 let mut out = format!("Quote `{}` at {}\n", q.quote_id, q.merchant_name);
98 for l in &q.lines {
99 out.push_str(&format!(
100 "- {}× {} {}\n",
101 l.quantity,
102 l.name,
103 money(l.amount, c)
104 ));
105 }
106 out.push_str(&format!("Subtotal {}\n", money(q.subtotal, c)));
107 for f in &q.fees {
108 out.push_str(&format!("{} {}\n", f.label, money(f.amount, c)));
109 }
110 if q.discount > 0 {
111 out.push_str(&format!(
112 "Discount ({}) -{}\n",
113 q.voucher,
114 money(q.discount, c)
115 ));
116 }
117 out.push_str(&format!("Total {}\n", money(q.total, c)));
118 if let Some(e) = q.eta_min {
119 out.push_str(&format!("ETA about {e} min\n"));
120 }
121 out.push_str(&format!(
122 "Pay with {} · deliver to {}",
123 q.payment_method, q.address_label
124 ));
125 out
126}
127
128pub fn tracked(ts: &[Tracked]) -> String {
130 if ts.is_empty() {
131 return "No active orders.".into();
132 }
133 ts.iter()
134 .map(|t| {
135 let mut s = format!("- `{}` {}: {}", t.order_id, t.merchant_name, t.status);
136 s.push_str(&opt("ETA ", t.eta_min.map(|e| format!("{e} min"))));
137 if !t.driver.is_empty() {
138 s.push_str(&format!(" · driver {}", t.driver));
139 }
140 s
141 })
142 .collect::<Vec<_>>()
143 .join("\n")
144}