feat: Implement Store Details page with navigation and display of store information
This commit is contained in:
parent
fa161a9e0e
commit
730cae825c
495
src/modules/stores/components/StoreDetails.vue
Normal file
495
src/modules/stores/components/StoreDetails.vue
Normal file
@ -0,0 +1,495 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Toast Notifications -->
|
||||
<Toast position="bottom-right" />
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<Breadcrumb :home="breadcrumbHome" :model="breadcrumbItems" />
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h1
|
||||
class="text-surface-900 dark:text-white text-3xl md:text-4xl font-black leading-tight tracking-tight">
|
||||
{{ storeData?.name || 'Cargando...' }}
|
||||
</h1>
|
||||
<p class="text-surface-500 dark:text-surface-400 text-base font-normal leading-normal">
|
||||
Visualiza y gestiona la información de este punto de venta.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General Information Card -->
|
||||
<Card class="shadow-sm">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between p-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-surface-900 dark:text-white">Información General</h2>
|
||||
<p class="text-sm text-surface-500 dark:text-surface-400 mt-1">
|
||||
Datos principales del punto de venta.
|
||||
</p>
|
||||
</div>
|
||||
<Button label="Editar Información" icon="pi pi-pencil" outlined @click="editStore" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<!-- Store ID -->
|
||||
<div class="space-y-1">
|
||||
<label class="text-sm font-medium text-surface-500 dark:text-surface-400">
|
||||
ID del Punto de Venta
|
||||
</label>
|
||||
<p class="text-sm font-semibold text-surface-900 dark:text-white">
|
||||
{{ storeData?.id || 'N/A' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div class="space-y-1">
|
||||
<label class="text-sm font-medium text-surface-500 dark:text-surface-400">
|
||||
Dirección
|
||||
</label>
|
||||
<p class="text-sm font-semibold text-surface-900 dark:text-white">
|
||||
{{ storeData?.location || 'No especificada' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="space-y-1">
|
||||
<label class="text-sm font-medium text-surface-500 dark:text-surface-400">
|
||||
Estado
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
<Tag :value="storeData?.is_active ? 'Activo' : 'Inactivo'"
|
||||
:severity="storeData?.is_active ? 'success' : 'danger'" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Created Date -->
|
||||
<div class="space-y-1">
|
||||
<label class="text-sm font-medium text-surface-500 dark:text-surface-400">
|
||||
Fecha de Creación
|
||||
</label>
|
||||
<p class="text-sm font-semibold text-surface-900 dark:text-white">
|
||||
{{ formatDate(storeData?.created_at) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Updated Date -->
|
||||
<div class="space-y-1">
|
||||
<label class="text-sm font-medium text-surface-500 dark:text-surface-400">
|
||||
Última Actualización
|
||||
</label>
|
||||
<p class="text-sm font-semibold text-surface-900 dark:text-white">
|
||||
{{ formatDate(storeData?.updated_at) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<!-- Configuration Card -->
|
||||
<Card class="shadow-sm">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between p-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-surface-900 dark:text-white">Configuración</h2>
|
||||
<p class="text-sm text-surface-500 dark:text-surface-400 mt-1">
|
||||
Ajustes de inventario y catálogo para este punto de venta.
|
||||
</p>
|
||||
</div>
|
||||
<Button label="Guardar Cambios" icon="pi pi-save" :loading="isSaving" @click="saveConfiguration" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="space-y-6">
|
||||
<!-- Source Selection -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-base font-semibold text-surface-900 dark:text-white">
|
||||
Fuente de Productos y Stock
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<!-- Catalog Option -->
|
||||
<div class="flex items-start space-x-3 p-4 border rounded-lg cursor-pointer"
|
||||
:class="sourceType === 'catalog' ? 'border-primary bg-primary/5' : 'border-surface-300 dark:border-surface-600'"
|
||||
@click="sourceType = 'catalog'">
|
||||
<RadioButton v-model="sourceType" inputId="catalog" name="source" value="catalog" />
|
||||
<div class="flex-1">
|
||||
<label for="catalog"
|
||||
class="block text-sm font-medium text-surface-900 dark:text-white cursor-pointer">
|
||||
Catálogo General
|
||||
</label>
|
||||
<p class="text-sm text-surface-500 dark:text-surface-400 mt-1">
|
||||
Usar productos asignados directamente a este punto de venta desde el catálogo
|
||||
maestro.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Warehouse Option -->
|
||||
<div class="flex items-start space-x-3 p-4 border rounded-lg cursor-pointer"
|
||||
:class="sourceType === 'warehouse' ? 'border-primary bg-primary/5' : 'border-surface-300 dark:border-surface-600'"
|
||||
@click="sourceType = 'warehouse'">
|
||||
<RadioButton v-model="sourceType" inputId="warehouse" name="source" value="warehouse" />
|
||||
<div class="flex-1">
|
||||
<label for="warehouse"
|
||||
class="block text-sm font-medium text-surface-900 dark:text-white cursor-pointer">
|
||||
Extraer de un Almacén
|
||||
</label>
|
||||
<p class="text-sm text-surface-500 dark:text-surface-400 mt-1">
|
||||
Sincronizar el inventario de productos directamente desde un almacén específico.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Warehouse Selector -->
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-surface-700 dark:text-surface-300">
|
||||
Seleccionar Almacén
|
||||
</label>
|
||||
<Dropdown v-model="selectedWarehouse" :options="warehouseOptions" optionLabel="name"
|
||||
optionValue="id" placeholder="Selecciona un almacén..."
|
||||
:disabled="sourceType !== 'warehouse'" class="w-full" />
|
||||
<small class="text-surface-500 dark:text-surface-400">
|
||||
Esta opción se habilita al seleccionar "Extraer de un Almacén".
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
|
||||
|
||||
<!-- Terminals Section -->
|
||||
<Card class="shadow-sm">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between p-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-surface-900 dark:text-white">Gestionar Terminales</h2>
|
||||
<p class="text-sm text-surface-500 dark:text-surface-400 mt-1">
|
||||
Lista de terminales asociadas a este punto de venta.
|
||||
</p>
|
||||
</div>
|
||||
<Button label="Añadir Terminal" icon="pi pi-plus" @click="addTerminal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<!-- Search Bar -->
|
||||
<div class="mb-4">
|
||||
<IconField iconPosition="left">
|
||||
<InputIcon class="pi pi-search" />
|
||||
<InputText v-model="terminalSearchQuery"
|
||||
placeholder="Buscar terminal por ID, nombre o modelo..." class="w-full" />
|
||||
</IconField>
|
||||
</div>
|
||||
|
||||
<!-- Terminals Table -->
|
||||
<DataTable :value="terminals" :loading="loadingTerminals" responsiveLayout="scroll" stripedRows
|
||||
class="p-datatable-sm">
|
||||
<Column field="id" header="ID Terminal" sortable>
|
||||
<template #body="{ data }">
|
||||
<span class="font-medium">{{ data.id }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="alias" header="Alias" sortable>
|
||||
<template #body="{ data }">
|
||||
{{ data.alias }}
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="model" header="Modelo" sortable>
|
||||
<template #body="{ data }">
|
||||
{{ data.model }}
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="status" header="Estado" sortable>
|
||||
<template #body="{ data }">
|
||||
<Tag :value="getTerminalStatusLabel(data.status)"
|
||||
:severity="getTerminalStatusSeverity(data.status)" />
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="Acciones" style="width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex gap-2">
|
||||
<Button icon="pi pi-pencil" severity="secondary" text rounded
|
||||
@click="editTerminal(data)" v-tooltip="'Editar terminal'" />
|
||||
<Button icon="pi pi-trash" severity="danger" text rounded @click="deleteTerminal(data)"
|
||||
v-tooltip="'Eliminar terminal'" />
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import Breadcrumb from 'primevue/breadcrumb';
|
||||
import Button from 'primevue/button';
|
||||
import Card from 'primevue/card';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import IconField from 'primevue/iconfield';
|
||||
import InputIcon from 'primevue/inputicon';
|
||||
import DataTable from 'primevue/datatable';
|
||||
import Column from 'primevue/column';
|
||||
import Tag from 'primevue/tag';
|
||||
import RadioButton from 'primevue/radiobutton';
|
||||
import Dropdown from 'primevue/dropdown';
|
||||
import Toast from 'primevue/toast';
|
||||
import { storesService } from '../services/storesServices';
|
||||
import type { Store } from '../types/store';
|
||||
|
||||
const route = useRoute();
|
||||
const toast = useToast();
|
||||
|
||||
// Reactive data
|
||||
const storeData = ref<Store | null>(null);
|
||||
const loading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const loadingProducts = ref(false);
|
||||
const loadingTerminals = ref(false);
|
||||
|
||||
// Configuration
|
||||
const sourceType = ref('catalog');
|
||||
const selectedWarehouse = ref(null);
|
||||
const productSearchQuery = ref('');
|
||||
const terminalSearchQuery = ref('');
|
||||
|
||||
// Breadcrumb
|
||||
const breadcrumbHome = ref({
|
||||
icon: 'pi pi-home',
|
||||
route: '/'
|
||||
});
|
||||
|
||||
const breadcrumbItems = ref([
|
||||
{ label: 'Puntos de Venta', route: '/stores' },
|
||||
{ label: 'Detalles' }
|
||||
]);
|
||||
|
||||
// Mock data - Replace with real API calls
|
||||
const warehouseOptions = ref([
|
||||
{ id: 1, name: 'Almacén Central' },
|
||||
{ id: 2, name: 'Almacén Norte' },
|
||||
{ id: 3, name: 'Almacén Sur' }
|
||||
]);
|
||||
|
||||
const products = ref([
|
||||
{
|
||||
id: 1,
|
||||
sku: 'CAFE-GRN-01',
|
||||
name: 'Café Grano Entero 1kg',
|
||||
category: 'Café',
|
||||
price: 250.00
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sku: 'ACC-TZA-05',
|
||||
name: 'Taza de Cerámica Blanca',
|
||||
category: 'Accesorios',
|
||||
price: 120.00
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
sku: 'PST-CHC-12',
|
||||
name: 'Pastel de Chocolate',
|
||||
category: 'Repostería',
|
||||
price: 45.00
|
||||
}
|
||||
]);
|
||||
|
||||
const terminals = ref([
|
||||
{
|
||||
id: 'TERM-01A',
|
||||
alias: 'Caja Principal',
|
||||
model: 'Verifone VX520',
|
||||
status: 'online'
|
||||
},
|
||||
{
|
||||
id: 'TERM-02B',
|
||||
alias: 'Mostrador',
|
||||
model: 'Ingenico ICT250',
|
||||
status: 'offline'
|
||||
},
|
||||
{
|
||||
id: 'TERM-03C',
|
||||
alias: 'Autoservicio',
|
||||
model: 'Verifone VX520',
|
||||
status: 'inactive'
|
||||
}
|
||||
]);
|
||||
|
||||
// Methods
|
||||
const loadStoreData = async () => {
|
||||
const storeId = route.params.id as string;
|
||||
if (!storeId) return;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// Call the real API service
|
||||
const response = await storesService.getStoreById(parseInt(storeId));
|
||||
|
||||
// The API response structure should be: { status: string, data: { point_of_sale: Store } }
|
||||
if (response.data && response.data.data && response.data.data.point_of_sale) {
|
||||
storeData.value = response.data.data.point_of_sale;
|
||||
|
||||
// Update breadcrumb with store name
|
||||
if (storeData.value && breadcrumbItems.value[1]) {
|
||||
breadcrumbItems.value[1].label = storeData.value.name;
|
||||
}
|
||||
} else {
|
||||
throw new Error('Datos de tienda no encontrados en la respuesta');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading store data:', error);
|
||||
|
||||
let errorMessage = 'No se pudo cargar la información de la tienda';
|
||||
if (error instanceof Error) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Error al Cargar Tienda',
|
||||
detail: errorMessage,
|
||||
life: 5000
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string | undefined) => {
|
||||
if (!dateString) return 'N/A';
|
||||
return new Date(dateString).toLocaleDateString('es-ES', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat('es-MX', {
|
||||
style: 'currency',
|
||||
currency: 'MXN'
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
const getTerminalStatusLabel = (status: string) => {
|
||||
const labels = {
|
||||
'online': 'Online',
|
||||
'offline': 'Offline',
|
||||
'inactive': 'Inactiva'
|
||||
};
|
||||
return labels[status as keyof typeof labels] || status;
|
||||
};
|
||||
|
||||
const getTerminalStatusSeverity = (status: string) => {
|
||||
const severities = {
|
||||
'online': 'success',
|
||||
'offline': 'danger',
|
||||
'inactive': 'warning'
|
||||
};
|
||||
return severities[status as keyof typeof severities] || 'secondary';
|
||||
};
|
||||
|
||||
// Action methods
|
||||
const editStore = () => {
|
||||
// TODO: Implement edit functionality
|
||||
// This could navigate to edit route or open a modal
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Editar Tienda',
|
||||
detail: 'Funcionalidad de edición próximamente',
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const saveConfiguration = async () => {
|
||||
try {
|
||||
isSaving.value = true;
|
||||
// TODO: Implement save configuration API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Configuración Guardada',
|
||||
detail: 'La configuración se ha guardado exitosamente',
|
||||
life: 3000
|
||||
});
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'No se pudo guardar la configuración',
|
||||
life: 3000
|
||||
});
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const addProduct = () => {
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Añadir Producto',
|
||||
detail: 'Funcionalidad próximamente',
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const removeProduct = (product: any) => {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: 'Quitar Producto',
|
||||
detail: `¿Quitar ${product.name}?`,
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const addTerminal = () => {
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Añadir Terminal',
|
||||
detail: 'Funcionalidad próximamente',
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const editTerminal = (terminal: any) => {
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Editar Terminal',
|
||||
detail: `Editando ${terminal.alias}`,
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const deleteTerminal = (terminal: any) => {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: 'Eliminar Terminal',
|
||||
detail: `¿Eliminar ${terminal.alias}?`,
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadStoreData();
|
||||
});
|
||||
</script>
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import Card from 'primevue/card';
|
||||
@ -18,6 +19,7 @@ import { storesService } from '../services/storesServices';
|
||||
import StoreForm from './StoreForm.vue';
|
||||
import type { Store, CreateStoreData } from '../types/store';
|
||||
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
|
||||
@ -106,6 +108,14 @@ const openCreateDialog = () => {
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const handleViewDetails = (store: Store) => {
|
||||
// Navigate to store details page
|
||||
router.push({
|
||||
name: 'StoreDetails',
|
||||
params: { id: store.id }
|
||||
});
|
||||
};
|
||||
|
||||
const handleEdit = (store: Store) => {
|
||||
selectedStore.value = store;
|
||||
isEditing.value = true;
|
||||
@ -337,6 +347,14 @@ onMounted(() => {
|
||||
<Column header="Acciones" headerStyle="text-align: right" bodyStyle="text-align: right">
|
||||
<template #body="{ data }">
|
||||
<div class="flex gap-2 justify-end">
|
||||
<Button
|
||||
icon="pi pi-eye"
|
||||
severity="info"
|
||||
text
|
||||
rounded
|
||||
@click="handleViewDetails(data)"
|
||||
v-tooltip.top="'Ver Detalles'"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-pencil"
|
||||
severity="secondary"
|
||||
|
||||
363
src/modules/stores/components/index.html
Normal file
363
src/modules/stores/components/index.html
Normal file
@ -0,0 +1,363 @@
|
||||
<main class="w-full flex-1 p-6 lg:p-8">
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<a class="text-sm font-medium text-primary hover:underline" href="#">Puntos de Venta</a>
|
||||
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">/</span>
|
||||
<span class="text-sm font-medium text-gray-800 dark:text-gray-200">Sucursal Centro</span>
|
||||
</div>
|
||||
<div class="mt-4 flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h1 class="text-3xl font-black tracking-tight text-[#111418] dark:text-white">Sucursal Centro</h1>
|
||||
<p class="text-base text-[#617589] dark:text-gray-400">Visualiza y gestiona la información de este punto
|
||||
de venta.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 rounded-xl border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-900">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-[#111418] dark:text-white">Información General</h2>
|
||||
<p class="mt-1 text-sm text-[#617589] dark:text-gray-400">Datos principales del punto de venta.</p>
|
||||
</div>
|
||||
<button
|
||||
class="flex h-10 cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-lg bg-gray-100 px-4 text-sm font-bold text-[#111418] hover:bg-gray-200 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700">
|
||||
<span class="material-symbols-outlined text-lg">edit</span>
|
||||
<span class="truncate">Editar Información</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mt-6 grid grid-cols-1 gap-x-4 gap-y-6 border-t border-gray-200 pt-6 sm:grid-cols-2 lg:grid-cols-3 dark:border-gray-700">
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm text-[#617589] dark:text-gray-400">ID del Punto de Venta</p>
|
||||
<p class="text-sm font-medium text-[#111418] dark:text-white">POS-00123</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm text-[#617589] dark:text-gray-400">Dirección</p>
|
||||
<p class="text-sm font-medium text-[#111418] dark:text-white">Av. Principal 123, Ciudad Capital</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm text-[#617589] dark:text-gray-400">Estado</p>
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||
<span class="size-1.5 rounded-full bg-green-500"></span> Activo
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm text-[#617589] dark:text-gray-400">Teléfono</p>
|
||||
<p class="text-sm font-medium text-[#111418] dark:text-white">+52 55 1234 5678</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm text-[#617589] dark:text-gray-400">Fecha de Creación</p>
|
||||
<p class="text-sm font-medium text-[#111418] dark:text-white">15 de Enero, 2023</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm text-[#617589] dark:text-gray-400">Última Actualización</p>
|
||||
<p class="text-sm font-medium text-[#111418] dark:text-white">02 de Julio, 2024</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 rounded-xl border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-900">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-[#111418] dark:text-white">Configuración</h2>
|
||||
<p class="mt-1 text-sm text-[#617589] dark:text-gray-400">Ajustes de inventario y catálogo para este
|
||||
punto de venta.</p>
|
||||
</div>
|
||||
<button
|
||||
class="flex h-10 cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-lg bg-gray-100 px-4 text-sm font-bold text-[#111418] hover:bg-gray-200 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700">
|
||||
<span class="material-symbols-outlined text-lg">save</span>
|
||||
<span class="truncate">Guardar Cambios</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-6 border-t border-gray-200 pt-6 dark:border-gray-700">
|
||||
<fieldset class="space-y-6">
|
||||
<legend class="text-base font-semibold text-[#111418] dark:text-white">Fuente de Productos y Stock
|
||||
</legend>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<label
|
||||
class="flex cursor-pointer flex-col rounded-lg border border-primary bg-primary/5 p-4 ring-2 ring-primary dark:border-primary dark:bg-primary/20"
|
||||
for="catalogo-general">
|
||||
<div class="flex items-center">
|
||||
<input checked=""
|
||||
class="h-4 w-4 border-gray-300 text-primary focus:ring-primary dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-900"
|
||||
id="catalogo-general" name="fuente-stock" type="radio" value="catalogo" />
|
||||
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">Catálogo
|
||||
General</span>
|
||||
</div>
|
||||
<p class="ml-7 mt-1 text-sm text-gray-500 dark:text-gray-400">Usar productos asignados
|
||||
directamente a este punto de venta desde el catálogo maestro.</p>
|
||||
</label>
|
||||
<label
|
||||
class="flex cursor-pointer flex-col rounded-lg border border-gray-300 bg-white p-4 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700/50"
|
||||
for="almacen-especifico">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
class="h-4 w-4 border-gray-300 text-primary focus:ring-primary dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-900"
|
||||
id="almacen-especifico" name="fuente-stock" type="radio" value="almacen" />
|
||||
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-white">Extraer de un
|
||||
Almacén</span>
|
||||
</div>
|
||||
<p class="ml-7 mt-1 text-sm text-gray-500 dark:text-gray-400">Sincronizar el inventario de
|
||||
productos directamente desde un almacén específico.</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
for="almacen-selector">Seleccionar Almacén</label>
|
||||
<select
|
||||
class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm focus:border-primary focus:ring-primary disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:disabled:bg-gray-800"
|
||||
disabled="" id="almacen-selector">
|
||||
<option>Selecciona un almacén...</option>
|
||||
<option>Almacén Central</option>
|
||||
<option>Almacén Norte</option>
|
||||
<option>Almacén Sur</option>
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Esta opción se habilita al seleccionar
|
||||
"Extraer de un Almacén".</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-[#111418] dark:text-white">Productos Asignados</h2>
|
||||
<p class="mt-1 text-sm text-[#617589] dark:text-gray-400">Catálogo de productos disponibles en esta
|
||||
sucursal.</p>
|
||||
</div>
|
||||
<button
|
||||
class="flex h-10 min-w-[84px] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-lg bg-primary px-4 text-sm font-bold text-white shadow-sm hover:bg-primary/90">
|
||||
<span class="material-symbols-outlined text-lg">add_circle</span>
|
||||
<span class="truncate">Añadir Producto</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative mt-4">
|
||||
<span
|
||||
class="material-symbols-outlined pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">search</span>
|
||||
<input
|
||||
class="w-full rounded-lg border border-gray-300 bg-white py-2 pl-10 pr-4 text-sm focus:border-primary focus:ring-primary dark:border-gray-700 dark:bg-gray-800 dark:text-white dark:focus:border-primary"
|
||||
placeholder="Buscar producto por SKU, nombre o categoría..." type="text" />
|
||||
</div>
|
||||
<div class="mt-4 flow-root">
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-gray-800">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
|
||||
<thead class="bg-gray-50 dark:bg-gray-900">
|
||||
<tr>
|
||||
<th class="px-6 py-3.5 text-left text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
scope="col">SKU</th>
|
||||
<th class="px-3 py-3.5 text-left text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
scope="col">Producto</th>
|
||||
<th class="px-3 py-3.5 text-left text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
scope="col">Categoría</th>
|
||||
<th class="px-3 py-3.5 text-left text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
scope="col">Precio</th>
|
||||
<th class="relative py-3.5 pl-3 pr-4 sm:pr-6" scope="col"><span
|
||||
class="sr-only">Acciones</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="divide-y divide-gray-200 bg-white dark:divide-gray-800 dark:bg-gray-900/50">
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
CAFE-GRN-01</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Café Grano Entero 1kg</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Café</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
$250.00</td>
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
class="text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-500"><span
|
||||
class="material-symbols-outlined text-xl">remove_circle</span></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
ACC-TZA-05</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Taza de Cerámica Blanca</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Accesorios</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
$120.00</td>
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
class="text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-500"><span
|
||||
class="material-symbols-outlined text-xl">remove_circle</span></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
PST-CHC-12</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Pastel de Chocolate</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Repostería</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
$45.00</td>
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
class="text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-500"><span
|
||||
class="material-symbols-outlined text-xl">remove_circle</span></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-[#111418] dark:text-white">Gestionar Terminales</h2>
|
||||
<p class="mt-1 text-sm text-[#617589] dark:text-gray-400">Lista de terminales asociadas a este punto
|
||||
de venta.</p>
|
||||
</div>
|
||||
<button
|
||||
class="flex h-10 min-w-[84px] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-lg bg-primary px-4 text-sm font-bold text-white shadow-sm hover:bg-primary/90">
|
||||
<span class="material-symbols-outlined text-lg">add_circle</span>
|
||||
<span class="truncate">Añadir Terminal</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative mt-4">
|
||||
<span
|
||||
class="material-symbols-outlined pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">search</span>
|
||||
<input
|
||||
class="w-full rounded-lg border border-gray-300 bg-white py-2 pl-10 pr-4 text-sm focus:border-primary focus:ring-primary dark:border-gray-700 dark:bg-gray-800 dark:text-white dark:focus:border-primary"
|
||||
placeholder="Buscar terminal por ID, nombre o modelo..." type="text" />
|
||||
</div>
|
||||
<div class="mt-4 flow-root">
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-gray-800">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800">
|
||||
<thead class="bg-gray-50 dark:bg-gray-900">
|
||||
<tr>
|
||||
<th class="px-6 py-3.5 text-left text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
scope="col">ID Terminal</th>
|
||||
<th class="px-3 py-3.5 text-left text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
scope="col">Alias</th>
|
||||
<th class="px-3 py-3.5 text-left text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
scope="col">Modelo</th>
|
||||
<th class="px-3 py-3.5 text-left text-xs font-semibold text-gray-600 dark:text-gray-300"
|
||||
scope="col">Estado</th>
|
||||
<th class="relative py-3.5 pl-3 pr-4 sm:pr-6" scope="col"><span
|
||||
class="sr-only">Acciones</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="divide-y divide-gray-200 bg-white dark:divide-gray-800 dark:bg-gray-900/50">
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
TERM-01A</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Caja Principal</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Verifone VX520</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-200">Online</span>
|
||||
</td>
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
class="text-gray-500 hover:text-primary dark:text-gray-400 dark:hover:text-primary"><span
|
||||
class="material-symbols-outlined text-xl">edit</span></button>
|
||||
<button
|
||||
class="text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-500"><span
|
||||
class="material-symbols-outlined text-xl">delete</span></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
TERM-02B</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Mostrador</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Ingenico ICT250</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800 dark:bg-red-900 dark:text-red-200">Offline</span>
|
||||
</td>
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
class="text-gray-500 hover:text-primary dark:text-gray-400 dark:hover:text-primary"><span
|
||||
class="material-symbols-outlined text-xl">edit</span></button>
|
||||
<button
|
||||
class="text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-500"><span
|
||||
class="material-symbols-outlined text-xl">delete</span></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
TERM-03C</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Autoservicio</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Verifone VX520</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">Inactiva</span>
|
||||
</td>
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
class="text-gray-500 hover:text-primary dark:text-gray-400 dark:hover:text-primary"><span
|
||||
class="material-symbols-outlined text-xl">edit</span></button>
|
||||
<button
|
||||
class="text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-500"><span
|
||||
class="material-symbols-outlined text-xl">delete</span></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
3
src/modules/stores/services/terminalServices.ts
Normal file
3
src/modules/stores/services/terminalServices.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const terminalServices = {
|
||||
|
||||
};
|
||||
9
src/modules/stores/types/store.d.ts
vendored
9
src/modules/stores/types/store.d.ts
vendored
@ -7,6 +7,7 @@ export interface Store {
|
||||
name: string;
|
||||
location: string;
|
||||
is_active: boolean;
|
||||
phone?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at: string | null;
|
||||
@ -53,9 +54,11 @@ export interface StoreResponse {
|
||||
data: StoreData;
|
||||
}
|
||||
|
||||
export interface SingleStoreData {
|
||||
point_of_sale: Store;
|
||||
}
|
||||
|
||||
export interface SingleStoreResponse {
|
||||
status: string;
|
||||
data: {
|
||||
point_of_sale: Store;
|
||||
};
|
||||
data: SingleStoreData;
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import StoresIndex from '../modules/stores/components/StoresIndex.vue';
|
||||
import RolesIndex from '../modules/users/components/RoleIndex.vue';
|
||||
import RoleForm from '../modules/users/components/RoleForm.vue';
|
||||
import UserIndex from '../modules/users/components/UserIndex.vue';
|
||||
import StoreDetails from '../modules/stores/components/StoreDetails.vue';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
@ -154,7 +155,16 @@ const routes: RouteRecordRaw[] = [
|
||||
meta: {
|
||||
title: 'Tiendas',
|
||||
requiresAuth: true
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'stores/:id',
|
||||
name: 'StoreDetails',
|
||||
component: StoreDetails,
|
||||
meta: {
|
||||
title: 'Detalles de la Tienda',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user