Files
konstitisyon.nu/components/versions/version-timeline.js
T

301 lines
8.3 KiB
JavaScript
Raw Normal View History

2026-01-04 11:00:48 +04:00
import {useRef, useState, useEffect} from 'react'
2025-07-23 17:40:45 +04:00
import PropTypes from 'prop-types'
import Box from '@mui/material/Box'
import Typography from '@mui/material/Typography'
2026-01-24 21:34:02 +04:00
import IconButton from '@mui/material/IconButton'
import Collapse from '@mui/material/Collapse'
import Snackbar from '@mui/material/Snackbar'
import Alert from '@mui/material/Alert'
2026-01-24 21:34:02 +04:00
import CompareArrowsIcon from '@mui/icons-material/CompareArrows'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import ExpandLessIcon from '@mui/icons-material/ExpandLess'
2025-07-23 17:40:45 +04:00
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'
2025-07-23 17:40:45 +04:00
import {formatDate} from '@/lib/format.js'
import {compareVersion} from '@/lib/directus.js'
2026-01-24 21:34:02 +04:00
function getStatusColor(isOutdated, index) {
if (isOutdated) {
return '#D32F2F'
2025-07-23 17:40:45 +04:00
}
2026-01-24 21:34:02 +04:00
if (index === 0) {
return '#1976D2'
2026-01-04 11:00:48 +04:00
}
2026-01-24 21:34:02 +04:00
return '#9E9E9E'
2025-07-23 17:40:45 +04:00
}
2026-01-24 21:34:02 +04:00
function VersionItem({
2025-07-23 17:40:45 +04:00
version,
index,
accessToken,
userId,
countdownRef,
setError,
setIsErrorAlertOpen,
setIsOpenComparison,
setVersionCompare,
onVoteResult
2025-07-23 17:40:45 +04:00
}) {
2026-01-04 11:00:48 +04:00
const [isOutdated, setIsOutdated] = useState(false)
2026-01-24 21:34:02 +04:00
const [expanded, setExpanded] = useState(false)
2026-01-04 11:00:48 +04:00
useEffect(() => {
2026-01-24 21:34:02 +04:00
async function fetchStatus() {
2026-01-04 11:00:48 +04:00
try {
const comparisonData = await compareVersion({
accessToken,
userId,
versionId: version.id,
countdownRef,
setError,
setIsErrorAlertOpen
})
if (comparisonData) {
setIsOutdated(comparisonData.outdated)
}
2026-01-24 21:34:02 +04:00
} catch {
setIsOutdated(false)
2026-01-04 11:00:48 +04:00
}
}
2026-01-24 21:34:02 +04:00
fetchStatus()
}, [version.id, accessToken, userId, countdownRef, setError, setIsErrorAlertOpen])
2026-01-04 11:00:48 +04:00
2026-01-24 21:34:02 +04:00
const statusColor = getStatusColor(isOutdated, index)
const createdAt = new Date(version.date_created)
const threeDaysAgo = new Date(Date.now() - (3 * 24 * 60 * 60 * 1000))
2026-01-24 21:34:02 +04:00
const isVoteDisabled = createdAt < threeDaysAgo || isOutdated
2026-01-24 21:34:02 +04:00
const handleCompare = async () => {
2025-07-23 17:40:45 +04:00
const comparisonData = await compareVersion({
accessToken,
userId,
versionId: version.id,
countdownRef,
setError,
setIsErrorAlertOpen
})
if (comparisonData) {
setVersionCompare({...comparisonData, versionId: version.id})
setIsOpenComparison(true)
}
}
return (
2026-01-24 21:34:02 +04:00
<Box
2025-07-23 17:40:45 +04:00
sx={{
2026-01-24 21:34:02 +04:00
display: 'flex',
gap: 1.5,
py: 1.5,
borderBottom: '1px solid',
borderColor: 'divider',
'&:last-child': {borderBottom: 'none'}
2025-07-23 17:40:45 +04:00
}}
>
2026-01-24 21:34:02 +04:00
{/* 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
}}
2025-07-23 17:40:45 +04:00
>
2026-01-24 21:34:02 +04:00
<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>
2025-07-23 17:40:45 +04:00
</Box>
2026-01-24 21:34:02 +04:00
{/* Actions */}
<Box sx={{display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0}}>
<VoteButtons
hasCountsVisible
versionId={version.id}
isDisabled={isVoteDisabled}
onVoteResult={onVoteResult}
/>
2026-01-24 21:34:02 +04:00
<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>
2025-07-23 17:40:45 +04:00
</Box>
2026-01-24 21:34:02 +04:00
{/* Expanded content */}
<Collapse in={expanded}>
<Box sx={{mt: 1.5, display: 'flex', flexDirection: 'column', gap: 1}}>
{/* Preview */}
{version.delta?.contenu && (
<Typography
2026-01-24 21:34:02 +04:00
variant='caption'
color='text.secondary'
2026-01-24 21:34:02 +04:00
sx={{
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
fontStyle: 'italic'
}}
>
2026-01-24 21:34:02 +04:00
{version.delta.contenu}
</Typography>
)}
2025-07-23 17:40:45 +04:00
2026-01-24 21:34:02 +04:00
{/* 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>
2026-01-24 21:34:02 +04:00
</Collapse>
</Box>
</Box>
2025-07-23 17:40:45 +04:00
)
}
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'})
2025-07-23 17:40:45 +04:00
const versionData = data.find(({id}) => id === versionCompare?.versionId)
const handleVoteResult = result => {
setSnackbar({
open: true,
message: result.message,
severity: result.success ? 'success' : 'error'
})
}
2025-07-23 17:40:45 +04:00
return (
<>
2026-01-24 21:34:02 +04:00
<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}
/>
))}
2025-07-23 17:40:45 +04:00
</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'}}
2026-01-24 21:34:02 +04:00
onClose={() => setSnackbar(prev => ({...prev, open: false}))}
>
2026-01-24 21:34:02 +04:00
<Alert
variant='filled'
severity={snackbar.severity}
sx={{width: '100%'}}
onClose={() => setSnackbar(prev => ({...prev, open: false}))}
>
{snackbar.message}
</Alert>
</Snackbar>
2025-07-23 17:40:45 +04:00
</>
)
}
VersionTimeline.propTypes = {
data: PropTypes.array.isRequired,
accessToken: PropTypes.string.isRequired,
userId: PropTypes.string.isRequired,
setError: PropTypes.func.isRequired,
setIsErrorAlertOpen: PropTypes.func.isRequired
}
2026-01-24 21:34:02 +04:00
VersionItem.propTypes = {
2025-07-23 17:40:45 +04:00
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
2025-07-23 17:40:45 +04:00
}