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:
@@ -10,7 +10,7 @@
|
|||||||
* e inviato al backend via POST al momento del download CSV.
|
* 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 { useEffect, useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { useDownloadEditedCsv, useJobResults } from '../api/hooks'
|
import { useDownloadEditedCsv, useJobResults } from '../api/hooks'
|
||||||
@@ -24,6 +24,8 @@ export function OutputReview() {
|
|||||||
|
|
||||||
// Stato locale dei post — viene aggiornato da edit inline e rigenerazione
|
// Stato locale dei post — viene aggiornato da edit inline e rigenerazione
|
||||||
const [localResults, setLocalResults] = useState<PostResult[]>([])
|
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
|
// Inizializza i risultati e merge con CalendarSlot dal calendario
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -47,6 +49,7 @@ export function OutputReview() {
|
|||||||
setLocalResults((prev) =>
|
setLocalResults((prev) =>
|
||||||
prev.map((r) => (r.slot_index === updated.slot_index ? updated : r))
|
prev.map((r) => (r.slot_index === updated.slot_index ? updated : r))
|
||||||
)
|
)
|
||||||
|
setRegeneratedSlots((prev) => new Set(prev).add(updated.slot_index))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDownloadCsv() {
|
async function handleDownloadCsv() {
|
||||||
@@ -82,6 +85,15 @@ export function OutputReview() {
|
|||||||
|
|
||||||
const successCount = localResults.filter((r) => r.status === 'success').length
|
const successCount = localResults.filter((r) => r.status === 'success').length
|
||||||
const failedCount = localResults.filter((r) => r.status === 'failed').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
|
// UI principale
|
||||||
@@ -94,11 +106,23 @@ export function OutputReview() {
|
|||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-stone-100">Output Review</h1>
|
<h1 className="text-2xl font-bold text-stone-100">Output Review</h1>
|
||||||
<p className="mt-1 text-stone-400 text-sm">{jobData.campagna}</p>
|
<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>
|
<span className="text-xs text-emerald-400">{successCount} generati</span>
|
||||||
{failedCount > 0 && (
|
{failedCount > 0 && (
|
||||||
<span className="text-xs text-red-400">{failedCount} falliti</span>
|
<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>
|
<span className="text-xs text-stone-600">job: {jobId}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,7 +151,9 @@ export function OutputReview() {
|
|||||||
|
|
||||||
{/* Info edit inline */}
|
{/* 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">
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Griglia post */}
|
{/* Griglia post */}
|
||||||
@@ -146,6 +172,7 @@ export function OutputReview() {
|
|||||||
tono={null}
|
tono={null}
|
||||||
onRegenerated={handleRegenerated}
|
onRegenerated={handleRegenerated}
|
||||||
onEdit={handleEdit}
|
onEdit={handleEdit}
|
||||||
|
isRegenerated={regeneratedSlots.has(result.slot_index)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user