feat(admin): new AdminBusinessEditor with live mobile preview + fix admin logout bug on file picker timeout
This commit is contained in:
641
frontend/src/views/AdminBusinessEditor.vue
Normal file
641
frontend/src/views/AdminBusinessEditor.vue
Normal file
@ -0,0 +1,641 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { businessService } from '@/services/businessService';
|
||||
import type { Business } from '@/types';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
const isLoading = ref(false);
|
||||
const isEditing = ref(false);
|
||||
const showMessage = ref({ text: '', type: '' });
|
||||
|
||||
const selectedFile = ref<File | null>(null);
|
||||
const selectedFileName = ref('');
|
||||
|
||||
// Business state
|
||||
const businessForm = ref<Partial<Business>>({
|
||||
name: '',
|
||||
address: '',
|
||||
phone: '',
|
||||
image_url: '',
|
||||
social_media: '',
|
||||
category: 'Restaurante',
|
||||
area: 'Boquete',
|
||||
description: '',
|
||||
website: '',
|
||||
schedule: '',
|
||||
whatsapp: '',
|
||||
instagram: '',
|
||||
facebook: '',
|
||||
gallery_images: []
|
||||
});
|
||||
|
||||
const previewImageUrl = ref('https://images.unsplash.com/photo-1555396273-367ea4eb4db5?q=80&w=2074&auto=format&fit=crop');
|
||||
|
||||
onMounted(async () => {
|
||||
const id = route.params.id as string;
|
||||
if (id && id !== 'new') {
|
||||
isEditing.value = true;
|
||||
try {
|
||||
isLoading.value = true;
|
||||
const data = await businessService.getBusiness(id);
|
||||
businessForm.value = { ...data };
|
||||
if (data.image_url) {
|
||||
previewImageUrl.value = data.image_url;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showMessage.value = { text: 'Error cargando negocio', type: 'error' };
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function handleImageChange(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (input.files && input.files[0]) {
|
||||
const file = input.files[0];
|
||||
selectedFile.value = file;
|
||||
selectedFileName.value = file.name;
|
||||
|
||||
// Preview logic
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
previewImageUrl.value = e.target?.result as string;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveBusiness() {
|
||||
isLoading.value = true;
|
||||
showMessage.value = { text: '', type: '' };
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('name', businessForm.value.name || '');
|
||||
formData.append('category', businessForm.value.category || 'Restaurante');
|
||||
formData.append('address', businessForm.value.address || '');
|
||||
formData.append('phone', businessForm.value.phone || '');
|
||||
formData.append('area', businessForm.value.area || 'Boquete');
|
||||
formData.append('description', businessForm.value.description || '');
|
||||
formData.append('website', businessForm.value.website || '');
|
||||
formData.append('schedule', businessForm.value.schedule || '');
|
||||
formData.append('whatsapp', businessForm.value.whatsapp || '');
|
||||
formData.append('instagram', businessForm.value.instagram || '');
|
||||
formData.append('facebook', businessForm.value.facebook || '');
|
||||
|
||||
if (businessForm.value.gallery_images?.length) {
|
||||
formData.append('gallery_images', JSON.stringify(businessForm.value.gallery_images));
|
||||
}
|
||||
|
||||
if (selectedFile.value) {
|
||||
formData.append('image', selectedFile.value);
|
||||
}
|
||||
|
||||
if (isEditing.value && businessForm.value.id) {
|
||||
await businessService.updateBusiness(businessForm.value.id, formData);
|
||||
} else {
|
||||
await businessService.createBusiness(formData);
|
||||
}
|
||||
|
||||
showMessage.value = { text: '¡Negocio Guardado Exitosamente!', type: 'success' };
|
||||
setTimeout(() => {
|
||||
router.push(authStore.isAdmin ? '/admin' : '/promoter');
|
||||
}, 2000);
|
||||
} catch (error: any) {
|
||||
console.error('Error saving business:', error);
|
||||
showMessage.value = { text: 'Error al guardar. Intenta de nuevo.', type: 'error' };
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const CATEGORY_EMOJI: Record<string, string> = {
|
||||
'Restaurante': '🍽️', 'Hotel': '🏨', 'Café': '☕',
|
||||
'Comercio': '🏪', 'Turismo': '🌄', 'Bebidas': '🍹',
|
||||
}
|
||||
const catEmoji = computed(() => CATEGORY_EMOJI[businessForm.value.category || ''] || '📍');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-editor-view">
|
||||
<div class="nexus-admin-header">
|
||||
<button class="back-btn" @click="router.back()">
|
||||
<span class="material-icons">arrow_back</span>
|
||||
</button>
|
||||
<h1>{{ isEditing ? 'Editar Negocio' : 'Nuevo Negocio' }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="admin-grid-layout">
|
||||
<!-- FORM PANEL -->
|
||||
<section class="form-panel nexus-glass">
|
||||
<div class="section-title">
|
||||
<span class="material-icons">storefront</span>
|
||||
<h2>Datos del Negocio</h2>
|
||||
</div>
|
||||
|
||||
<div class="nexus-form">
|
||||
<div class="form-group grid-row">
|
||||
<div class="input-box">
|
||||
<label>Nombre del Negocio *</label>
|
||||
<input v-model="businessForm.name" type="text" placeholder="Ej: Restaurante La Casona" required>
|
||||
</div>
|
||||
<div class="input-box">
|
||||
<label>Imagen de Portada</label>
|
||||
<div class="file-upload-wrapper">
|
||||
<input type="file" @change="handleImageChange" accept="image/*" id="file-input">
|
||||
<label for="file-input" class="file-label">
|
||||
<span class="material-icons">cloud_upload</span>
|
||||
{{ selectedFileName || 'SELECCIONAR FOTO' }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group grid-row">
|
||||
<div class="input-box">
|
||||
<label>Categoría</label>
|
||||
<select v-model="businessForm.category" class="nexus-select">
|
||||
<option value="Restaurante">Restaurante</option>
|
||||
<option value="Hotel">Hotel</option>
|
||||
<option value="Café">Café</option>
|
||||
<option value="Bebidas">Bar / Bebidas</option>
|
||||
<option value="Comercio">Comercio</option>
|
||||
<option value="Turismo">Turismo</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-box">
|
||||
<label>Área / Región</label>
|
||||
<select v-model="businessForm.area" class="nexus-select">
|
||||
<option value="Boquete">Boquete</option>
|
||||
<option value="Alto Boquete">Alto Boquete</option>
|
||||
<option value="Dolega">Dolega</option>
|
||||
<option value="David">David</option>
|
||||
<option value="Caldera">Caldera</option>
|
||||
<option value="Chiriquí">Chiriquí</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Descripción del Lugar</label>
|
||||
<textarea
|
||||
v-model="businessForm.description"
|
||||
placeholder="Describe el ambiente, la especialidad, qué lo hace único..."
|
||||
rows="3"
|
||||
class="nexus-textarea"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group grid-row">
|
||||
<div class="input-box">
|
||||
<label>Horario de Atención</label>
|
||||
<input v-model="businessForm.schedule" type="text" placeholder="Ej: Lun-Sáb 8am-10pm">
|
||||
</div>
|
||||
<div class="input-box">
|
||||
<label>Teléfono Fijo</label>
|
||||
<input v-model="businessForm.phone" type="text" placeholder="+507 720-0000">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Dirección Física</label>
|
||||
<input v-model="businessForm.address" type="text" placeholder="Ej: Calle Principal #123">
|
||||
</div>
|
||||
|
||||
<div class="form-section-label mt-4">Redes y Contacto Directo</div>
|
||||
|
||||
<div class="form-group grid-row">
|
||||
<div class="input-box">
|
||||
<label>WhatsApp (Sin +)</label>
|
||||
<div class="whatsapp-input">
|
||||
<span class="prefix">+</span>
|
||||
<input v-model="businessForm.whatsapp" type="text" placeholder="50760000000">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-box">
|
||||
<label>Instagram / Facebook</label>
|
||||
<input v-model="businessForm.instagram" type="text" placeholder="@usuario">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Página Web</label>
|
||||
<input v-model="businessForm.website" type="text" placeholder="https://">
|
||||
</div>
|
||||
|
||||
<button class="deploy-btn" :disabled="isLoading" @click="saveBusiness">
|
||||
<span class="material-icons">{{ isLoading ? 'sync' : 'save' }}</span>
|
||||
{{ isLoading ? 'GUARDANDO...' : 'GUARDAR NEGOCIO' }}
|
||||
</button>
|
||||
|
||||
<p v-if="showMessage.text" :class="['message', showMessage.type]">{{ showMessage.text }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PREVIEW PANEL -->
|
||||
<section class="preview-panel">
|
||||
<div class="section-title white">
|
||||
<span class="material-icons">visibility</span>
|
||||
<h2>Visualización en App</h2>
|
||||
</div>
|
||||
|
||||
<div class="preview-container">
|
||||
<!-- MOBILE PHONE MOCKUP -->
|
||||
<div class="phone-mockup">
|
||||
<div class="phone-content biz-page-preview custom-scrollbar">
|
||||
|
||||
<section class="hero">
|
||||
<img :src="previewImageUrl" class="hero-img" alt="Hero" />
|
||||
<div class="hero-gradient"></div>
|
||||
|
||||
<div class="hero-body">
|
||||
<span class="hero-cat">{{ catEmoji }} {{ businessForm.category }}</span>
|
||||
<h1 class="hero-name">{{ businessForm.name || 'Nombre del Negocio' }}</h1>
|
||||
</div>
|
||||
<div class="hero-rule"></div>
|
||||
</section>
|
||||
|
||||
<div class="pills-row-preview">
|
||||
<div class="pill" v-if="businessForm.area"><span class="material-icons">location_on</span> {{ businessForm.area }}</div>
|
||||
<div class="pill" v-if="businessForm.schedule"><span class="material-icons">schedule</span> {{ businessForm.schedule }}</div>
|
||||
<div class="pill" v-if="businessForm.phone"><span class="material-icons">phone</span> {{ businessForm.phone }}</div>
|
||||
</div>
|
||||
|
||||
<section class="biz-section">
|
||||
<div class="section-header">
|
||||
<span class="section-accent"></span>
|
||||
<h2 class="section-title">Sobre Nosotros</h2>
|
||||
</div>
|
||||
<p class="biz-desc">{{ businessForm.description || 'Aquí aparecerá la descripción del negocio. Un lugar espectacular para disfrutar de lo mejor en compañía de amigos y familiares.' }}</p>
|
||||
|
||||
<!-- Social links -->
|
||||
<div class="social-links mt-4">
|
||||
<button class="social-btn social-wa" v-if="businessForm.whatsapp">
|
||||
<span class="material-icons">chat</span> WhatsApp
|
||||
</button>
|
||||
<button class="social-btn social-ig" v-if="businessForm.instagram">
|
||||
<span class="material-icons">photo_camera</span> Instagram
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Sticky CTA -->
|
||||
<div class="cta-bar">
|
||||
<button class="cta-map"><span class="material-icons">near_me</span> Ver en el Mapa</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="preview-hint mt-3 text-center text-sm text-gray-400">Interactúa con el scroll para ver cómo luce el perfil</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.admin-editor-view {
|
||||
min-height: 100vh;
|
||||
background: #0f172a;
|
||||
color: white;
|
||||
padding: 40px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.nexus-admin-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: none;
|
||||
color: white;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nexus-admin-header h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #fff 0%, #94a3b8 100%);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.admin-grid-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 400px;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.nexus-glass {
|
||||
background: rgba(30, 41, 59, 0.5);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 32px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 32px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.section-title h2 {
|
||||
font-size: 1.25rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-title.white {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nexus-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.grid-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.form-group input, .nexus-select, .nexus-textarea {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 14px 16px;
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.nexus-select {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.nexus-textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.form-group input:focus, .nexus-select:focus, .nexus-textarea:focus {
|
||||
border-color: #fee715;
|
||||
box-shadow: 0 0 0 4px rgba(254, 231, 21, 0.1);
|
||||
}
|
||||
|
||||
.file-upload-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-upload-wrapper input[type="file"] {
|
||||
position: absolute;
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.file-label {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
background: rgba(254, 231, 21, 0.1) !important;
|
||||
border: 1px dashed #fee715 !important;
|
||||
padding: 12px !important;
|
||||
border-radius: 12px;
|
||||
color: #fee715 !important;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.whatsapp-input {
|
||||
display: flex;
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.whatsapp-input .prefix {
|
||||
padding: 14px 16px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
color: #94a3b8;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.whatsapp-input input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.deploy-btn {
|
||||
margin-top: 20px;
|
||||
background: #fee715;
|
||||
color: #101820;
|
||||
border: none;
|
||||
padding: 20px;
|
||||
border-radius: 16px;
|
||||
font-weight: 800;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.deploy-btn:hover:not(:disabled) {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.deploy-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.message {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.message.success { color: #25d366; }
|
||||
.message.error { color: #ef4444; }
|
||||
|
||||
/* ── PHONE MOCKUP FOR PREVIEW ── */
|
||||
.phone-mockup {
|
||||
width: 375px;
|
||||
height: 750px;
|
||||
background: #101820;
|
||||
border: 12px solid #1e293b;
|
||||
border-radius: 40px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 30px 60px rgba(0,0,0,0.5);
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.phone-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ── PREVIEW BIZ PAGE STYLES ── */
|
||||
.biz-page-preview {
|
||||
background: #101820;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
height: 280px;
|
||||
}
|
||||
.hero-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.hero-gradient {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(transparent 30%, rgba(0,0,0,0.9) 100%);
|
||||
}
|
||||
.hero-body {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.hero-cat {
|
||||
background: #fee715;
|
||||
color: black;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 800;
|
||||
padding: 4px 10px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.hero-name {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 900;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 10px rgba(0,0,0,0.5);
|
||||
}
|
||||
.hero-rule {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, #fee715 40%, transparent);
|
||||
}
|
||||
|
||||
.pills-row-preview {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.pill {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pill .material-icons { font-size: 14px; color: #fee715; }
|
||||
|
||||
.biz-section { padding: 20px 16px; border-bottom: 1px solid rgba(255,255,255,0.05); }
|
||||
.section-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
|
||||
.section-accent { width: 4px; height: 18px; background: #fee715; border-radius: 4px; }
|
||||
.section-title { font-size: 1rem; font-weight: 800; margin: 0; }
|
||||
.biz-desc { font-size: 0.85rem; color: #a1a1aa; line-height: 1.5; }
|
||||
|
||||
.social-links { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.social-btn {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.social-wa { background: #128c7e; color: white; }
|
||||
.social-ig { background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%); color: white; }
|
||||
|
||||
.cta-bar {
|
||||
position: sticky;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: rgba(16, 24, 32, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.cta-map {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
border-radius: 100px;
|
||||
background: white;
|
||||
color: black;
|
||||
font-weight: 800;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@ -24,11 +24,7 @@ const categories = ['Todas', 'Restaurante', 'Turismo', 'Bebidas', 'Comercio']
|
||||
|
||||
// Modals
|
||||
const showModal = ref(false)
|
||||
const showBusinessModal = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const isEditingBusiness = ref(false)
|
||||
const businessImageFile = ref<File | null>(null)
|
||||
const businessImagePreview = ref<string | null>(null)
|
||||
|
||||
|
||||
// Current data
|
||||
@ -47,24 +43,6 @@ const currentCoupon = ref<Partial<Coupon>>({
|
||||
is_active: true
|
||||
})
|
||||
|
||||
const currentBusiness = ref<Partial<Business>>({
|
||||
name: '',
|
||||
address: '',
|
||||
phone: '',
|
||||
image_url: '',
|
||||
social_media: '',
|
||||
category: 'Restaurante',
|
||||
area: 'Boquete',
|
||||
description: '',
|
||||
website: '',
|
||||
// Template fields
|
||||
schedule: '',
|
||||
whatsapp: '',
|
||||
instagram: '',
|
||||
facebook: '',
|
||||
gallery_images: []
|
||||
})
|
||||
|
||||
const userName = localStorage.getItem('user_name') || 'Promotor'
|
||||
|
||||
onMounted(async () => {
|
||||
@ -174,82 +152,15 @@ async function deleteShuttle(id: string) {
|
||||
}
|
||||
|
||||
// Business Methods
|
||||
import { useRouter } from 'vue-router'
|
||||
const localRouter = useRouter()
|
||||
|
||||
function openCreateBusinessModal() {
|
||||
isEditingBusiness.value = false
|
||||
currentBusiness.value = {
|
||||
name: '',
|
||||
address: '',
|
||||
phone: '',
|
||||
image_url: '',
|
||||
social_media: '',
|
||||
category: 'Restaurante',
|
||||
area: 'Boquete',
|
||||
description: '',
|
||||
website: '',
|
||||
schedule: '',
|
||||
whatsapp: '',
|
||||
instagram: '',
|
||||
facebook: '',
|
||||
gallery_images: []
|
||||
}
|
||||
showBusinessModal.value = true
|
||||
businessImageFile.value = null
|
||||
businessImagePreview.value = null
|
||||
}
|
||||
|
||||
|
||||
function handleBusinessImage(event: any) {
|
||||
const file = event.target.files[0]
|
||||
if (file) {
|
||||
businessImageFile.value = file
|
||||
businessImagePreview.value = URL.createObjectURL(file)
|
||||
}
|
||||
localRouter.push('/admin/business-edit/new')
|
||||
}
|
||||
|
||||
function openEditBusinessModal(biz: Business) {
|
||||
isEditingBusiness.value = true
|
||||
currentBusiness.value = { ...biz }
|
||||
showBusinessModal.value = true
|
||||
businessImageFile.value = null
|
||||
businessImagePreview.value = getImageUrl(biz.image_url)
|
||||
}
|
||||
|
||||
async function saveBusiness() {
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('name', currentBusiness.value.name || '')
|
||||
formData.append('category', currentBusiness.value.category || 'Restaurante')
|
||||
formData.append('address', currentBusiness.value.address || '')
|
||||
formData.append('phone', currentBusiness.value.phone || '')
|
||||
formData.append('social_media', currentBusiness.value.social_media || '')
|
||||
formData.append('area', currentBusiness.value.area || 'Boquete')
|
||||
formData.append('description', currentBusiness.value.description || '')
|
||||
formData.append('website', currentBusiness.value.website || '')
|
||||
// Template fields
|
||||
formData.append('schedule', currentBusiness.value.schedule || '')
|
||||
formData.append('whatsapp', currentBusiness.value.whatsapp || '')
|
||||
formData.append('instagram', currentBusiness.value.instagram || '')
|
||||
formData.append('facebook', currentBusiness.value.facebook || '')
|
||||
// gallery_images handled separately via JSON
|
||||
if (currentBusiness.value.gallery_images?.length) {
|
||||
formData.append('gallery_images', JSON.stringify(currentBusiness.value.gallery_images))
|
||||
}
|
||||
|
||||
if (businessImageFile.value) {
|
||||
formData.append('image', businessImageFile.value)
|
||||
}
|
||||
|
||||
if (isEditingBusiness.value && currentBusiness.value.id) {
|
||||
await businessService.updateBusiness(currentBusiness.value.id, formData)
|
||||
} else {
|
||||
await businessService.createBusiness(formData)
|
||||
}
|
||||
showBusinessModal.value = false
|
||||
await loadBusinesses()
|
||||
} catch (e) {
|
||||
console.error('Error saving business:', e)
|
||||
alert('Error al guardar el negocio')
|
||||
}
|
||||
localRouter.push(`/admin/business-edit/${biz.id}`)
|
||||
}
|
||||
|
||||
async function deleteBusiness(id: string) {
|
||||
@ -704,125 +615,6 @@ async function toggleCouponStatus(coupon: Coupon) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Business Modal -->
|
||||
<div v-if="showBusinessModal" class="modal-overlay" @click.self="showBusinessModal = false">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>{{ isEditingBusiness ? 'Editar Negocio' : 'Registrar Negocio' }}</h2>
|
||||
<button class="close-btn" @click="showBusinessModal = false"><span class="material-icons">close</span></button>
|
||||
</div>
|
||||
<form @submit.prevent="saveBusiness" class="coupon-form">
|
||||
|
||||
<!-- ── Info básica ── -->
|
||||
<div class="form-section-label">📋 Información Básica</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Nombre del Negocio *</label>
|
||||
<input v-model="currentBusiness.name" type="text" required placeholder="Ej: Restaurante La Casona">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Categoría</label>
|
||||
<select v-model="currentBusiness.category">
|
||||
<option value="Restaurante">Restaurante</option>
|
||||
<option value="Hotel">Hotel</option>
|
||||
<option value="Café">Café</option>
|
||||
<option value="Bebidas">Bar / Bebidas</option>
|
||||
<option value="Comercio">Comercio</option>
|
||||
<option value="Turismo">Turismo</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Área / Región</label>
|
||||
<select v-model="currentBusiness.area">
|
||||
<option value="Boquete">Boquete</option>
|
||||
<option value="Alto Boquete">Alto Boquete</option>
|
||||
<option value="Dolega">Dolega</option>
|
||||
<option value="David">David</option>
|
||||
<option value="Caldera">Caldera</option>
|
||||
<option value="Chiriquí">Chiriquí</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Teléfono</label>
|
||||
<input v-model="currentBusiness.phone" type="text" placeholder="+507 6000-0000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>🕐 Horario de Atención</label>
|
||||
<input v-model="currentBusiness.schedule" type="text" placeholder="Ej: Lun-Sáb 8am-10pm">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Dirección Física</label>
|
||||
<input v-model="currentBusiness.address" type="text" placeholder="Ej: Calle Principal #123, Frente al Parque">
|
||||
</div>
|
||||
|
||||
<!-- ── Imagen principal ── -->
|
||||
<div class="form-section-label">🖼️ Imagen de Portada</div>
|
||||
<div class="form-group">
|
||||
<label>Foto Principal (Logo o Fachada)</label>
|
||||
<div class="file-upload-wrapper">
|
||||
<input type="file" @change="handleBusinessImage" accept="image/*" class="file-input">
|
||||
<div class="file-preview" v-if="businessImagePreview">
|
||||
<img :src="businessImagePreview" alt="Vista previa">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Descripción ── -->
|
||||
<div class="form-section-label">📝 Descripción</div>
|
||||
<div class="form-group">
|
||||
<label>Sobre el Negocio (Texto de marketing)</label>
|
||||
<textarea v-model="currentBusiness.description" placeholder="Describe la experiencia del lugar, su especialidad y ambiente para atraer clientes..." rows="4"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- ── Redes Sociales y Contacto ── -->
|
||||
<div class="form-section-label">🌐 Contacto y Redes Sociales</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>💬 WhatsApp</label>
|
||||
<input v-model="currentBusiness.whatsapp" type="text" placeholder="50760000000 (con código de país)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>📸 Instagram</label>
|
||||
<input v-model="currentBusiness.instagram" type="text" placeholder="@usuario">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>👍 Facebook</label>
|
||||
<input v-model="currentBusiness.facebook" type="text" placeholder="/nombre-de-pagina">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>🌐 Página Web</label>
|
||||
<input v-model="currentBusiness.website" type="url" placeholder="https://www.ejemplo.com">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Galería ── -->
|
||||
<div class="form-section-label">📸 Galería de Imágenes (Carrusel)</div>
|
||||
<div class="form-group">
|
||||
<label>URLs de fotos adicionales (una por línea)</label>
|
||||
<textarea
|
||||
:value="currentBusiness.gallery_images?.join('\n') || ''"
|
||||
@input="(e: any) => currentBusiness.gallery_images = e.target.value.split('\n').map((s: string) => s.trim()).filter((s: string) => s.length > 0)"
|
||||
placeholder="https://url-foto1.jpg https://url-foto2.jpg https://url-foto3.jpg"
|
||||
rows="3"
|
||||
></textarea>
|
||||
<small class="form-hint">Agrega URLs de imágenes para el carrusel (menú, ambiente, experiencias). Una URL por línea.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="submit-btn">
|
||||
<span class="material-icons">{{ isEditingBusiness ? 'save' : 'store' }}</span>
|
||||
{{ isEditingBusiness ? 'Guardar Cambios' : 'Publicar Negocio' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user