227 lines
9.8 KiB
Vue
227 lines
9.8 KiB
Vue
<script setup>
|
|
import { ref, watch } from 'vue';
|
|
import { api, apiURL } from '@Services/Api';
|
|
|
|
import Modal from '@Holos/Modal.vue';
|
|
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
|
import Loader from '@Shared/Loader.vue';
|
|
|
|
/** Props */
|
|
const props = defineProps({
|
|
show: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
client: {
|
|
type: Object,
|
|
default: null
|
|
}
|
|
});
|
|
|
|
/** Emits */
|
|
const emit = defineEmits(['close']);
|
|
|
|
/** Estado */
|
|
const loading = ref(false);
|
|
const error = ref(null);
|
|
const stats = ref(null);
|
|
|
|
/** Métodos */
|
|
const fetchStats = () => {
|
|
if (!props.client?.id) return;
|
|
|
|
loading.value = true;
|
|
error.value = null;
|
|
stats.value = null;
|
|
|
|
api.get(apiURL(`clients/${props.client.id}/stats`), {
|
|
onSuccess: (data) => {
|
|
stats.value = data.stats;
|
|
loading.value = false;
|
|
},
|
|
onFail: (data) => {
|
|
error.value = data.message || 'Error al obtener estadísticas del cliente.';
|
|
loading.value = false;
|
|
},
|
|
onError: () => {
|
|
error.value = 'Error de conexión. Por favor intente más tarde.';
|
|
loading.value = false;
|
|
}
|
|
});
|
|
};
|
|
|
|
const formatCurrency = (value) => {
|
|
return new Intl.NumberFormat('es-MX', {
|
|
style: 'currency',
|
|
currency: 'MXN'
|
|
}).format(value || 0);
|
|
};
|
|
|
|
const formatDate = (date) => {
|
|
if (!date) return 'N/A';
|
|
return new Date(date).toLocaleDateString('es-MX', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
});
|
|
};
|
|
|
|
const close = () => {
|
|
emit('close');
|
|
};
|
|
|
|
/** Watchers */
|
|
watch(() => props.show, (newVal) => {
|
|
if (newVal) {
|
|
fetchStats();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<Modal :show="show" max-width="3xl" @close="close">
|
|
<div class="p-6">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-full bg-indigo-100 dark:bg-indigo-900/30">
|
|
<GoogleIcon name="bar_chart" class="text-xl text-indigo-600 dark:text-indigo-400" />
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
Estadísticas del Cliente
|
|
</h3>
|
|
<p v-if="client" class="text-sm text-gray-500 dark:text-gray-400">
|
|
{{ client.name }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
@click="close"
|
|
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="max-h-[70vh] overflow-y-auto">
|
|
<!-- Loading -->
|
|
<div v-if="loading" class="flex justify-center py-12">
|
|
<Loader />
|
|
</div>
|
|
|
|
<!-- Error -->
|
|
<div v-else-if="error" class="text-center py-8">
|
|
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-red-100 dark:bg-red-900/30 mb-4">
|
|
<GoogleIcon name="error" class="text-3xl text-red-500" />
|
|
</div>
|
|
<p class="text-gray-600 dark:text-gray-400">{{ error }}</p>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div v-else-if="stats" class="space-y-6">
|
|
<!-- Tier Actual -->
|
|
<div v-if="stats.current_tier" class="bg-gradient-to-br from-indigo-500 to-purple-600 rounded-lg p-6 text-white">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm opacity-90 mb-1">Nivel Actual</p>
|
|
<h3 class="text-2xl font-bold">{{ stats.current_tier.tier_name }}</h3>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="text-sm opacity-90 mb-1">Descuento</p>
|
|
<h3 class="text-3xl font-bold">{{ stats.current_tier.discount_percentage }}%</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resumen de Compras -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<!-- Total Compras -->
|
|
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-4 border border-green-200 dark:border-green-800">
|
|
<div class="flex items-start justify-between">
|
|
<div>
|
|
<p class="text-sm text-green-700 dark:text-green-400 mb-1">Total Compras</p>
|
|
<p class="text-2xl font-bold text-green-900 dark:text-green-300">
|
|
{{ formatCurrency(stats.total_purchases) }}
|
|
</p>
|
|
</div>
|
|
<GoogleIcon name="shopping_cart" class="text-2xl text-green-600 dark:text-green-400" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Devoluciones -->
|
|
<div class="bg-red-50 dark:bg-red-900/20 rounded-lg p-4 border border-red-200 dark:border-red-800">
|
|
<div class="flex items-start justify-between">
|
|
<div>
|
|
<p class="text-sm text-red-700 dark:text-red-400 mb-1">Devoluciones</p>
|
|
<p class="text-2xl font-bold text-red-900 dark:text-red-300">
|
|
{{ formatCurrency(stats.lifetime_returns) }}
|
|
</p>
|
|
</div>
|
|
<GoogleIcon name="keyboard_return" class="text-2xl text-red-600 dark:text-red-400" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Compras Netas -->
|
|
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 border border-blue-200 dark:border-blue-800">
|
|
<div class="flex items-start justify-between">
|
|
<div>
|
|
<p class="text-sm text-blue-700 dark:text-blue-400 mb-1">Compras Netas</p>
|
|
<p class="text-2xl font-bold text-blue-900 dark:text-blue-300">
|
|
{{ formatCurrency(stats.net_purchases) }}
|
|
</p>
|
|
</div>
|
|
<GoogleIcon name="payments" class="text-2xl text-blue-600 dark:text-blue-400" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Descuentos Recibidos -->
|
|
<div class="bg-purple-50 dark:bg-purple-900/20 rounded-lg p-4 border border-purple-200 dark:border-purple-800">
|
|
<div class="flex items-start justify-between">
|
|
<div>
|
|
<p class="text-sm text-purple-700 dark:text-purple-400 mb-1">Descuentos Recibidos</p>
|
|
<p class="text-2xl font-bold text-purple-900 dark:text-purple-300">
|
|
{{ formatCurrency(stats.total_discounts_received) }}
|
|
</p>
|
|
</div>
|
|
<GoogleIcon name="discount" class="text-2xl text-purple-600 dark:text-purple-400" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Información Adicional -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">Total de Transacciones</p>
|
|
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
{{ stats.total_transactions }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">Promedio por Compra</p>
|
|
<p class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
{{ formatCurrency(stats.average_purchase) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Última Compra -->
|
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<GoogleIcon name="schedule" class="text-lg text-gray-600 dark:text-gray-400" />
|
|
<p class="text-sm font-semibold text-gray-600 dark:text-gray-400">Última Compra</p>
|
|
</div>
|
|
<p class="text-lg text-gray-900 dark:text-gray-100">
|
|
{{ formatDate(stats.last_purchase_at) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
</template>
|