refactor(tables): standardise Added column as first column across all tables

Move the Added/Date column to first position in brew, gear, and bag
list templates and apply consistent text-xs font-medium text-stone-600
styling. Document the pattern in CLAUDE.md.
This commit is contained in:
Jon Seager 2026-02-03 13:03:43 +00:00
parent 57a6736b9a
commit 2d27228059
No known key found for this signature in database
4 changed files with 27 additions and 11 deletions

View file

@ -354,6 +354,18 @@ Three macros are available:
- **`pagination_header(items, navigator, target_selector)`** — prev/next buttons, page count, rows-per-page selector; hidden on mobile via `pagination-controls hidden md:flex` - **`pagination_header(items, navigator, target_selector)`** — prev/next buttons, page count, rows-per-page selector; hidden on mobile via `pagination-controls hidden md:flex`
- **`sortable_header(label, key, navigator, target_selector)`** — clickable column header with sort direction arrows - **`sortable_header(label, key, navigator, target_selector)`** — clickable column header with sort direction arrows
### "Added" Column
Every table has a sortable "Added" column as its **first column**, sorted by `created-at`. It uses a distinct smaller style to visually separate it from content columns:
```html
<td data-label="Added" class="whitespace-nowrap px-4 py-3 text-xs font-medium text-stone-600">
{{ item.created_at }}
</td>
```
The `text-xs font-medium text-stone-600` classes give it a muted, compact appearance compared to the default `text-sm` body text.
### Responsive Table Pattern ### Responsive Table Pattern
Tables use the `responsive-table` CSS class which converts rows to card-style layout on mobile (`max-width: 767px`). The CSS in `styles.css` hides `<thead>` and uses `data-label` attributes on `<td>` elements to show field labels. Tables use the `responsive-table` CSS class which converts rows to card-style layout on mobile (`max-width: 767px`). The CSS in `styles.css` hides `<thead>` and uses `data-label` attributes on `<td>` elements to show field labels.

View file

