Merge pull request #24 from Shaac/knowledge_stats

Knowledge stats
This commit is contained in:
forceoranj 2023-05-26 06:44:57 +02:00 committed by GitHub
commit f0ecf4acda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 3 deletions

View File

@ -0,0 +1,60 @@
import React, { memo } from "react"
import { useSelector } from "react-redux"
import styles from "./styles.module.scss"
import { fetchBoxListIfNeed, selectSortedUniqueDetailedBoxes } from "../../store/boxList"
import {
fetchVolunteerDetailedKnowledgeListIfNeed,
selectVolunteerDetailedKnowledgeList,
} from "../../store/volunteerDetailedKnowledgeList"
import { DetailedBox } from "../../services/boxes"
import { VolunteerDetailedKnowledge } from "../../services/volunteers"
const KnowledgeStats: React.FC = (): JSX.Element | null => {
const detailedBoxes = useSelector(selectSortedUniqueDetailedBoxes) as DetailedBox[]
const volunteerDetailedKnowledgeList = useSelector(selectVolunteerDetailedKnowledgeList)
const knowledgeStats = detailedBoxes.map((box) => ({
ok: wiseVolunteersNumber(volunteerDetailedKnowledgeList, box.gameId, "ok"),
bof: wiseVolunteersNumber(volunteerDetailedKnowledgeList, box.gameId, "bof"),
box,
}))
const unknownGames = knowledgeStats.filter((stat) => stat.ok === 0 && stat.bof === 0)
return (
<div>
<p>{unknownGames.length} jeux non connus par des bénévoles :</p>
<ul>
{knowledgeStats
.filter((stat) => stat.ok === 0 && stat.bof === 0)
.map((stat) => (
<li key={stat.box.id}>
<a
href={`https://boardgamegeek.com/boardgame/${stat.box.bggId}`}
target="_blank"
rel="noreferrer"
className={styles.title}
>
{stat.box.title}
</a>
</li>
))}
</ul>
</div>
)
}
function wiseVolunteersNumber(
volunteersKnowledge: VolunteerDetailedKnowledge[],
gameId: number,
wiseness: "ok" | "bof"
): number {
return volunteersKnowledge.filter(
(v) =>
v[wiseness].includes(gameId) && (v.dayWishes.includes("S") || v.dayWishes.includes("D"))
).length
}
export default memo(KnowledgeStats)
export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerDetailedKnowledgeListIfNeed]

View File

