feature-comercial-module-ts #13
157
src/modules/stores/components/StoreForm.vue
Normal file
157
src/modules/stores/components/StoreForm.vue
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
import Textarea from 'primevue/textarea';
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
import InputSwitch from 'primevue/inputswitch';
|
||||||
|
import type { Store, CreateStoreData } from '../types/store';
|
||||||
|
|
||||||
|
// Props
|
||||||
|
interface Props {
|
||||||
|
store?: Store | null;
|
||||||
|
isEditing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
store: null,
|
||||||
|
isEditing: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits<{
|
||||||
|
save: [data: CreateStoreData];
|
||||||
|
cancel: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// Form data
|
||||||
|
const formData = ref<CreateStoreData>({
|
||||||
|
name: '',
|
||||||
|
location: '',
|
||||||
|
is_active: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Track if form has been touched
|
||||||
|
const touched = ref(false);
|
||||||
|
|
||||||
|
// Watch for store changes (when editing)
|
||||||
|
watch(() => props.store, (newStore) => {
|
||||||
|
if (newStore) {
|
||||||
|
formData.value = {
|
||||||
|
name: newStore.name,
|
||||||
|
location: newStore.location,
|
||||||
|
is_active: newStore.is_active
|
||||||
|
};
|
||||||
|
touched.value = false;
|
||||||
|
} else {
|
||||||
|
// Reset form
|
||||||
|
formData.value = {
|
||||||
|
name: '',
|
||||||
|
location: '',
|
||||||
|
is_active: true
|
||||||
|
};
|
||||||
|
touched.value = false;
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
const isFormValid = () => {
|
||||||
|
return formData.value.name.trim() !== '' &&
|
||||||
|
formData.value.location.trim() !== '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if field should show error
|
||||||
|
const showError = (field: keyof CreateStoreData) => {
|
||||||
|
if (!touched.value) return false;
|
||||||
|
if (field === 'name') return formData.value.name.trim() === '';
|
||||||
|
if (field === 'location') return formData.value.location.trim() === '';
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Form submission
|
||||||
|
const handleSubmit = () => {
|
||||||
|
touched.value = true;
|
||||||
|
if (!isFormValid()) return;
|
||||||
|
|
||||||
|
emit('save', formData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="store-form">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Store Name -->
|
||||||
|
<div>
|
||||||
|
<label for="store-name" class="block text-sm font-medium mb-2 text-surface-900 dark:text-white">
|
||||||
|
Nombre del Punto de Venta *
|
||||||
|
</label>
|
||||||
|
<InputText
|
||||||
|
id="store-name"
|
||||||
|
v-model="formData.name"
|
||||||
|
class="w-full"
|
||||||
|
placeholder="ej., Sucursal Centro"
|
||||||
|
:invalid="showError('name')"
|
||||||
|
/>
|
||||||
|
<small class="text-surface-500 dark:text-surface-400 block mt-1">
|
||||||
|
Ingresa un nombre descriptivo para el punto de venta
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location -->
|
||||||
|
<div>
|
||||||
|
<label for="location" class="block text-sm font-medium mb-2 text-surface-900 dark:text-white">
|
||||||
|
Ubicación *
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
id="location"
|
||||||
|
v-model="formData.location"
|
||||||
|
class="w-full"
|
||||||
|
rows="3"
|
||||||
|
placeholder="ej., Av. Principal 123, Ciudad, Estado, CP 12345"
|
||||||
|
:invalid="showError('location')"
|
||||||
|
/>
|
||||||
|
<small class="text-surface-500 dark:text-surface-400 block mt-1">
|
||||||
|
Dirección física completa del punto de venta
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Status -->
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<label for="is-active" class="text-sm font-medium text-surface-900 dark:text-white">
|
||||||
|
Estado Activo
|
||||||
|
</label>
|
||||||
|
<InputSwitch
|
||||||
|
id="is-active"
|
||||||
|
v-model="formData.is_active"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-surface-500 dark:text-surface-400">
|
||||||
|
{{ formData.is_active ? 'Activo' : 'Inactivo' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Form Actions -->
|
||||||
|
<div class="mt-6 flex justify-end items-center gap-3">
|
||||||
|
<Button
|
||||||
|
label="Cancelar"
|
||||||
|
severity="secondary"
|
||||||
|
outlined
|
||||||
|
@click="handleCancel"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="isEditing ? 'Actualizar' : 'Guardar'"
|
||||||
|
:disabled="!isFormValid()"
|
||||||
|
@click="handleSubmit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.store-form {
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,5 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { useConfirm } from 'primevue/useconfirm';
|
||||||
import Card from 'primevue/card';
|
import Card from 'primevue/card';
|
||||||
import InputText from 'primevue/inputtext';
|
import InputText from 'primevue/inputtext';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
@ -9,6 +11,15 @@ import Tag from 'primevue/tag';
|
|||||||
import IconField from 'primevue/iconfield';
|
import IconField from 'primevue/iconfield';
|
||||||
import InputIcon from 'primevue/inputicon';
|
import InputIcon from 'primevue/inputicon';
|
||||||
import Dropdown from 'primevue/dropdown';
|
import Dropdown from 'primevue/dropdown';
|
||||||
|
import Dialog from 'primevue/dialog';
|
||||||
|
import Toast from 'primevue/toast';
|
||||||
|
import ConfirmDialog from 'primevue/confirmdialog';
|
||||||
|
import { storesService } from '../services/storesServices';
|
||||||
|
import StoreForm from './StoreForm.vue';
|
||||||
|
import type { Store, CreateStoreData } from '../types/store';
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
const confirm = useConfirm();
|
||||||
|
|
||||||
// Search and filters
|
// Search and filters
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
@ -16,12 +27,28 @@ const selectedStatus = ref('all');
|
|||||||
const selectedType = ref('all');
|
const selectedType = ref('all');
|
||||||
const selectedLocation = ref('all');
|
const selectedLocation = ref('all');
|
||||||
|
|
||||||
|
// Data and loading state
|
||||||
|
const stores = ref<Store[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const totalRecords = ref(0);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const rowsPerPage = ref(5);
|
||||||
|
|
||||||
|
// Dialog state
|
||||||
|
const showDialog = ref(false);
|
||||||
|
const isEditing = ref(false);
|
||||||
|
const selectedStore = ref<Store | null>(null);
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
const dialogTitle = computed(() =>
|
||||||
|
isEditing.value ? 'Editar Punto de Venta' : 'Nuevo Punto de Venta'
|
||||||
|
);
|
||||||
|
|
||||||
// Filter options
|
// Filter options
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
{ label: 'Todos', value: 'all' },
|
{ label: 'Todos', value: 'all' },
|
||||||
{ label: 'Activo', value: 'active' },
|
{ label: 'Activo', value: 'active' },
|
||||||
{ label: 'Inactivo', value: 'inactive' },
|
{ label: 'Inactivo', value: 'inactive' },
|
||||||
{ label: 'Archivado', value: 'archived' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
@ -38,65 +65,121 @@ const locationOptions = [
|
|||||||
{ label: 'Sur', value: 'sur' },
|
{ label: 'Sur', value: 'sur' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Sample data
|
// Fetch stores from API
|
||||||
const stores = ref([
|
const fetchStores = async () => {
|
||||||
{
|
loading.value = true;
|
||||||
id: 1,
|
try {
|
||||||
name: 'Tienda Principal',
|
const response = await storesService.getStores(currentPage.value, rowsPerPage.value);
|
||||||
location: 'Av. Siempre Viva 742',
|
|
||||||
status: 'active',
|
if (response.data.status === 'success') {
|
||||||
terminals: 5,
|
stores.value = response.data.data.points_of_sale.data;
|
||||||
lastActivity: 'Hoy, 10:45 AM'
|
totalRecords.value = response.data.data.points_of_sale.total;
|
||||||
},
|
currentPage.value = response.data.data.points_of_sale.current_page;
|
||||||
{
|
|
||||||
id: 2,
|
console.log('✅ Stores loaded:', stores.value.length);
|
||||||
name: 'Sucursal Centro',
|
}
|
||||||
location: 'Calle Falsa 123',
|
} catch (error) {
|
||||||
status: 'active',
|
console.error('❌ Error loading stores:', error);
|
||||||
terminals: 3,
|
stores.value = [];
|
||||||
lastActivity: 'Ayer, 08:15 PM'
|
toast.add({
|
||||||
},
|
severity: 'error',
|
||||||
{
|
summary: 'Error',
|
||||||
id: 3,
|
detail: 'No se pudieron cargar los puntos de venta.',
|
||||||
name: 'Kiosco del Parque',
|
life: 3000
|
||||||
location: 'Parque Central, Stand 4',
|
});
|
||||||
status: 'inactive',
|
} finally {
|
||||||
terminals: 1,
|
loading.value = false;
|
||||||
lastActivity: '25/08/2023'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: 'Almacén Norte',
|
|
||||||
location: 'Blvd. Industrial 500',
|
|
||||||
status: 'active',
|
|
||||||
terminals: 8,
|
|
||||||
lastActivity: 'Hoy, 11:00 AM'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: 'Pop-Up de Verano',
|
|
||||||
location: 'Playa Grande, Paseo Marítimo',
|
|
||||||
status: 'archived',
|
|
||||||
terminals: 2,
|
|
||||||
lastActivity: '15/07/2023'
|
|
||||||
}
|
}
|
||||||
]);
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
const handleCreateStore = () => {
|
|
||||||
console.log('Create new store');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (store: any) => {
|
// Pagination handler
|
||||||
console.log('Edit store:', store);
|
const onPage = (event: any) => {
|
||||||
|
currentPage.value = event.page + 1;
|
||||||
|
rowsPerPage.value = event.rows;
|
||||||
|
fetchStores();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleView = (store: any) => {
|
// Dialog methods
|
||||||
console.log('View store:', store);
|
const openCreateDialog = () => {
|
||||||
|
selectedStore.value = null;
|
||||||
|
isEditing.value = false;
|
||||||
|
showDialog.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleStatus = (store: any) => {
|
const handleEdit = (store: Store) => {
|
||||||
console.log('Toggle status:', store);
|
selectedStore.value = store;
|
||||||
|
isEditing.value = true;
|
||||||
|
showDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveStore = async (storeData: CreateStoreData) => {
|
||||||
|
try {
|
||||||
|
if (isEditing.value && selectedStore.value) {
|
||||||
|
await storesService.updateStore(selectedStore.value.id, storeData);
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Punto de Venta Actualizado',
|
||||||
|
detail: 'El punto de venta ha sido actualizado exitosamente.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await storesService.createStore(storeData);
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Punto de Venta Creado',
|
||||||
|
detail: 'El punto de venta ha sido creado exitosamente.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
showDialog.value = false;
|
||||||
|
selectedStore.value = null;
|
||||||
|
await fetchStores();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error saving store:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'No se pudo guardar el punto de venta.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelForm = () => {
|
||||||
|
showDialog.value = false;
|
||||||
|
selectedStore.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteStore = (store: Store) => {
|
||||||
|
confirm.require({
|
||||||
|
message: `¿Estás seguro de eliminar el punto de venta "${store.name}"?`,
|
||||||
|
header: 'Confirmar Eliminación',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
rejectLabel: 'Cancelar',
|
||||||
|
acceptLabel: 'Eliminar',
|
||||||
|
rejectClass: 'p-button-secondary p-button-outlined',
|
||||||
|
acceptClass: 'p-button-danger',
|
||||||
|
accept: async () => {
|
||||||
|
try {
|
||||||
|
await storesService.deleteStore(store.id);
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Punto de Venta Eliminado',
|
||||||
|
detail: `El punto de venta "${store.name}" ha sido eliminado exitosamente.`,
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
await fetchStores();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error deleting store:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'No se pudo eliminar el punto de venta.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
@ -106,29 +189,27 @@ const clearFilters = () => {
|
|||||||
selectedLocation.value = 'all';
|
selectedLocation.value = 'all';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get status label
|
// Get status configuration
|
||||||
const getStatusLabel = (status: string) => {
|
const getStatusConfig = (isActive: boolean) => {
|
||||||
const statusMap: Record<string, string> = {
|
return isActive
|
||||||
active: 'Activo',
|
? { label: 'Activo', severity: 'success' as const }
|
||||||
inactive: 'Inactivo',
|
: { label: 'Inactivo', severity: 'warning' as const };
|
||||||
archived: 'Archivado'
|
|
||||||
};
|
|
||||||
return statusMap[status] || status;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get status severity
|
// Load stores on mount
|
||||||
const getStatusSeverity = (status: string) => {
|
onMounted(() => {
|
||||||
const severityMap: Record<string, 'success' | 'warning' | 'secondary'> = {
|
fetchStores();
|
||||||
active: 'success',
|
});
|
||||||
inactive: 'warning',
|
|
||||||
archived: 'secondary'
|
|
||||||
};
|
|
||||||
return severityMap[status] || 'secondary';
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
|
<!-- Toast Notifications -->
|
||||||
|
<Toast position="bottom-right" />
|
||||||
|
|
||||||
|
<!-- Confirm Dialog -->
|
||||||
|
<ConfirmDialog />
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex flex-wrap justify-between gap-4 items-center">
|
<div class="flex flex-wrap justify-between gap-4 items-center">
|
||||||
<div class="flex min-w-72 flex-col gap-1">
|
<div class="flex min-w-72 flex-col gap-1">
|
||||||
@ -142,7 +223,7 @@ const getStatusSeverity = (status: string) => {
|
|||||||
<Button
|
<Button
|
||||||
label="Crear Nuevo Punto de Venta"
|
label="Crear Nuevo Punto de Venta"
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
@click="handleCreateStore"
|
@click="openCreateDialog"
|
||||||
class="min-w-[200px]"
|
class="min-w-[200px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -199,11 +280,15 @@ const getStatusSeverity = (status: string) => {
|
|||||||
<!-- Table -->
|
<!-- Table -->
|
||||||
<DataTable
|
<DataTable
|
||||||
:value="stores"
|
:value="stores"
|
||||||
|
:loading="loading"
|
||||||
:paginator="true"
|
:paginator="true"
|
||||||
:rows="5"
|
:rows="rowsPerPage"
|
||||||
|
:totalRecords="totalRecords"
|
||||||
:rowsPerPageOptions="[5, 10, 20, 50]"
|
:rowsPerPageOptions="[5, 10, 20, 50]"
|
||||||
stripedRows
|
stripedRows
|
||||||
responsiveLayout="scroll"
|
responsiveLayout="scroll"
|
||||||
|
lazy
|
||||||
|
@page="onPage"
|
||||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
||||||
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} resultados"
|
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} resultados"
|
||||||
>
|
>
|
||||||
@ -226,29 +311,24 @@ const getStatusSeverity = (status: string) => {
|
|||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<!-- Status Column -->
|
<!-- Status Column -->
|
||||||
<Column field="status" header="Estado" :sortable="true">
|
<Column field="is_active" header="Estado" :sortable="true">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Tag
|
<Tag
|
||||||
:value="getStatusLabel(data.status)"
|
:value="getStatusConfig(data.is_active).label"
|
||||||
:severity="getStatusSeverity(data.status)"
|
:severity="getStatusConfig(data.is_active).severity"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<!-- Terminals Column -->
|
<!-- Created Date Column -->
|
||||||
<Column field="terminals" header="Terminales" :sortable="true">
|
<Column field="created_at" header="Fecha de Creación" :sortable="true">
|
||||||
<template #body="{ data }">
|
|
||||||
<span class="text-center block text-surface-500 dark:text-surface-400 text-sm">
|
|
||||||
{{ data.terminals }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
<!-- Last Activity Column -->
|
|
||||||
<Column field="lastActivity" header="Última Actividad" :sortable="true">
|
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<span class="text-surface-500 dark:text-surface-400 text-sm">
|
<span class="text-surface-500 dark:text-surface-400 text-sm">
|
||||||
{{ data.lastActivity }}
|
{{ new Date(data.created_at).toLocaleDateString('es-ES', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
}) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
@ -266,39 +346,12 @@ const getStatusSeverity = (status: string) => {
|
|||||||
v-tooltip.top="'Editar'"
|
v-tooltip.top="'Editar'"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-eye"
|
icon="pi pi-trash"
|
||||||
severity="secondary"
|
|
||||||
text
|
|
||||||
rounded
|
|
||||||
@click="handleView(data)"
|
|
||||||
v-tooltip.top="'Ver detalles'"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
v-if="data.status === 'active'"
|
|
||||||
icon="pi pi-power-off"
|
|
||||||
severity="danger"
|
severity="danger"
|
||||||
text
|
text
|
||||||
rounded
|
rounded
|
||||||
@click="handleToggleStatus(data)"
|
@click="handleDeleteStore(data)"
|
||||||
v-tooltip.top="'Desactivar'"
|
v-tooltip.top="'Eliminar'"
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
v-else-if="data.status === 'inactive'"
|
|
||||||
icon="pi pi-check"
|
|
||||||
severity="success"
|
|
||||||
text
|
|
||||||
rounded
|
|
||||||
@click="handleToggleStatus(data)"
|
|
||||||
v-tooltip.top="'Activar'"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
v-else
|
|
||||||
icon="pi pi-inbox"
|
|
||||||
severity="secondary"
|
|
||||||
text
|
|
||||||
rounded
|
|
||||||
@click="handleToggleStatus(data)"
|
|
||||||
v-tooltip.top="'Desarchivar'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -306,6 +359,22 @@ const getStatusSeverity = (status: string) => {
|
|||||||
</DataTable>
|
</DataTable>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<!-- Create/Edit Dialog -->
|
||||||
|
<Dialog
|
||||||
|
v-model:visible="showDialog"
|
||||||
|
modal
|
||||||
|
:header="dialogTitle"
|
||||||
|
:style="{ width: '90vw', maxWidth: '800px' }"
|
||||||
|
:contentStyle="{ padding: '1.5rem' }"
|
||||||
|
>
|
||||||
|
<StoreForm
|
||||||
|
:store="selectedStore"
|
||||||
|
:isEditing="isEditing"
|
||||||
|
@save="handleSaveStore"
|
||||||
|
@cancel="handleCancelForm"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
106
src/modules/stores/services/storesServices.ts
Normal file
106
src/modules/stores/services/storesServices.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import api from '../../../services/api';
|
||||||
|
import type { StoreResponse, CreateStoreData, UpdateStoreData, SingleStoreResponse } from '../types/store';
|
||||||
|
|
||||||
|
export const storesService = {
|
||||||
|
/**
|
||||||
|
* Get all stores/points of sale with pagination
|
||||||
|
* @param page - Page number (default: 1)
|
||||||
|
* @param perPage - Items per page (default: 20)
|
||||||
|
*/
|
||||||
|
async getStores(page: number = 1, perPage: number = 20) {
|
||||||
|
try {
|
||||||
|
const response = await api.get<StoreResponse>('/api/stores', {
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
per_page: perPage
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('📦 Stores response:', response);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error fetching stores:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single store by ID
|
||||||
|
* @param id - Store ID
|
||||||
|
*/
|
||||||
|
async getStoreById(id: number) {
|
||||||
|
try {
|
||||||
|
const response = await api.get<SingleStoreResponse>(`/api/stores/${id}`);
|
||||||
|
console.log('📦 Store detail:', response);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error fetching store:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new store/point of sale
|
||||||
|
* @param data - Store data
|
||||||
|
*/
|
||||||
|
async createStore(data: CreateStoreData) {
|
||||||
|
try {
|
||||||
|
const response = await api.post<SingleStoreResponse>('/api/stores', data);
|
||||||
|
console.log('✅ Store created:', response);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error creating store:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing store
|
||||||
|
* @param id - Store ID
|
||||||
|
* @param data - Updated store data
|
||||||
|
*/
|
||||||
|
async updateStore(id: number, data: UpdateStoreData) {
|
||||||
|
try {
|
||||||
|
const response = await api.put<SingleStoreResponse>(`/api/stores/${id}`, data);
|
||||||
|
console.log('✅ Store updated:', response);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error updating store:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a store (soft delete)
|
||||||
|
* @param id - Store ID
|
||||||
|
*/
|
||||||
|
async deleteStore(id: number) {
|
||||||
|
try {
|
||||||
|
const response = await api.delete(`/api/stores/${id}`);
|
||||||
|
console.log('✅ Store deleted:', response);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error deleting store:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle store active status
|
||||||
|
* @param id - Store ID
|
||||||
|
* @param isActive - New active status
|
||||||
|
*/
|
||||||
|
async toggleStoreStatus(id: number, isActive: boolean) {
|
||||||
|
try {
|
||||||
|
const response = await api.put<SingleStoreResponse>(`/api/stores/${id}`, {
|
||||||
|
is_active: isActive
|
||||||
|
});
|
||||||
|
console.log('✅ Store status toggled:', response);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error toggling store status:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
61
src/modules/stores/types/store.d.ts
vendored
Normal file
61
src/modules/stores/types/store.d.ts
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Store/Point of Sale Type Definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Store {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
location: string;
|
||||||
|
is_active: boolean;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
deleted_at: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateStoreData {
|
||||||
|
name: string;
|
||||||
|
location: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateStoreData {
|
||||||
|
name?: string;
|
||||||
|
location?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorePagination {
|
||||||
|
current_page: number;
|
||||||
|
data: Store[];
|
||||||
|
first_page_url: string;
|
||||||
|
from: number;
|
||||||
|
last_page: number;
|
||||||
|
last_page_url: string;
|
||||||
|
links: Array<{
|
||||||
|
url: string | null;
|
||||||
|
label: string;
|
||||||
|
active: boolean;
|
||||||
|
}>;
|
||||||
|
next_page_url: string | null;
|
||||||
|
path: string;
|
||||||
|
per_page: number;
|
||||||
|
prev_page_url: string | null;
|
||||||
|
to: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoreData {
|
||||||
|
points_of_sale: StorePagination;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoreResponse {
|
||||||
|
status: string;
|
||||||
|
data: StoreData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SingleStoreResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
point_of_sale: Store;
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user