@ -102,22 +102,24 @@
<table class="responsive-table min-w-full divide-y divide-amber-200 text-left text-sm text-stone-700"> <table class="responsive-table min-w-full divide-y divide-amber-200 text-left text-sm text-stone-700">
<thead class="bg-amber-200/60 text-xs font-semibold tracking-wide text-amber-900"> <thead class="bg-amber-200/60 text-xs font-semibold tracking-wide text-amber-900">
<tr> <tr>
{% call table::sortable_header("Roaster", "roaster", navigator, "#bag-list") %} {% {% call table::sortable_header("Added", "created-at", navigator, "#bag-list") %}
call table::sortable_header("Roast", "roast", navigator, "#bag-list") %} {% call table::sortable_header("Roaster", "roaster", navigator, "#bag-list") %}
{% call table::sortable_header("Roast", "roast", navigator, "#bag-list") %}
<th scope="col" class="px-4 py-3">Weight</th> <th scope="col" class="px-4 py-3">Weight</th>
{% call table::sortable_header("Added", "created-at", navigator, "#bag-list") %} {% {% call table::sortable_header("Finished", "finished-at", navigator, "#bag-list") %}
call table::sortable_header("Finished", "finished-at", navigator, "#bag-list") %}
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-amber-200/70"> <tbody class="divide-y divide-amber-200/70">
{% for bag in bags.items %} {% for bag in bags.items %}
<tr class="bg-amber-50/40 transition hover:bg-amber-50"> <tr class="bg-amber-50/40 transition hover:bg-amber-50">
<td data-label="Added" class="whitespace-nowrap px-4 py-3 text-xs font-medium text-stone-600">
{{ bag.created_at }}
</td>
<td data-label="Roaster" class="px-4 py-3 whitespace-nowrap font-medium text-stone-600"> <td data-label="Roaster" class="px-4 py-3 whitespace-nowrap font-medium text-stone-600">
{{ bag.roaster_name }} {{ bag.roaster_name }}
</td> </td>
<td data-label="Roast" class="px-4 py-3 whitespace-nowrap">{{ bag.roast_name }}</td> <td data-label="Roast" class="px-4 py-3 whitespace-nowrap">{{ bag.roast_name }}</td>
<td data-label="Weight" class="px-4 py-3 whitespace-nowrap">{{ bag.amount }}g</td> <td data-label="Weight" class="px-4 py-3 whitespace-nowrap">{{ bag.amount }}g</td>
<td data-label="Added" class="px-4 py-3 whitespace-nowrap">{{ bag.created_at }}</td>
<td data-label="Finished" class="px-4 py-3 whitespace-nowrap">{{ bag.finished_at }}</td> <td data-label="Finished" class="px-4 py-3 whitespace-nowrap">{{ bag.finished_at }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -10,12 +10,12 @@
<table class="responsive-table min-w-full divide-y divide-amber-200 text-left text-sm text-stone-700"> <table class="responsive-table min-w-full divide-y divide-amber-200 text-left text-sm text-stone-700">
<thead class="bg-amber-200/60 text-xs font-semibold tracking-wide text-amber-900"> <thead class="bg-amber-200/60 text-xs font-semibold tracking-wide text-amber-900">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#brew-list") %}
<th scope="col" class="px-4 py-3">Coffee</th> <th scope="col" class="px-4 py-3">Coffee</th>
<th scope="col" class="px-4 py-3">Grind</th> <th scope="col" class="px-4 py-3">Grind</th>
<th scope="col" class="px-4 py-3">Recipe</th> <th scope="col" class="px-4 py-3">Recipe</th>
<th scope="col" class="px-4 py-3">Temp</th> <th scope="col" class="px-4 py-3">Temp</th>
<th scope="col" class="px-4 py-3">Brewer</th> <th scope="col" class="px-4 py-3">Brewer</th>
{% call table::sortable_header("Date", "created-at", navigator, "#brew-list") %}
{% if is_authenticated %} {% if is_authenticated %}
<th scope="col" class="px-4 py-3 text-center">Actions</th> <th scope="col" class="px-4 py-3 text-center">Actions</th>
{% endif %} {% endif %}
@ -24,6 +24,9 @@
<tbody class="divide-y divide-amber-200/70"> <tbody class="divide-y divide-amber-200/70">
{% for brew in brews.items %} {% for brew in brews.items %}
<tr class="bg-amber-50/40 transition hover:bg-amber-50"> <tr class="bg-amber-50/40 transition hover:bg-amber-50">
<td data-label="Added" class="whitespace-nowrap px-4 py-3 text-xs font-medium text-stone-600">
{{ brew.created_at }}
</td>
<td data-label="Coffee" class="px-4 py-3 whitespace-nowrap"> <td data-label="Coffee" class="px-4 py-3 whitespace-nowrap">
<div class="font-medium text-stone-800"> <div class="font-medium text-stone-800">
<a href="/roasters/{{ brew.roaster_slug }}/roasts/{{ brew.roast_slug }}" class="hover:text-amber-700 hover:underline"> <a href="/roasters/{{ brew.roaster_slug }}/roasts/{{ brew.roast_slug }}" class="hover:text-amber-700 hover:underline">
@ -61,7 +64,6 @@
{% if let Some(fp_name) = brew.filter_paper_name %} {% if let Some(fp_name) = brew.filter_paper_name %}
<td data-label="Filter" class="px-4 py-3 whitespace-nowrap md:hidden">{{ fp_name }}</td> <td data-label="Filter" class="px-4 py-3 whitespace-nowrap md:hidden">{{ fp_name }}</td>
{% endif %} {% endif %}
<td data-label="Date" class="px-4 py-3 whitespace-nowrap text-stone-500">{{ brew.created_at }}</td>
{% if is_authenticated %} {% if is_authenticated %}
<td data-label="" class="px-4 py-3 text-right"> <td data-label="" class="px-4 py-3 text-right">
<form class="inline" data-on:submit="@post('/api/v1/brews?{{ navigator.query() }}', {contentType: 'form', responseOverrides: {selector: '#brew-list', mode: 'replace'}})"> <form class="inline" data-on:submit="@post('/api/v1/brews?{{ navigator.query() }}', {contentType: 'form', responseOverrides: {selector: '#brew-list', mode: 'replace'}})">

View file

@ -18,10 +18,10 @@
<table class="responsive-table min-w-full divide-y divide-amber-200 text-left text-sm text-stone-700"> <table class="responsive-table min-w-full divide-y divide-amber-200 text-left text-sm text-stone-700">
<thead class="bg-amber-200/60 text-xs font-semibold tracking-wide text-amber-900"> <thead class="bg-amber-200/60 text-xs font-semibold tracking-wide text-amber-900">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#gear-list") %}
{% call table::sortable_header("Category", "category", navigator, "#gear-list") %} {% call table::sortable_header("Category", "category", navigator, "#gear-list") %}
{% call table::sortable_header("Make", "make", navigator, "#gear-list") %} {% call table::sortable_header("Make", "make", navigator, "#gear-list") %}
{% call table::sortable_header("Model", "model", navigator, "#gear-list") %} {% call table::sortable_header("Model", "model", navigator, "#gear-list") %}
{% call table::sortable_header("Added", "created-at", navigator, "#gear-list") %}
{% if is_authenticated %} {% if is_authenticated %}
<th scope="col" class="px-4 py-3 text-right">Actions</th> <th scope="col" class="px-4 py-3 text-right">Actions</th>
{% endif %} {% endif %}
@ -30,6 +30,9 @@
<tbody class="divide-y divide-amber-200/70"> <tbody class="divide-y divide-amber-200/70">
{% for item in gear.items %} {% for item in gear.items %}
<tr class="bg-amber-50/40 transition hover:bg-amber-50"> <tr class="bg-amber-50/40 transition hover:bg-amber-50">
<td data-label="Added" class="whitespace-nowrap px-4 py-3 text-xs font-medium text-stone-600">
{{ item.created_at }}
</td>
<td data-label="Category" class="px-4 py-3 whitespace-nowrap"> <td data-label="Category" class="px-4 py-3 whitespace-nowrap">
<span class="inline-flex rounded-full bg-amber-100 px-2 py-1 text-xs font-medium text-amber-800"> <span class="inline-flex rounded-full bg-amber-100 px-2 py-1 text-xs font-medium text-amber-800">
{{ item.category_label }} {{ item.category_label }}
@ -41,9 +44,6 @@
<td data-label="Model" class="px-4 py-3 whitespace-nowrap"> <td data-label="Model" class="px-4 py-3 whitespace-nowrap">
{{ item.model }} {{ item.model }}
</td> </td>
<td data-label="Added" class="px-4 py-3 whitespace-nowrap">
{{ item.created_at }}
</td>
{% if is_authenticated %} {% if is_authenticated %}
<td data-label="" class="px-4 py-3 text-right"> <td data-label="" class="px-4 py-3 text-right">
<button <button