Add /fiches

This commit is contained in:
pikiou 2022-06-25 02:12:25 +02:00
parent 88549bf42d
commit 3a5f2792c9
26 changed files with 601 additions and 49 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -12,37 +12,46 @@ import LogoutButton from "../components/LogoutButton/LogoutButton"
interface Route { interface Route {
route: { routes: RouteConfig[] } route: { routes: RouteConfig[] }
location: Location
} }
export const reactAppId = "react-view" export const reactAppId = "react-view"
const App = ({ route }: Route): JSX.Element => ( // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
<div> const App = ({ route, location }: Route): JSX.Element => {
<Helmet {...config.APP}> if (location.pathname === "/fiches") {
<meta return <div className={styles.cardPage}>{renderRoutes(route.routes)}</div>
name="viewport" }
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" // else
/>
</Helmet> return (
<header className={styles.header}> <div>
<div className={styles.logo} /> <Helmet {...config.APP}>
<div> <meta
<h1 className={styles.siteName}> name="viewport"
<a href="/">{config.APP.title}</a> content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
</h1> />
<div className={styles.siteDescription}>{config.APP.description}</div> </Helmet>
</div> <header className={styles.header}>
<div className={styles.menuWrapper}> <div className={styles.logo} />
<MainMenu /> <div>
</div> <h1 className={styles.siteName}>
<div className={styles.logoutWrapper}> <a href="/">{config.APP.title}</a>
<LogoutButton /> </h1>
</div> <div className={styles.siteDescription}>{config.APP.description}</div>
</header> </div>
{/* Child routes won't render without this */} <div className={styles.menuWrapper}>
{renderRoutes(route.routes)} <MainMenu />
<ToastContainer /> </div>
</div> <div className={styles.logoutWrapper}>
) <LogoutButton />
</div>
</header>
{/* Child routes won't render without this */}
{renderRoutes(route.routes)}
<ToastContainer />
</div>
)
}
export default App export default App

View File

