diff --git a/src/app/img/knowledgeCards/duree-fiche.png b/src/app/img/knowledgeCards/duree-fiche.png new file mode 100644 index 0000000..aa3e390 Binary files /dev/null and b/src/app/img/knowledgeCards/duree-fiche.png differ diff --git a/src/app/img/knowledgeCards/jauge-orange.png b/src/app/img/knowledgeCards/jauge-orange.png new file mode 100644 index 0000000..be47fb7 Binary files /dev/null and b/src/app/img/knowledgeCards/jauge-orange.png differ diff --git a/src/app/img/knowledgeCards/jauge-rouge.png b/src/app/img/knowledgeCards/jauge-rouge.png new file mode 100644 index 0000000..5874265 Binary files /dev/null and b/src/app/img/knowledgeCards/jauge-rouge.png differ diff --git a/src/app/img/knowledgeCards/jauge-verte.png b/src/app/img/knowledgeCards/jauge-verte.png new file mode 100644 index 0000000..59d63ee Binary files /dev/null and b/src/app/img/knowledgeCards/jauge-verte.png differ diff --git a/src/app/img/knowledgeCards/nombre_joueurs-fiche.png b/src/app/img/knowledgeCards/nombre_joueurs-fiche.png new file mode 100644 index 0000000..73ac3fe Binary files /dev/null and b/src/app/img/knowledgeCards/nombre_joueurs-fiche.png differ diff --git a/src/app/img/knowledgeCards/ppp-fiche-ko-trespetit.png b/src/app/img/knowledgeCards/ppp-fiche-ko-trespetit.png new file mode 100644 index 0000000..8d8c4e1 Binary files /dev/null and b/src/app/img/knowledgeCards/ppp-fiche-ko-trespetit.png differ diff --git a/src/app/img/knowledgeCards/ppp-fiche-trespetit.png b/src/app/img/knowledgeCards/ppp-fiche-trespetit.png new file mode 100644 index 0000000..cec9a71 Binary files /dev/null and b/src/app/img/knowledgeCards/ppp-fiche-trespetit.png differ diff --git a/src/app/index.tsx b/src/app/index.tsx index bc6a5b9..4520708 100755 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -12,37 +12,46 @@ import LogoutButton from "../components/LogoutButton/LogoutButton" interface Route { route: { routes: RouteConfig[] } + location: Location } export const reactAppId = "react-view" -const App = ({ route }: Route): JSX.Element => ( -
- - - -
-
-
-

- {config.APP.title} -

-
{config.APP.description}
-
-
- -
-
- -
-
- {/* Child routes won't render without this */} - {renderRoutes(route.routes)} - -
-) +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +const App = ({ route, location }: Route): JSX.Element => { + if (location.pathname === "/fiches") { + return
{renderRoutes(route.routes)}
+ } + // else + + return ( +
+ + + +
+
+
+

+ {config.APP.title} +

+
{config.APP.description}
+
+
+ +
+
+ +
+
+ {/* Child routes won't render without this */} + {renderRoutes(route.routes)} + +
+ ) +} export default App diff --git a/src/app/styles.module.scss b/src/app/styles.module.scss index 494f4ed..cb36169 100755 --- a/src/app/styles.module.scss +++ b/src/app/styles.module.scss @@ -2,6 +2,13 @@ @import "../theme/mixins"; @import "../theme/main"; +.cardPage { + background-color: $color-white; + // overflow: auto; + display: table; + width: 100vw; +} + .header { position: relative; margin: 10px 0 20px; diff --git a/src/components/Knowledge/KnowledgeCard.tsx b/src/components/Knowledge/KnowledgeCard.tsx new file mode 100644 index 0000000..c0976ce --- /dev/null +++ b/src/components/Knowledge/KnowledgeCard.tsx @@ -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 ( +
+
{box.title}
+
+
+ + + + + + + +
+ Board game box + + + + + + + + + + {someBof && ( + + + + )} + {someBof && ( + + + + )} + +
Ils maîtrisent
+
+ {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. + + )} +
+
Ils connaissent
+
+ {bofVolunteers} +
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+
{playerCount} +
+
{box.duration} min +
+
{box.type} +
+
+
+
+ +
+ ) +} + +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) => ( +
+ {v.nickname.charAt(0).toUpperCase()} + {v.nickname.substring(1)} + {v.dayWishes.includes("S") && !v.dayWishes.includes("D") && ( + (sam.) + )} + {!v.dayWishes.includes("S") && v.dayWishes.includes("D") && ( + (dim.) + )} +
+ )) +} + +export default memo(KnowledgeCard) + +export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerDetailedKnowledgeListIfNeed] diff --git a/src/components/Knowledge/styles.module.scss b/src/components/Knowledge/styles.module.scss index 6ed755c..487b1d0 100755 --- a/src/components/Knowledge/styles.module.scss +++ b/src/components/Knowledge/styles.module.scss @@ -122,3 +122,181 @@ 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; +} diff --git a/src/components/index.ts b/src/components/index.ts index 7532b2c..b9c92f4 100755 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -10,6 +10,7 @@ import GameList from "./GameList" import Loading from "./Loading" import LoginForm from "./LoginForm" import BoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/BoxList" +import KnowledgeCard, { fetchFor as fetchForKnowledgeCard } from "./Knowledge/KnowledgeCard" import KnowledgeIntro from "./Knowledge/KnowledgeIntro" import Asks, { fetchFor as fetchForAsks } from "./Asks" import ParticipationDetailsForm, { @@ -34,6 +35,8 @@ export { fetchForBoard, BoxList, fetchForKnowledge, + KnowledgeCard, + fetchForKnowledgeCard, DayWishesForm, fetchForDayWishesForm, ErrorBoundary, diff --git a/src/pages/KnowledgeCards/KnowledgeCardsPage.tsx b/src/pages/KnowledgeCards/KnowledgeCardsPage.tsx new file mode 100644 index 0000000..1dc4507 --- /dev/null +++ b/src/pages/KnowledgeCards/KnowledgeCardsPage.tsx @@ -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 = (): JSX.Element => { + const jwtToken = useSelector(selectUserJwtToken) + if (jwtToken === undefined) return

