style comma-separate progress deltas; harmonize the calendar widget

Join a progress delta with a comma ("+2 kg, +1 rep") instead of a middot, which read oddly. Restructure the calendar into one standard bordered widget: a header row with prev/next arrows flanking the month and year dropdowns, a Today link, then the weekday grid.

Commit
2fc3770a6811e5f06eaab26a35b3d47d5d453e02
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/presenter/pages_logbook.ml
index c3d6462e..11a24302 100644..100644
@@ -152,7 +152,7 @@
152 152 (if abs reps = 1 then "" else "s");
153 153 ]
154 154 in
155 Removed: if pieces = [] then "no change" else String.concat " · " pieces
155 Added: if pieces = [] then "no change" else String.concat ", " pieces
156 156
157 157 (* The colour class for a per-exercise verdict: progress oxblood, regress green,
158 158 stagnation brass. A first performance (no verdict) is neutral. *)
@@ -197,7 +197,7 @@
197 197 (if abs reps = 1 then "" else "s");
198 198 ]
199 199 in
200 Removed: if pieces = [] then "no change" else String.concat " · " pieces
200 Added: if pieces = [] then "no change" else String.concat ", " pieces
201 201 | _ -> "first performance"
202 202
203 203 let imported_effort_line (e : View_model.Imported_effort.t) =
@@ -304,23 +304,6 @@
304 304 @ if value = selected then [ Dream_html.attr "selected" ] else [])
305 305 [ txt "%s" label ]
306 306 in
307 Removed: let picker =
308 Removed: tag "form"
309 Removed: [ action Routes.logbook; class_ "calendar-picker" ]
310 Removed: [
311 Removed: tag "select"
312 Removed: [ name "month"; Dream_html.string_attr "aria-label" "Month" ]
313 Removed: (List.map
314 Removed: (fun (n, nm) -> option_ ~selected:cal.selected_month n nm)
315 Removed: cal.months);
316 Removed: tag "select"
317 Removed: [ name "year"; Dream_html.string_attr "aria-label" "Year" ]
318 Removed: (List.map
319 Removed: (fun y -> option_ ~selected:cal.selected_year y (string_of_int y))
320 Removed: cal.years);
321 Removed: tag "button" [ type_ "submit" ] [ txt "Show" ];
322 Removed: ]
323 Removed: in
324 307 let prev_month =
325 308 if cal.selected_month = 1 then (cal.selected_year - 1, 12)
326 309 else (cal.selected_year, cal.selected_month - 1)
@@ -329,38 +312,60 @@
329 312 if cal.selected_month = 12 then (cal.selected_year + 1, 1)
330 313 else (cal.selected_year, cal.selected_month + 1)
331 314 in
332 Removed: let nav_link ~label (y, m) rel =
315 Added: let arrow ~label (y, m) rel =
333 316 tag "a"
334 317 [
335 318 Dream_html.string_attr "href" "/logbook?year=%d&month=%d" y m;
336 Removed: class_ "calendar-nav-link";
319 Added: class_ "calendar-arrow";
337 320 Dream_html.attr "data-hito-app-link";
338 321 Dream_html.string_attr "aria-label" "%s" rel;
339 322 ]
340 323 [ txt "%s" label ]
341 324 in
342 Removed: let nav =
343 Removed: tag "div"
344 Removed: [ class_ "calendar-nav" ]
325 Added: (* The picker doubles as the title: month and year dropdowns between the prev
326 Added: and next arrows, submitting on Go. *)
327 Added: let picker =
328 Added: tag "form"
329 Added: [ action Routes.logbook; class_ "calendar-picker" ]
345 330 [
346 Removed: nav_link ~label:"‹" prev_month "Previous month";
347 Removed: tag "a"
348 Removed: [
349 Removed: href Routes.logbook;
350 Removed: class_ "calendar-nav-now";
351 Removed: Dream_html.attr "data-hito-app-link";
352 Removed: ]
353 Removed: [ txt "Now" ];
354 Removed: nav_link ~label:"›" next_month "Next month";
331 Added: tag "select"
332 Added: [ name "month"; Dream_html.string_attr "aria-label" "Month" ]
333 Added: (List.map
334 Added: (fun (n, nm) -> option_ ~selected:cal.selected_month n nm)
335 Added: cal.months);
336 Added: tag "select"
337 Added: [ name "year"; Dream_html.string_attr "aria-label" "Year" ]
338 Added: (List.map
339 Added: (fun y -> option_ ~selected:cal.selected_year y (string_of_int y))
340 Added: cal.years);
341 Added: tag "button"
342 Added: [ type_ "submit"; Dream_html.string_attr "aria-label" "Go" ]
343 Added: [ txt "Go" ];
355 344 ]
356 345 in
357 346 tag "div"
358 Removed: [ class_ "calendar-block" ]
347 Added: [ class_ "calendar" ]
359 348 [
360 Removed: nav;
361 Removed: picker;
349 Added: tag "div"
350 Added: [ class_ "calendar-header" ]
351 Added: [
352 Added: arrow ~label:"‹" prev_month "Previous month";
353 Added: picker;
354 Added: arrow ~label:"›" next_month "Next month";
355 Added: ];
356 Added: tag "div"
357 Added: [ class_ "calendar-subhead" ]
358 Added: [
359 Added: tag "a"
360 Added: [
361 Added: href Routes.logbook;
362 Added: class_ "calendar-today-link";
363 Added: Dream_html.attr "data-hito-app-link";
364 Added: ]
365 Added: [ txt "Today" ];
366 Added: ];
362 367 tag "table"
363 Removed: [ class_ "calendar" ]
368 Added: [ class_ "calendar-grid" ]
364 369 [
365 370 tag "thead" []
366 371 [
lib/web/assets/hito.css
index df31796c..e0ff0f39 100644..100644
@@ -733,53 +733,70 @@
733 733 /* The action bar under the logbook heading. */
734 734 .logbook-actions-bar { margin: 0.5rem 0 1rem; }
735 735
736 Removed: /* The month calendar above the logbook. A fixed seven-column grid; days with a
737 Removed: workout are filled oxblood, today is ringed. */
738 Removed: .calendar-block {
736 Added: /* The month calendar above the logbook: a self-contained widget. A bordered
737 Added: card holds a header row (prev arrow, month/year picker, next arrow), a Today
738 Added: link, and the seven-column day grid. Days with a workout are filled oxblood;
739 Added: today is ringed. */
740 Added: .calendar {
739 741 max-width: 22rem;
740 742 margin: 0 auto 1.5rem;
743 Added: padding: 0.6rem 0.75rem 0.75rem;
744 Added: border: 1px solid var(--rule-strong);
745 Added: border-radius: var(--radius);
746 Added: background: var(--paper-raised);
741 747 }
742 Removed: .calendar-picker {
748 Added: .calendar-header {
743 749 display: flex;
744 Removed: gap: 0.5rem;
745 750 align-items: center;
746 Removed: margin-bottom: 0.5rem;
751 Added: gap: 0.4rem;
747 752 }
748 Removed: .calendar-picker select { flex: 1 1 auto; }
749 Removed: .calendar-picker button { flex: 0 0 auto; }
750 Removed: .calendar-nav {
751 Removed: display: flex;
752 Removed: align-items: center;
753 Removed: justify-content: space-between;
754 Removed: gap: 0.5rem;
755 Removed: margin-bottom: 0.4rem;
756 Removed: }
757 Removed: .calendar-nav-link,
758 Removed: .calendar-nav-now {
753 Added: .calendar-arrow {
759 754 display: inline-flex;
760 755 align-items: center;
761 Removed: min-height: 2.25rem;
762 Removed: padding: 0 0.6rem;
756 Added: justify-content: center;
757 Added: flex: 0 0 auto;
758 Added: width: 2.25rem;
759 Added: height: 2.25rem;
763 760 border-radius: var(--radius);
761 Added: color: var(--ink);
762 Added: font-size: 1.35rem;
763 Added: line-height: 1;
764 Added: text-decoration: none;
765 Added: }
766 Added: .calendar-arrow:hover { background: var(--hover-surface); }
767 Added: .calendar-picker {
768 Added: display: flex;
769 Added: flex: 1 1 auto;
770 Added: gap: 0.35rem;
771 Added: align-items: center;
772 Added: justify-content: center;
773 Added: }
774 Added: .calendar-picker select { flex: 1 1 auto; min-width: 0; }
775 Added: .calendar-picker button { flex: 0 0 auto; }
776 Added: .calendar-subhead {
777 Added: margin: 0.35rem 0 0.5rem;
778 Added: text-align: center;
779 Added: }
780 Added: .calendar-today-link {
764 781 color: var(--oxblood-dark);
782 Added: font-size: var(--font-size-small);
765 783 font-weight: 700;
766 784 text-decoration: none;
767 785 }
768 Removed: .calendar-nav-link:hover,
769 Removed: .calendar-nav-now:hover { background: var(--hover-surface); }
770 Removed: .calendar {
786 Added: .calendar-today-link:hover { text-decoration: underline; }
787 Added: .calendar-grid {
771 788 width: 100%;
772 789 border-collapse: collapse;
773 790 table-layout: fixed;
774 791 }
775 Removed: .calendar th {
792 Added: .calendar-grid th {
776 793 padding: 0.25rem 0;
777 794 color: var(--muted-ink);
778 795 font: 700 var(--font-size-small)/1 var(--sans);
779 796 text-align: center;
780 797 }
781 Removed: .calendar td {
782 Removed: padding: 0.15rem;
798 Added: .calendar-grid td {
799 Added: padding: 0.1rem;
783 800 text-align: center;
784 801 vertical-align: middle;
785 802 }
test/web/logbook.ml
index 1da851d9..525def18 100644..100644
@@ -104,7 +104,7 @@
104 104 "18 min";
105 105 "Laterals";
106 106 "10 kg × 8 → 12 kg × 9";
107 Removed: "+2 kg · +1 rep";
107 Added: "+2 kg, +1 rep";
108 108 "verdict-progress";
109 109 ] );
110 110 ( "each saved workout row has an edit and confirmation-gated delete menu",
@@ -760,8 +760,8 @@
760 760 "a month/year picker is present" true
761 761 (Option.is_some (index_from ~needle:"calendar-picker" page 0));
762 762 Alcotest.(check bool)
763 Removed: "prev/next/now navigation is present" true
764 Removed: (Option.is_some (index_from ~needle:"calendar-nav" page 0));
763 Added: "prev/next/today navigation is present" true
764 Added: (Option.is_some (index_from ~needle:"calendar-arrow" page 0));
765 765 Alcotest.(check bool)
766 766 "a workout day is highlighted" true
767 767 (Option.is_some (index_from ~needle:"calendar-workout" page 0));