feat: improve catalog responsive design, cart layout and card component

- Redesign Cart component with improved alignments, gaps, and responsive padding
- Refactor Card component with mobile-first layout and consistent spacing
- Update catalog layout to 60/40 split for products/cart on desktop (lg)
- Add PaginationControls component (previously untracked)
- Clean up obsolete CSS styles from catalog page
This commit is contained in:
2026-05-15 13:10:54 -05:00
parent 87f12f76ea
commit 99d3881d61
4 changed files with 1442 additions and 205 deletions

View File

@@ -0,0 +1,269 @@
<template>
<v-row class="pagination-container my-3" align="center">
<!-- Fila 1: Navegador de páginas (centrado, ancho completo) -->
<v-col cols="12" class="d-flex justify-center mb-2">
<v-pagination
:model-value="currentPage"
@update:model-value="$emit('page-change', $event)"
:length="totalPages"
:total-visible="computedTotalVisible"
:show-first-last-page="showFirstLastButtons"
rounded="circle"
color="primary"
:size="paginationSize"
></v-pagination>
</v-col>
<!-- Fila 2: Info + Selector (en línea) - Solo Desktop -->
<v-col
cols="12"
class="d-none d-md-flex justify-center align-center pagination-info-row"
>
<!-- Información de resultados -->
<span class="pagination-info-text">
Mostrando {{ paginationInfo.start }}-{{ paginationInfo.end }}
de {{ paginationInfo.total }} productos
</span>
<!-- Separador visual -->
<v-divider vertical class="mx-4" style="height: 24px;"></v-divider>
<!-- Selector de items por página -->
<span class="mr-2 text-body-1 font-weight-medium">
Productos por página:
</span>
<v-select
:model-value="itemsPerPage"
@update:model-value="$emit('items-per-page-change', $event)"
:items="itemsPerPageOptions"
density="comfortable"
variant="outlined"
hide-details
class="items-per-page-selector"
></v-select>
</v-col>
<!-- Fila 2 Mobile: Info de resultados - Solo Mobile -->
<v-col cols="12" class="d-flex d-md-none justify-center">
<span class="pagination-info-text">
Mostrando {{ paginationInfo.start }}-{{ paginationInfo.end }}
de {{ paginationInfo.total }} productos
</span>
</v-col>
<!-- Fila 3 Mobile: Selector - Solo Mobile -->
<v-col
cols="12"
class="d-flex d-md-none justify-center align-center"
>
<span class="mr-2 text-body-1 font-weight-medium">
Productos por página:
</span>
<v-select
:model-value="itemsPerPage"
@update:model-value="$emit('items-per-page-change', $event)"
:items="itemsPerPageOptions"
density="comfortable"
variant="outlined"
hide-details
class="items-per-page-selector"
></v-select>
</v-col>
</v-row>
</template>
<script>
import { computed, ref, onMounted, onUnmounted } from 'vue';
export default {
name: 'PaginationControls',
props: {
currentPage: {
type: Number,
required: true
},
totalPages: {
type: Number,
required: true
},
itemsPerPage: {
type: Number,
required: true
},
itemsPerPageOptions: {
type: Array,
required: true
},
paginationInfo: {
type: Object,
required: true
},
position: {
type: String,
default: 'top',
validator: (value) => ['top', 'bottom'].includes(value)
},
totalVisiblePages: {
type: Number,
default: null
}
},
emits: ['page-change', 'items-per-page-change'],
setup(props) {
const windowWidth = ref(window.innerWidth);
// Actualizar ancho de ventana en resize
const updateWidth = () => {
windowWidth.value = window.innerWidth;
};
onMounted(() => {
window.addEventListener('resize', updateWidth);
});
onUnmounted(() => {
window.removeEventListener('resize', updateWidth);
});
const isMobile = computed(() => windowWidth.value < 680);
// Computed para tamaño de paginación: más pequeño en tablet para ahorrar espacio
const paginationSize = computed(() => {
const width = windowWidth.value;
// En pantallas pequeñas/medianas, usar tamaño default para ahorrar espacio
if (width < 960) {
return 'default';
}
// En desktop, usar tamaño large
return 'large';
});
// Computed para mostrar botones first/last: solo en pantallas >= 680px
const showFirstLastButtons = computed(() => {
const width = windowWidth.value;
// Mostrar first/last solo en tablet y desktop (>= 680px)
// En mobile (<680px), solo prev/next para ahorrar espacio
return width >= 680;
});
// Computed property para total-visible: prioriza prop recibida, sino calcula localmente
const computedTotalVisible = computed(() => {
// Si se recibe totalVisiblePages desde el padre, usarlo (SINCRONIZACIÓN)
if (props.totalVisiblePages !== null) {
return props.totalVisiblePages;
}
// Fallback: cálculo local (por compatibilidad)
const width = windowWidth.value;
const totalPages = props.totalPages;
// Si hay pocas páginas, mostrarlas todas
if (totalPages <= 7) {
return totalPages;
}
// Breakpoints responsivos
if (width < 400) {
return 3;
} else if (width < 680) {
return 5;
} else if (width < 960) {
return 7;
} else if (width < 1280) {
return 9;
} else {
return 11;
}
});
return {
isMobile,
computedTotalVisible,
paginationSize,
showFirstLastButtons
};
}
};
</script>
<style scoped>
.pagination-container {
padding: 12px 0;
gap: 8px;
}
.pagination-info-text {
font-size: 1.05rem;
color: #666;
font-weight: 500;
}
/* Nueva clase para la fila de info en desktop */
.pagination-info-row {
gap: 16px;
}
.items-per-page-selector {
max-width: 100px;
}
/* Mobile */
@media (max-width: 680px) {
.pagination-container {
gap: 12px;
}
.items-per-page-selector {
max-width: 80px;
}
.text-body-1 {
font-size: 0.9rem;
}
}
/* Ajustes visuales */
.v-pagination {
margin: 0 auto;
min-width: 400px; /* Garantizar espacio mínimo para iconos + páginas */
}
/* En móviles muy pequeños, reducir min-width */
@media (max-width: 480px) {
.v-pagination {
min-width: 320px;
}
}
/* En pantallas medianas problemáticas (680-960px), asegurar espacio suficiente */
@media (min-width: 680px) and (max-width: 960px) {
.v-pagination {
min-width: 450px; /* Más espacio para evitar que desaparezcan los iconos */
}
}
/* Separador vertical */
.v-divider--vertical {
opacity: 0.5;
}
/* Aumentar tamaño del icono del dropdown en v-select */
.items-per-page-selector :deep(.v-icon) {
font-size: 1.5rem !important;
}
/* Aumentar tamaño del texto dentro del select */
.items-per-page-selector :deep(.v-field__input) {
font-size: 1rem !important;
font-weight: 500;
}
/* Aumentar tamaño de los items del menú dropdown */
.items-per-page-selector :deep(.v-list-item-title) {
font-size: 1rem !important;
font-weight: 500;
}
</style>