From 3488023142ea2c612461753fbcf980f746fd5c19 Mon Sep 17 00:00:00 2001 From: Michele Date: Sun, 8 Mar 2026 21:01:47 +0100 Subject: [PATCH] feat(02-02): PostCard regen button with inline form, topic override, and regenerated badge - Add handleRegen function with topic/notes override support - Add inline popover form with optional topic and notes fields - Add isRegenerated prop with amber RefreshCw badge for regenerated posts - Regen button positioned in card header next to expand/collapse chevron - Form appears below header with Rigenera/Annulla actions Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/PostCard.tsx | 128 ++++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/PostCard.tsx b/frontend/src/components/PostCard.tsx index 73ae9da..60f3396 100644 --- a/frontend/src/components/PostCard.tsx +++ b/frontend/src/components/PostCard.tsx @@ -26,6 +26,8 @@ interface PostCardProps { onRegenerated: (updated: PostResult) => void /** Callback quando le slide vengono modificate inline */ onEdit: (updated: PostResult) => void + /** True se questo post è stato rigenerato (non originale) */ + isRegenerated?: boolean } export function PostCard({ @@ -35,13 +37,40 @@ export function PostCard({ tono, onRegenerated, onEdit, + isRegenerated, }: PostCardProps) { const [expanded, setExpanded] = useState(false) + const [showRegenForm, setShowRegenForm] = useState(false) + const [regenTopic, setRegenTopic] = useState('') + const [regenNotes, setRegenNotes] = useState('') const generateSingle = useGenerateSingle() const slot = result.slot const post = result.post + async function handleRegen() { + if (!slot) return + // Se l'utente ha specificato un topic, usalo come override + const overriddenSlot = regenTopic.trim() + ? { ...slot, topic: regenTopic.trim() } + : slot + const req: GenerateRequest = { + slot: overriddenSlot, + obiettivo_campagna: obiettivoCampagna, + brand_name: brandName, + tono: regenNotes.trim() || tono, // Se note fornite, usale come tono override + } + try { + const newResult = await generateSingle.mutateAsync(req) + onRegenerated({ ...newResult, slot }) + setShowRegenForm(false) + setRegenTopic('') + setRegenNotes('') + } catch { + // Errore gestito da generateSingle.error + } + } + async function handleRetry() { if (!slot) return const req: GenerateRequest = { @@ -123,17 +152,22 @@ export function PostCard({ ].join(' ')} > {/* Header card — sempre visibile, click per espandere */} - + setExpanded((v) => !v)} + > + {expanded ? : } + + - + + + {/* Regen inline form */} + {showRegenForm && ( +
+
+ + setRegenTopic(e.target.value)} + placeholder="Es: 3 errori comuni nel marketing digitale" + className="w-full px-3 py-2 rounded-lg bg-stone-900 border border-stone-700 text-sm text-stone-200 placeholder:text-stone-600 focus:border-amber-500/50 focus:outline-none" + /> +
+
+ + setRegenNotes(e.target.value)} + placeholder="Es: tono piu' provocatorio, focus su ROI" + className="w-full px-3 py-2 rounded-lg bg-stone-900 border border-stone-700 text-sm text-stone-200 placeholder:text-stone-600 focus:border-amber-500/50 focus:outline-none" + /> +
+
+ + +
+ {generateSingle.error && ( +

{generateSingle.error.message}

+ )} +
+ )} {/* Expanded: SlideViewer */} {expanded && (