feat: récupère le total des votes

This commit is contained in:
2026-01-24 23:35:48 +04:00
parent b838f46b2b
commit 22130529f6
7 changed files with 140 additions and 25 deletions
+28 -2
View File
@@ -46,7 +46,7 @@ const renderMarkdownToHtml = async content => {
} }
} }
export default function ExportPdfButton({versionData, isOutdated = false, size = 'medium', variant = 'outlined'}) { export default function ExportPdfButton({versionData, isOutdated = false, voteCounts = null, size = 'medium', variant = 'outlined'}) {
const [isExporting, setIsExporting] = useState(false) const [isExporting, setIsExporting] = useState(false)
const handleExportPdf = async () => { const handleExportPdf = async () => {
@@ -146,6 +146,11 @@ export default function ExportPdfButton({versionData, isOutdated = false, size =
const voteStatus = (isExpired || isOutdated) ? 'fermé' : 'ouvert' const voteStatus = (isExpired || isOutdated) ? 'fermé' : 'ouvert'
const voteColor = voteStatus === 'ouvert' ? '#2e7d32' : '#d32f2f' const voteColor = voteStatus === 'ouvert' ? '#2e7d32' : '#d32f2f'
// Vote counts display
const voteTotal = voteCounts ? voteCounts.total : 0
const voteTotalColor = voteTotal > 0 ? '#2e7d32' : (voteTotal < 0 ? '#d32f2f' : '#666')
const voteTotalSign = voteTotal >= 0 ? '+' : ''
// Render markdown content to HTML // Render markdown content to HTML
const renderedContent = await renderMarkdownToHtml(versionData.delta?.contenu) const renderedContent = await renderMarkdownToHtml(versionData.delta?.contenu)
@@ -162,9 +167,25 @@ export default function ExportPdfButton({versionData, isOutdated = false, size =
<strong>Date de création :</strong> ${formatDate(versionData.date_created, 'PPpp', {withTimezone: true})} <strong>Date de création :</strong> ${formatDate(versionData.date_created, 'PPpp', {withTimezone: true})}
</p> </p>
<p style="margin: 5px 0; color: #666; font-size: 14px;"> <p style="margin: 5px 0; color: #666; font-size: 14px;">
<strong>Statut du vote :</strong> <strong>Statut du vote :</strong>
<span style="color: ${voteColor}; font-weight: bold;">${voteStatus}</span> <span style="color: ${voteColor}; font-weight: bold;">${voteStatus}</span>
</p> </p>
${voteCounts ? `
<div style="margin-top: 15px; padding: 15px; background-color: #f5f5f5; border-radius: 8px;">
<p style="margin: 0 0 10px 0; font-size: 16px; font-weight: bold; color: #333;">
📊 Résultats des votes
</p>
<p style="margin: 5px 0; font-size: 14px;">
👍 Votes positifs : <strong style="color: #2e7d32;">${voteCounts.positive}</strong>
</p>
<p style="margin: 5px 0; font-size: 14px;">
👎 Votes négatifs : <strong style="color: #d32f2f;">${voteCounts.negative}</strong>
</p>
<p style="margin: 10px 0 0 0; font-size: 16px; font-weight: bold;">
🏆 Total : <span style="color: ${voteTotalColor};">${voteTotalSign}${voteTotal}</span>
</p>
</div>
` : ''}
</div> </div>
</div> </div>
@@ -253,6 +274,11 @@ export default function ExportPdfButton({versionData, isOutdated = false, size =
ExportPdfButton.propTypes = { ExportPdfButton.propTypes = {
versionData: PropTypes.object.isRequired, versionData: PropTypes.object.isRequired,
isOutdated: PropTypes.bool, isOutdated: PropTypes.bool,
voteCounts: PropTypes.shape({
positive: PropTypes.number,
negative: PropTypes.number,
total: PropTypes.number
}),
size: PropTypes.oneOf(['small', 'medium', 'large']), size: PropTypes.oneOf(['small', 'medium', 'large']),
variant: PropTypes.oneOf(['text', 'outlined', 'contained']) variant: PropTypes.oneOf(['text', 'outlined', 'contained'])
} }
+43 -8
View File
@@ -24,7 +24,7 @@ import ShareButton from './share-button.js'
import ExportPdfButton from './export-pdf-button.js' import ExportPdfButton from './export-pdf-button.js'
import PrintButton from './print-button.js' import PrintButton from './print-button.js'
import {formatDate} from '@/lib/format.js' import {formatDate} from '@/lib/format.js'
import {compareVersion} from '@/lib/directus.js' import {compareVersion, getVoteCounts} from '@/lib/directus.js'
import {filterVersions, getFilterStats} from '@/lib/version-utils.js' import {filterVersions, getFilterStats} from '@/lib/version-utils.js'
const columns = [ const columns = [
@@ -85,7 +85,8 @@ function rowContent({
setIsErrorAlertOpen, setIsErrorAlertOpen,
setIsOpenComparison, setIsOpenComparison,
setVersionCompare, setVersionCompare,
outdatedStatusMap outdatedStatusMap,
voteCountsMap
}) { }) {
const handleButtonClick = async versionId => { const handleButtonClick = async versionId => {
const version = await compareVersion({ const version = await compareVersion({
@@ -104,6 +105,7 @@ function rowContent({
} }
const isOutdated = outdatedStatusMap[row.id] || false const isOutdated = outdatedStatusMap[row.id] || false
const voteCounts = voteCountsMap[row.id] || null
return ( return (
<> <>
@@ -141,12 +143,14 @@ function rowContent({
<ExportPdfButton <ExportPdfButton
versionData={row} versionData={row}
isOutdated={isOutdated} isOutdated={isOutdated}
voteCounts={voteCounts}
size='small' size='small'
variant='text' variant='text'
/> />
<PrintButton <PrintButton
versionData={row} versionData={row}
isOutdated={isOutdated} isOutdated={isOutdated}
voteCounts={voteCounts}
size='small' size='small'
variant='text' variant='text'
/> />
@@ -188,11 +192,13 @@ export default function ListVersions({
status: '' status: ''
}) })
const [outdatedStatusMap, setOutdatedStatusMap] = useState({}) const [outdatedStatusMap, setOutdatedStatusMap] = useState({})
const [voteCountsMap, setVoteCountsMap] = useState({})
// Fetch outdated status for all versions // Fetch outdated status and vote counts for all versions
useEffect(() => { useEffect(() => {
async function fetchOutdatedStatus() { async function fetchVersionsData() {
const statusMap = {} const statusMap = {}
const countsMap = {}
await Promise.all( await Promise.all(
data.map(async version => { data.map(async version => {
@@ -209,18 +215,27 @@ export default function ListVersions({
if (comparisonData) { if (comparisonData) {
statusMap[version.id] = comparisonData.outdated || false statusMap[version.id] = comparisonData.outdated || false
} }
// Fetch vote counts
const counts = await getVoteCounts({
accessToken,
versionId: version.id
})
countsMap[version.id] = counts
} catch (error) { } catch (error) {
console.warn(`Failed to fetch outdated status for version ${version.id}:`, error) console.warn(`Failed to fetch data for version ${version.id}:`, error)
statusMap[version.id] = false statusMap[version.id] = false
countsMap[version.id] = {positive: 0, negative: 0, total: 0}
} }
}) })
) )
setOutdatedStatusMap(statusMap) setOutdatedStatusMap(statusMap)
setVoteCountsMap(countsMap)
} }
if (data.length > 0) { if (data.length > 0) {
fetchOutdatedStatus() fetchVersionsData()
} }
}, [data, accessToken, userId, countdownRef, setError, setIsErrorAlertOpen]) }, [data, accessToken, userId, countdownRef, setError, setIsErrorAlertOpen])
@@ -230,6 +245,19 @@ export default function ListVersions({
const versionData = data.find(({id}) => id === versionCompare?.versionId) const versionData = data.find(({id}) => id === versionCompare?.versionId)
// Function to refresh vote counts for a specific version after voting
const refreshVoteCounts = async versionId => {
try {
const counts = await getVoteCounts({
accessToken,
versionId
})
setVoteCountsMap(prev => ({...prev, [versionId]: counts}))
} catch (error) {
console.warn(`Failed to refresh vote counts for version ${versionId}:`, error)
}
}
const handleSearchChange = newSearchTerm => { const handleSearchChange = newSearchTerm => {
setSearchTerm(newSearchTerm) setSearchTerm(newSearchTerm)
} }
@@ -305,7 +333,7 @@ export default function ListVersions({
components={VirtuosoTableComponents} components={VirtuosoTableComponents}
fixedHeaderContent={fixedHeaderContent} fixedHeaderContent={fixedHeaderContent}
itemContent={(index, row) => rowContent({ itemContent={(index, row) => rowContent({
index, row, accessToken, userId, countdownRef, setError, setIsErrorAlertOpen, setIsOpenComparison, setVersionCompare, outdatedStatusMap index, row, accessToken, userId, countdownRef, setError, setIsErrorAlertOpen, setIsOpenComparison, setVersionCompare, outdatedStatusMap, voteCountsMap
})} })}
/> />
</Paper> </Paper>
@@ -316,13 +344,20 @@ export default function ListVersions({
userId={userId} userId={userId}
setError={setError} setError={setError}
setIsErrorAlertOpen={setIsErrorAlertOpen} setIsErrorAlertOpen={setIsErrorAlertOpen}
onVoteSuccess={refreshVoteCounts}
/> />
) )
)} )}
</Box> </Box>
{isOpenComparison && ( {isOpenComparison && (
<VersionDialog versionData={versionData} versionCompare={versionCompare} isOpen={isOpenComparison} setIsOpen={setIsOpenComparison} /> <VersionDialog
versionData={versionData}
versionCompare={versionCompare}
isOpen={isOpenComparison}
setIsOpen={setIsOpenComparison}
onVoteSuccess={refreshVoteCounts}
/>
)} )}
<SessionExpired ref={countdownRef} setError={setError} setIsErrorAlertOpen={setIsErrorAlertOpen} /> <SessionExpired ref={countdownRef} setError={setError} setIsErrorAlertOpen={setIsErrorAlertOpen} />
</> </>
+28 -2
View File
@@ -49,7 +49,7 @@ const renderMarkdownToHtml = async content => {
} }
} }
export default function PrintButton({versionData, isOutdated = false, size = 'medium', variant = 'outlined'}) { export default function PrintButton({versionData, isOutdated = false, voteCounts = null, size = 'medium', variant = 'outlined'}) {
const [isPrinting, setIsPrinting] = useState(false) const [isPrinting, setIsPrinting] = useState(false)
const [snackbar, setSnackbar] = useState({open: false, message: '', severity: 'success'}) const [snackbar, setSnackbar] = useState({open: false, message: '', severity: 'success'})
@@ -65,6 +65,11 @@ export default function PrintButton({versionData, isOutdated = false, size = 'me
const voteStatus = (isExpired || isOutdated) ? 'fermé' : 'ouvert' const voteStatus = (isExpired || isOutdated) ? 'fermé' : 'ouvert'
const voteColor = voteStatus === 'ouvert' ? '#2e7d32' : '#d32f2f' const voteColor = voteStatus === 'ouvert' ? '#2e7d32' : '#d32f2f'
// Vote counts display
const voteTotal = voteCounts ? voteCounts.total : 0
const voteTotalColor = voteTotal > 0 ? '#2e7d32' : (voteTotal < 0 ? '#d32f2f' : '#666')
const voteTotalSign = voteTotal >= 0 ? '+' : ''
// Render markdown content to HTML // Render markdown content to HTML
const renderedContent = await renderMarkdownToHtml(versionData.delta?.contenu) const renderedContent = await renderMarkdownToHtml(versionData.delta?.contenu)
@@ -306,9 +311,25 @@ export default function PrintButton({versionData, isOutdated = false, size = 'me
<strong>Date de création :</strong> ${formatDate(versionData.date_created, 'PPpp', {withTimezone: true})} <strong>Date de création :</strong> ${formatDate(versionData.date_created, 'PPpp', {withTimezone: true})}
</div> </div>
<div class="metadata"> <div class="metadata">
<strong>Statut du vote :</strong> <strong>Statut du vote :</strong>
<span class="vote-status">${voteStatus}</span> <span class="vote-status">${voteStatus}</span>
</div> </div>
${voteCounts ? `
<div style="margin-top: 15px; padding: 15px; background-color: #f8f9fa; border-radius: 8px; border: 1px solid #e0e0e0;">
<p style="margin: 0 0 10px 0; font-size: 16px; font-weight: bold; color: #333;">
📊 Résultats des votes
</p>
<p style="margin: 5px 0; font-size: 14px;">
👍 Votes positifs : <strong style="color: #2e7d32;">${voteCounts.positive}</strong>
</p>
<p style="margin: 5px 0; font-size: 14px;">
👎 Votes négatifs : <strong style="color: #d32f2f;">${voteCounts.negative}</strong>
</p>
<p style="margin: 10px 0 0 0; font-size: 16px; font-weight: bold;">
🏆 Total : <span style="color: ${voteTotalColor};">${voteTotalSign}${voteTotal}</span>
</p>
</div>
` : ''}
</div> </div>
<div class="content-section"> <div class="content-section">
@@ -392,6 +413,11 @@ export default function PrintButton({versionData, isOutdated = false, size = 'me
PrintButton.propTypes = { PrintButton.propTypes = {
versionData: PropTypes.object.isRequired, versionData: PropTypes.object.isRequired,
isOutdated: PropTypes.bool, isOutdated: PropTypes.bool,
voteCounts: PropTypes.shape({
positive: PropTypes.number,
negative: PropTypes.number,
total: PropTypes.number
}),
size: PropTypes.oneOf(['small', 'medium', 'large']), size: PropTypes.oneOf(['small', 'medium', 'large']),
variant: PropTypes.oneOf(['text', 'outlined', 'contained']) variant: PropTypes.oneOf(['text', 'outlined', 'contained'])
} }
+7 -2
View File
@@ -16,7 +16,7 @@ import CopyButton from './copy-button.js'
import {formatDate} from '@/lib/format.js' import {formatDate} from '@/lib/format.js'
import {getVoteCounts} from '@/lib/directus.js' import {getVoteCounts} from '@/lib/directus.js'
export default function VersionComparison({versionData, versionCompare, voteRefreshKey = 0, onVoteResult}) { export default function VersionComparison({versionData, versionCompare, voteRefreshKey = 0, onVoteResult, onVoteSuccess}) {
const {data: session} = useSession() const {data: session} = useSession()
const {current, main, outdated} = versionCompare const {current, main, outdated} = versionCompare
const [snackbar, setSnackbar] = useState({open: false, message: '', severity: 'success'}) const [snackbar, setSnackbar] = useState({open: false, message: '', severity: 'success'})
@@ -44,6 +44,10 @@ export default function VersionComparison({versionData, versionCompare, voteRefr
}) })
setVoteCounts(counts) setVoteCounts(counts)
if (onVoteSuccess) {
onVoteSuccess(versionCompare.versionId)
}
} }
if (onVoteResult) { if (onVoteResult) {
@@ -265,5 +269,6 @@ VersionComparison.propTypes = {
versionId: PropTypes.string versionId: PropTypes.string
}).isRequired, }).isRequired,
voteRefreshKey: PropTypes.number, voteRefreshKey: PropTypes.number,
onVoteResult: PropTypes.func onVoteResult: PropTypes.func,
onVoteSuccess: PropTypes.func
} }
+4 -3
View File
@@ -14,7 +14,7 @@ import CompareArrowsIcon from '@mui/icons-material/CompareArrows'
import {useTheme} from '@mui/material/styles' import {useTheme} from '@mui/material/styles'
import VersionComparison from './version-comparison.js' import VersionComparison from './version-comparison.js'
export default function VersionDialog({versionData, versionCompare, isOpen, setIsOpen}) { export default function VersionDialog({versionData, versionCompare, isOpen, setIsOpen, onVoteSuccess}) {
const theme = useTheme() const theme = useTheme()
const fullScreen = useMediaQuery(theme.breakpoints.down('md')) const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
@@ -59,7 +59,7 @@ export default function VersionDialog({versionData, versionCompare, isOpen, setI
</DialogTitle> </DialogTitle>
<DialogContent sx={{minHeight: '60vh'}}> <DialogContent sx={{minHeight: '60vh'}}>
<VersionComparison versionData={versionData} versionCompare={versionCompare} /> <VersionComparison versionData={versionData} versionCompare={versionCompare} onVoteSuccess={onVoteSuccess} />
</DialogContent> </DialogContent>
<DialogActions sx={{px: 3, py: 2}}> <DialogActions sx={{px: 3, py: 2}}>
@@ -84,5 +84,6 @@ VersionDialog.propTypes = {
main: PropTypes.object.isRequired main: PropTypes.object.isRequired
}).isRequired, }).isRequired,
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
setIsOpen: PropTypes.func.isRequired setIsOpen: PropTypes.func.isRequired,
onVoteSuccess: PropTypes.func
} }
+20 -4
View File
@@ -24,7 +24,7 @@ import CopyButton from './copy-button.js'
import ExportPdfButton from './export-pdf-button.js' import ExportPdfButton from './export-pdf-button.js'
import PrintButton from './print-button.js' import PrintButton from './print-button.js'
import VersionComparison from './version-comparison.js' import VersionComparison from './version-comparison.js'
import {getVersion, compareVersion} from '@/lib/directus.js' import {getVersion, compareVersion, getVoteCounts} from '@/lib/directus.js'
import {formatDate} from '@/lib/format.js' import {formatDate} from '@/lib/format.js'
export default function VersionPage({session, versionId, viewMode}) { export default function VersionPage({session, versionId, viewMode}) {
@@ -39,6 +39,7 @@ export default function VersionPage({session, versionId, viewMode}) {
const [isErrorAlertOpen, setIsErrorAlertOpen] = useState(false) const [isErrorAlertOpen, setIsErrorAlertOpen] = useState(false)
const [snackbar, setSnackbar] = useState({open: false, message: '', severity: 'success'}) const [snackbar, setSnackbar] = useState({open: false, message: '', severity: 'success'})
const [voteRefreshKey, setVoteRefreshKey] = useState(0) const [voteRefreshKey, setVoteRefreshKey] = useState(0)
const [voteCounts, setVoteCounts] = useState(null)
useEffect(() => { useEffect(() => {
async function fetchVersionData() { async function fetchVersionData() {
@@ -67,6 +68,13 @@ export default function VersionPage({session, versionId, viewMode}) {
if (comparison) { if (comparison) {
setVersionCompare({...comparison, versionId}) setVersionCompare({...comparison, versionId})
} }
const counts = await getVoteCounts({
accessToken,
versionId
})
setVoteCounts(counts)
} catch (error) { } catch (error) {
console.error('Failed to fetch version:', error) console.error('Failed to fetch version:', error)
setError('Impossible de charger cette version') setError('Impossible de charger cette version')
@@ -83,7 +91,7 @@ export default function VersionPage({session, versionId, viewMode}) {
router.push('/dashboard') router.push('/dashboard')
} }
const handleVoteResult = result => { const handleVoteResult = async result => {
setSnackbar({ setSnackbar({
open: true, open: true,
message: result.message, message: result.message,
@@ -91,6 +99,14 @@ export default function VersionPage({session, versionId, viewMode}) {
}) })
// Force refresh of both VoteButtons components by changing the key // Force refresh of both VoteButtons components by changing the key
setVoteRefreshKey(prev => prev + 1) setVoteRefreshKey(prev => prev + 1)
if (result.success) {
const counts = await getVoteCounts({
accessToken,
versionId
})
setVoteCounts(counts)
}
} }
const handleCloseSnackbar = () => { const handleCloseSnackbar = () => {
@@ -222,8 +238,8 @@ export default function VersionPage({session, versionId, viewMode}) {
</Button> </Button>
<Box sx={{display: 'flex', alignItems: 'center', gap: 2}}> <Box sx={{display: 'flex', alignItems: 'center', gap: 2}}>
<ExportPdfButton versionData={versionData} size='medium' /> <ExportPdfButton versionData={versionData} isOutdated={versionCompare?.outdated} voteCounts={voteCounts} size='medium' />
<PrintButton versionData={versionData} size='medium' /> <PrintButton versionData={versionData} isOutdated={versionCompare?.outdated} voteCounts={voteCounts} size='medium' />
<Tooltip title='Partager cette version'> <Tooltip title='Partager cette version'>
<IconButton color='primary' onClick={handleShare}> <IconButton color='primary' onClick={handleShare}>
<ShareIcon /> <ShareIcon />
+10 -4
View File
@@ -207,7 +207,8 @@ export default function VersionTimeline({
accessToken, accessToken,
userId, userId,
setError, setError,
setIsErrorAlertOpen setIsErrorAlertOpen,
onVoteSuccess
}) { }) {
const countdownRef = useRef() const countdownRef = useRef()
const [isOpenComparison, setIsOpenComparison] = useState(false) const [isOpenComparison, setIsOpenComparison] = useState(false)
@@ -216,12 +217,16 @@ export default function VersionTimeline({
const versionData = data.find(({id}) => id === versionCompare?.versionId) const versionData = data.find(({id}) => id === versionCompare?.versionId)
const handleVoteResult = result => { const handleVoteResult = (result, versionId) => {
setSnackbar({ setSnackbar({
open: true, open: true,
message: result.message, message: result.message,
severity: result.success ? 'success' : 'error' severity: result.success ? 'success' : 'error'
}) })
if (result.success && onVoteSuccess && versionId) {
onVoteSuccess(versionId)
}
} }
return ( return (
@@ -239,7 +244,7 @@ export default function VersionTimeline({
setIsErrorAlertOpen={setIsErrorAlertOpen} setIsErrorAlertOpen={setIsErrorAlertOpen}
setIsOpenComparison={setIsOpenComparison} setIsOpenComparison={setIsOpenComparison}
setVersionCompare={setVersionCompare} setVersionCompare={setVersionCompare}
onVoteResult={handleVoteResult} onVoteResult={result => handleVoteResult(result, version.id)}
/> />
))} ))}
</Box> </Box>
@@ -283,7 +288,8 @@ VersionTimeline.propTypes = {
accessToken: PropTypes.string.isRequired, accessToken: PropTypes.string.isRequired,
userId: PropTypes.string.isRequired, userId: PropTypes.string.isRequired,
setError: PropTypes.func.isRequired, setError: PropTypes.func.isRequired,
setIsErrorAlertOpen: PropTypes.func.isRequired setIsErrorAlertOpen: PropTypes.func.isRequired,
onVoteSuccess: PropTypes.func
} }
VersionItem.propTypes = { VersionItem.propTypes = {