feat(ui): add usage progress bars to bag cards and data table
- Add used_percent field to BagView computed from amount/remaining - Display accent-colored progress bar on homepage bag cards - Replace Open pill with progress bar in bag data table (desktop and mobile) - Merge Weight column into Status column showing remaining/total - Add full-width progress bar to mobile card view for open bags - Sort Status column by remaining grams (closed bags treated as 10000g) - Hide Finished field on mobile for open bags - Use small-caps typography for remaining text labels
This commit is contained in:
parent
3a960b9d14
commit
dcaac1ee9c
5 changed files with 47 additions and 15 deletions
|
|
@ -42,7 +42,9 @@ impl SqlBagRepository {
|
|||
BagSortKey::UpdatedAt => format!("b.updated_at {dir_sql}, b.id DESC"),
|
||||
BagSortKey::Roaster => format!("LOWER(rr.name) {dir_sql}, b.created_at DESC"),
|
||||
BagSortKey::Roast => format!("LOWER(r.name) {dir_sql}, b.created_at DESC"),
|
||||
BagSortKey::Status => format!("b.closed {dir_sql}, b.created_at DESC"),
|
||||
BagSortKey::Status => format!(
|
||||
"CASE WHEN b.closed THEN 10000 ELSE b.remaining END {dir_sql}, b.created_at DESC"
|
||||
),
|
||||
BagSortKey::FinishedAt => format!("b.finished_at {dir_sql}, b.created_at DESC"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,17 @@ pub struct BagView {
|
|||
pub roaster_name: String,
|
||||
pub roast_slug: String,
|
||||
pub roaster_slug: String,
|
||||
pub used_percent: u8,
|
||||
}
|
||||
|
||||
impl BagView {
|
||||
pub fn from_domain(bag: BagWithRoast) -> Self {
|
||||
let used_percent = if bag.bag.amount > 0.0 {
|
||||
(((bag.bag.amount - bag.bag.remaining) / bag.bag.amount) * 100.0).clamp(0.0, 100.0)
|
||||
as u8
|
||||
} else {
|
||||
0
|
||||
};
|
||||
Self {
|
||||
id: bag.bag.id.to_string(),
|
||||
roast_id: bag.bag.roast_id.to_string(),
|
||||
|
|
@ -36,6 +43,7 @@ impl BagView {
|
|||
roaster_name: bag.roaster_name,
|
||||
roast_slug: bag.roast_slug,
|
||||
roaster_slug: bag.roaster_slug,
|
||||
used_percent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -445,6 +445,18 @@ a {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* Progress bar: full-width above footer */
|
||||
.responsive-table td.card-progress {
|
||||
order: 98;
|
||||
display: block;
|
||||
margin: 0.25rem 0 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.responsive-table td.card-progress::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
<span class="block truncate font-semibold text-text">{{ bag.roast_name }}</span>
|
||||
<p class="truncate text-sm text-text-muted">{{ bag.roaster_name }}</p>
|
||||
</div>
|
||||
<p class="mt-2 truncate text-sm text-text-secondary">
|
||||
<span class="font-medium">{{ bag.remaining }}g</span>
|
||||
<span class="text-text-muted">of {{ bag.amount }}g remaining</span>
|
||||
</p>
|
||||
<div class="mt-2 h-1.5 rounded-full bg-surface-alt overflow-hidden">
|
||||
<div class="h-full rounded-full bg-accent" style="width: {{ bag.used_percent }}%"></div>
|
||||
</div>
|
||||
<p class="mt-1 truncate text-text-muted" style="font-variant: small-caps; font-size: 0.7rem">{{ bag.remaining }} / {{ bag.amount }}g</p>
|
||||
{% if is_authenticated %}
|
||||
<div class="mt-3 pt-3 border-t flex gap-3">
|
||||
<a href="/add?type=brew&bag_id={{ bag.id }}" class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover">
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
{% call table::sortable_header("Added", "created-at", 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>
|
||||
{% call table::sortable_header("Status", "status", navigator, "#bag-list") %}
|
||||
{% call table::sortable_header("Finished", "finished-at", navigator, "#bag-list") %}
|
||||
{% if is_authenticated %}
|
||||
|
|
@ -49,21 +48,34 @@
|
|||
<td data-label="Roast" class="card-title px-4 py-3 whitespace-nowrap">
|
||||
{{ bag.roast_name }}
|
||||
</td>
|
||||
<td data-label="Weight" class="px-4 py-3 whitespace-nowrap text-xs font-medium text-text-secondary">{{ bag.amount }}g</td>
|
||||
<td data-label="Status" class="mobile-hidden px-4 py-3 whitespace-nowrap md:text-center">
|
||||
<td data-label="Status" class="mobile-hidden px-4 py-3 whitespace-nowrap">
|
||||
{% if bag.closed %}
|
||||
<span class="pill pill-warning">Closed</span>
|
||||
{% else %}
|
||||
<span class="pill pill-success">Open</span>
|
||||
<div class="text-xs text-text-muted">{{ bag.remaining }}g left</div>
|
||||
<div class="w-14">
|
||||
<div class="h-1.5 rounded-full bg-surface-alt overflow-hidden">
|
||||
<div class="h-full rounded-full bg-accent" style="width: {{ bag.used_percent }}%"></div>
|
||||
</div>
|
||||
<div class="mt-0.5 text-text-muted" style="font-variant: small-caps; font-size: 0.7rem">{{ bag.remaining }} / {{ bag.amount }}g</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if !bag.closed %}
|
||||
<td data-label="Remaining" class="px-4 py-3 whitespace-nowrap md:hidden">{{ bag.remaining }}g left</td>
|
||||
<td data-label="" class="card-progress md:hidden">
|
||||
<div class="h-1.5 rounded-full bg-surface-alt overflow-hidden">
|
||||
<div class="h-full rounded-full bg-accent" style="width: {{ bag.used_percent }}%"></div>
|
||||
</div>
|
||||
<div class="mt-0.5 text-right text-text-muted" style="font-variant: small-caps; font-size: 0.7rem">{{ bag.remaining }} / {{ bag.amount }}g</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td data-label="Finished" class="whitespace-nowrap px-4 py-3 text-xs font-medium text-text-secondary">
|
||||
<td data-label="Finished" class="mobile-hidden whitespace-nowrap px-4 py-3 text-xs font-medium text-text-secondary">
|
||||
<div>{{ bag.finished_date }}</div>
|
||||
</td>
|
||||
{% if bag.closed %}
|
||||
<td data-label="Finished" class="whitespace-nowrap px-4 py-3 text-xs font-medium text-text-secondary md:hidden">
|
||||
<div>{{ bag.finished_date }}</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if is_authenticated %}
|
||||
<td data-label="" class="card-detail hidden">
|
||||
<div class="flex items-center gap-4">
|
||||
|
|
@ -86,8 +98,6 @@
|
|||
<span class="md:hidden">
|
||||
{% if bag.closed %}
|
||||
<span class="pill pill-warning text-[0.625rem] py-0 px-1.5">Closed</span>
|
||||
{% else %}
|
||||
<span class="pill pill-success text-[0.625rem] py-0 px-1.5">Open</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<button type="button"
|
||||
|
|
@ -102,7 +112,7 @@
|
|||
</tr>
|
||||
{% if is_authenticated %}
|
||||
<tr class="hidden detail-row">
|
||||
<td colspan="7" class="bg-surface-alt px-4 py-2">
|
||||
<td colspan="6" class="bg-surface-alt px-4 py-2">
|
||||
<div class="flex items-center gap-4">
|
||||
{% if !bag.closed %}
|
||||
<button type="button"
|
||||
|
|
|
|||
Loading…
Reference in a new issue