// AdminWorkEditor.jsx — formulario de creación/edición de trabajo function AdminWorkEditor({ work, articles, onSaved, onCancel }) { const isNew = !work; const [title, setTitle] = React.useState(work?.title || ''); const [slug, setSlug] = React.useState(work?.slug || ''); const [slugTouched, setSlugTouched] = React.useState(!!work); const [client, setClient] = React.useState(work?.client || ''); const [summary, setSummary] = React.useState(work?.summary || ''); const [body, setBody] = React.useState(work?.body || null); const [metric, setMetric] = React.useState(work?.metric || ''); const [metricLabel, setMetricLabel] = React.useState(work?.metricLabel || ''); const [accent, setAccent] = React.useState(work?.accent || '#005BC2'); const [tagsText, setTagsText] = React.useState((work?.tags || []).join(', ')); const [coverUrl, setCoverUrl] = React.useState(work?.coverUrl || ''); const [articleId, setArticleId] = React.useState(work?.articleId || ''); const [status, setStatus] = React.useState(work?.status || 'private'); const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(''); const editorHostRef = React.useRef(null); const editorRef = React.useRef(null); React.useEffect(() => { if (!slugTouched && title) setSlug(window.supabaseApi.slugify(title)); }, [title, slugTouched]); React.useEffect(() => { if (!editorHostRef.current || editorRef.current) return; if (!window.EditorJS) { console.warn('EditorJS no disponible'); return; } const ej = new window.EditorJS({ holder: editorHostRef.current, placeholder: 'Describe el caso, metodología, resultados…', data: body || { blocks: [] }, tools: { header: window.EditorJSHeader ? { class: window.EditorJSHeader, config: { levels: [2, 3], defaultLevel: 2 } } : undefined, list: window.EditorJSList ? { class: window.EditorJSList, inlineToolbar: true } : undefined, quote: window.EditorJSQuote || undefined, code: window.EditorJSCode || undefined, delimiter: window.EditorJSDelimiter || undefined, image: window.EditorJSImage ? { class: window.EditorJSImage, config: { uploader: { uploadByFile: async (file) => { const url = await window.supabaseApi.uploadCover(file); return { success: 1, file: { url } }; }, uploadByUrl: async (url) => ({ success: 1, file: { url } }), }, }, } : undefined, embed: window.EditorJSEmbed ? { class: window.EditorJSEmbed, config: { services: { youtube: true, vimeo: true, twitter: true } }, } : undefined, }, }); editorRef.current = ej; return () => { editorRef.current = null; }; }, []); const onPickFile = async (e) => { const file = e.target.files?.[0]; if (!file) return; setBusy(true); setErr(''); try { const url = await window.supabaseApi.uploadCover(file); setCoverUrl(url); } catch (e) { setErr(e.message || 'Error subiendo imagen'); } finally { setBusy(false); } }; const save = async (newStatus) => { setBusy(true); setErr(''); try { let finalBody = body; if (editorRef.current) { try { finalBody = await editorRef.current.save(); } catch (_) {} } const payload = { slug: slug || window.supabaseApi.slugify(title), title, client, summary, body: finalBody, metric, metricLabel, accent, tags: tagsText.split(',').map(s => s.trim()).filter(Boolean), coverUrl, articleId: articleId || null, status: newStatus || status, }; const saved = await window.supabaseApi.upsertWork(payload, work?._row?.id); window.dispatchEvent(new Event('works:refresh')); onSaved && onSaved(saved); } catch (e) { setErr(e.message || 'Error guardando'); } finally { setBusy(false); } }; return (