feat: business template/mold redesign

- Supabase: Add schedule, whatsapp, instagram, facebook, gallery_images
  columns to businesses table
- types/index.ts: Add 5 new fields to Business interface
- businessService.ts: Update all SELECT queries to include new fields,
  add _parseFormData helper, handle gallery_images as JSON array
- BusinessDetailsView.vue: Full redesign matching the approved mockup:
  * Hero 300px with centered name + yellow category badge
  * Horizontal scrollable quick-info pills (area, schedule, phone, web)
  * Image carousel (main image + gallery_images) with arrows + dots
  * About section with yellow left-accent bar
  * 2x2 social grid (WhatsApp, Instagram, Facebook, Maps) with brand colors
  * Coupon section preserved
  * Sticky CTA bar: Ver en el Mapa + Llamar buttons
- PromoterDashboard.vue: Updated business modal with grouped sections
  (Info Basica / Portada / Descripcion / Redes / Galeria) + new fields
This commit is contained in:
2026-03-03 21:31:35 -05:00
parent af7464be43
commit bdfcd55370
4 changed files with 858 additions and 497 deletions

File diff suppressed because it is too large Load Diff

View File

@ -56,7 +56,13 @@ const currentBusiness = ref<Partial<Business>>({
category: 'Restaurante',
area: 'Boquete',
description: '',
website: ''
website: '',
// Template fields
schedule: '',
whatsapp: '',
instagram: '',
facebook: '',
gallery_images: []
})
const userName = localStorage.getItem('user_name') || 'Promotor'
@ -179,7 +185,12 @@ function openCreateBusinessModal() {
category: 'Restaurante',
area: 'Boquete',
description: '',
website: ''
website: '',
schedule: '',
whatsapp: '',
instagram: '',
facebook: '',
gallery_images: []
}
showBusinessModal.value = true
businessImageFile.value = null
@ -214,7 +225,16 @@ async function saveBusiness() {
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)
}
@ -692,39 +712,57 @@ async function toggleCouponStatus(coupon: Coupon) {
<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>
<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="Area Turistica">Área Turística</option>
<option value="Hotel">Hotel</option>
<option value="Café">Café</option>
<option value="Bebidas">Bar / Bebidas</option>
<option value="Viajes de Turismo">Viajes de Turismo</option>
<option value="Comercio">Comercio</option>
<option value="Turismo">Turismo</option>
</select>
</div>
<div class="form-group">
<label>Teléfono</label>
<input v-model="currentBusiness.phone" type="text">
</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>Imagen del Negocio (Logo o Fachada)</label>
<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">
@ -732,20 +770,55 @@ async function toggleCouponStatus(coupon: Coupon) {
</div>
</div>
</div>
<!-- Descripción -->
<div class="form-section-label">📝 Descripción</div>
<div class="form-group">
<label>Descripción / Historia del Negocio</label>
<textarea v-model="currentBusiness.description" placeholder="Cuéntanos un poco sobre el negocio para darle un toque premium..." rows="3"></textarea>
<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>Página Web (Opcional)</label>
<input v-model="currentBusiness.website" type="url" placeholder="https://www.ejemplo.com">
</div>
<div class="form-group">
<label>Redes Sociales</label>
<input v-model="currentBusiness.social_media" type="text" placeholder="Ej: @pizzeria_centro">
<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&#10;https://url-foto2.jpg&#10;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">Guardar Negocio</button>
<button type="submit" class="submit-btn">
<span class="material-icons">{{ isEditingBusiness ? 'save' : 'store' }}</span>
{{ isEditingBusiness ? 'Guardar Cambios' : 'Publicar Negocio' }}
</button>
</div>
</form>
</div>
@ -1182,6 +1255,35 @@ async function toggleCouponStatus(coupon: Coupon) {
.submit-btn:hover { opacity: 0.9; }
/* Form section labels (organize modal into visual sections) */
.form-section-label {
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--active-color);
padding: 8px 0 2px;
border-bottom: 1px solid var(--border-color);
margin-top: 4px;
}
.form-hint {
font-size: 0.75rem;
color: var(--text-secondary);
line-height: 1.5;
margin-top: 4px;
}
/* Submit btn icon alignment */
.submit-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.submit-btn .material-icons { font-size: 1.1rem; }
.spin {
animation: spin 1s linear infinite;
}