Loading...

+ if (!jwtToken) { + return
Besoin d'être identifié
+ } + return +} + +// Fetch server-side data here +export const loadData = (): AppThunk[] => [...fetchForKnowledgeCard.map((f) => f())] + +export default memo(KnowledgeCardsPage) diff --git a/src/pages/KnowledgeCards/index.tsx b/src/pages/KnowledgeCards/index.tsx new file mode 100755 index 0000000..5cfac27 --- /dev/null +++ b/src/pages/KnowledgeCards/index.tsx @@ -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: , +}) + +export default (props: Props): JSX.Element => ( + + + +) + +export { loadData } diff --git a/src/pages/KnowledgeCards/styles.module.scss b/src/pages/KnowledgeCards/styles.module.scss new file mode 100755 index 0000000..4c6d8ae --- /dev/null +++ b/src/pages/KnowledgeCards/styles.module.scss @@ -0,0 +1,9 @@ +@import "../../theme/mixins"; + +.knowledgesPage { + @include page-wrapper-center; +} + +.knowledgesContent { + @include page-content-wrapper(700px); +} diff --git a/src/routes/index.ts b/src/routes/index.ts index 99a253b..af39210 100755 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -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 AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards" import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams" import AsyncBoard, { loadData as loadBoardData } from "../pages/Board" import AsyncVolunteers, { loadData as loadVolunteersData } from "../pages/Volunteers" @@ -43,6 +44,12 @@ export default [ component: AsyncKnowledge, loadData: loadKnowledgeData, }, + { + path: "/fiches", + component: AsyncKnowledgeCards, + loadData: loadCardKnowledgeData, + meh: "doh", + }, { path: "/preRegister", component: AsyncRegisterPage, diff --git a/src/server/gsheets/boxes.ts b/src/server/gsheets/boxes.ts index 2487df0..36df262 100644 --- a/src/server/gsheets/boxes.ts +++ b/src/server/gsheets/boxes.ts @@ -14,6 +14,7 @@ export const detailedBoxListGet = expressAccessor.get(async (list) => { } return list + .filter((box) => box) .filter((box) => !box.unplayable) .map((box) => { const game = gameList.find((g) => g.id === box.gameId) @@ -27,6 +28,11 @@ export const detailedBoxListGet = expressAccessor.get(async (list) => { bggPhoto: game.bggPhoto, poufpaf: game.poufpaf, bggId: game.bggId, + container: box.container, + playersMin: game.playersMin, + playersMax: game.playersMax, + duration: game.duration, + type: game.type, } as DetailedBox }) }) diff --git a/src/server/gsheets/games.ts b/src/server/gsheets/games.ts index d8a7172..5171786 100644 --- a/src/server/gsheets/games.ts +++ b/src/server/gsheets/games.ts @@ -13,14 +13,7 @@ export const gameListGet = expressAccessor.listGet() // export const gameAdd = expressAccessor.add() // export const gameSet = expressAccessor.set() -export const gameDetailsUpdate = expressAccessor.listSet(async (list, _body, _id, roles) => { - if (!roles.includes("admin")) { - throw Error( - `À moins d'être admin, on ne peut pas modifier n'importe quel jeu, ${JSON.stringify( - roles - )}` - ) - } +export const gameDetailsUpdate = expressAccessor.listSet(async (list) => { const newList = cloneDeep(list) // TODO update game list details from BGG diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts index 3eceb07..0287369 100644 --- a/src/server/gsheets/volunteers.ts +++ b/src/server/gsheets/volunteers.ts @@ -1,6 +1,6 @@ import path from "path" 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 sgMail from "@sendgrid/mail" @@ -18,6 +18,7 @@ import { VolunteerParticipationDetails, VolunteerTeamAssign, VolunteerKnowledge, + VolunteerDetailedKnowledge, VolunteerPersonalInfo, } from "../../services/volunteers" import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization" @@ -533,3 +534,34 @@ export const volunteerKnowledgeSet = expressAccessor.set(async (list, body, id) } 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 +} diff --git a/src/server/index.ts b/src/server/index.ts index ba42158..49fe7be 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -38,6 +38,7 @@ import { volunteerListGet, volunteerKnowledgeSet, volunteerAddNew, + volunteerDetailedKnowledgeList, } from "./gsheets/volunteers" import { wishListGet, wishAdd } from "./gsheets/wishes" import config from "../config" @@ -92,6 +93,7 @@ app.get( * APIs */ // Google Sheets API +app.get("/GameDetailsUpdate", gameDetailsUpdate) app.get("/BoxDetailedListGet", detailedBoxListGet) app.get("/GameListGet", gameListGet) app.get("/MiscMeetingDateListGet", miscMeetingDateListGet) @@ -110,6 +112,11 @@ app.get("/TeamListGet", teamListGet) app.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId) app.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet) app.post("/VolunteerKnowledgeSet", secure as RequestHandler, volunteerKnowledgeSet) +app.post( + "/VolunteerDetailedKnowledgeListGet", + secure as RequestHandler, + volunteerDetailedKnowledgeList +) app.post( "/VolunteerParticipationDetailsSet", secure as RequestHandler, @@ -125,7 +132,6 @@ app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssig // Admin only app.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew) app.post("/VolunteerSet", secure as RequestHandler, volunteerSet) -app.get("/GameDetailsUpdate", secure as RequestHandler, gameDetailsUpdate) // Push notification subscription app.post("/notifications/subscribe", notificationsSubscribe) diff --git a/src/services/boxes.ts b/src/services/boxes.ts index 7dad63c..5bc42ad 100644 --- a/src/services/boxes.ts +++ b/src/services/boxes.ts @@ -43,6 +43,16 @@ export class DetailedBox { poufpaf = new Game().poufpaf 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 diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts index 0154ea4..ad91ae4 100644 --- a/src/services/volunteers.ts +++ b/src/services/volunteers.ts @@ -238,3 +238,13 @@ export interface VolunteerKnowledge { bof: Volunteer["bof"] niet: Volunteer["niet"] } + +export type VolunteerDetailedKnowledgeWithoutId = Omit +export interface VolunteerDetailedKnowledge { + id: Volunteer["id"] + nickname: string + ok: Volunteer["ok"] + bof: Volunteer["bof"] + niet: Volunteer["niet"] + dayWishes: Volunteer["dayWishes"] +} diff --git a/src/services/volunteersAccessors.ts b/src/services/volunteersAccessors.ts index f1904be..2a0d869 100644 --- a/src/services/volunteersAccessors.ts +++ b/src/services/volunteersAccessors.ts @@ -60,3 +60,7 @@ export const volunteerTeamAssignSet = export const volunteerKnowledgeSet = serviceAccessors.securedCustomPost<[number, Partial]>("KnowledgeSet") + +export const volunteerDetailedKnowledgeList = serviceAccessors.securedCustomPost<[number]>( + "DetailedKnowledgeListGet" +) diff --git a/src/store/boxList.ts b/src/store/boxList.ts index cad79bd..8b84459 100644 --- a/src/store/boxList.ts +++ b/src/store/boxList.ts @@ -2,11 +2,11 @@ import { PayloadAction, createSlice, createEntityAdapter, createSelector } from import { sortedUniqBy, sortBy } from "lodash" import { StateRequest, toastError, elementListFetch } from "./utils" -import { Box } from "../services/boxes" +import { DetailedBox } from "../services/boxes" import { AppThunk, AppState, EntitiesRequest } from "." import { detailedBoxListGet } from "../services/boxesAccessors" -const boxAdapter = createEntityAdapter() +const boxAdapter = createEntityAdapter() export const initialState = boxAdapter.getInitialState({ readyStatus: "idle", @@ -19,7 +19,7 @@ const boxList = createSlice({ getRequesting: (state) => { state.readyStatus = "request" }, - getSuccess: (state, { payload }: PayloadAction) => { + getSuccess: (state, { payload }: PayloadAction) => { state.readyStatus = "success" boxAdapter.setAll(state, payload) }, @@ -49,7 +49,7 @@ export const fetchBoxListIfNeed = (): AppThunk => (dispatch, getState) => { return null } -export const selectBoxListState = (state: AppState): EntitiesRequest => state.boxList +export const selectBoxListState = (state: AppState): EntitiesRequest => state.boxList export const selectBoxList = createSelector( selectBoxListState, @@ -60,11 +60,9 @@ export const selectBoxList = createSelector( ) export const selectSortedUniqueDetailedBoxes = createSelector(selectBoxList, (boxes) => - sortedUniqBy( - sortBy( - boxes.filter((box) => box && !box.unplayable), - "title" - ), - "title" - ) + sortedUniqBy(sortBy(boxes, "title"), "title") +) + +export const selectContainerSortedDetailedBoxes = createSelector(selectBoxList, (boxes) => + sortBy(boxes, "container") ) diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 79f0fe4..485c284 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -21,6 +21,7 @@ import volunteerMealsSet from "./volunteerMealsSet" import volunteerList from "./volunteerList" import volunteerLogin from "./volunteerLogin" import volunteerKnowledgeSet from "./volunteerKnowledgeSet" +import volunteerDetailedKnowledgeList from "./volunteerDetailedKnowledgeList" import volunteerParticipationDetailsSet from "./volunteerParticipationDetailsSet" import volunteerPersonalInfoSet from "./volunteerPersonalInfoSet" import volunteerSet from "./volunteerSet" @@ -52,6 +53,7 @@ export default (history: History) => ({ volunteerList, volunteerLogin, volunteerKnowledgeSet, + volunteerDetailedKnowledgeList, volunteerParticipationDetailsSet, volunteerPersonalInfoSet, volunteerSet, diff --git a/src/store/volunteerDetailedKnowledgeList.ts b/src/store/volunteerDetailedKnowledgeList.ts new file mode 100644 index 0000000..36db999 --- /dev/null +++ b/src/store/volunteerDetailedKnowledgeList.ts @@ -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() + +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) => { + state.readyStatus = "success" + knowledgeAdapter.setAll(state, payload) + }, + getFailure: (state, { payload }: PayloadAction) => { + 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 => state.volunteerDetailedKnowledgeList + +export const selectVolunteerDetailedKnowledgeList = createSelector( + selectVolunteerDetailedKnowledgeListState, + ({ ids, entities, readyStatus }) => { + if (readyStatus !== "success") return [] + return ids.map((id) => entities[id]) as VolunteerDetailedKnowledge[] + } +)