@ -2,6 +2,13 @@
@import "../theme/mixins"; @import "../theme/mixins";
@import "../theme/main"; @import "../theme/main";
.cardPage {
background-color: $color-white;
// overflow: auto;
display: table;
width: 100vw;
}
.header { .header {
position: relative; position: relative;
margin: 10px 0 20px; margin: 10px 0 20px;

View File

@ -0,0 +1,167 @@
import React, { memo } from "react"
import { useSelector } from "react-redux"
import styles from "./styles.module.scss"
// import styles from "./styles.module.scss"
import { fetchBoxListIfNeed, selectContainerSortedDetailedBoxes } from "../../store/boxList"
import {
fetchVolunteerDetailedKnowledgeListIfNeed,
selectVolunteerDetailedKnowledgeList,
} from "../../store/volunteerDetailedKnowledgeList"
import { DetailedBox } from "../../services/boxes"
import { VolunteerDetailedKnowledge } from "../../services/volunteers"
const KnowledgeCard: React.FC = (): JSX.Element | null => {
const detailedBoxes = useSelector(selectContainerSortedDetailedBoxes) as DetailedBox[]
const volunteerDetailedKnowledgeList = useSelector(selectVolunteerDetailedKnowledgeList)
return <>{detailedBoxes.map((box) => boxElement(box, volunteerDetailedKnowledgeList))}</>
}
const boxElement = (
box: DetailedBox,
volunteerDetailedKnowledgeList: VolunteerDetailedKnowledge[]
): JSX.Element => {
const playerCount: string =
box.playersMin === box.playersMax
? `${box.playersMin}`
: `${box.playersMin} à ${box.playersMax}`
const typeStyle = {
"": null,
Ambiance: styles.verteImg,
Famille: styles.orangeImg,
Expert: styles.rougeImg,
}[box.type]
const year = new Date().getFullYear()
const okVolunteers = wiseVolunteers(volunteerDetailedKnowledgeList, box.gameId, "ok")
const bofVolunteers = wiseVolunteers(volunteerDetailedKnowledgeList, box.gameId, "bof")
const someOk = okVolunteers.length > 0
const someBof = bofVolunteers.length > 0
const some = someOk || someBof
return (
<div key={box.id} className={styles.card}>
<header className={styles.header}>{box.title}</header>
<div className={styles.showUnknownOnlyLabeldetail}>
<div className={styles.masteryContainer}>
<table>
<tbody>
<tr>
<td className={styles.imageContainer}>
<img
className={styles.gameImage}
src={box.bggPhoto}
alt="Board game box"
/>
</td>
<td className={styles.tableBenevoles}>
<table className={styles.benevolesContainer}>
<tbody>
<tr className={styles.okHeader}>
<td>Ils maîtrisent</td>
</tr>
<tr className={styles.listOk}>
<td>
<div className={styles.nicknameContainer}>
{okVolunteers}
{!some && (
<>
Désolé aucun bénévole n'y a joué, il
va falloir lire la règle.
</>
)}
{!someOk && someBof && (
<>
Aucun bénévole ne maîtrise les
règles de ce jeu.
</>
)}
</div>
</td>
</tr>
{someBof && (
<tr className={styles.bofHeader}>
<td>Ils connaissent</td>
</tr>
)}
{someBof && (
<tr className={styles.listBof}>
<td>
<div className={styles.nicknameContainer}>
{bofVolunteers}
</div>
</td>
</tr>
)}
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<div className={styles.gamedetailContainer}>
<table>
<tbody>
<tr>
<td className={styles.numberOfPlayersImgContainer}>
<div className={styles.numberOfPlayersImg} />
</td>
<td className={styles.numberOfPlayers}>{playerCount}</td>
<td className={styles.durationImgContainer}>
<div className={styles.durationImg} />
</td>
<td className={styles.duration}>{box.duration} min</td>
<td className={styles.typeImgContainer}>
<div className={typeStyle} />
</td>
<td className={styles.type}>{box.type}</td>
<td className={styles.pppImgContainer}>
<div className={styles.pppImg} />
</td>
</tr>
</tbody>
</table>
</div>
</div>
<footer className={styles.footer}>
<div className={styles.year}>{year}</div>
<div className={styles.container}>{box.container}</div>
</footer>
</div>
)
}
function wiseVolunteers(
volunteersKnowledge: VolunteerDetailedKnowledge[],
gameId: number,
wiseness: "ok" | "bof"
): JSX.Element[] {
return volunteersKnowledge
.filter(
(v) =>
v[wiseness].includes(gameId) &&
(v.dayWishes.includes("S") || v.dayWishes.includes("D"))
)
.map((v) => (
<div key={v.id} className={styles.nickname}>
<b>{v.nickname.charAt(0).toUpperCase()}</b>
{v.nickname.substring(1)}
{v.dayWishes.includes("S") && !v.dayWishes.includes("D") && (
<span className={styles.oneDayOnly}>(sam.)</span>
)}
{!v.dayWishes.includes("S") && v.dayWishes.includes("D") && (
<span className={styles.oneDayOnly}>(dim.)</span>
)}
</div>
))
}
export default memo(KnowledgeCard)
export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerDetailedKnowledgeListIfNeed]

View File

@ -122,3 +122,181 @@
background-color: $color-active-niet; background-color: $color-active-niet;
} }
} }
/* Cards */
.card {
display: inline-block;
vertical-align: top;
width: 48vw;
margin: 1vw;
break-inside: avoid;
border: $color-black solid 0.1vw;
}
.header {
color: $color-white;
background-color: #e18502;
text-align: center;
font-size: 2.5vw;
line-height: 5vw;
font-family: $font-pel;
}
.imageContainer {
padding: 1vw;
}
.benevolesContainer {
width: 100%;
}
.tableBenevoles {
width: 100%;
vertical-align: top;
padding-top: 1vw;
}
.gameImage {
width: 10vw;
}
.okHeader,
.bofHeader {
text-align: center;
width: 100%;
font-size: 1.8vw;
font-weight: bold;
}
.okHeader {
background-color: #9dba5d;
}
.listOk {
text-align: center;
}
.listOk td {
padding-top: 15px;
padding-bottom: 15px;
}
.bofHeader {
background-color: #cb9902;
}
.listBof {
text-align: center;
}
.listBof td {
padding-top: 15px;
padding-bottom: 15px;
}
.nicknameContainer {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
align-content: center;
gap: 0.5vw;
}
.nickname {
flex: 1 1 auto;
white-space: nowrap;
}
.numberOfPlayersImgContainer {
width: 6.3vw;
}
.numberOfPlayersImg {
width: 6vw;
height: 4vw;
background: url("../../app/img/knowledgeCards/nombre_joueurs-fiche.png") no-repeat center center;
background-size: cover;
}
.numberOfPlayers {
font-family: $font-pel;
font-size: 1.6vw;
white-space: nowrap;
}
.durationImgContainer {
width: 6.1vw;
padding-left: 1vw;
}
.durationImg {
width: 6vw;
height: 4vw;
background: url("../../app/img/knowledgeCards/duree-fiche.png") no-repeat center center;
background-size: cover;
}
.duration {
font-family: $font-pel;
font-size: 1.6vw;
white-space: nowrap;
}
.typeImgContainer {
width: 6.2vw;
padding-left: 1.1vw;
}
.verteImg,
.orangeImg,
.rougeImg {
width: 6vw;
height: 6vw;
background-size: cover;
}
.verteImg {
background: url("../../app/img/knowledgeCards/jauge-verte.png") no-repeat center center;
}
.orangeImg {
background: url("../../app/img/knowledgeCards/jauge-orange.png") no-repeat center center;
}
.rougeImg {
background: url("../../app/img/knowledgeCards/jauge-rouge.png") no-repeat center center;
}
.type {
font-family: $font-pel;
font-size: 1.6vw;
white-space: nowrap;
}
.pppImgContainer {
width: 6vw;
padding-left: 0.7vw;
}
.pppImg {
width: 6vw;
height: 3.4vw;
background: url("../../app/img/knowledgeCards/ppp-fiche-ko-trespetit.png") no-repeat center
center;
background-size: cover;
}
.oneDayOnly {
color: $color-red;
}
.footer {
height: 3vw;
font-family: $font-pel;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-content: center;
background-color: #96b397;
font-style: oblique;
}
.year {
flex: 0 1 auto;
align-self: center;
margin-left: 1vw;
font-size: 1vw;
}
.container {
flex: 0 1 auto;
align-self: center;
margin-right: 1vw;
font-size: 1.5vw;
}

