From 97a3d162e0693884c5d32d78e443c56ae0874ee9 Mon Sep 17 00:00:00 2001 From: "Carlos (Almeira)" Date: Sat, 28 Mar 2026 14:06:38 -0500 Subject: [PATCH] feat: implement rich text, media previews, interactions, and fix state sync - Added RichTextEditor to PublicationLayout's 'contenido' - Added MediaThumbnail to RelationSelectField for dynamic image previews - Replaced PostCard edit overlay with single click (view) and double click (edit) mechanics - Fixed persistence/state bugs by adding revalidatePath to entity server actions - Replaced hard reload in PublicationsList with router.refresh() for optimistic syncing --- src/app/actions/entities.ts | 3 ++ src/components/publications/PostCard.tsx | 39 ++++++++++++------- .../publications/PublicationLayout.tsx | 14 +++---- .../publications/PublicationsList.tsx | 8 ++-- .../publications/RelationSelectField.tsx | 31 ++++++++++++++- 5 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/app/actions/entities.ts b/src/app/actions/entities.ts index 55b9830..cbd402e 100644 --- a/src/app/actions/entities.ts +++ b/src/app/actions/entities.ts @@ -3,6 +3,7 @@ import { collectionMap, SCHEMA_MAP } from '@/lib/schema'; import { createDocumentServer, updateDocumentServer } from './appwrite'; import { redirect } from 'next/navigation'; +import { revalidatePath } from 'next/cache'; /** * Centralized action to create a new entity document @@ -32,6 +33,7 @@ export async function saveEntityAction(entity: string, formData: FormData) { const res = await createDocumentServer(collectionId, data); if (res.success) { + revalidatePath(`/dashboard/${entity}`); redirect(`/dashboard/${entity}`); } else { throw new Error(res.error || "Error desconocido al crear documento"); @@ -66,6 +68,7 @@ export async function updateEntityAction(entity: string, id: string, formData: F const res = await updateDocumentServer(collectionId, id, data); if (res.success) { + revalidatePath(`/dashboard/${entity}`); redirect(`/dashboard/${entity}`); } else { throw new Error(res.error || "Error desconocido al actualizar documento"); diff --git a/src/components/publications/PostCard.tsx b/src/components/publications/PostCard.tsx index 781a39a..e876d1d 100644 --- a/src/components/publications/PostCard.tsx +++ b/src/components/publications/PostCard.tsx @@ -80,11 +80,34 @@ export const PostCard: React.FC = ({ ? post.tipo_publicacion_id.descripcion : post.tipo_publicacion_id; + const handleCardClick = (e: React.MouseEvent) => { + if ((e.target as HTMLElement).closest('button, a, [role="button"]')) return; + // Single click: abre vista de detalle + window.open(`/${post.slug || post.$id}`, '_blank'); + }; + + const handleCardDoubleClick = (e: React.MouseEvent) => { + e.preventDefault(); + if ((e.target as HTMLElement).closest('button, a, [role="button"]')) return; + // Double click: abre edición si es admin/editor + if (userRole === 'admin' || userRole === 'editor') { + onEdit?.(post); + } + }; + return (
+ {/* Tooltip instructivo para admins */} + {(userRole === 'admin' || userRole === 'editor') && ( +
+ Doble click para editar +
+ )} {/* Imagen Destacada */}
{imageUrl ? ( @@ -107,20 +130,6 @@ export const PostCard: React.FC = ({
- {/* Edit Overlay — solo visible si el usuario tiene permisos */} - -
onEdit?.(post)} - role="button" - aria-label={`Editar: ${safeTitle}`} - > - - - Editar - -
-
{/* Contenido */} diff --git a/src/components/publications/PublicationLayout.tsx b/src/components/publications/PublicationLayout.tsx index 5a25f14..7f22f30 100644 --- a/src/components/publications/PublicationLayout.tsx +++ b/src/components/publications/PublicationLayout.tsx @@ -29,6 +29,7 @@ import { usePublicationStore } from '@/store/usePublicationStore'; import FormSection from './FormSection'; import RelationSelectField from './RelationSelectField'; import PermissionGuard from './PermissionGuard'; +import { RichTextEditor } from '../dashboard/RichTextEditor'; // ───────────────────────────────────────────── // FIELD COMPONENTS @@ -93,6 +94,7 @@ export const PublicationLayout: React.FC = ({ const [titulo, setTitulo] = useState(initialData?.titulo || ''); const [slug, setSlug] = useState(initialData?.slug || ''); const [isAutoSlug, setIsAutoSlug] = useState(!initialData?.slug); + const [contenido, setContenido] = useState(initialData?.contenido || ''); const [validationErrors, setValidationErrors] = useState>({}); // Populate relation cache in the store on mount @@ -116,6 +118,7 @@ export const PublicationLayout: React.FC = ({ e.preventDefault(); const formData = new FormData(e.currentTarget); + formData.set('contenido', contenido); // Ensure rich text state is attached // Client-side Zod validation const parseResult = PostSchema.safeParse(Object.fromEntries(formData.entries())); @@ -264,13 +267,10 @@ export const PublicationLayout: React.FC = ({
} columns={1}> -