feature-comercial-module-ts #13

Merged
edgar.mendez merged 38 commits from feature-comercial-module-ts into develop 2026-03-04 15:07:09 +00:00
6 changed files with 896 additions and 4 deletions
Showing only changes of commit 730cae825c - Show all commits

View 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>

View File

@ -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"

View 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>

View File

@ -0,0 +1,3 @@
export const terminalServices = {
};

View File

@ -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;
}

View File

@ -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',