From 7c966364774b4d57b61409d8b164d25c7076cfaa Mon Sep 17 00:00:00 2001 From: memeriau Date: Sat, 16 Apr 2022 18:30:47 +0200 Subject: [PATCH] assigners can assign volunteer to a team --- .../TeamAssignment/TeamWithCandidates.tsx | 19 ++++-- .../TeamAssignment/teamAssign.utils.ts | 27 +++++++++ src/server/gsheets/volunteers.ts | 28 ++++++++- src/server/index.ts | 2 + src/services/volunteers.ts | 10 ++++ src/services/volunteersAccessors.ts | 4 ++ src/store/rootReducer.ts | 2 + src/store/volunteerTeamAssignSet.ts | 59 +++++++++++++++++++ 8 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 src/components/TeamAssignment/teamAssign.utils.ts create mode 100644 src/store/volunteerTeamAssignSet.ts diff --git a/src/components/TeamAssignment/TeamWithCandidates.tsx b/src/components/TeamAssignment/TeamWithCandidates.tsx index a916904..1820125 100644 --- a/src/components/TeamAssignment/TeamWithCandidates.tsx +++ b/src/components/TeamAssignment/TeamWithCandidates.tsx @@ -5,6 +5,7 @@ import classnames from "classnames" import { selectVolunteerList } from "../../store/volunteerList" import { selectTeamList } from "../../store/teamList" import styles from "./styles.module.scss" +import { useTeamAssign } from "./teamAssign.utils" const selectTeamsWithVolunteersCandidates = createSelector( selectVolunteerList, @@ -37,7 +38,13 @@ type PropsDaysDisplay = { const DaysDisplay: FC = ({ dayWishes }): JSX.Element => ( - {dayWishes.map((day) => (day === "S" || day === "D" ? {day} : day))} + {dayWishes.map((day) => + day === "S" || day === "D" ? ( + {day} + ) : ( + {day} + ) + )} ) @@ -48,10 +55,14 @@ type Props = { const TeamWithCandidates: FC = ({ teamId }): JSX.Element | null => { const teams = useSelector(selectTeamsWithVolunteersCandidates) const team = teams.find((t) => t.id === teamId) + const [, saveTeam] = useTeamAssign() - const onTeamSelected = useCallback((volunteerId, selectedTeamId) => { - console.log("select ", volunteerId, selectedTeamId) - }, []) + const onTeamSelected = useCallback( + (volunteerId, selectedTeamId) => { + saveTeam(volunteerId, selectedTeamId) + }, + [saveTeam] + ) if (!team) return null diff --git a/src/components/TeamAssignment/teamAssign.utils.ts b/src/components/TeamAssignment/teamAssign.utils.ts new file mode 100644 index 0000000..58023d1 --- /dev/null +++ b/src/components/TeamAssignment/teamAssign.utils.ts @@ -0,0 +1,27 @@ +import { shallowEqual, useSelector } from "react-redux" +import { useCallback } from "react" +import { selectUserJwtToken } from "../../store/auth" +import { AppState } from "../../store" +import useAction from "../../utils/useAction" +import { fetchVolunteerTeamAssignSet } from "../../store/volunteerTeamAssignSet" + +export const useTeamAssign = (): [any, any] => { + const save = useAction(fetchVolunteerTeamAssignSet) + const jwtToken = useSelector(selectUserJwtToken) + const teamSet = useSelector( + (state: AppState) => state.volunteerTeamAssignSet?.entity, + shallowEqual + ) + + const saveWishes = useCallback( + (volunteerId, teamId) => { + save(jwtToken, 0, { + volunteer: volunteerId, + team: teamId, + }) + }, + [save, jwtToken] + ) + + return [teamSet, saveWishes] +} diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts index 1e0b96b..e384afb 100644 --- a/src/server/gsheets/volunteers.ts +++ b/src/server/gsheets/volunteers.ts @@ -12,6 +12,7 @@ import { translationVolunteer, VolunteerDayWishes, VolunteerParticipationDetails, + VolunteerTeamAssign, } from "../../services/volunteers" import { canonicalEmail } from "../../utils/standardization" import { getJwt } from "../secure" @@ -246,8 +247,31 @@ export const volunteerParticipationDetailsSet = expressAccessor.set(async (list, } }) +export const volunteerTeamAssignSet = expressAccessor.set(async (list, body, id) => { + const requestedId = +body[0] || id + const assigner = list.find((v) => v.id === requestedId) + if (!assigner || !assigner.roles.includes("répartiteur")) { + throw Error(`Vous n'avez pas les droits pas assigner les équipes.`) + } + + const teamAssign = body[1] as VolunteerTeamAssign + const volunteer = list.find((v) => v.id === teamAssign.volunteer) + if (!volunteer) { + throw Error(`Il n'y a aucun bénévole avec cet identifiant ${teamAssign.volunteer}`) + } + const newVolunteer = _.cloneDeep(volunteer) + newVolunteer.team = teamAssign.team + + return { + toDatabase: newVolunteer, + toCaller: { + id: newVolunteer.id, + team: newVolunteer.team, + } as VolunteerTeamAssign, + } +}) + function getByEmail(list: T[], rawEmail: string): T | undefined { const email = canonicalEmail(rawEmail || "") - const volunteer = list.find((v) => canonicalEmail(v.email) === email) - return volunteer + return list.find((v) => canonicalEmail(v.email) === email) } diff --git a/src/server/index.ts b/src/server/index.ts index 7369ffb..a5d7734 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -29,6 +29,7 @@ import { volunteerParticipationDetailsSet, volunteerTeamWishesSet, volunteerDayWishesSet, + volunteerTeamAssignSet, volunteerListGet, } from "./gsheets/volunteers" import { wishListGet, wishAdd } from "./gsheets/wishes" @@ -102,6 +103,7 @@ app.post( ) app.post("/VolunteerDayWishesSet", secure as RequestHandler, volunteerDayWishesSet) app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet) +app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet) // Push notification subscription app.post("/notifications/subscribe", notificationsSubscribe) diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts index 372c704..d9895ff 100644 --- a/src/services/volunteers.ts +++ b/src/services/volunteers.ts @@ -29,6 +29,8 @@ export class Volunteer { food = "" + team = 0 + teamWishes: number[] = [] teamWishesComment = "" @@ -62,6 +64,7 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = { tshirtCount: "nbDeTshirts", tshirtSize: "tailleDeTshirts", food: "alimentation", + team: "équipe", teamWishes: "enviesEquipe", teamWishesComment: "commentaireEnviesEquipe", hiddenAsks: "questionsCachees", @@ -90,6 +93,7 @@ export const volunteerExample: Volunteer = { tshirtCount: "1", tshirtSize: "Femme M", food: "Végétarien", + team: 2, teamWishes: [], teamWishesComment: "", hiddenAsks: [], @@ -146,3 +150,9 @@ export interface VolunteerParticipationDetails { adult: Volunteer["adult"] food: Volunteer["food"] } + +export interface VolunteerTeamAssign { + id: Volunteer["id"] + volunteer: number + team: Volunteer["team"] +} diff --git a/src/services/volunteersAccessors.ts b/src/services/volunteersAccessors.ts index e7ec52d..32d2679 100644 --- a/src/services/volunteersAccessors.ts +++ b/src/services/volunteersAccessors.ts @@ -6,6 +6,7 @@ import { VolunteerAsks, VolunteerParticipationDetails, VolunteerTeamWishes, + VolunteerTeamAssign, VolunteerWithoutId, } from "./volunteers" @@ -34,3 +35,6 @@ export const volunteerParticipationDetailsSet = serviceAccessors.securedCustomPost<[number, Partial]>( "ParticipationDetailsSet" ) + +export const volunteerTeamAssignSet = + serviceAccessors.securedCustomPost<[number, Partial]>("TeamAssignSet") diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 2b6cbf7..f5880df 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -18,6 +18,7 @@ import volunteerAsksSet from "./volunteerAsksSet" import volunteerParticipationDetailsSet from "./volunteerParticipationDetailsSet" import volunteerDayWishesSet from "./volunteerDayWishesSet" import volunteerTeamWishesSet from "./volunteerTeamWishesSet" +import volunteerTeamAssignSet from "./volunteerTeamAssignSet" import wishAdd from "./wishAdd" import wishList from "./wishList" @@ -41,6 +42,7 @@ export default (history: History) => ({ volunteerParticipationDetailsSet, volunteerDayWishesSet, volunteerTeamWishesSet, + volunteerTeamAssignSet, wishAdd, wishList, router: connectRouter(history) as any, diff --git a/src/store/volunteerTeamAssignSet.ts b/src/store/volunteerTeamAssignSet.ts new file mode 100644 index 0000000..dbd9884 --- /dev/null +++ b/src/store/volunteerTeamAssignSet.ts @@ -0,0 +1,59 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit" + +import { StateRequest, toastError, elementFetch } from "./utils" +import { VolunteerTeamAssign } from "../services/volunteers" +import { AppThunk, AppState } from "." +import { volunteerTeamAssignSet } from "../services/volunteersAccessors" + +type StateVolunteerTeamAssignSet = { entity?: VolunteerTeamAssign } & StateRequest + +export const initialState: StateVolunteerTeamAssignSet = { + readyStatus: "idle", +} + +const volunteerTeamAssignSetSlice = createSlice({ + name: "volunteerTeamAssignSet", + initialState, + reducers: { + getRequesting: (_) => ({ + readyStatus: "request", + }), + getSuccess: (_, { payload }: PayloadAction) => ({ + readyStatus: "success", + entity: payload, + }), + getFailure: (_, { payload }: PayloadAction) => ({ + readyStatus: "failure", + error: payload, + }), + }, +}) + +export default volunteerTeamAssignSetSlice.reducer +export const { getRequesting, getSuccess, getFailure } = volunteerTeamAssignSetSlice.actions + +export const fetchVolunteerTeamAssignSet = elementFetch( + volunteerTeamAssignSet, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors du chargement des notifications: ${error.message}`) +) + +const shouldFetchVolunteerTeamAssignSet = (state: AppState, id: number) => + state.volunteerTeamAssignSet?.readyStatus !== "success" || + (state.volunteerTeamAssignSet?.entity && state.volunteerTeamAssignSet?.entity?.id !== id) + +export const fetchVolunteerTeamAssignSetIfNeed = + (id = 0, wishes: Partial = {}): AppThunk => + (dispatch, getState) => { + let jwt = "" + + if (!id) { + ;({ jwt, id } = getState().auth) + } + if (shouldFetchVolunteerTeamAssignSet(getState(), id)) + return dispatch(fetchVolunteerTeamAssignSet(jwt, id, wishes)) + + return null + }