feat(02-02): OutputReview summary counter with regenerated/edited tracking

- Add regeneratedSlots Set to track regenerated post indices
- Add summary counter showing: N post, X generati, Y rigenerati, Z modificati
- Counter updates in real-time after each regeneration or inline edit
- Pass isRegenerated prop to PostCard for visual badge
- Update info box to mention regen feature with RefreshCw icon
- Use JSON.stringify comparison for manual edit detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michele
2026-03-08 21:03:11 +01:00
parent 3488023142
commit ad2483d48d

View File

@@ -10,7 +10,7 @@
* e inviato al backend via POST al momento del download CSV.
*/
import { Download, Loader2 } from 'lucide-react'
import { Download, Loader2, RefreshCw } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import { useDownloadEditedCsv, useJobResults } from '../api/hooks'
@@ -24,6 +24,8 @@ export function OutputReview() {
// Stato locale dei post — viene aggiornato da edit inline e rigenerazione
const [localResults, setLocalResults] = useState<PostResult[]>([])
// Set di indici dei post che sono stati rigenerati
const [regeneratedSlots, setRegeneratedSlots] = useState<Set<number>>(new Set())
// Inizializza i risultati e merge con CalendarSlot dal calendario
useEffect(() => {
@@ -47,6 +49,7 @@ export function OutputReview() {
setLocalResults((prev) =>
prev.map((r) => (r.slot_index === updated.slot_index ? updated : r))
)
setRegeneratedSlots((prev) => new Set(prev).add(updated.slot_index))
}
async function handleDownloadCsv() {
@@ -82,6 +85,15 @@ export function OutputReview() {
const successCount = localResults.filter((r) => r.status === 'success').length
const failedCount = localResults.filter((r) => r.status === 'failed').length
const regeneratedCount = regeneratedSlots.size
// Conteggio post modificati manualmente (diversi dall'originale, esclusi i rigenerati)
const editedCount = localResults.filter((r) => {
if (r.status !== 'success' || !r.post) return false
const original = jobData?.results?.find((o) => o.slot_index === r.slot_index)
if (!original?.post) return false
return JSON.stringify(r.post) !== JSON.stringify(original.post)
}).length
const manuallyEditedCount = Math.max(0, editedCount - regeneratedCount)
// ---------------------------------------------------------------------------
// UI principale
@@ -94,11 +106,23 @@ export function OutputReview() {
<div>
<h1 className="text-2xl font-bold text-stone-100">Output Review</h1>
<p className="mt-1 text-stone-400 text-sm">{jobData.campagna}</p>
<div className="flex items-center gap-4 mt-2">
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 mt-2">
<span className="text-xs text-stone-300 font-medium">{localResults.length} post</span>
<span className="text-xs text-emerald-400">{successCount} generati</span>
{failedCount > 0 && (
<span className="text-xs text-red-400">{failedCount} falliti</span>
)}
{regeneratedCount > 0 && (
<span className="text-xs text-amber-400 flex items-center gap-1">
<RefreshCw size={10} />
{regeneratedCount} rigenerati
</span>
)}
{manuallyEditedCount > 0 && (
<span className="text-xs text-blue-400">
{manuallyEditedCount} modificati
</span>
)}
<span className="text-xs text-stone-600">job: {jobId}</span>
</div>
</div>
@@ -127,7 +151,9 @@ export function OutputReview() {
{/* Info edit inline */}
<div className="px-4 py-2.5 rounded-lg bg-stone-800/50 border border-stone-700 text-xs text-stone-500">
Clicca su una card per espandere le slide. I campi di testo sono editabili inline le modifiche saranno incluse nel CSV scaricato.
Clicca su una card per espandere le slide. I campi di testo sono editabili inline.
Usa il pulsante <RefreshCw size={10} className="inline" /> per rigenerare singoli post con topic diversi.
Le modifiche saranno incluse nel CSV scaricato.
</div>
{/* Griglia post */}
@@ -146,6 +172,7 @@ export function OutputReview() {
tono={null}
onRegenerated={handleRegenerated}
onEdit={handleEdit}
isRegenerated={regeneratedSlots.has(result.slot_index)}
/>
))
)}