301 lines
8.3 KiB
JavaScript
301 lines
8.3 KiB
JavaScript
import {useRef, useState, useEffect} from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import Box from '@mui/material/Box'
|
|
import Typography from '@mui/material/Typography'
|
|
import IconButton from '@mui/material/IconButton'
|
|
import Collapse from '@mui/material/Collapse'
|
|
import Snackbar from '@mui/material/Snackbar'
|
|
import Alert from '@mui/material/Alert'
|
|
import CompareArrowsIcon from '@mui/icons-material/CompareArrows'
|
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess'
|
|
import SessionExpired from '../session/session-expired.js'
|
|
import VersionDialog from './version-dialog.js'
|
|
import VoteButtons from './vote-buttons.js'
|
|
import CopyButton from './copy-button.js'
|
|
import {formatDate} from '@/lib/format.js'
|
|
import {compareVersion} from '@/lib/directus.js'
|
|
|
|
function getStatusColor(isOutdated, index) {
|
|
if (isOutdated) {
|
|
return '#D32F2F'
|
|
}
|
|
|
|
if (index === 0) {
|
|
return '#1976D2'
|
|
}
|
|
|
|
return '#9E9E9E'
|
|
}
|
|
|
|
function VersionItem({
|
|
version,
|
|
index,
|
|
accessToken,
|
|
userId,
|
|
countdownRef,
|
|
setError,
|
|
setIsErrorAlertOpen,
|
|
setIsOpenComparison,
|
|
setVersionCompare,
|
|
onVoteResult
|
|
}) {
|
|
const [isOutdated, setIsOutdated] = useState(false)
|
|
const [expanded, setExpanded] = useState(false)
|
|
|
|
useEffect(() => {
|
|
async function fetchStatus() {
|
|
try {
|
|
const comparisonData = await compareVersion({
|
|
accessToken,
|
|
userId,
|
|
versionId: version.id,
|
|
countdownRef,
|
|
setError,
|
|
setIsErrorAlertOpen
|
|
})
|
|
|
|
if (comparisonData) {
|
|
setIsOutdated(comparisonData.outdated)
|
|
}
|
|
} catch {
|
|
setIsOutdated(false)
|
|
}
|
|
}
|
|
|
|
fetchStatus()
|
|
}, [version.id, accessToken, userId, countdownRef, setError, setIsErrorAlertOpen])
|
|
|
|
const statusColor = getStatusColor(isOutdated, index)
|
|
|
|
const createdAt = new Date(version.date_created)
|
|
const threeDaysAgo = new Date(Date.now() - (3 * 24 * 60 * 60 * 1000))
|
|
const isVoteDisabled = createdAt < threeDaysAgo || isOutdated
|
|
|
|
const handleCompare = async () => {
|
|
const comparisonData = await compareVersion({
|
|
accessToken,
|
|
userId,
|
|
versionId: version.id,
|
|
countdownRef,
|
|
setError,
|
|
setIsErrorAlertOpen
|
|
})
|
|
|
|
if (comparisonData) {
|
|
setVersionCompare({...comparisonData, versionId: version.id})
|
|
setIsOpenComparison(true)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
gap: 1.5,
|
|
py: 1.5,
|
|
borderBottom: '1px solid',
|
|
borderColor: 'divider',
|
|
'&:last-child': {borderBottom: 'none'}
|
|
}}
|
|
>
|
|
{/* Status indicator */}
|
|
<Box sx={{display: 'flex', flexDirection: 'column', alignItems: 'center', pt: 0.5}}>
|
|
<Box
|
|
sx={{
|
|
width: 12,
|
|
height: 12,
|
|
borderRadius: '50%',
|
|
bgcolor: statusColor,
|
|
flexShrink: 0
|
|
}}
|
|
/>
|
|
<Box
|
|
sx={{
|
|
width: 2,
|
|
flex: 1,
|
|
bgcolor: 'divider',
|
|
mt: 0.5,
|
|
display: index === 0 ? 'none' : 'block'
|
|
}}
|
|
/>
|
|
</Box>
|
|
|
|
{/* Content */}
|
|
<Box sx={{flex: 1, minWidth: 0}}>
|
|
{/* Header row */}
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
gap: 1
|
|
}}
|
|
>
|
|
<Box sx={{minWidth: 0, flex: 1}}>
|
|
<Typography
|
|
variant='body2'
|
|
sx={{
|
|
fontWeight: 600,
|
|
color: statusColor,
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap'
|
|
}}
|
|
>
|
|
{version.name}
|
|
</Typography>
|
|
<Typography variant='caption' color='text.secondary'>
|
|
{formatDate(version.date_created, 'dd/MM/yy HH:mm')}
|
|
</Typography>
|
|
</Box>
|
|
|
|
{/* Actions */}
|
|
<Box sx={{display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0}}>
|
|
<VoteButtons
|
|
hasCountsVisible
|
|
versionId={version.id}
|
|
isDisabled={isVoteDisabled}
|
|
onVoteResult={onVoteResult}
|
|
/>
|
|
<IconButton size='small' onClick={handleCompare} title='Comparer'>
|
|
<CompareArrowsIcon fontSize='small' />
|
|
</IconButton>
|
|
<IconButton size='small' onClick={() => setExpanded(!expanded)}>
|
|
{expanded ? <ExpandLessIcon fontSize='small' /> : <ExpandMoreIcon fontSize='small' />}
|
|
</IconButton>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Expanded content */}
|
|
<Collapse in={expanded}>
|
|
<Box sx={{mt: 1.5, display: 'flex', flexDirection: 'column', gap: 1}}>
|
|
{/* Preview */}
|
|
{version.delta?.contenu && (
|
|
<Typography
|
|
variant='caption'
|
|
color='text.secondary'
|
|
sx={{
|
|
display: '-webkit-box',
|
|
WebkitLineClamp: 3,
|
|
WebkitBoxOrient: 'vertical',
|
|
overflow: 'hidden',
|
|
fontStyle: 'italic'
|
|
}}
|
|
>
|
|
{version.delta.contenu}
|
|
</Typography>
|
|
)}
|
|
|
|
{/* Actions row */}
|
|
<Box sx={{display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap'}}>
|
|
<CopyButton
|
|
content={version.delta?.contenu || version.name || ''}
|
|
label='Copier'
|
|
hasSnackbarVisible={false}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
</Collapse>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
export default function VersionTimeline({
|
|
data,
|
|
accessToken,
|
|
userId,
|
|
setError,
|
|
setIsErrorAlertOpen
|
|
}) {
|
|
const countdownRef = useRef()
|
|
const [isOpenComparison, setIsOpenComparison] = useState(false)
|
|
const [versionCompare, setVersionCompare] = useState(null)
|
|
const [snackbar, setSnackbar] = useState({open: false, message: '', severity: 'success'})
|
|
|
|
const versionData = data.find(({id}) => id === versionCompare?.versionId)
|
|
|
|
const handleVoteResult = result => {
|
|
setSnackbar({
|
|
open: true,
|
|
message: result.message,
|
|
severity: result.success ? 'success' : 'error'
|
|
})
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Box sx={{maxWidth: 500, mx: 'auto'}}>
|
|
{data.map((version, index) => (
|
|
<VersionItem
|
|
key={version.id}
|
|
version={version}
|
|
index={index}
|
|
accessToken={accessToken}
|
|
userId={userId}
|
|
countdownRef={countdownRef}
|
|
setError={setError}
|
|
setIsErrorAlertOpen={setIsErrorAlertOpen}
|
|
setIsOpenComparison={setIsOpenComparison}
|
|
setVersionCompare={setVersionCompare}
|
|
onVoteResult={handleVoteResult}
|
|
/>
|
|
))}
|
|
</Box>
|
|
|
|
{isOpenComparison && (
|
|
<VersionDialog
|
|
versionData={versionData}
|
|
versionCompare={versionCompare}
|
|
isOpen={isOpenComparison}
|
|
setIsOpen={setIsOpenComparison}
|
|
/>
|
|
)}
|
|
|
|
<SessionExpired
|
|
ref={countdownRef}
|
|
setError={setError}
|
|
setIsErrorAlertOpen={setIsErrorAlertOpen}
|
|
/>
|
|
|
|
<Snackbar
|
|
open={snackbar.open}
|
|
autoHideDuration={6000}
|
|
anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}
|
|
onClose={() => setSnackbar(prev => ({...prev, open: false}))}
|
|
>
|
|
<Alert
|
|
variant='filled'
|
|
severity={snackbar.severity}
|
|
sx={{width: '100%'}}
|
|
onClose={() => setSnackbar(prev => ({...prev, open: false}))}
|
|
>
|
|
{snackbar.message}
|
|
</Alert>
|
|
</Snackbar>
|
|
</>
|
|
)
|
|
}
|
|
|
|
VersionTimeline.propTypes = {
|
|
data: PropTypes.array.isRequired,
|
|
accessToken: PropTypes.string.isRequired,
|
|
userId: PropTypes.string.isRequired,
|
|
setError: PropTypes.func.isRequired,
|
|
setIsErrorAlertOpen: PropTypes.func.isRequired
|
|
}
|
|
|
|
VersionItem.propTypes = {
|
|
version: PropTypes.object.isRequired,
|
|
index: 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,
|
|
onVoteResult: PropTypes.func.isRequired
|
|
}
|