diff --git a/frontend/src/views/AdminBusinessEditor.vue b/frontend/src/views/AdminBusinessEditor.vue index 127fb5b..67cab0e 100644 --- a/frontend/src/views/AdminBusinessEditor.vue +++ b/frontend/src/views/AdminBusinessEditor.vue @@ -15,6 +15,10 @@ const showMessage = ref({ text: '', type: '' }); const selectedFile = ref(null); const selectedFileName = ref(''); +// Gallery de imágenes +const galleryFiles = ref([]); // archivos nuevos seleccionados +const galleryPreviews = ref([]); // previews locales URL.createObjectURL + // Business state const businessForm = ref>({ name: '', @@ -46,6 +50,10 @@ onMounted(async () => { if (data.image_url) { previewImageUrl.value = data.image_url; } + // Cargar URLs existentes de galeria como previews + if (data.gallery_images?.length) { + galleryPreviews.value = [...data.gallery_images]; + } } catch (e) { console.error(e); showMessage.value = { text: 'Error cargando negocio', type: 'error' }; @@ -61,16 +69,38 @@ function handleImageChange(event: Event) { 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.onload = (e) => { previewImageUrl.value = e.target?.result as string; }; reader.readAsDataURL(file); } } +function handleGalleryFiles(event: Event) { + const input = event.target as HTMLInputElement; + if (!input.files) return; + const newFiles = Array.from(input.files); + newFiles.forEach(file => { + galleryFiles.value.push(file); + galleryPreviews.value.push(URL.createObjectURL(file)); + }); + // Reset input so same file can be re-picked + input.value = ''; +} + +function removeGalleryItem(index: number) { + const preview = galleryPreviews.value[index]; + if (!preview) return; + if (preview.startsWith('blob:')) { + URL.revokeObjectURL(preview); + const existingCount = (businessForm.value.gallery_images ?? []).length; + const fileIndex = index - existingCount; + if (fileIndex >= 0) galleryFiles.value.splice(fileIndex, 1); + } else { + businessForm.value.gallery_images = (businessForm.value.gallery_images ?? []).filter((_, i) => i !== index); + } + galleryPreviews.value.splice(index, 1); +} + async function saveBusiness() { isLoading.value = true; showMessage.value = { text: '', type: '' }; @@ -93,6 +123,18 @@ async function saveBusiness() { formData.append('gallery_images', JSON.stringify(businessForm.value.gallery_images)); } + // Subir archivos nuevos de galeria a Supabase Storage + if (galleryFiles.value.length > 0) { + const uploadedUrls: string[] = []; + for (const gFile of galleryFiles.value) { + const url = await businessService.uploadImage(gFile); + uploadedUrls.push(url); + } + // Combinar las URL existentes ya serializadas + las nuevas subidas + const existing: string[] = businessForm.value.gallery_images ?? []; + formData.set('gallery_images', JSON.stringify([...existing, ...uploadedUrls])); + } + if (selectedFile.value) { formData.append('image', selectedFile.value); } @@ -229,6 +271,42 @@ const catEmoji = computed(() => CATEGORY_EMOJI[businessForm.value.category || '' + + +
+ + + + + + + +
+ @@ -638,4 +733,102 @@ const catEmoji = computed(() => CATEGORY_EMOJI[businessForm.value.category || '' justify-content: center; gap: 8px; } + +/* ── GALLERY UPLOAD ── */ +.gallery-upload-zone { + position: relative; +} +.gallery-upload-zone input[type="file"] { + position: absolute; + width: 0.1px; + height: 0.1px; + opacity: 0; + z-index: -1; +} +.gallery-upload-label { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 28px; + border: 2px dashed rgba(254, 231, 21, 0.4); + border-radius: 16px; + background: rgba(254, 231, 21, 0.05); + color: #fee715; + cursor: pointer; + font-weight: 700; + transition: all 0.25s; + text-align: center; +} +.gallery-upload-label:hover { + border-color: #fee715; + background: rgba(254, 231, 21, 0.1); +} + +.gallery-thumbs { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 14px; +} + +.gallery-thumb { + position: relative; + width: 90px; + height: 90px; + border-radius: 12px; + overflow: hidden; + border: 2px solid rgba(255,255,255,0.1); + flex-shrink: 0; +} + +.gallery-thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.thumb-remove { + position: absolute; + top: 4px; + right: 4px; + background: rgba(239,68,68,0.9); + border: none; + border-radius: 50%; + width: 22px; + height: 22px; + color: white; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + backdrop-filter: blur(4px); +} +.thumb-remove .material-icons { font-size: 14px; } + +.gallery-empty-hint { + margin-top: 10px; + font-size: 0.78rem; + color: #64748b; + font-style: italic; +} + +/* ── GALLERY PREVIEW CAROUSEL (mini) ── */ +.gallery-mini-carousel { + display: flex; + gap: 8px; + overflow-x: auto; + scrollbar-width: none; + padding-bottom: 4px; +} +.gallery-mini-carousel::-webkit-scrollbar { display: none; } +.gallery-mini-img { + width: 110px; + height: 80px; + border-radius: 10px; + object-fit: cover; + flex-shrink: 0; + border: 1px solid rgba(255,255,255,0.1); +}