View File

@ -10,6 +10,7 @@ import GameList from "./GameList"
import Loading from "./Loading" import Loading from "./Loading"
import LoginForm from "./LoginForm" import LoginForm from "./LoginForm"
import BoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/BoxList" import BoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/BoxList"
import KnowledgeCard, { fetchFor as fetchForKnowledgeCard } from "./Knowledge/KnowledgeCard"
import KnowledgeIntro from "./Knowledge/KnowledgeIntro" import KnowledgeIntro from "./Knowledge/KnowledgeIntro"
import Asks, { fetchFor as fetchForAsks } from "./Asks" import Asks, { fetchFor as fetchForAsks } from "./Asks"
import ParticipationDetailsForm, { import ParticipationDetailsForm, {
@ -34,6 +35,8 @@ export {
fetchForBoard, fetchForBoard,
BoxList, BoxList,
fetchForKnowledge, fetchForKnowledge,
KnowledgeCard,
fetchForKnowledgeCard,
DayWishesForm, DayWishesForm,
fetchForDayWishesForm, fetchForDayWishesForm,
ErrorBoundary, ErrorBoundary,

View File

@ -0,0 +1,23 @@
import { FC, memo } from "react"
import { useSelector } from "react-redux"
import { RouteComponentProps } from "react-router-dom"
import { AppThunk } from "../../store"
import { KnowledgeCard, fetchForKnowledgeCard } from "../../components"
import { selectUserJwtToken } from "../../store/auth"
export type Props = RouteComponentProps
const KnowledgeCardsPage: FC<Props> = (): JSX.Element => {
const jwtToken = useSelector(selectUserJwtToken)
if (jwtToken === undefined) return <p>Loading...</p>
if (!jwtToken) {
return <div>Besoin d'être identifié</div>
}
return <KnowledgeCard />
}
// Fetch server-side data here
export const loadData = (): AppThunk[] => [...fetchForKnowledgeCard.map((f) => f())]
export default memo(KnowledgeCardsPage)

View File

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

View File

@ -0,0 +1,9 @@
@import "../../theme/mixins";
.knowledgesPage {
@include page-wrapper-center;
}
.knowledgesContent {
@include page-content-wrapper(700px);
}

View File

@ -10,6 +10,7 @@ import AsyncAnnouncements, { loadData as loadAnnouncementsData } from "../pages/
import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../pages/TeamAssignment" import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../pages/TeamAssignment"
import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register" import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register"
import AsyncKnowledge, { loadData as loadKnowledgeData } from "../pages/Knowledge" import AsyncKnowledge, { loadData as loadKnowledgeData } from "../pages/Knowledge"
import AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards"
import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams" import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams"
import AsyncBoard, { loadData as loadBoardData } from "../pages/Board" import AsyncBoard, { loadData as loadBoardData } from "../pages/Board"
import AsyncVolunteers, { loadData as loadVolunteersData } from "../pages/Volunteers" import AsyncVolunteers, { loadData as loadVolunteersData } from "../pages/Volunteers"
@ -43,6 +44,12 @@ export default [
component: AsyncKnowledge, component: AsyncKnowledge,
loadData: loadKnowledgeData, loadData: loadKnowledgeData,
}, },
{
path: "/fiches",
component: AsyncKnowledgeCards,
loadData: loadCardKnowledgeData,
meh: "doh",
},
{ {
path: "/preRegister", path: "/preRegister",
component: AsyncRegisterPage, component: AsyncRegisterPage,

View File

@ -14,6 +14,7 @@ export const detailedBoxListGet = expressAccessor.get(async (list) => {
} }
return list return list
.filter((box) => box)
.filter((box) => !box.unplayable) .filter((box) => !box.unplayable)
.map((box) => { .map((box) => {
const game = gameList.find((g) => g.id === box.gameId) const game = gameList.find((g) => g.id === box.gameId)
@ -27,6 +28,11 @@ export const detailedBoxListGet = expressAccessor.get(async (list) => {
bggPhoto: game.bggPhoto, bggPhoto: game.bggPhoto,
poufpaf: game.poufpaf, poufpaf: game.poufpaf,
bggId: game.bggId, bggId: game.bggId,
container: box.container,
playersMin: game.playersMin,
playersMax: game.playersMax,
duration: game.duration,
type: game.type,
} as DetailedBox } as DetailedBox
}) })
}) })

