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-col cols="12" md="9" lg="7" :class="{ 'pb-mobile-cart': isMobile }">
<v-card-title>
<span class="headline">Catálogo</span>
</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">
<v-icon size="28" color="white" class="mr-2 d-sm-none">mdi-store</v-icon>
<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 -->
<PaginationControls
v-if="items.length > 0"
v-if="filteredItems.length > 0"
:current-page="currentPage"
:total-pages="totalPages"
:items-per-page="itemsPerPage"
@@ -52,10 +71,18 @@
>
No hay productos disponibles en el catálogo
</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 -->
<PaginationControls
v-if="items.length > 0"
v-if="filteredItems.length > 0"
:current-page="currentPage"
:total-pages="totalPages"
:items-per-page="itemsPerPage"
@@ -225,6 +252,7 @@ export default {
return {
api: inject("api"),
items: [],
searchQuery: "",
// Paginación
currentPage: 1,
itemsPerPage: 20,
@@ -257,14 +285,22 @@ export default {
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
paginatedItems() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.items.slice(start, end);
return this.filteredItems.slice(start, end);
},
totalPages() {
return Math.ceil(this.items.length / this.itemsPerPage);
return Math.ceil(this.filteredItems.length / this.itemsPerPage);
},
paginationInfo() {
const start = (this.currentPage - 1) * this.itemsPerPage + 1;
@@ -275,7 +311,7 @@ export default {
return {
start,
end,
total: this.items.length,
total: this.filteredItems.length,
};
},
// Computed para total-visible dinámico y responsive (usado por ambos PaginationControls)
@@ -306,6 +342,11 @@ export default {
this.loadItemsPerPagePreference();
this.fetchProducts();
},
watch: {
searchQuery() {
this.currentPage = 1;
},
},
methods: {
fetchProducts() {
this.api
@@ -446,7 +487,7 @@ export default {
},
scrollToTop() {
this.$nextTick(() => {
const catalogHeader = this.$el?.querySelector(".headline");
const catalogHeader = this.$el?.querySelector(".page-header");
if (catalogHeader) {
catalogHeader.scrollIntoView({
behavior: "smooth",
@@ -468,59 +509,60 @@ export default {
</script>
<style scoped>
.headline {
font-weight: bold;
font-size: 1.25rem;
.page-header {
position: sticky;
top: 80px;
z-index: 10;
background: linear-gradient(135deg, #1565C0 0%, #0D47A1 100%) !important;
color: white !important;
overflow: visible;
}
/* === LISTADO DE PRODUCTOS === */
.catalog-item {
padding: 0;
margin-bottom: 12px;
}
.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;
@media (max-width: 959px) {
.page-header {
top: 0;
border-radius: 0 !important;
}
}
/* Padding para el contenido del catálogo en mobile */
.pb-mobile-cart {
padding-bottom: 76px !important; /* 60px sidebar + 16px margen */
@media (max-width: 559px) {
.page-header {
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) {
.cart-sidebar {
position: sticky;
@@ -543,18 +585,12 @@ export default {
}
}
/* Resolución <560px - Mobile extra small */
@media (max-width: 559px) {
.headline {
font-size: 1.1rem;
}
.catalog-item {
margin-bottom: 10px;
}
}
/* Resolución 560-959px - Tablet */
@media (min-width: 560px) and (max-width: 959px) {
.catalog-item {
margin-bottom: 14px;