ux(review): spinner, tooltip, chip en cours et snackbar sur Révision IA

This commit is contained in:
4NK IA 2025-09-18 12:56:58 +00:00
parent 4bed3562b1
commit 1e207f734e

View File

@ -8,6 +8,8 @@ import {
Skeleton, Skeleton,
Alert, Alert,
Button, Button,
Snackbar,
Alert as MuiAlert,
Chip, Chip,
List, List,
ListItem, ListItem,
@ -50,7 +52,7 @@ import type { Document } from '../types'
import { confirmDetectedAddress, deleteFolderFile, reviewFileWithAI } from '../services/folderApi' import { confirmDetectedAddress, deleteFolderFile, reviewFileWithAI } from '../services/folderApi'
// Composant mémorisé pour les items de la liste // Composant mémorisé pour les items de la liste
const DocumentListItem = memo(({ doc, index, onPreview, onDelete, onReplace, onConfirmAddress, onReview, onOpenCorrections, totalCount }: { const DocumentListItem = memo(({ doc, index, onPreview, onDelete, onReplace, onConfirmAddress, onReview, onOpenCorrections, isReviewing, totalCount }: {
doc: Document, doc: Document,
index: number, index: number,
onPreview: (doc: Document) => void, onPreview: (doc: Document) => void,
@ -59,6 +61,7 @@ const DocumentListItem = memo(({ doc, index, onPreview, onDelete, onReplace, onC
onConfirmAddress: (doc: Document) => void, onConfirmAddress: (doc: Document) => void,
onReview: (doc: Document) => void, onReview: (doc: Document) => void,
onOpenCorrections: (doc: Document, corrections: any[]) => void, onOpenCorrections: (doc: Document, corrections: any[]) => void,
isReviewing: boolean,
totalCount: number totalCount: number
}) => { }) => {
const getFileIcon = (mimeType: string) => { const getFileIcon = (mimeType: string) => {
@ -151,6 +154,9 @@ const DocumentListItem = memo(({ doc, index, onPreview, onDelete, onReplace, onC
variant="outlined" variant="outlined"
/> />
)} )}
{isReviewing && (
<Chip color="default" label="Révision IA en cours…" size="small" />
)}
{(() => { {(() => {
const anyDoc: any = doc as any const anyDoc: any = doc as any
const score: number | undefined = const score: number | undefined =
@ -223,14 +229,18 @@ const DocumentListItem = memo(({ doc, index, onPreview, onDelete, onReplace, onC
> >
Aperçu Aperçu
</Button> </Button>
<Button <Tooltip title="Analyse IA (Ollama): calcule un score de fiabilité, propose des corrections, ajoute un avis.">
size="small" <span>
onClick={() => onReview(doc)} <Button
disabled={doc.status !== 'completed'} size="small"
fullWidth onClick={() => onReview(doc)}
> disabled={doc.status !== 'completed' || isReviewing}
Révision IA fullWidth
</Button> >
{isReviewing ? 'Révision IA…' : 'Révision IA'}
</Button>
</span>
</Tooltip>
<Button <Button
size="small" size="small"
color="error" color="error"
@ -276,6 +286,8 @@ export default function UploadView() {
const [reviewDialogOpen, setReviewDialogOpen] = useState(false) const [reviewDialogOpen, setReviewDialogOpen] = useState(false)
const [reviewDoc, setReviewDoc] = useState<Document | null>(null) const [reviewDoc, setReviewDoc] = useState<Document | null>(null)
const [reviewCorrections, setReviewCorrections] = useState<any[]>([]) const [reviewCorrections, setReviewCorrections] = useState<any[]>([])
const [reviewingId, setReviewingId] = useState<string | null>(null)
const [snack, setSnack] = useState<{ open: boolean; severity: 'success' | 'error'; message: string }>({ open: false, severity: 'success', message: '' })
const handleConfirmAddress = useCallback((doc: Document) => { const handleConfirmAddress = useCallback((doc: Document) => {
try { try {
@ -308,10 +320,15 @@ export default function UploadView() {
const handleReview = useCallback(async (doc: Document) => { const handleReview = useCallback(async (doc: Document) => {
if (!currentFolderHash) return if (!currentFolderHash) return
try { try {
setReviewingId(doc.id)
await reviewFileWithAI(currentFolderHash, doc.id) await reviewFileWithAI(currentFolderHash, doc.id)
await dispatch(loadFolderResults(currentFolderHash)).unwrap() await dispatch(loadFolderResults(currentFolderHash)).unwrap()
setSnack({ open: true, severity: 'success', message: 'Révision IA terminée: score et corrections mis à jour.' })
} catch (e) { } catch (e) {
console.error('❌ Révision IA:', e) console.error('❌ Révision IA:', e)
setSnack({ open: true, severity: 'error', message: "Échec de la révision IA. Réessaie plus tard." })
} finally {
setReviewingId(null)
} }
}, [currentFolderHash, dispatch]) }, [currentFolderHash, dispatch])
@ -581,6 +598,7 @@ export default function UploadView() {
onConfirmAddress={handleConfirmAddress} onConfirmAddress={handleConfirmAddress}
onReview={handleReview} onReview={handleReview}
onOpenCorrections={(d, corr) => { setReviewDoc(d); setReviewCorrections(corr); setReviewDialogOpen(true) }} onOpenCorrections={(d, corr) => { setReviewDoc(d); setReviewCorrections(corr); setReviewDialogOpen(true) }}
isReviewing={reviewingId === doc.id}
totalCount={memoizedDocuments.length} totalCount={memoizedDocuments.length}
/> />
))} ))}
@ -780,6 +798,18 @@ export default function UploadView() {
<Button onClick={() => setReviewDialogOpen(false)}>Fermer</Button> <Button onClick={() => setReviewDialogOpen(false)}>Fermer</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
{/* Snackbar feedback */}
<Snackbar
open={snack.open}
autoHideDuration={3000}
onClose={() => setSnack({ ...snack, open: false })}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<MuiAlert onClose={() => setSnack({ ...snack, open: false })} severity={snack.severity} variant="filled" sx={{ width: '100%' }}>
{snack.message}
</MuiAlert>
</Snackbar>
</Layout> </Layout>
) )
} }