diff --git a/src/application/routes/data.rs b/src/application/routes/data.rs index 0dcbcb2..7191036 100644 --- a/src/application/routes/data.rs +++ b/src/application/routes/data.rs @@ -63,6 +63,7 @@ pub(crate) async fn data_page( ) -> Result { let entity_type = data_type.entity_type; let is_authenticated = super::is_authenticated(&state, &cookies).await; + let search_value = list_query.search_value(); let content = render_entity_content(&state, &entity_type, list_query, is_authenticated) .await @@ -97,6 +98,7 @@ pub(crate) async fn data_page( active_type: entity_type, tabs, content, + search_value, }; render_html(template).map(IntoResponse::into_response) diff --git a/src/application/routes/support.rs b/src/application/routes/support.rs index 149a529..6136da8 100644 --- a/src/application/routes/support.rs +++ b/src/application/routes/support.rs @@ -53,6 +53,10 @@ enum PageSizeParam { } impl ListQuery { + pub fn search_value(&self) -> String { + self.q.clone().unwrap_or_default() + } + pub fn into_request_and_search(self) -> (ListRequest, Option) { self.into_request_and_search_with_default::(DEFAULT_PAGE_SIZE) } diff --git a/src/presentation/web/templates.rs b/src/presentation/web/templates.rs index c53be96..8ed3bec 100644 --- a/src/presentation/web/templates.rs +++ b/src/presentation/web/templates.rs @@ -132,6 +132,7 @@ pub struct DataTemplate { pub active_type: String, pub tabs: Vec, pub content: String, + pub search_value: String, } pub struct DataTab { diff --git a/templates/data.html b/templates/data.html index 91eea4c..6a46992 100644 --- a/templates/data.html +++ b/templates/data.html @@ -16,24 +16,93 @@ {% endif %} - +
+ + + +
+ + +
+ +
+ +
+
-
+
{{ content|safe }}
+ + {% endblock %} diff --git a/templates/partials/bag_list.html b/templates/partials/bag_list.html index db86205..58900d7 100644 --- a/templates/partials/bag_list.html +++ b/templates/partials/bag_list.html @@ -33,13 +33,13 @@ {% for bag in bags.items %} - + {{ bag.created_at }} {{ bag.roaster_name }} - {{ bag.roast_name }} + {{ bag.roast_name }} {{ bag.amount }}g {% if bag.closed %} @@ -54,8 +54,8 @@ {% endif %} {{ bag.finished_at }} {% if is_authenticated %} - -
+ + +
+ {% call icons::ellipsis_vertical("h-5 w-5") %} +
+ {% if !bag.closed %} + + {% endif %} + +
+
{% endif %} diff --git a/templates/partials/brew_list.html b/templates/partials/brew_list.html index 95d0c6d..ca7591d 100644 --- a/templates/partials/brew_list.html +++ b/templates/partials/brew_list.html @@ -33,10 +33,10 @@ {% for brew in brews.items %} - + {{ brew.created_at }} - +
{{ brew.roast_name }}
@@ -62,8 +62,8 @@ {{ fp_name }} {% endif %} {% if is_authenticated %} - -
+ + +
+ {% call icons::ellipsis_vertical("h-5 w-5") %} +
+ + + + + + + {% if let Some(fp_id) = brew.filter_paper_id %} + + {% endif %} + + + + + +
+
{% endif %} diff --git a/templates/partials/cafe_list.html b/templates/partials/cafe_list.html index a6b11bd..6fadc15 100644 --- a/templates/partials/cafe_list.html +++ b/templates/partials/cafe_list.html @@ -35,10 +35,10 @@ data-sort-city="{{ cafe.city }}" class="bg-amber-50/40 transition hover:bg-amber-50" > - + {{ cafe.created_at }} - + {{ cafe.name }} @@ -46,8 +46,8 @@ {{ cafe.city }} - -
+ +
+ {% call icons::location("h-4 w-4") %} View on map + + {% if cafe.has_website %} + + {% call icons::external_link("h-4 w-4") %} Visit website + + {% endif %} + {% if is_authenticated %} + + {% endif %} +
+
{% endfor %} diff --git a/templates/partials/cup_list.html b/templates/partials/cup_list.html index 2de5da1..ee2ab20 100644 --- a/templates/partials/cup_list.html +++ b/templates/partials/cup_list.html @@ -32,10 +32,10 @@ data-star-key="{{ cup.id }}" class="bg-amber-50/40 transition hover:bg-amber-50" > - + {{ cup.created_at }} - + {{ cup.roast_name }} @@ -44,9 +44,9 @@ {{ cup.cafe_name }} {{ cup.rating }} - + {% if is_authenticated %} -
+ +
+ {% call icons::ellipsis_vertical("h-5 w-5") %} +
+ +
+
{% endif %} diff --git a/templates/partials/gear_list.html b/templates/partials/gear_list.html index 0f47d20..d633032 100644 --- a/templates/partials/gear_list.html +++ b/templates/partials/gear_list.html @@ -32,31 +32,41 @@ {% for item in gear.items %} - + {{ item.created_at }} + {{ item.make }} {{ item.model }} {{ item.category_label }} - + {{ item.make }} - + {{ item.model }} {% if is_authenticated %} - + +
+ {% call icons::ellipsis_vertical("h-5 w-5") %} +
+ +
+
{% endif %} diff --git a/templates/partials/icons.html b/templates/partials/icons.html index 3f73284..d12a4fb 100644 --- a/templates/partials/icons.html +++ b/templates/partials/icons.html @@ -142,8 +142,26 @@ {% endmacro %} +{% macro ellipsis_vertical(class) %} + +{% endmacro %} + {% macro x_circle(class) %} {% endmacro %} + +{% macro chevron_down(class) %} + +{% endmacro %} + +{% macro chevron_up(class) %} + +{% endmacro %} diff --git a/templates/partials/roast_list.html b/templates/partials/roast_list.html index 249842c..ffc45f9 100644 --- a/templates/partials/roast_list.html +++ b/templates/partials/roast_list.html @@ -37,10 +37,10 @@ data-sort-producer="{{ roast.producer }}" class="bg-amber-50/40 transition hover:bg-amber-50" > - + {{ roast.created_at }} - + {{ roast.name }} @@ -62,16 +62,25 @@ {% endif %} {% if is_authenticated %} - + +
+ {% call icons::ellipsis_vertical("h-5 w-5") %} +
+ +
+
{% endif %} diff --git a/templates/partials/roaster_list.html b/templates/partials/roaster_list.html index 1bf6920..ee083c3 100644 --- a/templates/partials/roaster_list.html +++ b/templates/partials/roaster_list.html @@ -35,10 +35,10 @@ data-sort-city="{{ roaster.city }}" class="bg-amber-50/40 transition hover:bg-amber-50" > - + {{ roaster.created_at }} - + {{ roaster.name }} @@ -50,11 +50,11 @@ {% if !roaster.city.is_empty() %} {{ roaster.city }} {% endif %} - -
+ +
+ {% call icons::external_link("h-4 w-4") %} Visit homepage + + {% endif %} + {% if is_authenticated %} + + {% endif %} +
+
+ {% endif %} {% endfor %} diff --git a/templates/styles.css b/templates/styles.css index 6ec1bff..befa94c 100644 --- a/templates/styles.css +++ b/templates/styles.css @@ -100,24 +100,72 @@ a { display: block; } +/* Hide duplicate search inside data page - search is in the tab bar */ +.data-page-content section:has(.responsive-table) > div:first-child:has(input[type="search"]) { + display: none; +} + +/* Remove extra margin from list containers inside data page */ +.data-page-content > div[id$="-list"] { + margin-top: 0; +} + /* Responsive tables: convert rows to cards on mobile */ @media (max-width: 767px) { + /* Remove outer section border/background on mobile - cards are standalone */ + section:has(.responsive-table) { + border: none !important; + background: transparent !important; + box-shadow: none !important; + padding: 0 !important; + } + + /* Search header: remove wrapper styling, let input fill width */ + section:has(.responsive-table) > div:first-child:has(input[type="search"]) { + border: none !important; + background: transparent !important; + padding: 0 !important; + margin-bottom: 1rem; + } + + /* Remove overflow wrapper styling */ + section:has(.responsive-table) > .overflow-x-auto { + overflow: visible; + } + + .responsive-table { + border: none !important; + background: transparent !important; + box-shadow: none !important; + } + + /* Remove divide-y borders from table */ + .responsive-table, + .responsive-table tbody { + border-color: transparent !important; + } + .responsive-table thead { display: none; } .responsive-table tbody { - display: block; + display: flex; + flex-direction: column; + gap: 1rem; + padding: 0; } + /* Match home page brew card style */ .responsive-table tr { - display: block; + display: flex; + flex-direction: column; + position: relative; padding: 1rem; - border-bottom: 1px solid rgba(217, 169, 55, 0.35); - } - - .responsive-table tr:last-child { - border-bottom: none; + border-radius: 0.5rem; + border: 1px solid #fde68a !important; /* amber-200 */ + background-color: #fffbeb; /* amber-50 */ + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); /* shadow-sm */ } .responsive-table td { @@ -126,7 +174,8 @@ a { align-items: baseline; padding: 0.25rem 0; white-space: normal; - font-size: 0.875rem; /* normalize all values to text-sm */ + font-size: 0.875rem; + border: none !important; } .responsive-table td::before { @@ -134,8 +183,8 @@ a { flex-shrink: 0; min-width: 5.5rem; font-size: 0.8125rem; - font-weight: 600; - color: #78350f; /* amber-900 */ + font-weight: 500; + color: #78716c; /* stone-500 - more muted like brew card */ margin-right: 0.75rem; } @@ -143,15 +192,140 @@ a { display: none; } - .responsive-table td[data-label=""] { - justify-content: flex-end; - padding-top: 0.25rem; - } - .responsive-table td.mobile-hidden { display: none; } + /* Card title: header section with bottom divider */ + .responsive-table td.card-title { + order: -2; + padding: 0 0 0.5rem 0; + padding-right: 2.5rem; + margin-bottom: 0.5rem; + border-bottom: 1px solid #fef3c7 !important; /* amber-100 */ + } + + .responsive-table td.card-title, + .responsive-table td.card-title > :first-child { + font-size: 1rem; + font-weight: 600; + color: #92400e; /* amber-800 */ + } + + .responsive-table td.card-title::before { + display: none; + } + + /* "Added" date: footer section with top divider */ + .responsive-table td.card-date { + order: 99; + padding-top: 0.5rem; + margin-top: 0.5rem; + border-top: 1px solid #fef3c7 !important; /* amber-100 */ + font-size: 0.75rem; + color: #78716c; + } + + .responsive-table td.card-date::before { + font-size: 0.75rem; + font-weight: 500; + color: #78716c; + min-width: auto; + margin-right: 0.5rem; + } + + /* Actions: absolute top-right in header area */ + .responsive-table td.card-actions { + order: -1; + position: absolute; + top: 0.75rem; + right: 0.75rem; + padding: 0; + justify-content: flex-end; + } + + .responsive-table td.card-actions::before { + display: none; + } + + /* Three-dot dropdown menu */ + .card-menu { + position: relative; + } + + .card-menu summary { + list-style: none; + cursor: pointer; + display: inline-flex; + height: 2rem; + width: 2rem; + align-items: center; + justify-content: center; + border-radius: 0.375rem; + color: #78716c; + transition: color 150ms ease, background-color 150ms ease; + } + + .card-menu summary:hover { + color: #b45309; + background-color: rgba(254, 243, 199, 0.8); + } + + .card-menu summary::-webkit-details-marker { + display: none; + } + + .card-menu[open] summary { + color: #b45309; + background-color: rgba(254, 243, 199, 0.8); + } + + .card-menu .card-menu-items { + position: absolute; + right: 0; + top: 100%; + z-index: 20; + min-width: 10rem; + margin-top: 0.25rem; + border-radius: 0.5rem; + border: 1px solid rgba(217, 169, 55, 0.4); + background-color: #fffbeb; + padding: 0.25rem 0; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); + } + + .card-menu .card-menu-items a, + .card-menu .card-menu-items button, + .card-menu .card-menu-items form button { + display: flex; + width: 100%; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + color: #44403c; + text-align: left; + background: none; + border: none; + cursor: pointer; + transition: background-color 150ms ease; + text-decoration: none; + } + + .card-menu .card-menu-items a:hover, + .card-menu .card-menu-items button:hover, + .card-menu .card-menu-items form button:hover { + background-color: rgba(254, 243, 199, 0.6); + } + + .card-menu .card-menu-items .menu-item-danger { + color: #dc2626; + } + + .card-menu .card-menu-items .menu-item-danger:hover { + background-color: rgba(254, 202, 202, 0.3); + } + .pagination-controls { display: none; }