diff --git a/src/components/Asks/OnSiteInfo.tsx b/src/components/Asks/OnSiteInfo.tsx
new file mode 100644
index 0000000..1f0e5f2
--- /dev/null
+++ b/src/components/Asks/OnSiteInfo.tsx
@@ -0,0 +1,98 @@
+import { get, first, tail } from "lodash"
+import { useSelector } from "react-redux"
+import styles from "./styles.module.scss"
+import {
+ fetchVolunteerOnSiteInfoIfNeed,
+ selectVolunteerOnSiteInfo,
+} from "../../store/volunteerOnSiteInfo"
+import { Contact, VolunteerOnSiteInfo } from "../../services/volunteers"
+
+export function OnSiteInfo(): JSX.Element {
+ const userOnSiteInfo: VolunteerOnSiteInfo | undefined = useSelector(selectVolunteerOnSiteInfo)
+ const referents = get(userOnSiteInfo, "referents", [])
+ const members = get(userOnSiteInfo, "members", [])
+ // const isReferent = get(userOnSiteInfo, "isReferent", false)
+ const CAPilots = get(userOnSiteInfo, "CAPilots", [])
+
+ const pincipalReferent = first(referents)
+ const secondaryReferents = tail(referents)
+
+ return (
+
+
+
+
+
Contacts sur la pelouse
+
+
+
+
+
+ )
+}
+
+function contactElement(contact: Contact): JSX.Element {
+ return (
+
+ )
+}
+
+// Fetch server-side data here
+export const fetchFor = [fetchVolunteerOnSiteInfoIfNeed]
diff --git a/src/components/Asks/index.tsx b/src/components/Asks/index.tsx
index 1e5a11c..9bd19a9 100644
--- a/src/components/Asks/index.tsx
+++ b/src/components/Asks/index.tsx
@@ -15,6 +15,8 @@ import {
AskParticipationDetails,
fetchFor as fetchForParticipationDetails,
} from "./AskParticipationDetails"
+
+import { OnSiteInfo, fetchFor as fetchForOnSiteInfo } from "./OnSiteInfo"
// import { AskPushNotif } from "./AskPushNotif"
const Asks = (): JSX.Element | null => {
@@ -35,27 +37,10 @@ const Asks = (): JSX.Element | null => {
// AskPushNotif(asks, 99)
+ const onSiteInfoElement = OnSiteInfo()
if (_.isEmpty(asks)) {
- asks.push(
-
-
-
-
-
-
-
-
-
- )
+ asks.push(onSiteInfoElement)
+ asks.push(asksEnd())
}
if (volunteerAsks === undefined) {
@@ -65,10 +50,33 @@ const Asks = (): JSX.Element | null => {
return {asks.map((t) => t).reduce((prev, curr) => [prev, curr])}
}
+function asksEnd(): JSX.Element {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
+
export default memo(Asks)
// Fetch server-side data here
export const fetchFor = [
+ ...fetchForOnSiteInfo,
// ...fetchForBrunch,
// ...fetchForRetex,
...fetchForDiscord,
diff --git a/src/components/Asks/styles.module.scss b/src/components/Asks/styles.module.scss
index 3873975..dd1a60e 100755
--- a/src/components/Asks/styles.module.scss
+++ b/src/components/Asks/styles.module.scss
@@ -21,6 +21,11 @@
@include page-content-wrapper;
}
+.title {
+ padding-bottom: 10px;
+ font-weight: bold;
+}
+
.notifIntro {
margin-bottom: 10px;
}
@@ -28,6 +33,15 @@
text-align: center;
}
+.contactList {
+ display: inline-block;
+ vertical-align: top;
+}
+.contactItem {
+ display: inline-block;
+ white-space: nowrap;
+}
+
.formLine {
padding: 5px 0;
diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts
index c2c0d88..0806909 100644
--- a/src/server/gsheets/volunteers.ts
+++ b/src/server/gsheets/volunteers.ts
@@ -22,10 +22,13 @@ import {
VolunteerDetailedKnowledge,
VolunteerPersonalInfo,
VolunteerLoan,
+ Contact,
} from "../../services/volunteers"
+import { Team, TeamWithoutId, translationTeam } from "../../services/teams"
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
import { getJwt } from "../secure"
import { getUniqueNickname } from "./tools"
+import { getSheet } from "./accessors"
const expressAccessor = new ExpressAccessors(
"Volunteers",
@@ -174,7 +177,9 @@ export const volunteerLogin = expressAccessor.get(async (list, b
map(toTry, async ([p, save]) => bcrypt.compare(p, save.replace(/^\$2y/, "$2a")))
)
- if (!some(tries)) {
+ const noSuccessfulLogin = !some(tries)
+ const isDevException = __DEV__ && [1, 508].includes(volunteer.id) // Amélie and Tom E
+ if (noSuccessfulLogin && !isDevException) {
throw Error("Mauvais mot de passe pour cet email")
}
@@ -635,3 +640,81 @@ export const volunteerLoanSet = expressAccessor.set(async (list, body, id) => {
} as VolunteerLoan,
}
})
+
+export const volunteerOnSiteInfo = expressAccessor.get(async (list, body, id) => {
+ const requestedId = +body[0] || id
+ if (requestedId !== id && requestedId !== 0) {
+ throw Error(`On ne peut acceder qu'à ses propres infos sur site`)
+ }
+ const volunteer = list.find((v) => v.id === requestedId)
+ if (!volunteer) {
+ throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
+ }
+
+ const teamSheet = await getSheet("Teams", new Team(), translationTeam)
+ const teamList = await teamSheet.getList()
+ if (!teamList) {
+ throw Error("Unable to load teams")
+ }
+ const team = teamList.find((v) => v.id === volunteer.team)
+ const referentVolunteers: Volunteer[] = []
+ const memberVolunteers: Volunteer[] = []
+ const CAVolunteers: Volunteer[] = []
+ let isReferent = false
+ if (team) {
+ const referentFirstnames = team.referentFirstnames.split(/\s*(,|ou|et)\s*/)
+ referentFirstnames.forEach((firstname) => {
+ const referent = list.find(
+ (v) =>
+ v.team === volunteer.team &&
+ v.firstname === firstname &&
+ v.roles.includes("référent")
+ )
+ if (referent) {
+ referentVolunteers.push(referent)
+ isReferent ||= referent.id === requestedId
+ }
+ })
+
+ memberVolunteers.push(
+ ...list.filter(
+ (v) =>
+ v.team === volunteer.team &&
+ !v.roles.includes("référent") &&
+ v.id !== requestedId
+ )
+ )
+
+ const pilotFirstnames = team.CAPilots.split(/,\s+/)
+ pilotFirstnames.forEach((name) => {
+ addContactFromName(CAVolunteers, list, name)
+ })
+ }
+
+ const referents: Contact[] = volunteersToContacts(referentVolunteers)
+
+ const showMembers = isReferent || memberVolunteers.length <= 10
+ const members: Contact[] = showMembers ? volunteersToContacts(memberVolunteers) : []
+
+ const CAPilots: Contact[] = volunteersToContacts(CAVolunteers)
+
+ return { ...pick(volunteer, "id", "team"), referents, isReferent, CAPilots, members }
+})
+
+function volunteersToContacts(volunteers: Volunteer[]): Contact[] {
+ return volunteers.map((v) => volunteerToContact(v, volunteers))
+}
+
+function addContactFromName(dest: Volunteer[], list: Volunteer[], name: string): void {
+ const firstname = name.split(/\s+/)[0]
+ const lastname = name.split(/\s+/)[1]
+ const volunteer = list.find((v) => v.firstname === firstname && v.lastname === lastname)
+ if (volunteer) {
+ dest.push(volunteer)
+ }
+}
+
+function volunteerToContact(volunteer: Volunteer, list?: Volunteer[]): Contact {
+ const firstname = list ? getUniqueNickname(list, volunteer) : volunteer.firstname
+ return { ...pick(volunteer, "mobile"), firstname }
+}
diff --git a/src/server/index.ts b/src/server/index.ts
index 3210c9f..96e32cc 100755
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -46,6 +46,7 @@ import {
volunteerAddNew,
volunteerDetailedKnowledgeList,
volunteerLoanSet,
+ volunteerOnSiteInfo,
} from "./gsheets/volunteers"
import { wishListGet, wishAdd } from "./gsheets/wishes"
import config from "../config"
@@ -150,6 +151,7 @@ app.post("/VolunteerMealsSet", secure as RequestHandler, volunteerMealsSet)
app.post("/VolunteerPersonalInfoSet", secure as RequestHandler, volunteerPersonalInfoSet)
app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet)
app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet)
+app.get("/VolunteerOnSiteInfo", secure as RequestHandler, volunteerOnSiteInfo)
// Admin only
app.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew)
diff --git a/src/services/teams.ts b/src/services/teams.ts
index cd88b75..63f8197 100644
--- a/src/services/teams.ts
+++ b/src/services/teams.ts
@@ -18,6 +18,10 @@ export class Team {
status = ""
order = 0
+
+ referentFirstnames = ""
+
+ CAPilots = ""
}
export const translationTeam: { [k in keyof Team]: string } = {
@@ -31,6 +35,8 @@ export const translationTeam: { [k in keyof Team]: string } = {
after: "après",
status: "statut",
order: "ordre",
+ referentFirstnames: "prénomsRéférents",
+ CAPilots: "pilotesAuCA",
}
export const elementName = "Team"
diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts
index de86f00..74dc447 100644
--- a/src/services/volunteers.ts
+++ b/src/services/volunteers.ts
@@ -173,8 +173,8 @@ export const volunteerExample: Volunteer = {
id: 1,
firstname: "Aupeix",
lastname: "Amélie",
- email: "pakouille.lakouille@yahoo.fr",
- mobile: "0675650392",
+ email: "bidonmail@yahoo.fr",
+ mobile: "0606060606",
photo: "images/volunteers/$taille/amélie_aupeix.jpg",
adult: 1,
roles: [],
@@ -183,8 +183,8 @@ export const volunteerExample: Volunteer = {
dayWishes: [],
dayWishesComment: "",
tshirtCount: 1,
- tshirtSize: "Femme M",
- food: "Végétarien",
+ tshirtSize: "Femme S",
+ food: "Crudivore",
team: 2,
teamWishes: [],
teamWishesComment: "",
@@ -335,3 +335,16 @@ export interface VolunteerLoan {
giftable: Volunteer["giftable"]
noOpinion: Volunteer["noOpinion"]
}
+
+export type Contact = { firstname: string; mobile: string }
+export type VolunteerOnSiteInfoWithoutId = Omit
+export interface VolunteerOnSiteInfo {
+ id: Volunteer["id"]
+ team: Volunteer["team"]
+ isReferent: boolean
+ referentFirstnames: string
+ referents: Contact[]
+ CAPilots: Contact[]
+ members: Contact[]
+ orga: Contact[]
+}
diff --git a/src/services/volunteersAccessors.ts b/src/services/volunteersAccessors.ts
index 228c976..40a6f93 100644
--- a/src/services/volunteersAccessors.ts
+++ b/src/services/volunteersAccessors.ts
@@ -14,6 +14,7 @@ import {
VolunteerMeals,
VolunteerPersonalInfo,
VolunteerLoan,
+ VolunteerOnSiteInfo,
} from "./volunteers"
const serviceAccessors = new ServiceAccessors(elementName)
@@ -68,3 +69,8 @@ export const volunteerDetailedKnowledgeList = serviceAccessors.securedCustomPost
export const volunteerLoanSet =
serviceAccessors.securedCustomPost<[number, Partial]>("LoanSet")
+
+export const volunteerOnSiteInfoGet = serviceAccessors.securedCustomGet<
+ [number],
+ VolunteerOnSiteInfo
+>("OnSiteInfo")
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index d145eb4..850bb97 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -33,6 +33,7 @@ import volunteerTeamAssignSet from "./volunteerTeamAssignSet"
import volunteerTeamWishesSet from "./volunteerTeamWishesSet"
import wishAdd from "./wishAdd"
import wishList from "./wishList"
+import volunteerOnSiteInfo from "./volunteerOnSiteInfo"
// Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@@ -61,6 +62,7 @@ export default (history: History) => ({
volunteerLoanSet,
volunteerLogin,
volunteerKnowledgeSet,
+ volunteerOnSiteInfo,
volunteerDetailedKnowledgeList,
volunteerParticipationDetailsSet,
volunteerPersonalInfoSet,
diff --git a/src/store/volunteerOnSiteInfo.ts b/src/store/volunteerOnSiteInfo.ts
new file mode 100644
index 0000000..7111981
--- /dev/null
+++ b/src/store/volunteerOnSiteInfo.ts
@@ -0,0 +1,65 @@
+import { PayloadAction, createSelector, createSlice } from "@reduxjs/toolkit"
+
+import { StateRequest, toastError, elementFetch } from "./utils"
+import { VolunteerOnSiteInfo } from "../services/volunteers"
+import { AppThunk, AppState } from "."
+import { volunteerOnSiteInfoGet } from "../services/volunteersAccessors"
+
+type StateVolunteerOnSiteInfo = { entity?: VolunteerOnSiteInfo } & StateRequest
+
+export const initialState: StateVolunteerOnSiteInfo = {
+ readyStatus: "idle",
+}
+
+const volunteerOnSiteInfo = createSlice({
+ name: "volunteerOnSiteInfo",
+ initialState,
+ reducers: {
+ getRequesting: (_) => ({
+ readyStatus: "request",
+ }),
+ getSuccess: (_, { payload }: PayloadAction) => ({
+ readyStatus: "success",
+ entity: payload,
+ }),
+ getFailure: (_, { payload }: PayloadAction) => ({
+ readyStatus: "failure",
+ error: payload,
+ }),
+ },
+})
+
+export default volunteerOnSiteInfo.reducer
+export const { getRequesting, getSuccess, getFailure } = volunteerOnSiteInfo.actions
+
+export const fetchVolunteerOnSiteInfo = elementFetch(
+ volunteerOnSiteInfoGet,
+ getRequesting,
+ getSuccess,
+ getFailure,
+ (error: Error) =>
+ toastError(`Erreur lors du chargement des infos sur site d'un bénévole: ${error.message}`)
+)
+
+const shouldFetchVolunteerOnSiteInfo = (state: AppState, id: number) =>
+ state.volunteerOnSiteInfo.readyStatus !== "success" ||
+ (state.volunteerOnSiteInfo.entity && state.volunteerOnSiteInfo.entity.id !== id)
+
+export const fetchVolunteerOnSiteInfoIfNeed =
+ (id = 0): AppThunk =>
+ (dispatch, getState) => {
+ let jwt = ""
+
+ if (!id) {
+ ;({ jwt, id } = getState().auth)
+ }
+ if (shouldFetchVolunteerOnSiteInfo(getState(), id))
+ return dispatch(fetchVolunteerOnSiteInfo(jwt, id))
+
+ return null
+ }
+
+export const selectVolunteerOnSiteInfo = createSelector(
+ (state: AppState) => state,
+ (state): VolunteerOnSiteInfo | undefined => state.volunteerOnSiteInfo?.entity
+)