"To try" rank

6a8dcc6 · Gabriel Morell · 2026-10-28 21:56

28 files +159 -40
Message
{commit_body(@commit)}

Files changed

modified README.md
+5 −3
@@ -3,7 +3,7 @@
3 3 Where are we going for lunch? Not there again.
4 4
5 5 People pin restaurants on an OpenStreetMap map and rank each one **Obsessed**
6 −(since a date), **Always**, **Positive**, **Neutral** or **Never**. When a group goes out, the app suggests
6 +(since a date), **Always**, **Positive**, **To try**, **Neutral** or **Never**. When a group goes out, the app suggests
7 7 somewhere from what everyone going likes and where the team hasn't been
8 8 lately, and the group logs where it actually went so the next pick knows.
9 9
@@ -122,8 +122,10 @@ may also need `proxy-read-timeout` raised past the 15 s keep-alive.
122 122 All in `src/suggest.rs`, pure functions with unit tests. For the people going:
123 123
124 124 - **Veto.** If anyone going ranked a place *Never*, it's out.
125 −- **Liked.** The group's average weight: Obsessed 6, Always 4, Positive 2,
126 − Neutral 1. Obsessed records when it started; picking it again keeps that
125 +- **Liked.** The group's average weight: Obsessed 6, Always 4, To try 3,
126 + Positive 2, Neutral 1. A "to try" counts as Neutral once that person has
127 + been on a logged lunch there (on or after the day they marked it), and the
128 + rank buttons ask how it was. Obsessed records when it started; picking it again keeps that
127 129 date, and it can be backdated from the rank widget.
128 130 Unranked counts as Neutral, so nobody has to rank everything.
129 131 - **Open.** If OSM says a place is closed at the meal time (noon by
modified assets/f/css/style.css
+1 −0
@@ -25,6 +25,7 @@
25 25 .rank-pick-obsessed:has(:checked) { @apply btn-warning; }
26 26 .rank-pick-always:has(:checked) { @apply btn-success; }
27 27 .rank-pick-positive:has(:checked) { @apply btn-accent; }
28 +.rank-pick-to_try:has(:checked) { @apply btn-info; }
28 29 .rank-pick-neutral:has(:checked) { @apply btn-neutral; }
29 30 .rank-pick-never:has(:checked) { @apply btn-error; }
30 31 /* daisyUI's colour classes only set --btn-color; .btn turns that into a
modified assets/f/js/map.js
+5 −3
@@ -22,11 +22,13 @@ const TILE_THEMES = {
22 22 sky: "blueprint", forest: "night", clays: "night",
23 23 };
24 24
25 −// Rank colours, as in the badges: flame / success / lime / neutral / error.
25 +// Rank colours, as in the badges: flame / success / lime / info / neutral /
26 +// error. Unranked is white, so blue can mean "to try".
26 27 const RANK_COLORS = {
27 − obsessed: "#ea580c", always: "#16a34a", positive: "#84cc16", neutral: "#a8a29e", never: "#dc2626",
28 + obsessed: "#ea580c", always: "#16a34a", positive: "#84cc16", to_try: "#3b82f6",
29 + neutral: "#a8a29e", never: "#dc2626",
28 30 };
29 −const UNRANKED = "#3b82f6";
31 +const UNRANKED = "#ffffff";
30 32 const CANDIDATE = "#f59e0b";
31 33
32 34 const maps = new Set();
modified src/entities/rankings.rs
+17 −2
@@ -13,6 +13,9 @@ pub enum RankKind {
13 13 Always,
14 14 #[sea_orm(string_value = "positive")]
15 15 Positive,
16 + /// Not been yet, want to go.
17 + #[sea_orm(string_value = "to_try")]
18 + ToTry,
16 19 #[sea_orm(string_value = "neutral")]
17 20 Neutral,
18 21 #[sea_orm(string_value = "never")]
@@ -21,14 +24,21 @@ pub enum RankKind {
21 24
22 25 impl RankKind {
23 26 /// Strongest first — the order the rank buttons appear in.
24 − pub const ALL: [RankKind; 5] =
25 − [RankKind::Obsessed, RankKind::Always, RankKind::Positive, RankKind::Neutral, RankKind::Never];
27 + pub const ALL: [RankKind; 6] = [
28 + RankKind::Obsessed,
29 + RankKind::Always,
30 + RankKind::Positive,
31 + RankKind::ToTry,
32 + RankKind::Neutral,
33 + RankKind::Never,
34 + ];
26 35
27 36 pub fn as_str(self) -> &'static str {
28 37 match self {
29 38 RankKind::Obsessed => "obsessed",
30 39 RankKind::Always => "always",
31 40 RankKind::Positive => "positive",
41 + RankKind::ToTry => "to_try",
32 42 RankKind::Neutral => "neutral",
33 43 RankKind::Never => "never",
34 44 }
@@ -48,6 +58,9 @@ pub enum Rank {
48 58 Obsessed { since: Date },
49 59 Always,
50 60 Positive,
61 + /// Not been yet, want to go. Once you have been (see `crate::suggest`),
62 + /// it counts as Neutral until you rank it properly.
63 + ToTry,
51 64 Neutral,
52 65 Never,
53 66 }
@@ -58,6 +71,7 @@ impl Rank {
58 71 Rank::Obsessed { .. } => RankKind::Obsessed,
59 72 Rank::Always => RankKind::Always,
60 73 Rank::Positive => RankKind::Positive,
74 + Rank::ToTry => RankKind::ToTry,
61 75 Rank::Neutral => RankKind::Neutral,
62 76 Rank::Never => RankKind::Never,
63 77 }
@@ -81,6 +95,7 @@ impl Rank {
81 95 RankKind::Obsessed => Rank::Obsessed { since: since.unwrap_or(fallback) },
82 96 RankKind::Always => Rank::Always,
83 97 RankKind::Positive => Rank::Positive,
98 + RankKind::ToTry => Rank::ToTry,
84 99 RankKind::Neutral => Rank::Neutral,
85 100 RankKind::Never => Rank::Never,
86 101 }
modified src/handlers/areas.rs
+7 −5
@@ -134,6 +134,8 @@ struct RestaurantRow {
134 134 restaurant: restaurants::Model,
135 135 my_rank: Option<Rank>,
136 136 my_since: Option<NaiveDate>,
137 + /// Your "to try" here has been tried; time to rank it properly.
138 + tried: bool,
137 139 cuisines: Vec<String>,
138 140 added_by: Option<String>,
139 141 visits: usize,
@@ -168,15 +170,14 @@ pub async fn show_area(
168 170 .map_err(internal("list restaurants failed"))?;
169 171 let ids: Vec<Uuid> = list.iter().map(|r| r.id).collect();
170 172
171 − let my_ranks: HashMap<Uuid, Rank> = rankings::Entity::find()
173 + let my_rows = rankings::Entity::find()
172 174 .filter(rankings::Column::UserId.eq(user.user.id))
173 175 .filter(rankings::Column::RestaurantId.is_in(ids.clone()))
174 176 .all(db)
175 177 .await
176 − .map_err(internal("load rankings failed"))?
177 − .into_iter()
178 − .map(|r| (r.restaurant_id, r.rank()))
179 − .collect();
178 + .map_err(internal("load rankings failed"))?;
179 + let tried = super::lunch::tried(db, &my_rows).await?;
180 + let my_ranks: HashMap<Uuid, Rank> = my_rows.into_iter().map(|r| (r.restaurant_id, r.rank())).collect();
180 181
181 182 let mut history: HashMap<Uuid, Vec<NaiveDate>> = HashMap::new();
182 183 for l in lunches::Entity::find()
@@ -195,6 +196,7 @@ pub async fn show_area(
195 196 RestaurantRow {
196 197 my_rank: my_ranks.get(&r.id).copied(),
197 198 my_since: my_ranks.get(&r.id).and_then(|k| k.since()),
199 + tried: tried.contains(&(user.user.id, r.id)),
198 200 cuisines: r.cuisine_tags(),
199 201 added_by: r.added_by.and_then(|u| names.get(&u)).map(|n| (*n).to_owned()),
200 202 visits: visits.map_or(0, Vec::len),
modified src/handlers/lunch.rs
+42 −5

Click to load diff…

modified src/handlers/restaurants.rs
+11 −6

Click to load diff…

modified src/lib.rs
+1 −1

Click to load diff…

modified src/suggest.rs
+15 −1

Click to load diff…

modified templates/_macros.html
+10 −6

Click to load diff…

modified templates/areas/show.html
+1 −1

Click to load diff…

modified templates/lunch/_plan.html
+1 −1

Click to load diff…

modified templates/restaurants/_rank.html
+6 −3

Click to load diff…

modified templates/restaurants/new.html
+2 −2

Click to load diff…

modified templates/restaurants/show.html
+1 −1

Click to load diff…

modified tests/templates.rs
+34 −0

Click to load diff…

Parents: 00e6765