- Add complete TypeScript types for all document entities - Implement Redux store with document slice and async thunks - Create comprehensive API services layer with external integrations - Build complete UI views with Material-UI components: * UploadView: drag&drop with file preview and status * ExtractionView: structured data display (identities, addresses, properties, contracts) * AnalyseView: CNI verification, credibility scoring, recommendations * ContexteView: external data sources (Cadastre, Géorisques, BODACC, etc.) * ConseilView: LLM analysis with risks and next steps - Add Layout component with navigation tabs - Configure environment variables for backend integration - Fix all TypeScript compilation errors - Replace Grid with Box for better compatibility - Add comprehensive error handling and loading states
288 lines
9.6 KiB
TypeScript
288 lines
9.6 KiB
TypeScript
import { useEffect } from 'react'
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
Card,
|
|
CardContent,
|
|
Chip,
|
|
List,
|
|
ListItem,
|
|
ListItemText,
|
|
Alert,
|
|
CircularProgress,
|
|
} from '@mui/material'
|
|
import {
|
|
Person,
|
|
LocationOn,
|
|
Home,
|
|
Description,
|
|
Language,
|
|
Verified,
|
|
} from '@mui/icons-material'
|
|
import { useAppDispatch, useAppSelector } from '../store'
|
|
import { extractDocument } from '../store/documentSlice'
|
|
import { Layout } from '../components/Layout'
|
|
|
|
export default function ExtractionView() {
|
|
const dispatch = useAppDispatch()
|
|
const { currentDocument, extractionResult, loading } = useAppSelector(
|
|
(state) => state.document
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (currentDocument && !extractionResult) {
|
|
dispatch(extractDocument(currentDocument.id))
|
|
}
|
|
}, [currentDocument, extractionResult, dispatch])
|
|
|
|
if (!currentDocument) {
|
|
return (
|
|
<Layout>
|
|
<Alert severity="info">
|
|
Veuillez d'abord téléverser et sélectionner un document.
|
|
</Alert>
|
|
</Layout>
|
|
)
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<Layout>
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
|
|
<CircularProgress />
|
|
<Typography sx={{ ml: 2 }}>Extraction en cours...</Typography>
|
|
</Box>
|
|
</Layout>
|
|
)
|
|
}
|
|
|
|
if (!extractionResult) {
|
|
return (
|
|
<Layout>
|
|
<Alert severity="warning">
|
|
Aucun résultat d'extraction disponible.
|
|
</Alert>
|
|
</Layout>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Layout>
|
|
<Typography variant="h4" gutterBottom>
|
|
Extraction des données
|
|
</Typography>
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
{/* Informations générales */}
|
|
<Paper sx={{ p: 2 }}>
|
|
<Typography variant="h6" gutterBottom>
|
|
Informations générales
|
|
</Typography>
|
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
|
<Chip
|
|
icon={<Language />}
|
|
label={`Langue: ${extractionResult.language}`}
|
|
color="primary"
|
|
variant="outlined"
|
|
/>
|
|
<Chip
|
|
icon={<Description />}
|
|
label={`Type: ${extractionResult.documentType}`}
|
|
color="secondary"
|
|
variant="outlined"
|
|
/>
|
|
<Chip
|
|
icon={<Verified />}
|
|
label={`Confiance: ${(extractionResult.confidence * 100).toFixed(1)}%`}
|
|
color={extractionResult.confidence > 0.8 ? 'success' : 'warning'}
|
|
variant="outlined"
|
|
/>
|
|
</Box>
|
|
</Paper>
|
|
|
|
<Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
|
|
{/* Identités */}
|
|
<Box sx={{ flex: '1 1 300px' }}>
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
<Person sx={{ mr: 1, verticalAlign: 'middle' }} />
|
|
Identités ({extractionResult.identities.length})
|
|
</Typography>
|
|
<List dense>
|
|
{extractionResult.identities.map((identity, index) => (
|
|
<ListItem key={index}>
|
|
<ListItemText
|
|
primary={
|
|
identity.type === 'person'
|
|
? `${identity.firstName} ${identity.lastName}`
|
|
: identity.companyName
|
|
}
|
|
secondary={
|
|
<Box>
|
|
<Typography variant="caption" display="block">
|
|
Type: {identity.type}
|
|
</Typography>
|
|
{identity.birthDate && (
|
|
<Typography variant="caption" display="block">
|
|
Naissance: {identity.birthDate}
|
|
</Typography>
|
|
)}
|
|
{identity.nationality && (
|
|
<Typography variant="caption" display="block">
|
|
Nationalité: {identity.nationality}
|
|
</Typography>
|
|
)}
|
|
<Typography variant="caption" display="block">
|
|
Confiance: {(identity.confidence * 100).toFixed(1)}%
|
|
</Typography>
|
|
</Box>
|
|
}
|
|
/>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</CardContent>
|
|
</Card>
|
|
</Box>
|
|
|
|
{/* Adresses */}
|
|
<Box sx={{ flex: '1 1 300px' }}>
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
<LocationOn sx={{ mr: 1, verticalAlign: 'middle' }} />
|
|
Adresses ({extractionResult.addresses.length})
|
|
</Typography>
|
|
<List dense>
|
|
{extractionResult.addresses.map((address, index) => (
|
|
<ListItem key={index}>
|
|
<ListItemText
|
|
primary={`${address.street}, ${address.city}`}
|
|
secondary={`${address.postalCode} ${address.country}`}
|
|
/>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</CardContent>
|
|
</Card>
|
|
</Box>
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
|
|
{/* Biens */}
|
|
<Box sx={{ flex: '1 1 300px' }}>
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
<Home sx={{ mr: 1, verticalAlign: 'middle' }} />
|
|
Biens ({extractionResult.properties.length})
|
|
</Typography>
|
|
<List dense>
|
|
{extractionResult.properties.map((property, index) => (
|
|
<ListItem key={index}>
|
|
<ListItemText
|
|
primary={`${property.type} - ${property.address.city}`}
|
|
secondary={
|
|
<Box>
|
|
<Typography variant="caption" display="block">
|
|
{property.address.street}
|
|
</Typography>
|
|
{property.surface && (
|
|
<Typography variant="caption" display="block">
|
|
Surface: {property.surface} m²
|
|
</Typography>
|
|
)}
|
|
{property.cadastralReference && (
|
|
<Typography variant="caption" display="block">
|
|
Cadastre: {property.cadastralReference}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
}
|
|
/>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</CardContent>
|
|
</Card>
|
|
</Box>
|
|
|
|
{/* Contrats */}
|
|
<Box sx={{ flex: '1 1 300px' }}>
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
<Description sx={{ mr: 1, verticalAlign: 'middle' }} />
|
|
Contrats ({extractionResult.contracts.length})
|
|
</Typography>
|
|
<List dense>
|
|
{extractionResult.contracts.map((contract, index) => (
|
|
<ListItem key={index}>
|
|
<ListItemText
|
|
primary={`${contract.type} - ${contract.amount ? `${contract.amount}€` : 'Montant non spécifié'}`}
|
|
secondary={
|
|
<Box>
|
|
<Typography variant="caption" display="block">
|
|
Parties: {contract.parties.length}
|
|
</Typography>
|
|
{contract.date && (
|
|
<Typography variant="caption" display="block">
|
|
Date: {contract.date}
|
|
</Typography>
|
|
)}
|
|
<Typography variant="caption" display="block">
|
|
Clauses: {contract.clauses.length}
|
|
</Typography>
|
|
</Box>
|
|
}
|
|
/>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</CardContent>
|
|
</Card>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Signatures */}
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
Signatures détectées ({extractionResult.signatures.length})
|
|
</Typography>
|
|
<List dense>
|
|
{extractionResult.signatures.map((signature, index) => (
|
|
<ListItem key={index}>
|
|
<ListItemText primary={signature} />
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Texte extrait */}
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
Texte extrait
|
|
</Typography>
|
|
<Paper
|
|
sx={{
|
|
p: 2,
|
|
bgcolor: 'grey.50',
|
|
maxHeight: 300,
|
|
overflow: 'auto',
|
|
}}
|
|
>
|
|
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
|
{extractionResult.text}
|
|
</Typography>
|
|
</Paper>
|
|
</CardContent>
|
|
</Card>
|
|
</Box>
|
|
</Layout>
|
|
)
|
|
} |