View File

@ -13,14 +13,7 @@ export const gameListGet = expressAccessor.listGet()
// export const gameAdd = expressAccessor.add() // export const gameAdd = expressAccessor.add()
// export const gameSet = expressAccessor.set() // export const gameSet = expressAccessor.set()
export const gameDetailsUpdate = expressAccessor.listSet(async (list, _body, _id, roles) => { export const gameDetailsUpdate = expressAccessor.listSet(async (list) => {
if (!roles.includes("admin")) {
throw Error(
`À moins d'être admin, on ne peut pas modifier n'importe quel jeu, ${JSON.stringify(
roles
)}`
)
}
const newList = cloneDeep(list) const newList = cloneDeep(list)
// TODO update game list details from BGG // TODO update game list details from BGG

View File

@ -1,6 +1,6 @@
import path from "path" import path from "path"
import * as fs from "fs" import * as fs from "fs"
import { assign, cloneDeep, max, omit, pick } from "lodash" import { assign, cloneDeep, max, omit, pick, remove } from "lodash"
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
import sgMail from "@sendgrid/mail" import sgMail from "@sendgrid/mail"
@ -18,6 +18,7 @@ import {
VolunteerParticipationDetails, VolunteerParticipationDetails,
VolunteerTeamAssign, VolunteerTeamAssign,
VolunteerKnowledge, VolunteerKnowledge,
VolunteerDetailedKnowledge,
VolunteerPersonalInfo, VolunteerPersonalInfo,
} from "../../services/volunteers" } from "../../services/volunteers"
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization" import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
@ -533,3 +534,34 @@ export const volunteerKnowledgeSet = expressAccessor.set(async (list, body, id)
} as VolunteerKnowledge, } as VolunteerKnowledge,
} }
}) })
export const volunteerDetailedKnowledgeList = expressAccessor.get(async (list) => {
const volunteerList = list.filter((v) => v.team === 2)
return volunteerList.map((volunteer) => {
const nickname = getUniqueNickname(volunteerList, volunteer)
return {
id: volunteer.id,
nickname,
ok: volunteer.ok,
bof: volunteer.bof,
niet: volunteer.niet,
dayWishes: volunteer.dayWishes,
} as VolunteerDetailedKnowledge
})
})
function getUniqueNickname(list: Volunteer[], volunteer: Volunteer): string {
const lastnameList = list
.filter((v) => v.firstname === volunteer.firstname)
.map((v) => v.lastname)
let lastnamePrefix = ""
while (lastnameList.length > 1) {
lastnamePrefix += volunteer.lastname.charAt(lastnamePrefix.length)
// eslint-disable-next-line no-loop-func
remove(lastnameList, (lastname) => !lastname.startsWith(lastnamePrefix))
}
const nickname = `${volunteer.firstname}${lastnamePrefix ? ` ${lastnamePrefix}.` : ""}`
return nickname
}

