feat: ajoute composant VersionTimeline
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
import {useRef, useState} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Box from '@mui/material/Box'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import CardActions from '@mui/material/CardActions'
|
||||
import Button from '@mui/material/Button'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Avatar from '@mui/material/Avatar'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Timeline from '@mui/lab/Timeline'
|
||||
import TimelineItem from '@mui/lab/TimelineItem'
|
||||
import TimelineSeparator from '@mui/lab/TimelineSeparator'
|
||||
import TimelineConnector from '@mui/lab/TimelineConnector'
|
||||
import TimelineContent from '@mui/lab/TimelineContent'
|
||||
import TimelineDot from '@mui/lab/TimelineDot'
|
||||
import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent'
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime'
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
|
||||
import ErrorIcon from '@mui/icons-material/Error'
|
||||
import EditIcon from '@mui/icons-material/Edit'
|
||||
import SessionExpired from '../session/session-expired.js'
|
||||
import VersionDialog from './version-dialog.js'
|
||||
import {formatDate} from '@/lib/format.js'
|
||||
import {compareVersion} from '@/lib/directus.js'
|
||||
|
||||
function getVersionStatus(version, index, totalVersions) {
|
||||
// Logic to determine version status based on position and data
|
||||
if (index === 0) {
|
||||
return 'current' // Most recent
|
||||
}
|
||||
|
||||
if (index === totalVersions - 1) {
|
||||
return 'initial' // First version
|
||||
}
|
||||
|
||||
return 'archived' // Intermediate versions
|
||||
}
|
||||
|
||||
function getStatusConfig(status) {
|
||||
switch (status) {
|
||||
case 'current': {
|
||||
return {
|
||||
color: '#1976D2',
|
||||
bgColor: '#E3F2FD',
|
||||
icon: <EditIcon />,
|
||||
label: 'En cours',
|
||||
chipColor: 'primary'
|
||||
}
|
||||
}
|
||||
|
||||
case 'published': {
|
||||
return {
|
||||
color: '#2E7D32',
|
||||
bgColor: '#E8F5E9',
|
||||
icon: <CheckCircleIcon />,
|
||||
label: 'Publié',
|
||||
chipColor: 'success'
|
||||
}
|
||||
}
|
||||
|
||||
case 'archived': {
|
||||
return {
|
||||
color: '#757575',
|
||||
bgColor: '#F5F5F5',
|
||||
icon: <AccessTimeIcon />,
|
||||
label: 'Archivé',
|
||||
chipColor: 'default'
|
||||
}
|
||||
}
|
||||
|
||||
case 'outdated': {
|
||||
return {
|
||||
color: '#D32F2F',
|
||||
bgColor: '#F9E8E8',
|
||||
icon: <ErrorIcon />,
|
||||
label: 'Obsolète',
|
||||
chipColor: 'error'
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
return {
|
||||
color: '#757575',
|
||||
bgColor: '#F5F5F5',
|
||||
icon: <AccessTimeIcon />,
|
||||
label: 'Archivé',
|
||||
chipColor: 'default'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function VersionCard({
|
||||
version,
|
||||
index,
|
||||
totalVersions,
|
||||
accessToken,
|
||||
userId,
|
||||
countdownRef,
|
||||
setError,
|
||||
setIsErrorAlertOpen,
|
||||
setIsOpenComparison,
|
||||
setVersionCompare
|
||||
}) {
|
||||
const status = getVersionStatus(version, index, totalVersions)
|
||||
const statusConfig = getStatusConfig(status)
|
||||
const userDisplayName = version.user_created?.split('-')[0] || 'Système'
|
||||
|
||||
const handleCompareClick = async () => {
|
||||
const comparisonData = await compareVersion({
|
||||
accessToken,
|
||||
userId,
|
||||
versionId: version.id,
|
||||
countdownRef,
|
||||
setError,
|
||||
setIsErrorAlertOpen
|
||||
})
|
||||
|
||||
if (comparisonData) {
|
||||
setVersionCompare({...comparisonData, versionId: version.id})
|
||||
setIsOpenComparison(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate content preview (first 100 chars)
|
||||
const contentPreview = version?.delta?.contenu
|
||||
? version.delta.contenu.slice(0, 100) + (version.delta.contenu.length > 100 ? '...' : '')
|
||||
: 'Contenu non disponible'
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
borderLeft: `4px solid ${statusConfig.color}`,
|
||||
backgroundColor: statusConfig.bgColor,
|
||||
mb: 2,
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: 3
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
mb: 2
|
||||
}}
|
||||
>
|
||||
<Box sx={{display: 'flex', alignItems: 'center', gap: 1}}>
|
||||
<Avatar sx={{bgcolor: statusConfig.color, width: 32, height: 32}}>
|
||||
{statusConfig.icon}
|
||||
</Avatar>
|
||||
<Box>
|
||||
<Typography variant='h6' sx={{fontWeight: 'bold', color: statusConfig.color}}>
|
||||
{version.name}
|
||||
</Typography>
|
||||
<Typography variant='caption' color='text.secondary'>
|
||||
par @{userDisplayName}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Chip
|
||||
label={statusConfig.label}
|
||||
color={statusConfig.chipColor}
|
||||
size='small'
|
||||
variant='outlined'
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography variant='body2' color='text.secondary' sx={{mb: 2, fontStyle: 'italic'}}>
|
||||
{contentPreview}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
|
||||
<Typography variant='caption' color='text.secondary'>
|
||||
{formatDate(version.date_created, 'PPpp')}
|
||||
</Typography>
|
||||
|
||||
{/* Placeholder for vote count - would need API enhancement */}
|
||||
<Box sx={{display: 'flex', gap: 1}}>
|
||||
<Chip
|
||||
label='👍 0'
|
||||
size='small'
|
||||
variant='outlined'
|
||||
sx={{fontSize: '0.7rem'}}
|
||||
/>
|
||||
<Chip
|
||||
label='👎 0'
|
||||
size='small'
|
||||
variant='outlined'
|
||||
sx={{fontSize: '0.7rem'}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
|
||||
<Divider />
|
||||
<CardActions sx={{justifyContent: 'flex-end'}}>
|
||||
<Button
|
||||
size='small'
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
onClick={handleCompareClick}
|
||||
>
|
||||
Comparer
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default function VersionTimeline({
|
||||
collection,
|
||||
data,
|
||||
accessToken,
|
||||
userId,
|
||||
setError,
|
||||
setIsErrorAlertOpen
|
||||
}) {
|
||||
const countdownRef = useRef()
|
||||
const [isOpenComparison, setIsOpenComparison] = useState(false)
|
||||
const [versionCompare, setVersionCompare] = useState(null)
|
||||
|
||||
const versionData = data.find(({id}) => id === versionCompare?.versionId)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<Typography variant='h5' textAlign='center' sx={{mb: 3, fontWeight: 'bold'}}>
|
||||
Historique des versions - {collection}
|
||||
</Typography>
|
||||
|
||||
<Timeline position='right'>
|
||||
{data.map((version, index) => (
|
||||
<TimelineItem key={version.id}>
|
||||
<TimelineOppositeContent sx={{flex: 0.3, pr: 2}}>
|
||||
<Typography variant='caption' color='text.secondary'>
|
||||
{formatDate(version.date_created, 'dd/MM/yyyy')}
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography variant='caption' color='text.secondary'>
|
||||
{formatDate(version.date_created, 'HH:mm')}
|
||||
</Typography>
|
||||
</TimelineOppositeContent>
|
||||
|
||||
<TimelineSeparator>
|
||||
<TimelineDot
|
||||
color={index === 0 ? 'primary' : 'grey'}
|
||||
variant={index === 0 ? 'filled' : 'outlined'}
|
||||
>
|
||||
{index === 0 ? <EditIcon /> : <AccessTimeIcon />}
|
||||
</TimelineDot>
|
||||
{index < data.length - 1 && <TimelineConnector />}
|
||||
</TimelineSeparator>
|
||||
|
||||
<TimelineContent sx={{flex: 1}}>
|
||||
<VersionCard
|
||||
version={version}
|
||||
index={index}
|
||||
totalVersions={data.length}
|
||||
accessToken={accessToken}
|
||||
userId={userId}
|
||||
countdownRef={countdownRef}
|
||||
setError={setError}
|
||||
setIsErrorAlertOpen={setIsErrorAlertOpen}
|
||||
setIsOpenComparison={setIsOpenComparison}
|
||||
setVersionCompare={setVersionCompare}
|
||||
/>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
))}
|
||||
</Timeline>
|
||||
</Box>
|
||||
|
||||
{isOpenComparison && (
|
||||
<VersionDialog
|
||||
versionData={versionData}
|
||||
versionCompare={versionCompare}
|
||||
isOpen={isOpenComparison}
|
||||
setIsOpen={setIsOpenComparison}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SessionExpired
|
||||
ref={countdownRef}
|
||||
setError={setError}
|
||||
setIsErrorAlertOpen={setIsErrorAlertOpen}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
VersionTimeline.propTypes = {
|
||||
collection: PropTypes.oneOf(['titres', 'articles']).isRequired,
|
||||
data: PropTypes.array.isRequired,
|
||||
accessToken: PropTypes.string.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
setError: PropTypes.func.isRequired,
|
||||
setIsErrorAlertOpen: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
VersionCard.propTypes = {
|
||||
version: PropTypes.object.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
totalVersions: PropTypes.number.isRequired,
|
||||
accessToken: PropTypes.string.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
countdownRef: PropTypes.object.isRequired,
|
||||
setError: PropTypes.func.isRequired,
|
||||
setIsErrorAlertOpen: PropTypes.func.isRequired,
|
||||
setIsOpenComparison: PropTypes.func.isRequired,
|
||||
setVersionCompare: PropTypes.func.isRequired
|
||||
}
|
||||
Reference in New Issue
Block a user