- 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
270 lines
6.7 KiB
Vue
270 lines
6.7 KiB
Vue
<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>
|