View File

@ -38,6 +38,7 @@ import {
volunteerListGet, volunteerListGet,
volunteerKnowledgeSet, volunteerKnowledgeSet,
volunteerAddNew, volunteerAddNew,
volunteerDetailedKnowledgeList,
} from "./gsheets/volunteers" } from "./gsheets/volunteers"
import { wishListGet, wishAdd } from "./gsheets/wishes" import { wishListGet, wishAdd } from "./gsheets/wishes"
import config from "../config" import config from "../config"
@ -92,6 +93,7 @@ app.get(
* APIs * APIs
*/ */
// Google Sheets API // Google Sheets API
app.get("/GameDetailsUpdate", gameDetailsUpdate)
app.get("/BoxDetailedListGet", detailedBoxListGet) app.get("/BoxDetailedListGet", detailedBoxListGet)
app.get("/GameListGet", gameListGet) app.get("/GameListGet", gameListGet)
app.get("/MiscMeetingDateListGet", miscMeetingDateListGet) app.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
@ -110,6 +112,11 @@ app.get("/TeamListGet", teamListGet)
app.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId) app.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId)
app.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet) app.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet)
app.post("/VolunteerKnowledgeSet", secure as RequestHandler, volunteerKnowledgeSet) app.post("/VolunteerKnowledgeSet", secure as RequestHandler, volunteerKnowledgeSet)
app.post(
"/VolunteerDetailedKnowledgeListGet",
secure as RequestHandler,
volunteerDetailedKnowledgeList
)
app.post( app.post(
"/VolunteerParticipationDetailsSet", "/VolunteerParticipationDetailsSet",
secure as RequestHandler, secure as RequestHandler,
@ -125,7 +132,6 @@ app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssig
// Admin only // Admin only
app.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew) app.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew)
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet) app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
app.get("/GameDetailsUpdate", secure as RequestHandler, gameDetailsUpdate)
// Push notification subscription // Push notification subscription
app.post("/notifications/subscribe", notificationsSubscribe) app.post("/notifications/subscribe", notificationsSubscribe)

