'use client'; import { useState, useEffect, useCallback, useRef } from 'react'; import { createPortal } from 'react-dom'; import { ImageIcon, Video, X, Search, CheckCircle2, UploadCloud, Loader2 } from 'lucide-react'; import { databases, APPWRITE_ALMEIRA_CONFIG, endpoint, projectId } from '@/lib/appwrite'; import { uploadMediaDocumentServer, listMediaServer, getMediaServer } from '@/app/actions/appwrite'; /** Utility to generate frontend Appwrite image previews */ export function getAppwriteImageUrl(fileId: string, bucketId?: string, width = 400, height = 250, isVideo = false) { if (!fileId) return ''; const finalBucketId = bucketId || APPWRITE_ALMEIRA_CONFIG.mediaBucketId; if (isVideo) { return `${endpoint}/storage/buckets/${finalBucketId}/files/${fileId}/view?project=${projectId}`; } return `${endpoint}/storage/buckets/${finalBucketId}/files/${fileId}/view?project=${projectId}&width=${width}&height=${height}&quality=80`; } interface MediaGridSelectorProps { value?: string; onChange: (fileId: string, url?: string, tipo?: string) => void; label?: string; mode?: 'field' | 'modal'; isOpen?: boolean; onClose?: () => void; } export const MediaGridSelector = ({ value, onChange, label = 'Seleccionar Medio', mode = 'field', isOpen: externalOpen, onClose }: MediaGridSelectorProps) => { const [isOpen, setIsOpen] = useState(mode === 'modal' ? !!externalOpen : false); const [mediaItems, setMediaItems] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isUploading, setIsUploading] = useState(false); const [hasMore, setHasMore] = useState(true); const fileInputRef = useRef(null); useEffect(() => { if (mode === 'modal' && externalOpen !== undefined) setIsOpen(externalOpen); }, [mode, externalOpen]); useEffect(() => { if (isOpen) fetchMedia(); }, [isOpen]); const fetchMedia = async (more = false, search = '') => { setIsLoading(true); try { const currentOffset = more ? mediaItems.length : 0; const res = await listMediaServer(search, currentOffset); if (res.success && res.data) { if (more) { setMediaItems(prev => [...prev, ...res.data]); } else { setMediaItems(res.data); } setHasMore(res.data.length === 20); } else { setHasMore(false); } } catch (error) { console.error('Error fetching media:', error); } finally { setIsLoading(false); } }; // Handle Search Debouncing useEffect(() => { if (!isOpen) return; const timer = setTimeout(() => { fetchMedia(false, searchTerm); }, 500); return () => clearTimeout(timer); }, [searchTerm, isOpen]); useEffect(() => { // If we have a value but haven't loaded its document, fetch just that one if (value && mode === 'field' && !mediaItems.some(m => m.$id === value)) { getMediaServer(value) .then(res => { if (res.success && res.data) { setMediaItems(prev => { if (prev.some(m => m.$id === value)) return prev; return [...prev, res.data]; }); } }).catch(err => console.error("Error resolver media thumbnail:", err)); } }, [value, mode]); const handleUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setIsUploading(true); try { const formData = new FormData(); formData.append('files', file); const res = await uploadMediaDocumentServer(formData); if (!res.success || !res.data || res.data.length === 0) { throw new Error(res.error || "Error from server edge while uploading media."); } const doc = res.data[0]; setMediaItems(prev => [doc, ...prev]); const isVideo = doc.tipo === 'video'; const url = getAppwriteImageUrl(doc.file_id, doc.bucket_id, 1200, 800, isVideo); onChange(doc.$id, url, doc.tipo); closeModal(); } catch (error) { console.error("Error upload:", error); alert("Error al subir el archivo. Revisa que el perfil tenga el scope files.write."); } finally { setIsUploading(false); } }; const closeModal = useCallback(() => { setIsOpen(false); if (onClose) onClose(); }, [onClose]); const filteredItems = mediaItems; // Render modal structure const renderModal = () => { if (!isOpen) return null; const popup = (
{/* Header */}

Biblioteca de Medios

Selecciona o sube un archivo

{/* Sub-header (Search + Upload) */}
setSearchTerm(e.target.value)} className="w-full bg-slate-950 border border-slate-800 rounded-2xl pl-12 pr-6 py-4 text-white focus:outline-none focus:border-cyan-500 transition-all font-medium" />
{/* Content Grid */}
{isLoading ? (
Cargando Galería...
) : filteredItems.length > 0 ? (
{filteredItems.map((item) => { const isVideo = item.tipo === 'video'; const url = getAppwriteImageUrl(item.file_id, item.bucket_id, 300, 300, isVideo); return (
{ const selectedUrl = getAppwriteImageUrl(item.file_id, item.bucket_id, 1200, 800, isVideo); onChange(item.$id, selectedUrl, item.tipo); if (mode !== 'modal') setIsOpen(false); closeModal(); }} className={`group relative aspect-square rounded-2xl border-2 transition-all cursor-pointer overflow-hidden bg-slate-950 ${ value === item.file_id ? 'border-cyan-500 shadow-xl shadow-cyan-500/20' : 'border-slate-800 hover:border-slate-600 shadow-lg' }`} > {isVideo ? (
) : (

Sin Resultados

)}
{mediaItems.length} Archivos Disponibles {hasMore && ( )}
); return typeof document !== 'undefined' ? createPortal(popup, document.body) : popup; }; if (mode === 'modal') { return renderModal(); } // FIELD MODE RENDER (Inline usage in forms) return (
{value && ( )}
setIsOpen(true)} className={`relative aspect-video rounded-2xl border-2 border-dashed transition-all cursor-pointer flex flex-col items-center justify-center gap-2 overflow-hidden bg-slate-950/50 group ${ value ? 'border-cyan-500/50 hover:border-cyan-400 shadow-[0_0_20px_rgba(6,182,212,0.1)]' : 'border-slate-800 hover:border-slate-700 hover:bg-slate-900' }`} > {value ? ( <> {(() => { // Si value existe, buscamos su documento para sacar el file_id const selectedDoc = mediaItems.find(m => m.$id === value); // Si aún no está en la lista (cargando), mostramos loader genérico if (!selectedDoc && mediaItems.length > 0) return null; const isVideo = selectedDoc?.tipo === 'video'; const fileIdToRender = selectedDoc ? selectedDoc.file_id : value; const bucketIdToRender = selectedDoc ? selectedDoc.bucket_id : undefined; const fileUrl = getAppwriteImageUrl(fileIdToRender, bucketIdToRender, 800, 450, isVideo); return isVideo ? (
{/* Sólo incluir hidden input en modo campo, para evitar conflictos cuando se usa como modal puro */} {mode === 'field' && label !== 'Logotipo Corporativo' && ( )} {renderModal()}
); }; export default MediaGridSelector;