Merge branch 'main' into feat/consulta-publica-pedido
This commit is contained in:
7
package-lock.json
generated
7
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.13.5",
|
||||||
"core-js": "^3.37.1",
|
"core-js": "^3.37.1",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "*",
|
||||||
"vee-validate": "^4.14.6",
|
"vee-validate": "^4.14.6",
|
||||||
"vue": "^3.4.31",
|
"vue": "^3.4.31",
|
||||||
@@ -4991,6 +4992,12 @@
|
|||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/leaflet": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
"node_modules/levn": {
|
"node_modules/levn": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.13.5",
|
||||||
"core-js": "^3.37.1",
|
"core-js": "^3.37.1",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "*",
|
||||||
"vee-validate": "^4.14.6",
|
"vee-validate": "^4.14.6",
|
||||||
"vue": "^3.4.31",
|
"vue": "^3.4.31",
|
||||||
|
|||||||
@@ -7,12 +7,7 @@
|
|||||||
<div class="glow-bubble bubble-red"></div>
|
<div class="glow-bubble bubble-red"></div>
|
||||||
|
|
||||||
<div class="login-card">
|
<div class="login-card">
|
||||||
<v-img
|
<SiteLogo max-width="140" class="mx-auto mb-4" />
|
||||||
:src="logo"
|
|
||||||
alt="Don Confiao"
|
|
||||||
max-width="140"
|
|
||||||
class="mx-auto mb-4"
|
|
||||||
/>
|
|
||||||
<h1 class="text-h5 text-sm-h4 font-weight-bold text-center mb-1">
|
<h1 class="text-h5 text-sm-h4 font-weight-bold text-center mb-1">
|
||||||
Iniciar Sesión
|
Iniciar Sesión
|
||||||
</h1>
|
</h1>
|
||||||
@@ -74,7 +69,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import AuthService from '@/services/auth'
|
import AuthService from '@/services/auth'
|
||||||
import logo from '@/assets/logo_colorful.png'
|
import SiteLogo from '@/components/SiteLogo.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@
|
|||||||
{ title: 'Compra adm', route: '/compra_admin', icon: 'mdi-cart'},
|
{ title: 'Compra adm', route: '/compra_admin', icon: 'mdi-cart'},
|
||||||
{ title: 'Gestión de Productos', route: '/admin/products', icon: 'mdi-package-variant'},
|
{ title: 'Gestión de Productos', route: '/admin/products', icon: 'mdi-package-variant'},
|
||||||
{ title: 'Imágenes de Catálogo', route: '/admin/catalogue-images', icon: 'mdi-image-multiple'},
|
{ 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: 'Ver Ventas por Catálogo', route: '/admin/catalog-sales', icon: 'mdi-cart-arrow-down'},
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ header: 'Sincronización Tryton' },
|
{ header: 'Sincronización Tryton' },
|
||||||
|
|||||||
39
src/components/SiteLogo.vue
Normal file
39
src/components/SiteLogo.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<v-img
|
||||||
|
v-if="logoUrl && !broken"
|
||||||
|
:src="logoUrl"
|
||||||
|
:alt="alt"
|
||||||
|
:max-width="maxWidth"
|
||||||
|
@error="broken = true"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, inject, onMounted, ref } from 'vue';
|
||||||
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
maxWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 180,
|
||||||
|
},
|
||||||
|
alt: {
|
||||||
|
type: String,
|
||||||
|
default: 'Don Confiao',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const api = inject('api');
|
||||||
|
const broken = ref(false);
|
||||||
|
|
||||||
|
const logoUrl = computed(() => settingsStore.logo);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
await settingsStore.fetchSettings(api);
|
||||||
|
} catch (e) {
|
||||||
|
broken.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
167
src/components/StoreLocation.vue
Normal file
167
src/components/StoreLocation.vue
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<v-container v-if="settings && settings.address">
|
||||||
|
<v-sheet
|
||||||
|
class="store-location-section rounded-xl pa-6 pa-md-8"
|
||||||
|
elevation="2"
|
||||||
|
>
|
||||||
|
<div class="d-flex flex-column align-center text-center mb-4">
|
||||||
|
<div class="d-flex align-center mb-1">
|
||||||
|
<v-icon color="primary" size="36" class="mr-2">mdi-map-marker</v-icon>
|
||||||
|
<h2 class="text-h4 font-weight-bold mb-0">Visítanos</h2>
|
||||||
|
</div>
|
||||||
|
<p class="text-body-1 text-medium-emphasis mb-0">
|
||||||
|
Encuentra nuestra tienda física y adquiere nuestros productos
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<div class="d-flex align-center justify-center">
|
||||||
|
<v-icon color="red" class="mr-2">mdi-map-marker</v-icon>
|
||||||
|
<span class="text-h6 font-weight-medium">
|
||||||
|
{{ settings.address }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<v-btn
|
||||||
|
v-if="hasCoordinates"
|
||||||
|
:href="directionsLink"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="small"
|
||||||
|
prepend-icon="mdi-directions"
|
||||||
|
class="mt-2"
|
||||||
|
>
|
||||||
|
Cómo llegar
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
v-else
|
||||||
|
:href="searchLink"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="small"
|
||||||
|
prepend-icon="mdi-map-search"
|
||||||
|
class="mt-2"
|
||||||
|
>
|
||||||
|
Ver en el mapa
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="hasCoordinates" ref="mapEl" class="map-wrapper"></div>
|
||||||
|
<v-alert v-else type="info" variant="tonal" class="ma-0">
|
||||||
|
La ubicación en el mapa aún no está configurada.
|
||||||
|
</v-alert>
|
||||||
|
</v-sheet>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, inject, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
|
import L from 'leaflet';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
|
|
||||||
|
const api = inject('api');
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const settings = computed(() => settingsStore.settings);
|
||||||
|
const mapEl = ref(null);
|
||||||
|
|
||||||
|
let map = null;
|
||||||
|
let marker = null;
|
||||||
|
|
||||||
|
const hasCoordinates = computed(
|
||||||
|
() =>
|
||||||
|
settings.value &&
|
||||||
|
settings.value.latitude != null &&
|
||||||
|
settings.value.longitude != null
|
||||||
|
);
|
||||||
|
|
||||||
|
const redPinIcon = L.divIcon({
|
||||||
|
className: 'store-map-pin',
|
||||||
|
html: '<i class="mdi mdi-map-marker" style="font-size:48px;line-height:1;color:#f44336;"></i>',
|
||||||
|
iconSize: [48, 48],
|
||||||
|
iconAnchor: [24, 48],
|
||||||
|
});
|
||||||
|
|
||||||
|
const directionsLink = computed(() => {
|
||||||
|
const lat = settings.value.latitude;
|
||||||
|
const lng = settings.value.longitude;
|
||||||
|
return `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lng}#map=16/${lat}/${lng}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchLink = computed(
|
||||||
|
() =>
|
||||||
|
`https://www.openstreetmap.org/search?query=${encodeURIComponent(
|
||||||
|
settings.value.address
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
function initMap() {
|
||||||
|
if (!hasCoordinates.value || !mapEl.value || map) return;
|
||||||
|
|
||||||
|
const lat = settings.value.latitude;
|
||||||
|
const lng = settings.value.longitude;
|
||||||
|
|
||||||
|
map = L.map(mapEl.value, {
|
||||||
|
scrollWheelZoom: false,
|
||||||
|
}).setView([lat, lng], 16);
|
||||||
|
|
||||||
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution:
|
||||||
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
marker = L.marker([lat, lng], { icon: redPinIcon }).addTo(map);
|
||||||
|
marker.bindPopup(settings.value.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResize() {
|
||||||
|
if (map) {
|
||||||
|
map.invalidateSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
await settingsStore.fetchSettings(api);
|
||||||
|
await nextTick();
|
||||||
|
initMap();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al cargar la información de la tienda:', error);
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (map) {
|
||||||
|
map.remove();
|
||||||
|
map = null;
|
||||||
|
marker = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.store-location-section {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-wrapper :deep(.leaflet-control-attribution) {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
235
src/components/StoreSettingsManagement.vue
Normal file
235
src/components/StoreSettingsManagement.vue
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<v-row align="center">
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<h1 class="text-h4">Datos de la Tienda</h1>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6" class="text-md-right">
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="elevated"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
:loading="submitting"
|
||||||
|
:disabled="submitting"
|
||||||
|
@click="submitForm"
|
||||||
|
>
|
||||||
|
Guardar Cambios
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form ref="formRef">
|
||||||
|
<v-text-field
|
||||||
|
v-model="form.address"
|
||||||
|
label="Dirección de la tienda"
|
||||||
|
:rules="[(v) => !!v || 'La dirección es obligatoria']"
|
||||||
|
variant="outlined"
|
||||||
|
prepend-inner-icon="mdi-map-marker"
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="form.latitude"
|
||||||
|
label="Latitud (opcional)"
|
||||||
|
variant="outlined"
|
||||||
|
prepend-inner-icon="mdi-crosshairs-gps"
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
class="mb-3"
|
||||||
|
hint="Ej: 4.6097"
|
||||||
|
persistent-hint
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="form.longitude"
|
||||||
|
label="Longitud (opcional)"
|
||||||
|
variant="outlined"
|
||||||
|
prepend-inner-icon="mdi-crosshairs-gps"
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
class="mb-3"
|
||||||
|
hint="Ej: -74.0817"
|
||||||
|
persistent-hint
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-divider class="my-4" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="text-h6 mb-2">Logo del sitio</h3>
|
||||||
|
<v-img
|
||||||
|
v-if="currentLogo"
|
||||||
|
:src="currentLogo"
|
||||||
|
alt="Logo actual"
|
||||||
|
max-width="180"
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<v-file-input
|
||||||
|
v-model="logoFile"
|
||||||
|
label="Seleccionar nueva imagen del logo"
|
||||||
|
accept="image/*"
|
||||||
|
prepend-icon="mdi-image"
|
||||||
|
variant="outlined"
|
||||||
|
class="mb-3"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
v-if="currentLogo"
|
||||||
|
variant="tonal"
|
||||||
|
color="error"
|
||||||
|
prepend-icon="mdi-image-off"
|
||||||
|
:loading="removingLogo"
|
||||||
|
:disabled="removingLogo || submitting"
|
||||||
|
@click="removeLogo"
|
||||||
|
>
|
||||||
|
Quitar logo actual
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
location="top"
|
||||||
|
>
|
||||||
|
{{ snackbar.message }}
|
||||||
|
<template #actions>
|
||||||
|
<v-btn variant="text" @click="snackbar.show = false">Cerrar</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, inject, onMounted } from "vue";
|
||||||
|
import { useSettingsStore } from "@/stores/settings";
|
||||||
|
|
||||||
|
const api = inject("api");
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const formRef = ref(null);
|
||||||
|
const submitting = ref(false);
|
||||||
|
const removingLogo = ref(false);
|
||||||
|
const logoFile = ref(null);
|
||||||
|
const currentLogo = ref(null);
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
address: "",
|
||||||
|
latitude: null,
|
||||||
|
longitude: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const snackbar = ref({ show: false, message: "", color: "success" });
|
||||||
|
|
||||||
|
async function loadSettings() {
|
||||||
|
try {
|
||||||
|
const data = await api.getStoreSettings();
|
||||||
|
settingsStore.setSettings(data);
|
||||||
|
form.value = {
|
||||||
|
address: data.address || "",
|
||||||
|
latitude: data.latitude ?? null,
|
||||||
|
longitude: data.longitude ?? null,
|
||||||
|
};
|
||||||
|
currentLogo.value = data.logo || null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar la información de la tienda:", error);
|
||||||
|
showSnackbar("Error al cargar la información de la tienda", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitForm() {
|
||||||
|
const formComponent = formRef.value;
|
||||||
|
if (formComponent) {
|
||||||
|
const { valid } = await formComponent.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
if (logoFile.value) {
|
||||||
|
const payload = new FormData();
|
||||||
|
payload.append("address", form.value.address);
|
||||||
|
if (form.value.latitude !== "" && form.value.latitude != null) {
|
||||||
|
payload.append("latitude", toNumberOrNull(form.value.latitude));
|
||||||
|
}
|
||||||
|
if (form.value.longitude !== "" && form.value.longitude != null) {
|
||||||
|
payload.append("longitude", toNumberOrNull(form.value.longitude));
|
||||||
|
}
|
||||||
|
payload.append("logo", logoFile.value);
|
||||||
|
await api.updateStoreSettings(payload);
|
||||||
|
} else {
|
||||||
|
await api.updateStoreSettings({
|
||||||
|
address: form.value.address,
|
||||||
|
latitude: toNumberOrNull(form.value.latitude),
|
||||||
|
longitude: toNumberOrNull(form.value.longitude),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
logoFile.value = null;
|
||||||
|
await refreshLogo();
|
||||||
|
showSnackbar("Información de la tienda actualizada", "success");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al guardar la información de la tienda:", error);
|
||||||
|
showSnackbar("Error al guardar la información de la tienda", "error");
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeLogo() {
|
||||||
|
removingLogo.value = true;
|
||||||
|
try {
|
||||||
|
await api.updateStoreSettings({ logo: null });
|
||||||
|
logoFile.value = null;
|
||||||
|
await refreshLogo();
|
||||||
|
showSnackbar("Logo eliminado", "success");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al quitar el logo:", error);
|
||||||
|
showSnackbar("Error al quitar el logo", "error");
|
||||||
|
} finally {
|
||||||
|
removingLogo.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshLogo() {
|
||||||
|
const data = await api.getStoreSettings();
|
||||||
|
settingsStore.setSettings(data);
|
||||||
|
currentLogo.value = data.logo || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumberOrNull(value) {
|
||||||
|
if (value === null || value === undefined || value === "") return null;
|
||||||
|
const number = Number(value);
|
||||||
|
return Number.isNaN(number) ? null : number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSnackbar(message, color) {
|
||||||
|
snackbar.value = { show: true, message, color };
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadSettings);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.text-md-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.text-md-right {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,12 +8,7 @@
|
|||||||
<div class="glow-bubble bubble-yellow"></div>
|
<div class="glow-bubble bubble-yellow"></div>
|
||||||
<div class="glow-bubble bubble-red"></div>
|
<div class="glow-bubble bubble-red"></div>
|
||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
<v-img
|
<SiteLogo max-width="180" class="mx-auto mb-4" />
|
||||||
:src="logo"
|
|
||||||
alt="Don Confiao"
|
|
||||||
max-width="180"
|
|
||||||
class="mx-auto mb-4"
|
|
||||||
/>
|
|
||||||
<h1 class="text-h4 font-weight-bold mb-2">Don Confiao te atiende</h1>
|
<h1 class="text-h4 font-weight-bold mb-2">Don Confiao te atiende</h1>
|
||||||
<p class="text-subtitle-1 font-italic font-weight-bold">
|
<p class="text-subtitle-1 font-italic font-weight-bold">
|
||||||
Economía solidaria, mercado justo, alimentación sana
|
Economía solidaria, mercado justo, alimentación sana
|
||||||
@@ -21,6 +16,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
|
|
||||||
|
<div class="py-6">
|
||||||
|
<StoreLocation />
|
||||||
|
</div>
|
||||||
|
|
||||||
<v-container class="py-6">
|
<v-container class="py-6">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
@@ -45,25 +44,39 @@
|
|||||||
<v-card class="h-100" elevation="2">
|
<v-card class="h-100" elevation="2">
|
||||||
<v-card-item>
|
<v-card-item>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon color="orange-darken-2" size="48"
|
<v-icon color="green" size="48">mdi-code-tags</v-icon>
|
||||||
>mdi-progress-wrench</v-icon
|
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
<v-card-title class="font-weight-bold"
|
<v-card-title class="font-weight-bold"
|
||||||
>En Desarrollo</v-card-title
|
>Software Libre</v-card-title
|
||||||
>
|
>
|
||||||
</v-card-item>
|
</v-card-item>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
Don Confiao apenas está entendiendo cómo funciona esta tienda y
|
Don Confiao es un proyecto de
|
||||||
por ahora
|
<ResaltedText>Software Libre</ResaltedText>. Su desarrollo está
|
||||||
<ResaltedText
|
dividido en dos repositorios: el backend y el frontend.
|
||||||
>solo puede atender las compras de contado</ResaltedText
|
|
||||||
>, ya sea en efectivo o consignación.
|
|
||||||
<v-alert type="warning" class="mt-3" density="compact">
|
|
||||||
Si no vas a pagar tu compra recuerda que debes hacerlo en la
|
|
||||||
planilla manual
|
|
||||||
</v-alert>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
href="https://gitea.onecluster.org/OneTeam/don_confiao_backend"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
prepend-icon="mdi-server"
|
||||||
|
variant="text"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Backend
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
href="https://gitea.onecluster.org/OneTeam/don_confiao_frontend"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
prepend-icon="mdi-web"
|
||||||
|
variant="text"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Frontend
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
@@ -127,8 +140,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ResaltedText from "@/components/ResaltedText.vue";
|
import ResaltedText from "@/components/ResaltedText.vue";
|
||||||
|
import StoreLocation from "@/components/StoreLocation.vue";
|
||||||
|
import SiteLogo from "@/components/SiteLogo.vue";
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import logo from "@/assets/logo_colorful.png";
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
10
src/pages/admin/store-settings.vue
Normal file
10
src/pages/admin/store-settings.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<StoreSettingsManagement v-if="authStore.isAdmin" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
import StoreSettingsManagement from '@/components/StoreSettingsManagement.vue';
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
</script>
|
||||||
@@ -24,6 +24,7 @@ const ADMIN_ROUTES = [
|
|||||||
'/admin/products',
|
'/admin/products',
|
||||||
'/admin/catalog-sales',
|
'/admin/catalog-sales',
|
||||||
'/admin/catalogue-images',
|
'/admin/catalogue-images',
|
||||||
|
'/admin/store-settings',
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
@@ -102,6 +102,14 @@ class Api {
|
|||||||
deleteCatalogueImage(id) {
|
deleteCatalogueImage(id) {
|
||||||
return this.apiImplementation.deleteCatalogueImage(id);
|
return this.apiImplementation.deleteCatalogueImage(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStoreSettings() {
|
||||||
|
return this.apiImplementation.getStoreSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStoreSettings(data) {
|
||||||
|
return this.apiImplementation.updateStoreSettings(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Api;
|
export default Api;
|
||||||
|
|||||||
@@ -160,6 +160,18 @@ class DjangoApi {
|
|||||||
const url = this.base + `/don_confiao/api/catalogue_images/${id}/`;
|
const url = this.base + `/don_confiao/api/catalogue_images/${id}/`;
|
||||||
return http.delete(url).then((r) => r.data);
|
return http.delete(url).then((r) => r.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStoreSettings() {
|
||||||
|
const url = this.base + "/don_confiao/api/store_settings";
|
||||||
|
return this.getRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStoreSettings(data) {
|
||||||
|
const url = this.base + "/don_confiao/api/store_settings";
|
||||||
|
return http.patch(url, data, {
|
||||||
|
headers: { 'Content-Type': undefined },
|
||||||
|
}).then((r) => r.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DjangoApi;
|
export default DjangoApi;
|
||||||
|
|||||||
26
src/stores/settings.js
Normal file
26
src/stores/settings.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useSettingsStore = defineStore('settings', {
|
||||||
|
state: () => ({
|
||||||
|
settings: null,
|
||||||
|
loaded: false
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
logo: (state) => state.settings?.logo || null,
|
||||||
|
address: (state) => state.settings?.address || '',
|
||||||
|
latitude: (state) => state.settings?.latitude,
|
||||||
|
longitude: (state) => state.settings?.longitude
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async fetchSettings(api) {
|
||||||
|
if (this.loaded) return this.settings
|
||||||
|
this.settings = await api.getStoreSettings()
|
||||||
|
this.loaded = true
|
||||||
|
return this.settings
|
||||||
|
},
|
||||||
|
setSettings(settings) {
|
||||||
|
this.settings = settings
|
||||||
|
this.loaded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user