#49 feat: CRUD admin de organizaciones, proveedores y geografía
This commit is contained in:
@@ -111,6 +111,9 @@
|
||||
{ title: 'Imágenes de Catálogo', route: '/admin/catalogue-images', icon: 'mdi-image-multiple'},
|
||||
{ title: 'Datos de la Tienda', route: '/admin/store-settings', icon: 'mdi-map-marker'},
|
||||
{ title: 'Ver Ventas por Catálogo', route: '/admin/catalog-sales', icon: 'mdi-cart-arrow-down'},
|
||||
{ title: 'Organizaciones', route: '/admin/organizations', icon: 'mdi-domain'},
|
||||
{ title: 'Proveedores', route: '/admin/suppliers', icon: 'mdi-truck'},
|
||||
{ title: 'Geografía', route: '/admin/geography', icon: 'mdi-earth'},
|
||||
{ divider: true },
|
||||
{ header: 'Sincronización Tryton' },
|
||||
{ title: 'Importar Productos', route: '/sincronizar_productos_tryton', icon: 'mdi-download'},
|
||||
|
||||
687
src/components/provenance/admin/GeographyManagement.vue
Normal file
687
src/components/provenance/admin/GeographyManagement.vue
Normal file
@@ -0,0 +1,687 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<h1 class="text-h4 mb-4">Gestión de Geografía</h1>
|
||||
|
||||
<v-tabs v-model="activeTab" color="primary">
|
||||
<v-tab
|
||||
data-testid="geo-tab-countries"
|
||||
value="countries"
|
||||
>
|
||||
Países
|
||||
</v-tab>
|
||||
<v-tab
|
||||
data-testid="geo-tab-departments"
|
||||
value="departments"
|
||||
>
|
||||
Departamentos
|
||||
</v-tab>
|
||||
<v-tab
|
||||
data-testid="geo-tab-municipalities"
|
||||
value="municipalities"
|
||||
>
|
||||
Municipios
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<!-- Países -->
|
||||
<v-row v-if="activeTab === 'countries'" class="mt-2">
|
||||
<v-col class="text-right" cols="12">
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="geo-country-create"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="openCreateCountry"
|
||||
>
|
||||
Nuevo país
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-data-table
|
||||
density="compact"
|
||||
:headers="countryHeaders"
|
||||
item-value="id"
|
||||
:items="countries"
|
||||
items-per-page="25"
|
||||
:items-per-page-options="[10, 25, 50, 100]"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
:data-testid="'geo-country-edit-' + item.id"
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openEditCountry(item)"
|
||||
/>
|
||||
<v-btn
|
||||
color="error"
|
||||
:data-testid="'geo-country-delete-' + item.id"
|
||||
icon="mdi-delete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openDeleteCountry(item)"
|
||||
/>
|
||||
</template>
|
||||
<template #loading>
|
||||
<v-skeleton-loader type="table-row@10" />
|
||||
</template>
|
||||
<template #no-data>
|
||||
<v-alert class="my-4" type="info" variant="tonal">
|
||||
No hay países para mostrar
|
||||
</v-alert>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Departamentos -->
|
||||
<v-row v-if="activeTab === 'departments'" class="mt-2">
|
||||
<v-col class="text-right" cols="12">
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="geo-department-create"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="openCreateDepartment"
|
||||
>
|
||||
Nuevo departamento
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-data-table
|
||||
density="compact"
|
||||
:headers="departmentHeaders"
|
||||
item-value="id"
|
||||
:items="departments"
|
||||
items-per-page="25"
|
||||
:items-per-page-options="[10, 25, 50, 100]"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #item.country="{ item }">
|
||||
{{ item.country_detail?.name }}
|
||||
</template>
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
:data-testid="'geo-department-edit-' + item.id"
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openEditDepartment(item)"
|
||||
/>
|
||||
<v-btn
|
||||
color="error"
|
||||
:data-testid="'geo-department-delete-' + item.id"
|
||||
icon="mdi-delete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openDeleteDepartment(item)"
|
||||
/>
|
||||
</template>
|
||||
<template #loading>
|
||||
<v-skeleton-loader type="table-row@10" />
|
||||
</template>
|
||||
<template #no-data>
|
||||
<v-alert class="my-4" type="info" variant="tonal">
|
||||
No hay departamentos para mostrar
|
||||
</v-alert>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Municipios -->
|
||||
<v-row v-if="activeTab === 'municipalities'" class="mt-2">
|
||||
<v-col class="text-right" cols="12">
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="geo-municipality-create"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="openCreateMunicipality"
|
||||
>
|
||||
Nuevo municipio
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-data-table
|
||||
density="compact"
|
||||
:headers="municipalityHeaders"
|
||||
item-value="id"
|
||||
:items="municipalities"
|
||||
items-per-page="25"
|
||||
:items-per-page-options="[10, 25, 50, 100]"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #item.department="{ item }">
|
||||
{{ item.department_detail?.name }}
|
||||
</template>
|
||||
<template #item.country="{ item }">
|
||||
{{ item.country_detail?.name }}
|
||||
</template>
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
:data-testid="'geo-municipality-edit-' + item.id"
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openEditMunicipality(item)"
|
||||
/>
|
||||
<v-btn
|
||||
color="error"
|
||||
:data-testid="'geo-municipality-delete-' + item.id"
|
||||
icon="mdi-delete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openDeleteMunicipality(item)"
|
||||
/>
|
||||
</template>
|
||||
<template #loading>
|
||||
<v-skeleton-loader type="table-row@10" />
|
||||
</template>
|
||||
<template #no-data>
|
||||
<v-alert class="my-4" type="info" variant="tonal">
|
||||
No hay municipios para mostrar
|
||||
</v-alert>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Diálogo país -->
|
||||
<v-dialog v-model="countryDialog.show" max-width="480">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ countryDialog.isEdit ? 'Editar país' : 'Nuevo país' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="saveCountry">
|
||||
<v-text-field
|
||||
v-model="countryForm.name"
|
||||
data-testid="geo-country-form-name"
|
||||
label="Nombre"
|
||||
required
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="countryForm.code"
|
||||
data-testid="geo-country-form-code"
|
||||
label="Código (ISO)"
|
||||
maxlength="3"
|
||||
required
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="countryDialog.show = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="geo-country-save"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="saveCountry"
|
||||
>
|
||||
Guardar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Diálogo departamento -->
|
||||
<v-dialog v-model="departmentDialog.show" max-width="480">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ departmentDialog.isEdit ? 'Editar departamento' : 'Nuevo departamento' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="saveDepartment">
|
||||
<v-text-field
|
||||
v-model="departmentForm.name"
|
||||
data-testid="geo-department-form-name"
|
||||
label="Nombre"
|
||||
required
|
||||
/>
|
||||
<v-select
|
||||
v-model="departmentForm.country"
|
||||
data-testid="geo-department-form-country"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
:items="countries"
|
||||
label="País"
|
||||
required
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="departmentDialog.show = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="geo-department-save"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="saveDepartment"
|
||||
>
|
||||
Guardar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Diálogo municipio -->
|
||||
<v-dialog v-model="municipalityDialog.show" max-width="480">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ municipalityDialog.isEdit ? 'Editar municipio' : 'Nuevo municipio' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="saveMunicipality">
|
||||
<v-text-field
|
||||
v-model="municipalityForm.name"
|
||||
data-testid="geo-municipality-form-name"
|
||||
label="Nombre"
|
||||
required
|
||||
/>
|
||||
<v-autocomplete
|
||||
v-model="municipalityForm.department"
|
||||
data-testid="geo-municipality-form-department"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
:items="departmentItems"
|
||||
label="Departamento"
|
||||
required
|
||||
:search-input="departmentSearch"
|
||||
/>
|
||||
<v-select
|
||||
v-model="municipalityForm.country"
|
||||
data-testid="geo-municipality-form-country"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
:items="countries"
|
||||
label="País"
|
||||
required
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="municipalityDialog.show = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="geo-municipality-save"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="saveMunicipality"
|
||||
>
|
||||
Guardar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="deleteCountryDialog" max-width="420">
|
||||
<v-card>
|
||||
<v-card-title>Eliminar país</v-card-title>
|
||||
<v-card-text>
|
||||
¿Está seguro de eliminar "{{ deleteCountryTarget?.name }}"?
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="deleteCountryDialog = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="confirmDeleteCountry"
|
||||
>
|
||||
Eliminar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="deleteDepartmentDialog" max-width="420">
|
||||
<v-card>
|
||||
<v-card-title>Eliminar departamento</v-card-title>
|
||||
<v-card-text>
|
||||
¿Está seguro de eliminar "{{ deleteDepartmentTarget?.name }}"?
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="deleteDepartmentDialog = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="confirmDeleteDepartment"
|
||||
>
|
||||
Eliminar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="deleteMunicipalityDialog" max-width="420">
|
||||
<v-card>
|
||||
<v-card-title>Eliminar municipio</v-card-title>
|
||||
<v-card-text>
|
||||
¿Está seguro de eliminar "{{ deleteMunicipalityTarget?.name }}"?
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="deleteMunicipalityDialog = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="confirmDeleteMunicipality"
|
||||
>
|
||||
Eliminar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
location="top"
|
||||
:timeout="3000"
|
||||
>
|
||||
{{ snackbar.message }}
|
||||
<template #actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false">Cerrar</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, inject, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const api = inject('api')
|
||||
|
||||
const activeTab = ref('countries')
|
||||
const countries = ref([])
|
||||
const departments = ref([])
|
||||
const municipalities = ref([])
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const departmentSearch = ref('')
|
||||
const snackbar = ref({ show: false, message: '', color: 'success' })
|
||||
|
||||
const countryHeaders = [
|
||||
{ title: 'ID', key: 'id' },
|
||||
{ title: 'Nombre', key: 'name' },
|
||||
{ title: 'Código', key: 'code' },
|
||||
{ title: 'Acciones', key: 'actions', sortable: false },
|
||||
]
|
||||
|
||||
const departmentHeaders = [
|
||||
{ title: 'ID', key: 'id' },
|
||||
{ title: 'Nombre', key: 'name' },
|
||||
{ title: 'País', key: 'country' },
|
||||
{ title: 'Acciones', key: 'actions', sortable: false },
|
||||
]
|
||||
|
||||
const municipalityHeaders = [
|
||||
{ title: 'ID', key: 'id' },
|
||||
{ title: 'Nombre', key: 'name' },
|
||||
{ title: 'Departamento', key: 'department' },
|
||||
{ title: 'País', key: 'country' },
|
||||
{ title: 'Acciones', key: 'actions', sortable: false },
|
||||
]
|
||||
|
||||
const countryDialog = ref({ show: false, isEdit: false, item: null })
|
||||
const countryForm = ref({ name: '', code: '' })
|
||||
|
||||
const departmentDialog = ref({ show: false, isEdit: false, item: null })
|
||||
const departmentForm = ref({ name: '', country: null })
|
||||
|
||||
const municipalityDialog = ref({ show: false, isEdit: false, item: null })
|
||||
const municipalityForm = ref({ name: '', department: null, country: null })
|
||||
|
||||
const departmentItems = computed(() => {
|
||||
const query = departmentSearch.value.trim().toLowerCase()
|
||||
let filtered = departments.value
|
||||
if (query) {
|
||||
filtered = departments.value.filter(department => {
|
||||
return (department.name || '').toLowerCase().includes(query)
|
||||
})
|
||||
}
|
||||
const selectedId = Number(municipalityForm.value.department)
|
||||
const hasSelected = filtered.some(department => department.id === selectedId)
|
||||
if (selectedId && !hasSelected) {
|
||||
const selected = departments.value.find(department => department.id === selectedId)
|
||||
if (selected) filtered = [selected, ...filtered]
|
||||
}
|
||||
return filtered
|
||||
})
|
||||
|
||||
async function loadCountries () {
|
||||
loading.value = true
|
||||
try {
|
||||
countries.value = await api.getCountries()
|
||||
} catch (error) {
|
||||
console.error('Error al cargar países:', error)
|
||||
showSnackbar('Error al cargar países', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDepartments () {
|
||||
loading.value = true
|
||||
try {
|
||||
departments.value = await api.getDepartments()
|
||||
} catch (error) {
|
||||
console.error('Error al cargar departamentos:', error)
|
||||
showSnackbar('Error al cargar departamentos', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMunicipalities () {
|
||||
loading.value = true
|
||||
try {
|
||||
municipalities.value = await api.getMunicipalities()
|
||||
} catch (error) {
|
||||
console.error('Error al cargar municipios:', error)
|
||||
showSnackbar('Error al cargar municipios', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateCountry () {
|
||||
countryForm.value = { name: '', code: '' }
|
||||
countryDialog.value = { show: true, isEdit: false, item: null }
|
||||
}
|
||||
|
||||
function openEditCountry (item) {
|
||||
countryForm.value = { name: item.name, code: item.code }
|
||||
countryDialog.value = { show: true, isEdit: true, item }
|
||||
}
|
||||
|
||||
async function saveCountry () {
|
||||
saving.value = true
|
||||
try {
|
||||
if (countryDialog.value.isEdit) {
|
||||
await api.updateCountry(countryDialog.value.item.id, countryForm.value)
|
||||
showSnackbar('País actualizado', 'success')
|
||||
} else {
|
||||
await api.createCountry(countryForm.value)
|
||||
showSnackbar('País creado', 'success')
|
||||
}
|
||||
countryDialog.value.show = false
|
||||
await loadCountries()
|
||||
} catch (error) {
|
||||
console.error('Error al guardar país:', error)
|
||||
showSnackbar('Error al guardar país', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openDeleteCountry (item) {
|
||||
deleteCountryTarget.value = item
|
||||
deleteCountryDialog.value = true
|
||||
}
|
||||
|
||||
const deleteCountryDialog = ref(false)
|
||||
const deleteCountryTarget = ref(null)
|
||||
|
||||
async function confirmDeleteCountry () {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.deleteCountry(deleteCountryTarget.value.id)
|
||||
deleteCountryDialog.value = false
|
||||
showSnackbar('País eliminado', 'success')
|
||||
await loadCountries()
|
||||
} catch (error) {
|
||||
console.error('Error al eliminar país:', error)
|
||||
showSnackbar('Error al eliminar país', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateDepartment () {
|
||||
departmentForm.value = { name: '', country: null }
|
||||
departmentDialog.value = { show: true, isEdit: false, item: null }
|
||||
}
|
||||
|
||||
function openEditDepartment (item) {
|
||||
departmentForm.value = { name: item.name, country: item.country }
|
||||
departmentDialog.value = { show: true, isEdit: true, item }
|
||||
}
|
||||
|
||||
async function saveDepartment () {
|
||||
saving.value = true
|
||||
try {
|
||||
if (departmentDialog.value.isEdit) {
|
||||
await api.updateDepartment(departmentDialog.value.item.id, departmentForm.value)
|
||||
showSnackbar('Departamento actualizado', 'success')
|
||||
} else {
|
||||
await api.createDepartment(departmentForm.value)
|
||||
showSnackbar('Departamento creado', 'success')
|
||||
}
|
||||
departmentDialog.value.show = false
|
||||
await loadDepartments()
|
||||
} catch (error) {
|
||||
console.error('Error al guardar departamento:', error)
|
||||
showSnackbar('Error al guardar departamento', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const deleteDepartmentDialog = ref(false)
|
||||
const deleteDepartmentTarget = ref(null)
|
||||
|
||||
function openDeleteDepartment (item) {
|
||||
deleteDepartmentTarget.value = item
|
||||
deleteDepartmentDialog.value = true
|
||||
}
|
||||
|
||||
async function confirmDeleteDepartment () {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.deleteDepartment(deleteDepartmentTarget.value.id)
|
||||
deleteDepartmentDialog.value = false
|
||||
showSnackbar('Departamento eliminado', 'success')
|
||||
await loadDepartments()
|
||||
} catch (error) {
|
||||
console.error('Error al eliminar departamento:', error)
|
||||
showSnackbar('Error al eliminar departamento', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateMunicipality () {
|
||||
municipalityForm.value = { name: '', department: null, country: null }
|
||||
municipalityDialog.value = { show: true, isEdit: false, item: null }
|
||||
}
|
||||
|
||||
function openEditMunicipality (item) {
|
||||
municipalityForm.value = {
|
||||
name: item.name,
|
||||
department: item.department,
|
||||
country: item.country,
|
||||
}
|
||||
municipalityDialog.value = { show: true, isEdit: true, item }
|
||||
}
|
||||
|
||||
watch(
|
||||
() => municipalityForm.value.department,
|
||||
departmentId => {
|
||||
if (!departmentId) return
|
||||
const department = departments.value.find(item => item.id === departmentId)
|
||||
if (department && department.country) {
|
||||
municipalityForm.value.country = department.country
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async function saveMunicipality () {
|
||||
saving.value = true
|
||||
try {
|
||||
if (municipalityDialog.value.isEdit) {
|
||||
await api.updateMunicipality(municipalityDialog.value.item.id, municipalityForm.value)
|
||||
showSnackbar('Municipio actualizado', 'success')
|
||||
} else {
|
||||
await api.createMunicipality(municipalityForm.value)
|
||||
showSnackbar('Municipio creado', 'success')
|
||||
}
|
||||
municipalityDialog.value.show = false
|
||||
await loadMunicipalities()
|
||||
} catch (error) {
|
||||
console.error('Error al guardar municipio:', error)
|
||||
showSnackbar('Error al guardar municipio', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const deleteMunicipalityDialog = ref(false)
|
||||
const deleteMunicipalityTarget = ref(null)
|
||||
|
||||
function openDeleteMunicipality (item) {
|
||||
deleteMunicipalityTarget.value = item
|
||||
deleteMunicipalityDialog.value = true
|
||||
}
|
||||
|
||||
async function confirmDeleteMunicipality () {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.deleteMunicipality(deleteMunicipalityTarget.value.id)
|
||||
deleteMunicipalityDialog.value = false
|
||||
showSnackbar('Municipio eliminado', 'success')
|
||||
await loadMunicipalities()
|
||||
} catch (error) {
|
||||
console.error('Error al eliminar municipio:', error)
|
||||
showSnackbar('Error al eliminar municipio', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function showSnackbar (message, color) {
|
||||
snackbar.value = { show: true, message, color }
|
||||
}
|
||||
|
||||
watch(activeTab, tab => {
|
||||
if (tab === 'departments') loadDepartments()
|
||||
if (tab === 'municipalities') {
|
||||
loadMunicipalities()
|
||||
loadDepartments()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(loadCountries)
|
||||
</script>
|
||||
299
src/components/provenance/admin/OrganizationsManagement.vue
Normal file
299
src/components/provenance/admin/OrganizationsManagement.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="6">
|
||||
<h1 class="text-h4">Gestión de Organizaciones</h1>
|
||||
</v-col>
|
||||
<v-col class="text-md-right" cols="12" md="6">
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="org-create"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
Nueva organización
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
clearable
|
||||
data-testid="org-search"
|
||||
density="compact"
|
||||
hide-details
|
||||
label="Buscar por nombre"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-data-table
|
||||
density="compact"
|
||||
:headers="headers"
|
||||
item-value="id"
|
||||
:items="filteredOrganizations"
|
||||
items-per-page="25"
|
||||
:items-per-page-options="[10, 25, 50, 100]"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #item.website="{ item }">
|
||||
<a
|
||||
v-if="item.website"
|
||||
:href="item.website"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{{ item.website }}
|
||||
</a>
|
||||
</template>
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
:data-testid="'org-edit-' + item.id"
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openEdit(item)"
|
||||
/>
|
||||
<v-btn
|
||||
color="error"
|
||||
:data-testid="'org-delete-' + item.id"
|
||||
icon="mdi-delete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openDelete(item)"
|
||||
/>
|
||||
</template>
|
||||
<template #loading>
|
||||
<v-skeleton-loader type="table-row@10" />
|
||||
</template>
|
||||
<template #no-data>
|
||||
<v-alert class="my-4" type="info" variant="tonal">
|
||||
No hay organizaciones para mostrar
|
||||
</v-alert>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-dialog v-model="dialog.show" max-width="520">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ dialog.isEdit ? 'Editar organización' : 'Nueva organización' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="save">
|
||||
<v-text-field
|
||||
v-model="formData.name"
|
||||
data-testid="org-form-name"
|
||||
label="Nombre"
|
||||
required
|
||||
/>
|
||||
<v-textarea
|
||||
v-model="formData.description"
|
||||
data-testid="org-form-description"
|
||||
label="Descripción"
|
||||
rows="3"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="formData.website"
|
||||
data-testid="org-form-website"
|
||||
label="Sitio web"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="formData.contact_email"
|
||||
data-testid="org-form-email"
|
||||
label="Correo de contacto"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="formData.contact_phone"
|
||||
data-testid="org-form-phone"
|
||||
label="Teléfono de contacto"
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="dialog.show = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="org-save"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="save"
|
||||
>
|
||||
Guardar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="deleteDialog.show" max-width="420">
|
||||
<v-card>
|
||||
<v-card-title>Eliminar organización</v-card-title>
|
||||
<v-card-text>
|
||||
¿Está seguro de eliminar "{{ deleteDialog.item?.name }}"?
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="deleteDialog.show = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
data-testid="org-confirm-delete"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="confirmDelete"
|
||||
>
|
||||
Eliminar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
location="top"
|
||||
:timeout="3000"
|
||||
>
|
||||
{{ snackbar.message }}
|
||||
<template #actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false">Cerrar</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, inject, onMounted, ref } from 'vue'
|
||||
|
||||
const api = inject('api')
|
||||
|
||||
const organizations = ref([])
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const snackbar = ref({ show: false, message: '', color: 'success' })
|
||||
const dialog = ref({ show: false, isEdit: false, item: null })
|
||||
const deleteDialog = ref({ show: false, item: null })
|
||||
|
||||
const headers = [
|
||||
{ title: 'ID', key: 'id' },
|
||||
{ title: 'Nombre', key: 'name' },
|
||||
{ title: 'Descripción', key: 'description' },
|
||||
{ title: 'Sitio web', key: 'website' },
|
||||
{ title: 'Correo', key: 'contact_email' },
|
||||
{ title: 'Teléfono', key: 'contact_phone' },
|
||||
{ title: 'Acciones', key: 'actions', sortable: false },
|
||||
]
|
||||
|
||||
const emptyForm = () => ({
|
||||
name: '',
|
||||
description: '',
|
||||
website: '',
|
||||
contact_email: '',
|
||||
contact_phone: '',
|
||||
})
|
||||
|
||||
const formData = ref(emptyForm())
|
||||
|
||||
const filteredOrganizations = computed(() => {
|
||||
const query = searchQuery.value.trim().toLowerCase()
|
||||
if (!query) return organizations.value
|
||||
return organizations.value.filter(organization => {
|
||||
return (organization.name || '').toLowerCase().includes(query)
|
||||
})
|
||||
})
|
||||
|
||||
async function load () {
|
||||
loading.value = true
|
||||
try {
|
||||
organizations.value = await api.getOrganizations()
|
||||
} catch (error) {
|
||||
console.error('Error al cargar organizaciones:', error)
|
||||
showSnackbar('Error al cargar organizaciones', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate () {
|
||||
formData.value = emptyForm()
|
||||
dialog.value = { show: true, isEdit: false, item: null }
|
||||
}
|
||||
|
||||
function openEdit (item) {
|
||||
formData.value = {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
website: item.website,
|
||||
contact_email: item.contact_email,
|
||||
contact_phone: item.contact_phone,
|
||||
}
|
||||
dialog.value = { show: true, isEdit: true, item }
|
||||
}
|
||||
|
||||
async function save () {
|
||||
saving.value = true
|
||||
try {
|
||||
if (dialog.value.isEdit) {
|
||||
await api.updateOrganization(dialog.value.item.id, formData.value)
|
||||
showSnackbar('Organización actualizada', 'success')
|
||||
} else {
|
||||
await api.createOrganization(formData.value)
|
||||
showSnackbar('Organización creada', 'success')
|
||||
}
|
||||
dialog.value.show = false
|
||||
await load()
|
||||
} catch (error) {
|
||||
console.error('Error al guardar organización:', error)
|
||||
showSnackbar('Error al guardar organización', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openDelete (item) {
|
||||
deleteDialog.value = { show: true, item }
|
||||
}
|
||||
|
||||
async function confirmDelete () {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.deleteOrganization(deleteDialog.value.item.id)
|
||||
deleteDialog.value.show = false
|
||||
showSnackbar('Organización eliminada', 'success')
|
||||
await load()
|
||||
} catch (error) {
|
||||
console.error('Error al eliminar organización:', error)
|
||||
showSnackbar('Error al eliminar organización', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function showSnackbar (message, color) {
|
||||
snackbar.value = { show: true, message, color }
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-md-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.text-md-right {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
338
src/components/provenance/admin/SuppliersManagement.vue
Normal file
338
src/components/provenance/admin/SuppliersManagement.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="6">
|
||||
<h1 class="text-h4">Gestión de Proveedores</h1>
|
||||
</v-col>
|
||||
<v-col class="text-md-right" cols="12" md="6">
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="supplier-create"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
Nuevo proveedor
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
clearable
|
||||
data-testid="supplier-search"
|
||||
density="compact"
|
||||
hide-details
|
||||
label="Buscar por nombre"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-data-table
|
||||
density="compact"
|
||||
:headers="headers"
|
||||
item-value="id"
|
||||
:items="filteredSuppliers"
|
||||
items-per-page="25"
|
||||
:items-per-page-options="[10, 25, 50, 100]"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #item.organization="{ item }">
|
||||
{{ item.organization_detail?.name }}
|
||||
</template>
|
||||
<template #item.municipality="{ item }">
|
||||
{{ item.municipality_detail?.name }}
|
||||
</template>
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
:data-testid="'supplier-edit-' + item.id"
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openEdit(item)"
|
||||
/>
|
||||
<v-btn
|
||||
color="error"
|
||||
:data-testid="'supplier-delete-' + item.id"
|
||||
icon="mdi-delete"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="openDelete(item)"
|
||||
/>
|
||||
</template>
|
||||
<template #loading>
|
||||
<v-skeleton-loader type="table-row@10" />
|
||||
</template>
|
||||
<template #no-data>
|
||||
<v-alert class="my-4" type="info" variant="tonal">
|
||||
No hay proveedores para mostrar
|
||||
</v-alert>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-dialog v-model="dialog.show" max-width="560">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ dialog.isEdit ? 'Editar proveedor' : 'Nuevo proveedor' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form @submit.prevent="save">
|
||||
<v-text-field
|
||||
v-model="formData.name"
|
||||
data-testid="supplier-form-name"
|
||||
label="Nombre"
|
||||
required
|
||||
/>
|
||||
<v-textarea
|
||||
v-model="formData.description"
|
||||
data-testid="supplier-form-description"
|
||||
label="Descripción"
|
||||
rows="3"
|
||||
/>
|
||||
<v-select
|
||||
v-model="formData.organization"
|
||||
clearable
|
||||
data-testid="supplier-form-organization"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
:items="organizations"
|
||||
label="Organización"
|
||||
/>
|
||||
<v-autocomplete
|
||||
v-model="formData.municipality"
|
||||
clearable
|
||||
data-testid="supplier-form-municipality"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
:items="municipalityItems"
|
||||
label="Municipio"
|
||||
:search-input="municipalitySearch"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="formData.contact_email"
|
||||
data-testid="supplier-form-email"
|
||||
label="Correo de contacto"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="formData.contact_phone"
|
||||
data-testid="supplier-form-phone"
|
||||
label="Teléfono de contacto"
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="dialog.show = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
data-testid="supplier-save"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="save"
|
||||
>
|
||||
Guardar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="deleteDialog.show" max-width="420">
|
||||
<v-card>
|
||||
<v-card-title>Eliminar proveedor</v-card-title>
|
||||
<v-card-text>
|
||||
¿Está seguro de eliminar "{{ deleteDialog.item?.name }}"?
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="deleteDialog.show = false">Cancelar</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
data-testid="supplier-confirm-delete"
|
||||
:disabled="saving"
|
||||
variant="elevated"
|
||||
@click="confirmDelete"
|
||||
>
|
||||
Eliminar
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
location="top"
|
||||
:timeout="3000"
|
||||
>
|
||||
{{ snackbar.message }}
|
||||
<template #actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false">Cerrar</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, inject, onMounted, ref } from 'vue'
|
||||
|
||||
const api = inject('api')
|
||||
|
||||
const suppliers = ref([])
|
||||
const organizations = ref([])
|
||||
const municipalities = ref([])
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const municipalitySearch = ref('')
|
||||
const snackbar = ref({ show: false, message: '', color: 'success' })
|
||||
const dialog = ref({ show: false, isEdit: false, item: null })
|
||||
const deleteDialog = ref({ show: false, item: null })
|
||||
|
||||
const headers = [
|
||||
{ title: 'ID', key: 'id' },
|
||||
{ title: 'Nombre', key: 'name' },
|
||||
{ title: 'Organización', key: 'organization' },
|
||||
{ title: 'Municipio', key: 'municipality' },
|
||||
{ title: 'Correo', key: 'contact_email' },
|
||||
{ title: 'Teléfono', key: 'contact_phone' },
|
||||
{ title: 'Acciones', key: 'actions', sortable: false },
|
||||
]
|
||||
|
||||
const emptyForm = () => ({
|
||||
name: '',
|
||||
description: '',
|
||||
organization: null,
|
||||
municipality: null,
|
||||
contact_email: '',
|
||||
contact_phone: '',
|
||||
})
|
||||
|
||||
const formData = ref(emptyForm())
|
||||
|
||||
const filteredSuppliers = computed(() => {
|
||||
const query = searchQuery.value.trim().toLowerCase()
|
||||
if (!query) return suppliers.value
|
||||
return suppliers.value.filter(supplier => {
|
||||
return (supplier.name || '').toLowerCase().includes(query)
|
||||
})
|
||||
})
|
||||
|
||||
const municipalityItems = computed(() => {
|
||||
const query = municipalitySearch.value.trim().toLowerCase()
|
||||
let filtered = municipalities.value
|
||||
if (query) {
|
||||
filtered = municipalities.value.filter(municipality => {
|
||||
return (municipality.name || '').toLowerCase().includes(query)
|
||||
})
|
||||
}
|
||||
const selectedId = Number(formData.value.municipality)
|
||||
const hasSelected = filtered.some(municipality => municipality.id === selectedId)
|
||||
if (selectedId && !hasSelected) {
|
||||
const selected = municipalities.value.find(municipality => municipality.id === selectedId)
|
||||
if (selected) filtered = [selected, ...filtered]
|
||||
}
|
||||
return filtered
|
||||
})
|
||||
|
||||
async function load () {
|
||||
loading.value = true
|
||||
try {
|
||||
const [suppliersData, organizationsData, municipalitiesData] = await Promise.all([
|
||||
api.getSuppliers(),
|
||||
api.getOrganizations(),
|
||||
api.getMunicipalities(),
|
||||
])
|
||||
suppliers.value = suppliersData
|
||||
organizations.value = organizationsData
|
||||
municipalities.value = municipalitiesData
|
||||
} catch (error) {
|
||||
console.error('Error al cargar proveedores:', error)
|
||||
showSnackbar('Error al cargar proveedores', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate () {
|
||||
formData.value = emptyForm()
|
||||
dialog.value = { show: true, isEdit: false, item: null }
|
||||
}
|
||||
|
||||
function openEdit (item) {
|
||||
formData.value = {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
organization: item.organization,
|
||||
municipality: item.municipality,
|
||||
contact_email: item.contact_email,
|
||||
contact_phone: item.contact_phone,
|
||||
}
|
||||
dialog.value = { show: true, isEdit: true, item }
|
||||
}
|
||||
|
||||
async function save () {
|
||||
saving.value = true
|
||||
try {
|
||||
if (dialog.value.isEdit) {
|
||||
await api.updateSupplier(dialog.value.item.id, formData.value)
|
||||
showSnackbar('Proveedor actualizado', 'success')
|
||||
} else {
|
||||
await api.createSupplier(formData.value)
|
||||
showSnackbar('Proveedor creado', 'success')
|
||||
}
|
||||
dialog.value.show = false
|
||||
await load()
|
||||
} catch (error) {
|
||||
console.error('Error al guardar proveedor:', error)
|
||||
showSnackbar('Error al guardar proveedor', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openDelete (item) {
|
||||
deleteDialog.value = { show: true, item }
|
||||
}
|
||||
|
||||
async function confirmDelete () {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.deleteSupplier(deleteDialog.value.item.id)
|
||||
deleteDialog.value.show = false
|
||||
showSnackbar('Proveedor eliminado', 'success')
|
||||
await load()
|
||||
} catch (error) {
|
||||
console.error('Error al eliminar proveedor:', error)
|
||||
showSnackbar('Error al eliminar proveedor', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function showSnackbar (message, color) {
|
||||
snackbar.value = { show: true, message, color }
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-md-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.text-md-right {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
10
src/pages/admin/geography.vue
Normal file
10
src/pages/admin/geography.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<GeographyManagement v-if="authStore.isAdmin" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import GeographyManagement from '@/components/provenance/admin/GeographyManagement.vue'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
</script>
|
||||
10
src/pages/admin/organizations.vue
Normal file
10
src/pages/admin/organizations.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<OrganizationsManagement v-if="authStore.isAdmin" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import OrganizationsManagement from '@/components/provenance/admin/OrganizationsManagement.vue'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
</script>
|
||||
10
src/pages/admin/suppliers.vue
Normal file
10
src/pages/admin/suppliers.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<SuppliersManagement v-if="authStore.isAdmin" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import SuppliersManagement from '@/components/provenance/admin/SuppliersManagement.vue'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
</script>
|
||||
@@ -25,6 +25,9 @@ const ADMIN_ROUTES = [
|
||||
'/admin/catalog-sales',
|
||||
'/admin/catalogue-images',
|
||||
'/admin/store-settings',
|
||||
'/admin/organizations',
|
||||
'/admin/suppliers',
|
||||
'/admin/geography',
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
Reference in New Issue
Block a user