Files

338 lines
10 KiB
JavaScript

import {forwardRef, useRef, useState, useEffect} from 'react'
import PropTypes from 'prop-types'
import Table from '@mui/material/Table'
import TableBody from '@mui/material/TableBody'
import TableCell from '@mui/material/TableCell'
import TableContainer from '@mui/material/TableContainer'
import TableHead from '@mui/material/TableHead'
import TableRow from '@mui/material/TableRow'
import Paper from '@mui/material/Paper'
import Button from '@mui/material/Button'
import ToggleButton from '@mui/material/ToggleButton'
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
import ViewListIcon from '@mui/icons-material/ViewList'
import TimelineIcon from '@mui/icons-material/Timeline'
import {TableVirtuoso} from 'react-virtuoso'
import {Box, Typography} from '@mui/material'
import SessionExpired from '../session/session-expired.js'
import VersionDialog from './version-dialog.js'
import VersionTimeline from './version-timeline.js'
import VersionSearch from './version-search.js'
import VersionFilters from './version-filters.js'
import CopyButton from './copy-button.js'
import ShareButton from './share-button.js'
import ExportPdfButton from './export-pdf-button.js'
import PrintButton from './print-button.js'
import {formatDate} from '@/lib/format.js'
import {compareVersion} from '@/lib/directus.js'
import {filterVersions, getFilterStats} from '@/lib/version-utils.js'
const columns = [
{
width: 180,
label: 'Version',
dataKey: 'name',
},
{
width: 140,
label: 'Créée le',
dataKey: 'date_created',
numeric: true,
},
{
width: 200,
label: 'Actions',
dataKey: 'actions',
}
]
const VirtuosoTableComponents = {
Scroller: forwardRef((props, ref) => (
<TableContainer component={Paper} {...props} ref={ref} />
)),
Table: props => (
<Table {...props} sx={{borderCollapse: 'separate', tableLayout: 'fixed'}} />
),
TableHead: forwardRef((props, ref) => <TableHead {...props} ref={ref} />),
TableRow,
TableBody: forwardRef((props, ref) => <TableBody {...props} ref={ref} />),
}
function fixedHeaderContent() {
return (
<TableRow>
{columns.map(column => (
<TableCell
key={column.dataKey}
variant='head'
align={column.numeric || false ? 'right' : 'left'}
style={{width: column.width}}
sx={{backgroundColor: 'background.paper'}}
>
{column.label}
</TableCell>
))}
</TableRow>
)
}
function rowContent({
row,
accessToken,
userId,
countdownRef,
setError,
setIsErrorAlertOpen,
setIsOpenComparison,
setVersionCompare,
outdatedStatusMap
}) {
const handleButtonClick = async versionId => {
const version = await compareVersion({
accessToken,
userId,
versionId,
countdownRef,
setError,
setIsErrorAlertOpen
})
if (version) {
setVersionCompare({...version, versionId})
setIsOpenComparison(true)
}
}
const isOutdated = outdatedStatusMap[row.id] || false
return (
<>
{columns.map(column => (
<TableCell
key={column.dataKey}
align={column.numeric || false ? 'right' : 'left'}
sx={{
minHeight: column.dataKey === 'actions' ? 64 : 'auto',
verticalAlign: 'middle'
}}
>
{column.dataKey === 'date_created'
? formatDate(row[column.dataKey], 'Pp')
: (column.dataKey === 'actions'
? (
<Box sx={{
display: 'flex',
gap: 0.3,
alignItems: 'center',
flexWrap: 'wrap',
justifyContent: 'flex-start'
}}
>
<CopyButton
content={row.delta?.contenu || row.name || ''}
label='Copier le contenu'
hasSnackbarVisible={false}
/>
<ShareButton
versionId={row.id}
versionName={row.name}
hasSnackbarVisible={false}
/>
<ExportPdfButton
versionData={row}
isOutdated={isOutdated}
size='small'
variant='text'
/>
<PrintButton
versionData={row}
isOutdated={isOutdated}
size='small'
variant='text'
/>
<Button
size='small'
variant='outlined'
color='primary'
sx={{minWidth: 'auto', px: 1}}
onClick={() => handleButtonClick(row.id)}
>
Comparer
</Button>
</Box>
)
: <Button variant='outlined' color='success' onClick={() => handleButtonClick(row.id)}>{row[column.dataKey]}</Button>)}
</TableCell>
))}
</>
)
}
export default function ListVersions({
collection,
data,
accessToken,
userId,
setError,
setIsErrorAlertOpen
}) {
const countdownRef = useRef()
const [viewMode, setViewMode] = useState('table')
const [isOpenComparison, setIsOpenComparison] = useState(false)
const [versionCompare, setVersionCompare] = useState(null)
const [searchTerm, setSearchTerm] = useState('')
const [filters, setFilters] = useState({
author: '',
dateFrom: '',
dateTo: '',
status: ''
})
const [outdatedStatusMap, setOutdatedStatusMap] = useState({})
// Fetch outdated status for all versions
useEffect(() => {
async function fetchOutdatedStatus() {
const statusMap = {}
await Promise.all(
data.map(async version => {
try {
const comparisonData = await compareVersion({
accessToken,
userId,
versionId: version.id,
countdownRef,
setError,
setIsErrorAlertOpen
})
if (comparisonData) {
statusMap[version.id] = comparisonData.outdated || false
}
} catch (error) {
console.warn(`Failed to fetch outdated status for version ${version.id}:`, error)
statusMap[version.id] = false
}
})
)
setOutdatedStatusMap(statusMap)
}
if (data.length > 0) {
fetchOutdatedStatus()
}
}, [data, accessToken, userId, countdownRef, setError, setIsErrorAlertOpen])
// Filter data based on search and filters
const filteredData = filterVersions(data, searchTerm, filters)
const stats = getFilterStats(data, filteredData)
const versionData = data.find(({id}) => id === versionCompare?.versionId)
const handleSearchChange = newSearchTerm => {
setSearchTerm(newSearchTerm)
}
const handleFiltersChange = newFilters => {
setFilters(newFilters)
}
const handleViewModeChange = (event, newViewMode) => {
if (newViewMode !== null) {
setViewMode(newViewMode)
}
}
return (
<>
<Box>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 2
}}
>
<Box>
<Typography variant='button' sx={{fontWeight: 'bold'}}>
{collection}
</Typography>
{stats.hidden > 0 && (
<Typography variant='caption' color='text.secondary' sx={{ml: 1}}>
({stats.filtered}/{stats.total} versions)
</Typography>
)}
</Box>
<ToggleButtonGroup
exclusive
color='primary'
size='small'
value={viewMode}
onChange={handleViewModeChange}
>
<ToggleButton value='table' aria-label='vue tableau'>
<ViewListIcon sx={{mr: 1}} />
Table
</ToggleButton>
<ToggleButton value='timeline' aria-label='vue chronologique'>
<TimelineIcon sx={{mr: 1}} />
Timeline
</ToggleButton>
</ToggleButtonGroup>
</Box>
<VersionSearch onSearchChange={handleSearchChange} />
<VersionFilters data={data} onFiltersChange={handleFiltersChange} />
{filteredData.length === 0 ? (
<Box sx={{textAlign: 'center', py: 4}}>
<Typography variant='body1' color='text.secondary'>
{searchTerm || Object.values(filters).some(Boolean)
? 'Aucune version ne correspond aux critères de recherche'
: 'Aucune version disponible'}
</Typography>
</Box>
) : (
viewMode === 'table' ? (
<Paper style={{height: 350, width: '100%', marginBlock: 5}}>
<TableVirtuoso
data={filteredData}
components={VirtuosoTableComponents}
fixedHeaderContent={fixedHeaderContent}
itemContent={(index, row) => rowContent({
index, row, accessToken, userId, countdownRef, setError, setIsErrorAlertOpen, setIsOpenComparison, setVersionCompare, outdatedStatusMap
})}
/>
</Paper>
) : (
<VersionTimeline
collection={collection}
data={filteredData}
accessToken={accessToken}
userId={userId}
setError={setError}
setIsErrorAlertOpen={setIsErrorAlertOpen}
/>
)
)}
</Box>
{isOpenComparison && (
<VersionDialog versionData={versionData} versionCompare={versionCompare} isOpen={isOpenComparison} setIsOpen={setIsOpenComparison} />
)}
<SessionExpired ref={countdownRef} setError={setError} setIsErrorAlertOpen={setIsErrorAlertOpen} />
</>
)
}
ListVersions.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
}