feat: add search bar and restyle catalog header matching Nueva Compra

- Replace plain title with gradient page-header (icon, title, subtitle)
- Add search field with mdi-magnify icon and real-time name filtering
- Integrate search into the header as a single sticky unit
- Add filteredItems computed for client-side product search
- Reset to page 1 on search query change
- Show distinct message when search yields no results
- Adapt pagination to work with filtered results
This commit is contained in:
2026-05-28 22:19:10 -05:00
parent 690c8ff288
commit da45c4c1f7

View File

@@ -9,13 +9,32 @@
<v-row> <v-row>
<v-col cols="12" md="9" lg="7" :class="{ 'pb-mobile-cart': isMobile }"> <v-col cols="12" md="9" lg="7" :class="{ 'pb-mobile-cart': isMobile }">
<v-card-title> <v-sheet class="page-header d-flex align-center pa-3 pa-sm-4 pa-md-6 mb-3 mb-sm-4 rounded-lg">
<span class="headline">Catálogo</span> <v-icon size="28" color="white" class="mr-2 d-sm-none">mdi-store</v-icon>
</v-card-title> <v-icon size="36" color="white" class="mr-3 d-none d-sm-inline">mdi-store</v-icon>
<div class="d-flex flex-column flex-sm-row align-start align-sm-center w-100 ga-2 ga-sm-4">
<div class="flex-shrink-0">
<h1 class="text-h6 text-sm-h5 text-md-h4 font-weight-bold text-white mb-0">Catálogo</h1>
<p class="text-body-2 text-white text-medium-emphasis mb-0">Explora y agrega productos a tu compra</p>
</div>
<v-spacer></v-spacer>
<v-text-field
v-model="searchQuery"
prepend-inner-icon="mdi-magnify"
label="Buscar producto..."
variant="solo-filled"
density="compact"
clearable
hide-details
single-line
class="search-field"
/>
</div>
</v-sheet>
<!-- Controles de paginación superiores --> <!-- Controles de paginación superiores -->
<PaginationControls <PaginationControls
v-if="items.length > 0" v-if="filteredItems.length > 0"
:current-page="currentPage" :current-page="currentPage"
:total-pages="totalPages" :total-pages="totalPages"
:items-per-page="itemsPerPage" :items-per-page="itemsPerPage"
@@ -52,10 +71,18 @@
> >
No hay productos disponibles en el catálogo No hay productos disponibles en el catálogo
</v-alert> </v-alert>
<v-alert
v-else-if="filteredItems.length === 0"
type="warning"
class="my-4"
variant="tonal"
>
No se encontraron productos con ese nombre
</v-alert>
<!-- Controles de paginación inferiores --> <!-- Controles de paginación inferiores -->
<PaginationControls <PaginationControls
v-if="items.length > 0" v-if="filteredItems.length > 0"
:current-page="currentPage" :current-page="currentPage"
:total-pages="totalPages" :total-pages="totalPages"
:items-per-page="itemsPerPage" :items-per-page="itemsPerPage"
@@ -225,6 +252,7 @@ export default {
return { return {
api: inject("api"), api: inject("api"),
items: [], items: [],
searchQuery: "",
// Paginación // Paginación
currentPage: 1, currentPage: 1,
itemsPerPage: 20, itemsPerPage: 20,
@@ -257,14 +285,22 @@ export default {
cartCount() { cartCount() {
return this.cartStore.cartCount; return this.cartStore.cartCount;
}, },
// Búsqueda
filteredItems() {
if (!this.searchQuery) return this.items;
const query = this.searchQuery.toLowerCase();
return this.items.filter((item) =>
item.name.toLowerCase().includes(query),
);
},
// Paginación // Paginación
paginatedItems() { paginatedItems() {
const start = (this.currentPage - 1) * this.itemsPerPage; const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage; const end = start + this.itemsPerPage;
return this.items.slice(start, end); return this.filteredItems.slice(start, end);
}, },
totalPages() { totalPages() {
return Math.ceil(this.items.length / this.itemsPerPage); return Math.ceil(this.filteredItems.length / this.itemsPerPage);
}, },
paginationInfo() { paginationInfo() {
const start = (this.currentPage - 1) * this.itemsPerPage + 1; const start = (this.currentPage - 1) * this.itemsPerPage + 1;
@@ -275,7 +311,7 @@ export default {
return { return {
start, start,
end, end,
total: this.items.length, total: this.filteredItems.length,
}; };
}, },
// Computed para total-visible dinámico y responsive (usado por ambos PaginationControls) // Computed para total-visible dinámico y responsive (usado por ambos PaginationControls)
@@ -306,6 +342,11 @@ export default {
this.loadItemsPerPagePreference(); this.loadItemsPerPagePreference();
this.fetchProducts(); this.fetchProducts();
}, },
watch: {
searchQuery() {
this.currentPage = 1;
},
},
methods: { methods: {
fetchProducts() { fetchProducts() {
this.api this.api
@@ -446,7 +487,7 @@ export default {
}, },
scrollToTop() { scrollToTop() {
this.$nextTick(() => { this.$nextTick(() => {
const catalogHeader = this.$el?.querySelector(".headline"); const catalogHeader = this.$el?.querySelector(".page-header");
if (catalogHeader) { if (catalogHeader) {
catalogHeader.scrollIntoView({ catalogHeader.scrollIntoView({
behavior: "smooth", behavior: "smooth",
@@ -468,59 +509,60 @@ export default {
</script> </script>
<style scoped> <style scoped>
.headline { .page-header {
font-weight: bold; position: sticky;
font-size: 1.25rem; top: 80px;
z-index: 10;
background: linear-gradient(135deg, #1565C0 0%, #0D47A1 100%) !important;
color: white !important;
overflow: visible;
} }
/* === LISTADO DE PRODUCTOS === */ @media (max-width: 959px) {
.catalog-item { .page-header {
padding: 0; top: 0;
margin-bottom: 12px; border-radius: 0 !important;
}
.catalog-item:last-child {
margin-bottom: 0;
}
/* Mobile First: Estilos base (< 960px) */
.cart-sidebar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
transition: all 0.3s ease-in-out;
}
.cart-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
} }
} }
/* Padding para el contenido del catálogo en mobile */ @media (max-width: 559px) {
.pb-mobile-cart { .page-header {
padding-bottom: 76px !important; /* 60px sidebar + 16px margen */ padding: 12px 16px !important;
}
}
.page-header .search-field {
background: rgba(255, 255, 255, 0.15);
border-radius: 8px;
transition: background 0.2s;
width: 100%;
}
.page-header .search-field:hover,
.page-header .search-field:focus-within {
background: rgba(255, 255, 255, 0.25);
}
@media (max-width: 559px) {
.page-header .search-field :deep(.v-field__input) {
font-size: 0.875rem;
}
}
@media (min-width: 560px) {
.page-header .search-field {
min-width: 260px;
max-width: 360px;
}
}
@media (min-width: 960px) {
.page-header .search-field {
min-width: 320px;
max-width: 460px;
}
} }
/* Desktop (≥ 960px) - Sidebar sticky con scroll */
/* Layout: 960-1023px = md breakpoint (75% productos / 25% cart) */
/* Layout: ≥1024px = lg breakpoint (60% productos / 40% cart) */
@media (min-width: 960px) { @media (min-width: 960px) {
.cart-sidebar { .cart-sidebar {
position: sticky; position: sticky;
@@ -543,18 +585,12 @@ export default {
} }
} }
/* Resolución <560px - Mobile extra small */
@media (max-width: 559px) { @media (max-width: 559px) {
.headline {
font-size: 1.1rem;
}
.catalog-item { .catalog-item {
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
/* Resolución 560-959px - Tablet */
@media (min-width: 560px) and (max-width: 959px) { @media (min-width: 560px) and (max-width: 959px) {
.catalog-item { .catalog-item {
margin-bottom: 14px; margin-bottom: 14px;