Merge pull request 'Ajout de la page dashboard' (#1) from feat-dashboard into master

Reviewed-on: https://codeberg.org/OKI/konstitisyon.la/pulls/1
This commit is contained in:
Cédric FAMIBELLE-PRONZOLA
2024-09-15 14:07:42 +00:00
9 changed files with 309 additions and 9 deletions
+35 -1
View File
@@ -1,5 +1,19 @@
import {redirect} from 'next/navigation' import {redirect} from 'next/navigation'
import Box from '@mui/material/Box'
import Container from '@mui/material/Container'
import Typography from '@mui/material/Typography'
import HomeIcon from '@mui/icons-material/Home'
import {auth} from '../../auth.js' import {auth} from '../../auth.js'
import GetVersions from '@/components/versions/get-versions.js'
import Footer from '@/components/footer.js'
import Sign from '@/components/session/sign.js'
const navButton = {
title: 'Accueil',
path: '/',
color: 'success',
icon: <HomeIcon fontSize='large' />
}
export default async function DashboardPage() { export default async function DashboardPage() {
const session = await auth() const session = await auth()
@@ -9,6 +23,26 @@ export default async function DashboardPage() {
} }
return ( return (
<div>DashboardPage</div> <Box sx={{
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
}}
>
<Container>
<Typography
textTransform='uppercase'
mt={1}
component='h1'
textAlign='center'
variant='h4'
>
Tableau de bord
</Typography>
<Sign session={session} navButton={navButton} />
<GetVersions session={session} />
</Container>
<Footer />
</Box>
) )
} }
+9 -1
View File
@@ -2,6 +2,7 @@ import {createDirectus, rest, readItems} from '@directus/sdk'
import Container from '@mui/material/Container' import Container from '@mui/material/Container'
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings'
import {auth} from '../auth.js' import {auth} from '../auth.js'
import Konstitisyon from '@/components/konstitisyon/index.js' import Konstitisyon from '@/components/konstitisyon/index.js'
import Footer from '@/components/footer.js' import Footer from '@/components/footer.js'
@@ -11,6 +12,13 @@ import Create from '@/components/konstitisyon/create/index.js'
const apiUrl = process.env.DIRECTUS_API_URL const apiUrl = process.env.DIRECTUS_API_URL
const appTitle = process.env.APP_TITLE const appTitle = process.env.APP_TITLE
const navButton = {
title: 'Tableau de bord',
path: '/dashboard',
color: 'success',
icon: <AdminPanelSettingsIcon fontSize='large' />
}
async function getData() { async function getData() {
if (!apiUrl) { if (!apiUrl) {
throw new Error('DIRECTUS_API_URL is required') throw new Error('DIRECTUS_API_URL is required')
@@ -67,7 +75,7 @@ export default async function Page() {
> >
<Container maxWidth='sm'> <Container maxWidth='sm'>
<Typography mt={1} component='h1' textAlign='center' variant='h4'>{appTitle.toUpperCase()}</Typography> <Typography mt={1} component='h1' textAlign='center' variant='h4'>{appTitle.toUpperCase()}</Typography>
<Sign session={session} /> <Sign session={session} navButton={navButton} />
{session && ( {session && (
<Create session={session} titres={titres} /> <Create session={session} titres={titres} />
)} )}
+15
View File
@@ -0,0 +1,15 @@
import Box from '@mui/material/Box'
import CircularProgress from '@mui/material/CircularProgress'
export function Loading() {
return (
<Box
display='flex'
justifyContent='center'
alignItems='center'
height='100vh'
>
<CircularProgress color='warning' />
</Box>
)
}
+6 -6
View File
@@ -12,7 +12,6 @@ import Tooltip, {tooltipClasses} from '@mui/material/Tooltip'
import LogoutIcon from '@mui/icons-material/Logout' import LogoutIcon from '@mui/icons-material/Logout'
import LoginIcon from '@mui/icons-material/Login' import LoginIcon from '@mui/icons-material/Login'
import PersonAddIcon from '@mui/icons-material/PersonAdd' import PersonAddIcon from '@mui/icons-material/PersonAdd'
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings'
import ConfirmationAlert from './confirmation-alert.js' import ConfirmationAlert from './confirmation-alert.js'
const LightTooltip = styled(({className, ...props}) => ( const LightTooltip = styled(({className, ...props}) => (
@@ -26,7 +25,7 @@ const LightTooltip = styled(({className, ...props}) => (
}, },
})) }))
export default function Sign({session}) { export default function Sign({session, navButton}) {
const router = useRouter() const router = useRouter()
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
@@ -45,9 +44,9 @@ export default function Sign({session}) {
<LogoutIcon fontSize='large' /> <LogoutIcon fontSize='large' />
</Fab> </Fab>
</LightTooltip> </LightTooltip>
<LightTooltip title='Tableau de bord' placement='right'> <LightTooltip title={navButton.title} placement='right'>
<Fab sx={{mr: 3}} size='large' color='warning' onClick={() => router.push('/dashboard')}> <Fab sx={{mr: 3}} size='large' color={navButton.color} onClick={() => router.push(navButton.path)}>
<AdminPanelSettingsIcon fontSize='large' /> {navButton.icon}
</Fab> </Fab>
</LightTooltip> </LightTooltip>
</Stack> </Stack>
@@ -78,5 +77,6 @@ export default function Sign({session}) {
} }
Sign.propTypes = { Sign.propTypes = {
session: PropTypes.object session: PropTypes.object,
navButton: PropTypes.object.isRequired
} }
+108
View File
@@ -0,0 +1,108 @@
'use client'
import {useState, useRef, useEffect} from 'react'
import PropTypes from 'prop-types'
import Grid from '@mui/material/Grid2'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
import AuthAlert from '../auth-form/auth-alert.js'
import LogoutCountdown from '../session/logout-countdown.js'
import {Loading} from '../loading.js'
import ListVersions from './list-versions.js'
import {listVersions} from '@/lib/directus.js'
export default function GetVersions({session}) {
const {accessToken, userId} = session.user
const [versions, setVersions] = useState(null)
const [isErrorAlertOpen, setIsErrorAlertOpen] = useState(false)
const [error, setError] = useState('')
const countdownRef = useRef()
useEffect(() => {
async function fetchVersions() {
const data = await listVersions({
accessToken, userId, countdownRef, setError, setIsErrorAlertOpen
})
setVersions(data)
}
fetchVersions()
}, [accessToken, userId])
if (!versions) {
return (
<>
{error && <AuthAlert
isOpen={isErrorAlertOpen}
setIsOpen={setIsErrorAlertOpen}
message={error}
severity='error'
/>}
<Loading />
<LogoutCountdown ref={countdownRef} setError={setError} setIsErrorAlertOpen={setIsErrorAlertOpen} />
</>
)
}
const titres = versions?.filter(({collection}) => collection === 'titres')
const articles = versions?.filter(({collection}) => collection === 'articles')
return (
<>
{error && <AuthAlert
isOpen={isErrorAlertOpen}
setIsOpen={setIsErrorAlertOpen}
message={error}
severity='error'
/>}
<Box
sx={{
border: '2px solid #ccc',
borderRadius: '8px',
padding: 1,
my: 4
}}
>
<Typography
gutterBottom
variant='h5'
component='h2'
textAlign='center'
sx={{display: 'block', fontWeight: 'bold', textTransform: 'uppercase'}}
>
Liste des versions
</Typography>
<Grid
container
spacing={2}
justifyContent={
(titres.length > 0 && articles.length === 0) || (articles.length > 0 && titres.length === 0)
? 'center'
: 'flex-start'
}
>
{titres.length > 0 && (
<Grid size={{xs: 12, md: articles.length > 0 ? 6 : 12}}>
<ListVersions collection='Titres' data={titres} />
</Grid>
)}
{articles.length > 0 && (
<Grid size={{xs: 12, md: titres.length > 0 ? 6 : 12}}>
<ListVersions collection='Articles' data={articles} />
</Grid>
)}
</Grid>
</Box>
<LogoutCountdown ref={countdownRef} setError={setError} setIsErrorAlertOpen={setIsErrorAlertOpen} />
</>
)
}
GetVersions.propTypes = {
session: PropTypes.object.isRequired
}
+96
View File
@@ -0,0 +1,96 @@
import * as React 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 {TableVirtuoso} from 'react-virtuoso'
import {Box, Typography} from '@mui/material'
import {formatDate} from '@/lib/format.js'
const columns = [
{
width: 200,
label: 'Version',
dataKey: 'name',
},
{
width: 120,
label: 'Créée le',
dataKey: 'date_created',
numeric: true,
}
]
const VirtuosoTableComponents = {
Scroller: React.forwardRef((props, ref) => (
<TableContainer component={Paper} {...props} ref={ref} />
)),
Table: props => (
<Table {...props} sx={{borderCollapse: 'separate', tableLayout: 'fixed'}} />
),
TableHead: React.forwardRef((props, ref) => <TableHead {...props} ref={ref} />),
TableRow,
TableBody: React.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(_index, row) {
return (
<>
{columns.map(column => (
<TableCell
key={column.dataKey}
align={column.numeric || false ? 'right' : 'left'}
>
{column.dataKey === 'date_created' ? formatDate(row[column.dataKey], 'Pp') : <Button variant='outlined' color='success'>{row[column.dataKey]}</Button>}
</TableCell>
))}
</>
)
}
export default function ListVersions({collection, data}) {
return (
<Box>
<Typography variant='button' textAlign='center' sx={{display: 'block', fontWeight: 'bold'}}>
{collection}
</Typography>
<Paper style={{height: 350, width: '100%', marginBlock: 5}}>
<TableVirtuoso
data={data}
components={VirtuosoTableComponents}
fixedHeaderContent={fixedHeaderContent}
itemContent={rowContent}
/>
</Paper>
</Box>
)
}
ListVersions.propTypes = {
collection: PropTypes.oneOf(['titres', 'articles']).isRequired,
data: PropTypes.array.isRequired
}
+33
View File
@@ -69,6 +69,39 @@ export async function handleSubmit({
} }
} }
export async function listVersions({
accessToken,
userId,
countdownRef,
setError,
setIsErrorAlertOpen
}) {
try {
await handleUserStatus(accessToken, userId)
const versions = await directusClient.request(
withToken(
accessToken,
readContentVersions({
sort: '-date_created'
})
)
)
return versions
} catch (error) {
console.log('error', error)
if (error?.errors[0]?.message === 'Token expired.') {
countdownRef.current.startCountdown()
} else {
console.log(error?.errors[0]?.message)
setError(error?.errors[0]?.message)
setIsErrorAlertOpen(true)
}
}
}
export async function createVersion({ export async function createVersion({
accessToken, accessToken,
userId, userId,
+2 -1
View File
@@ -18,7 +18,8 @@
"next": "^14.2.3", "next": "^14.2.3",
"next-auth": "^5.0.0-beta.18", "next-auth": "^5.0.0-beta.18",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1",
"react-virtuoso": "^4.10.2"
}, },
"devDependencies": { "devDependencies": {
"eslint-config-xo-nextjs": "^6.0.0", "eslint-config-xo-nextjs": "^6.0.0",
+5
View File
@@ -3115,6 +3115,11 @@ react-transition-group@^4.4.5:
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.6.2" prop-types "^15.6.2"
react-virtuoso@^4.10.2:
version "4.10.2"
resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.10.2.tgz#a27308a3c4cfeb24722032acc0b6a46055c26967"
integrity sha512-os6n9QKeKRF+8mnQR/vGy/xrFf6vXIzuaAVL54q5k2st2d5QIEwI+KDKaflMUmMvnDbPxf68bs+CF5bY3YI7qA==
react@^18.3.1: react@^18.3.1:
version "18.3.1" version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"