@ -1,9 +1,11 @@
import { FC, useCallback, useState } from "react"
import { FC, useCallback, useState, useEffect, useMemo } from "react"
import { useSelector } from "react-redux"
import classnames from "classnames"
import { isUserConnected, routerSelector, selectUserRoles } from "../../store/auth"
import { isUserConnected, routerSelector, selectUserRoles, selectUserId } from "../../store/auth"
import styles from "./styles.module.scss"
import ROLES from "../../utils/roles.constants"
import useAction from "../../utils/useAction"
import { fetchVolunteerListIfNeed, selectVolunteerList } from "../../store/volunteerList"
interface MenuItemProps {
name: string
@ -29,6 +31,27 @@ const RestrictMenuItem: FC<RestrictMenuItemProps> = ({ name, pathname, role }):
return roles.includes(role) ? <MenuItem name={name} pathname={pathname} /> : <div />
}
interface TeamMenuItemProps extends MenuItemProps {
team: number
}
const TeamMenuItem: FC<TeamMenuItemProps> = ({ name, pathname, team }): JSX.Element => {
const fetch = useAction(fetchVolunteerListIfNeed)
const userId = useSelector(selectUserId)
useEffect(() => {
if (userId) fetch()
}, [userId, fetch])
const volunteers = useSelector(selectVolunteerList)
const user = useMemo(
() => volunteers.find((volunteer) => volunteer.id === userId),
[volunteers, userId]
)
return user?.team === team ? <MenuItem name={name} pathname={pathname} /> : <div />
}
// Hardcoded value of the "Jeux à volonté" team
const TEAM_JAV = 2
const MainMenu: FC = (): JSX.Element => {
const connected = useSelector(isUserConnected)
const [opened, setOpened] = useState(false)
@ -54,7 +77,8 @@ const MainMenu: FC = (): JSX.Element => {
<MenuItem name="Mon profil" pathname="/profil" />
{/* <MenuItem name="Emprunter" pathname="/emprunter" />
<MenuItem name="Emprunts" pathname="/emprunts" /> */}
{/* <MenuItem name="Mes connaissances" pathname="/connaissances" /> */}
<TeamMenuItem team={TEAM_JAV} name="Mes connaissances" pathname="/connaissances" />
<TeamMenuItem team={TEAM_JAV} name="Stats" pathname="/connaissancesStats" />
<RestrictMenuItem
role={ROLES.ASSIGNER}
name="Gestion équipes"

View File

@ -16,6 +16,7 @@ import LoginForm from "./LoginForm"
import KnowledgeBoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/KnowledgeBoxList"
import KnowledgeCard, { fetchFor as fetchForKnowledgeCard } from "./Knowledge/KnowledgeCard"
import KnowledgeIntro from "./Knowledge/KnowledgeIntro"
import KnowledgeStats from "./Knowledge/KnowledgeStats"
import Asks, { fetchFor as fetchForAsks } from "./Asks"
import ParticipationDetailsForm, {
fetchFor as fetchForParticipationDetailsForm,
@ -46,6 +47,7 @@ export {
KnowledgeBoxList,
fetchForKnowledge,
KnowledgeIntro,
KnowledgeStats,
Loading,
LoaningIntro,
Loaning,

View File

@ -0,0 +1,32 @@
import { FC, memo } from "react"
import { useSelector } from "react-redux"
import { RouteComponentProps } from "react-router-dom"
import { Helmet } from "react-helmet"
import { AppThunk } from "../../store"
import styles from "../Knowledge/styles.module.scss"
import { KnowledgeStats, fetchForKnowledgeCard, LoginForm } from "../../components"
import { selectUserJwtToken } from "../../store/auth"
export type Props = RouteComponentProps
const KnowledgeStatsPage: FC<Props> = (): JSX.Element => {
const jwtToken = useSelector(selectUserJwtToken)
if (jwtToken === undefined) return <p>Loading...</p>
if (!jwtToken) {
return <LoginForm loginNeeded />
}
return (
<div className={styles.knowledgesPage}>
<div className={styles.knowledgesContent}>
<Helmet title="Stats des jeux connus" />
<KnowledgeStats />
</div>
</div>
)
}
// Fetch server-side data here
export const loadData = (): AppThunk[] => [...fetchForKnowledgeCard.map((f) => f())]
export default memo(KnowledgeStatsPage)

View File

@ -0,0 +1,16 @@
import loadable from "@loadable/component"
import { Loading, ErrorBoundary } from "../../components"
import { Props, loadData } from "./KnowledgeStatsPage"
const Knowledges = loadable(() => import("./KnowledgeStatsPage"), {
fallback: <Loading />,
})
export default (props: Props): JSX.Element => (
<ErrorBoundary>
<Knowledges {...props} />
</ErrorBoundary>
)
export { loadData }

View File

@ -10,6 +10,7 @@ import AsyncAnnouncements, { loadData as loadAnnouncementsData } from "../pages/
import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../pages/TeamAssignment"
import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register"
import AsyncKnowledge, { loadData as loadKnowledgeData } from "../pages/Knowledge"
import AsyncKnowledgeStats, { loadData as loadKnowledgeStatsData } from "../pages/KnowledgeStats"
// import AsyncLoans, { loadData as loadLoansData } from "../pages/Loans"
import AsyncLoaning, { loadData as loadLoaningData } from "../pages/Loaning"
import AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards"
@ -46,6 +47,11 @@ export default [
component: AsyncKnowledge,
loadData: loadKnowledgeData,
},
{
path: "/connaissancesStats",
component: AsyncKnowledgeStats,
loadData: loadKnowledgeStatsData,
},
// {
// path: "/emprunts",
// component: AsyncLoans,