View File

@ -43,6 +43,16 @@ export class DetailedBox {
poufpaf = new Game().poufpaf poufpaf = new Game().poufpaf
bggId = new Game().bggId bggId = new Game().bggId
playersMin = new Game().playersMin
playersMax = new Game().playersMax
duration = new Game().duration
type = new Game().type
container = ""
} }
export type DetailedBoxWithoutId = Omit<DetailedBox, "id"> export type DetailedBoxWithoutId = Omit<DetailedBox, "id">

View File

@ -238,3 +238,13 @@ export interface VolunteerKnowledge {
bof: Volunteer["bof"] bof: Volunteer["bof"]
niet: Volunteer["niet"] niet: Volunteer["niet"]
} }
export type VolunteerDetailedKnowledgeWithoutId = Omit<VolunteerDetailedKnowledge, "id">
export interface VolunteerDetailedKnowledge {
id: Volunteer["id"]
nickname: string
ok: Volunteer["ok"]
bof: Volunteer["bof"]
niet: Volunteer["niet"]
dayWishes: Volunteer["dayWishes"]
}

View File

@ -60,3 +60,7 @@ export const volunteerTeamAssignSet =
export const volunteerKnowledgeSet = export const volunteerKnowledgeSet =
serviceAccessors.securedCustomPost<[number, Partial<VolunteerKnowledge>]>("KnowledgeSet") serviceAccessors.securedCustomPost<[number, Partial<VolunteerKnowledge>]>("KnowledgeSet")
export const volunteerDetailedKnowledgeList = serviceAccessors.securedCustomPost<[number]>(
"DetailedKnowledgeListGet"
)

View File

@ -2,11 +2,11 @@ import { PayloadAction, createSlice, createEntityAdapter, createSelector } from
import { sortedUniqBy, sortBy } from "lodash" import { sortedUniqBy, sortBy } from "lodash"
import { StateRequest, toastError, elementListFetch } from "./utils" import { StateRequest, toastError, elementListFetch } from "./utils"
import { Box } from "../services/boxes" import { DetailedBox } from "../services/boxes"
import { AppThunk, AppState, EntitiesRequest } from "." import { AppThunk, AppState, EntitiesRequest } from "."
import { detailedBoxListGet } from "../services/boxesAccessors" import { detailedBoxListGet } from "../services/boxesAccessors"
const boxAdapter = createEntityAdapter<Box>() const boxAdapter = createEntityAdapter<DetailedBox>()
export const initialState = boxAdapter.getInitialState({ export const initialState = boxAdapter.getInitialState({
readyStatus: "idle", readyStatus: "idle",
@ -19,7 +19,7 @@ const boxList = createSlice({
getRequesting: (state) => { getRequesting: (state) => {
state.readyStatus = "request" state.readyStatus = "request"
}, },
getSuccess: (state, { payload }: PayloadAction<Box[]>) => { getSuccess: (state, { payload }: PayloadAction<DetailedBox[]>) => {
state.readyStatus = "success" state.readyStatus = "success"
boxAdapter.setAll(state, payload) boxAdapter.setAll(state, payload)
}, },
@ -49,7 +49,7 @@ export const fetchBoxListIfNeed = (): AppThunk => (dispatch, getState) => {
return null return null
} }
export const selectBoxListState = (state: AppState): EntitiesRequest<Box> => state.boxList export const selectBoxListState = (state: AppState): EntitiesRequest<DetailedBox> => state.boxList
export const selectBoxList = createSelector( export const selectBoxList = createSelector(
selectBoxListState, selectBoxListState,
@ -60,11 +60,9 @@ export const selectBoxList = createSelector(
) )
export const selectSortedUniqueDetailedBoxes = createSelector(selectBoxList, (boxes) => export const selectSortedUniqueDetailedBoxes = createSelector(selectBoxList, (boxes) =>
sortedUniqBy( sortedUniqBy(sortBy(boxes, "title"), "title")
sortBy( )
boxes.filter((box) => box && !box.unplayable),
"title" export const selectContainerSortedDetailedBoxes = createSelector(selectBoxList, (boxes) =>
), sortBy(boxes, "container")
"title"
)
) )

View File

@ -21,6 +21,7 @@ import volunteerMealsSet from "./volunteerMealsSet"
import volunteerList from "./volunteerList" import volunteerList from "./volunteerList"
import volunteerLogin from "./volunteerLogin" import volunteerLogin from "./volunteerLogin"
import volunteerKnowledgeSet from "./volunteerKnowledgeSet" import volunteerKnowledgeSet from "./volunteerKnowledgeSet"
import volunteerDetailedKnowledgeList from "./volunteerDetailedKnowledgeList"
import volunteerParticipationDetailsSet from "./volunteerParticipationDetailsSet" import volunteerParticipationDetailsSet from "./volunteerParticipationDetailsSet"
import volunteerPersonalInfoSet from "./volunteerPersonalInfoSet" import volunteerPersonalInfoSet from "./volunteerPersonalInfoSet"
import volunteerSet from "./volunteerSet" import volunteerSet from "./volunteerSet"
@ -52,6 +53,7 @@ export default (history: History) => ({
volunteerList, volunteerList,
volunteerLogin, volunteerLogin,
volunteerKnowledgeSet, volunteerKnowledgeSet,
volunteerDetailedKnowledgeList,
volunteerParticipationDetailsSet, volunteerParticipationDetailsSet,
volunteerPersonalInfoSet, volunteerPersonalInfoSet,
volunteerSet, volunteerSet,

View File

@ -0,0 +1,72 @@
import { PayloadAction, createSlice, createSelector, createEntityAdapter } from "@reduxjs/toolkit"
import { StateRequest, toastError, elementListFetch } from "./utils"
import { VolunteerDetailedKnowledge } from "../services/volunteers"
import { AppThunk, AppState, EntitiesRequest } from "."
import { volunteerDetailedKnowledgeList } from "../services/volunteersAccessors"
const knowledgeAdapter = createEntityAdapter<VolunteerDetailedKnowledge>()
export const initialState = knowledgeAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest)
const volunteerDetailedKnowledgeListSlice = createSlice({
name: "volunteerDetailedKnowledgeList",
initialState,
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<VolunteerDetailedKnowledge[]>) => {
state.readyStatus = "success"
knowledgeAdapter.setAll(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default volunteerDetailedKnowledgeListSlice.reducer
export const { getRequesting, getSuccess, getFailure } = volunteerDetailedKnowledgeListSlice.actions
export const fetchVolunteerDetailedKnowledgeList = elementListFetch(
volunteerDetailedKnowledgeList,
getRequesting,
getSuccess,
getFailure,
(error: Error) =>
toastError(
`Erreur lors du chargement de la liste de connaissances détaillée: ${error.message}`
)
)
const shouldFetchVolunteerDetailedKnowledgeList = (state: AppState) =>
state.volunteerDetailedKnowledgeList?.readyStatus !== "success"
export const fetchVolunteerDetailedKnowledgeListIfNeed =
(id = 0): AppThunk =>
(dispatch, getState) => {
let jwt = ""
if (!id) {
;({ jwt, id } = getState().auth)
}
if (shouldFetchVolunteerDetailedKnowledgeList(getState()))
return dispatch(fetchVolunteerDetailedKnowledgeList(jwt, id))
return null
}
export const selectVolunteerDetailedKnowledgeListState = (
state: AppState
): EntitiesRequest<VolunteerDetailedKnowledge> => state.volunteerDetailedKnowledgeList
export const selectVolunteerDetailedKnowledgeList = createSelector(
selectVolunteerDetailedKnowledgeListState,
({ ids, entities, readyStatus }) => {
if (readyStatus !== "success") return []
return ids.map((id) => entities[id]) as VolunteerDetailedKnowledge[]
}
)