mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-09 09:04:20 +02:00
Add email listing support for mobile in component TeamMembers
This commit is contained in:
parent
3a5f2792c9
commit
74603ee252
37056
package-lock.json
generated
Normal file
37056
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -123,7 +123,8 @@
|
|||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"serialize-javascript": "^6.0.0",
|
"serialize-javascript": "^6.0.0",
|
||||||
"serve-favicon": "^2.5.0",
|
"serve-favicon": "^2.5.0",
|
||||||
"web-push": "^3.4.5"
|
"web-push": "^3.4.5",
|
||||||
|
"xml2js": "^0.4.23"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.14.6",
|
"@babel/core": "^7.14.6",
|
||||||
@ -163,6 +164,7 @@
|
|||||||
"@types/webpack-bundle-analyzer": "^4.4.1",
|
"@types/webpack-bundle-analyzer": "^4.4.1",
|
||||||
"@types/webpack-manifest-plugin": "^3.0.5",
|
"@types/webpack-manifest-plugin": "^3.0.5",
|
||||||
"@types/webpack-node-externals": "^2.5.2",
|
"@types/webpack-node-externals": "^2.5.2",
|
||||||
|
"@types/xml2js": "^0.4.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||||
"@typescript-eslint/parser": "^4.28.2",
|
"@typescript-eslint/parser": "^4.28.2",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
|
@ -28,6 +28,9 @@ const boxElement = (
|
|||||||
|
|
||||||
const typeStyle = {
|
const typeStyle = {
|
||||||
"": null,
|
"": null,
|
||||||
|
"Jeux à deux": null,
|
||||||
|
Rapide: null,
|
||||||
|
Enfants: null,
|
||||||
Ambiance: styles.verteImg,
|
Ambiance: styles.verteImg,
|
||||||
Famille: styles.orangeImg,
|
Famille: styles.orangeImg,
|
||||||
Expert: styles.rougeImg,
|
Expert: styles.rougeImg,
|
||||||
|
@ -7,8 +7,8 @@ import { selectTeamList } from "../../store/teamList"
|
|||||||
import styles from "./styles.module.scss"
|
import styles from "./styles.module.scss"
|
||||||
import { Volunteer } from "../../services/volunteers"
|
import { Volunteer } from "../../services/volunteers"
|
||||||
import { Team } from "../../services/teams"
|
import { Team } from "../../services/teams"
|
||||||
import withUserRole from "../../utils/withUserRole"
|
|
||||||
import ROLES from "../../utils/roles.constants"
|
import ROLES from "../../utils/roles.constants"
|
||||||
|
import { selectUserRoles } from "../../store/auth"
|
||||||
|
|
||||||
interface ExtendedVolunteer extends Volunteer {
|
interface ExtendedVolunteer extends Volunteer {
|
||||||
teamObject: Team | undefined
|
teamObject: Team | undefined
|
||||||
@ -28,31 +28,30 @@ const selectVolunteersWithTeam = createSelector(
|
|||||||
|
|
||||||
const hasDay = (day: string) => (volunteer: Volunteer) => volunteer.dayWishes.includes(day)
|
const hasDay = (day: string) => (volunteer: Volunteer) => volunteer.dayWishes.includes(day)
|
||||||
|
|
||||||
type VolunteerEmailProps = {
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const VolunteerEmail: FC<VolunteerEmailProps> = withUserRole(ROLES.TEAMLEAD, ({ email }) => (
|
|
||||||
<td> {email}</td>
|
|
||||||
), null)
|
|
||||||
|
|
||||||
type DaysAvailabilityProps = {
|
type DaysAvailabilityProps = {
|
||||||
volunteer: Volunteer
|
volunteer: Volunteer
|
||||||
}
|
}
|
||||||
|
|
||||||
const DaysAvailability: FC<DaysAvailabilityProps> = ({ volunteer }): JSX.Element => {
|
const DaysAvailability: FC<DaysAvailabilityProps> = ({ volunteer }): JSX.Element => {
|
||||||
if (volunteer.dayWishes.length === 0) {
|
const hasWishes = volunteer.dayWishes.length > 0
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<td className={classnames(styles.day, styles.unknown)}>S</td>
|
|
||||||
<td className={classnames(styles.day, styles.unknown)}>D</td>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<td className={classnames(styles.day, hasDay("S")(volunteer) && styles.available)} />
|
<td
|
||||||
<td className={classnames(styles.day, hasDay("D")(volunteer) && styles.available)} />
|
className={classnames(
|
||||||
|
styles.day,
|
||||||
|
hasWishes ? hasDay("S")(volunteer) && styles.available : styles.unknown
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{hasWishes ? "" : "?"}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className={classnames(
|
||||||
|
styles.day,
|
||||||
|
hasWishes ? hasDay("D")(volunteer) && styles.available : styles.unknown
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{hasWishes ? "" : "?"}
|
||||||
|
</td>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -65,26 +64,39 @@ const TeamMembers: FC<Props> = ({ teamId }): JSX.Element => {
|
|||||||
const volunteers = useSelector(selectVolunteersWithTeam).filter(
|
const volunteers = useSelector(selectVolunteersWithTeam).filter(
|
||||||
(volunteer) => volunteer?.teamObject?.id === teamId
|
(volunteer) => volunteer?.teamObject?.id === teamId
|
||||||
)
|
)
|
||||||
|
const roles = useSelector(selectUserRoles)
|
||||||
|
|
||||||
if (volunteers.length === 0) return <div />
|
if (volunteers.length === 0) return <div />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table>
|
<table className={styles.teamMembers}>
|
||||||
<tr>
|
<tbody>
|
||||||
<th>Volontaires</th>
|
<tr>
|
||||||
<th className={styles.dayTitle}>S ({volunteers.filter(hasDay("S")).length})</th>
|
<th>Bénévoles</th>
|
||||||
<th className={styles.dayTitle}>D ({volunteers.filter(hasDay("D")).length})</th>
|
<th className={styles.dayTitle}>S ({volunteers.filter(hasDay("S")).length})</th>
|
||||||
<th>@</th>
|
<th className={styles.dayTitle}>D ({volunteers.filter(hasDay("D")).length})</th>
|
||||||
</tr>
|
|
||||||
{volunteers.map((volunteer) => (
|
|
||||||
<tr key={volunteer.id}>
|
|
||||||
<td>
|
|
||||||
{volunteer.firstname} {volunteer.lastname}
|
|
||||||
</td>
|
|
||||||
<DaysAvailability volunteer={volunteer} />
|
|
||||||
<VolunteerEmail email={volunteer.email} />
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
{volunteers.map((volunteer) => (
|
||||||
|
<tr key={volunteer.id}>
|
||||||
|
<td
|
||||||
|
className={classnames(
|
||||||
|
styles.volunteerName,
|
||||||
|
roles.includes(ROLES.TEAMLEAD) && styles.extendedVolunteerName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{volunteer.firstname} {volunteer.lastname}
|
||||||
|
{roles.includes(ROLES.TEAMLEAD) && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
<span className={styles.email}><{volunteer.email}></span>
|
||||||
|
<span className={styles.hiddenText}>,</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<DaysAvailability volunteer={volunteer} />
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
@import "../../theme/mixins";
|
@import "../../theme/mixins";
|
||||||
.day {
|
.day {
|
||||||
background-color: $color-red;
|
background-color: $color-red;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 10%;
|
||||||
&.unknown {
|
&.unknown {
|
||||||
background-color: $color-grey-medium;
|
background-color: $color-grey-medium;
|
||||||
}
|
}
|
||||||
@ -9,3 +11,30 @@
|
|||||||
background-color: $color-green;
|
background-color: $color-green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.teamMembers {
|
||||||
|
@include mobile {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.volunteerName {
|
||||||
|
text-align: left;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extendedVolunteerName {
|
||||||
|
@include mobile {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.email {
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hiddenText {
|
||||||
|
font-size: 1px;
|
||||||
|
width: 0.1px;
|
||||||
|
}
|
||||||
|
@ -13,26 +13,27 @@ export const detailedBoxListGet = expressAccessor.get(async (list) => {
|
|||||||
throw Error("Unable to load gameList")
|
throw Error("Unable to load gameList")
|
||||||
}
|
}
|
||||||
|
|
||||||
return list
|
const toBeAsked: DetailedBox[] = []
|
||||||
.filter((box) => box)
|
|
||||||
.filter((box) => !box.unplayable)
|
gameList.forEach((game) => {
|
||||||
.map((box) => {
|
const box: Box | undefined = list.find((g) => g.gameId === game.id)
|
||||||
const game = gameList.find((g) => g.id === box.gameId)
|
if ((box && box.unplayable) || (!box && !game.toBeKnown)) {
|
||||||
if (!game) {
|
return
|
||||||
throw Error(`Unable to find game #${box.gameId}`)
|
}
|
||||||
}
|
toBeAsked.push({
|
||||||
return {
|
id: box?.id || 10000 + game.id,
|
||||||
id: box.id,
|
gameId: game.id,
|
||||||
gameId: box.gameId,
|
title: game.title,
|
||||||
title: game.title,
|
bggPhoto: game.bggPhoto,
|
||||||
bggPhoto: game.bggPhoto,
|
poufpaf: game.poufpaf,
|
||||||
poufpaf: game.poufpaf,
|
bggId: game.bggId,
|
||||||
bggId: game.bggId,
|
container: box?.container || "Non stocké",
|
||||||
container: box.container,
|
playersMin: game.playersMin,
|
||||||
playersMin: game.playersMin,
|
playersMax: game.playersMax,
|
||||||
playersMax: game.playersMax,
|
duration: game.duration,
|
||||||
duration: game.duration,
|
type: game.type,
|
||||||
type: game.type,
|
} as DetailedBox)
|
||||||
} as DetailedBox
|
})
|
||||||
})
|
|
||||||
|
return toBeAsked
|
||||||
})
|
})
|
||||||
|
@ -67,7 +67,9 @@ export default class ExpressAccessors<
|
|||||||
list: Element[],
|
list: Element[],
|
||||||
body: RequestBody,
|
body: RequestBody,
|
||||||
id: number,
|
id: number,
|
||||||
roles: string[]
|
roles: string[],
|
||||||
|
request: Request,
|
||||||
|
response: Response
|
||||||
) => Promise<CustomSetReturn<Element[]>> | CustomSetReturn<Element[]>
|
) => Promise<CustomSetReturn<Element[]>> | CustomSetReturn<Element[]>
|
||||||
) {
|
) {
|
||||||
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
||||||
@ -84,7 +86,9 @@ export default class ExpressAccessors<
|
|||||||
list,
|
list,
|
||||||
request.body,
|
request.body,
|
||||||
memberId,
|
memberId,
|
||||||
roles
|
roles,
|
||||||
|
request,
|
||||||
|
response
|
||||||
)
|
)
|
||||||
if (toDatabase !== undefined) {
|
if (toDatabase !== undefined) {
|
||||||
await sheet.setList(toDatabase)
|
await sheet.setList(toDatabase)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { cloneDeep } from "lodash"
|
import axios from "axios"
|
||||||
|
import { Parser } from "xml2js"
|
||||||
|
import { assign, cloneDeep, find, maxBy, some } from "lodash"
|
||||||
import ExpressAccessors from "./expressAccessors"
|
import ExpressAccessors from "./expressAccessors"
|
||||||
import { Game, GameWithoutId, translationGame } from "../../services/games"
|
import { Game, GameWithoutId, translationGame } from "../../services/games"
|
||||||
|
|
||||||
@ -13,13 +15,71 @@ 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) => {
|
export const gameDetailsUpdate = expressAccessor.listSet(
|
||||||
const newList = cloneDeep(list)
|
async (list, _body, _id, _roles, request) => {
|
||||||
|
request.setTimeout(500000)
|
||||||
|
const newList = cloneDeep(list)
|
||||||
|
|
||||||
// TODO update game list details from BGG
|
const data = await getData()
|
||||||
|
const parser = new Parser()
|
||||||
|
const parsed = await parser.parseStringPromise(data)
|
||||||
|
|
||||||
return {
|
newList.forEach((game, index, arr) => {
|
||||||
toDatabase: newList,
|
const box = find(parsed.items.item, (item: any) => game.bggId === +item.$.objectid)
|
||||||
toCaller: newList,
|
if (box && game.bggPhoto === "") {
|
||||||
|
assign(arr[index], xmlToGame(game.id, box), {
|
||||||
|
ean: arr[index].ean,
|
||||||
|
type: arr[index].type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const newGames = parsed.items.item.filter(
|
||||||
|
(item: any) =>
|
||||||
|
(item.status[0]?.$?.own === "1" ||
|
||||||
|
item.status[0]?.$?.want === "1" ||
|
||||||
|
item.status[0]?.$?.wishlist === "1" ||
|
||||||
|
item.status[0]?.$?.preordered === "1") &&
|
||||||
|
!some(list, (i) => +i.bggId === +item.$.objectid)
|
||||||
|
)
|
||||||
|
|
||||||
|
let id = maxBy(newList, "id")?.id || 0
|
||||||
|
newGames.forEach((item: any) => {
|
||||||
|
id += 1
|
||||||
|
newList.push(xmlToGame(id, item))
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
toDatabase: newList,
|
||||||
|
toCaller: newList,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
|
function xmlToGame(id: number, item: any): Game {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
title: item.name?.[0]._ || "",
|
||||||
|
playersMin: item.stats?.[0]?.$?.minplayers || 0,
|
||||||
|
playersMax: item.stats?.[0]?.$?.maxplayers || 0,
|
||||||
|
duration: item.stats?.[0]?.$?.playingtime || 0,
|
||||||
|
type: "Famille",
|
||||||
|
poufpaf: "",
|
||||||
|
bggId: +item.$.objectid || 0,
|
||||||
|
ean: "",
|
||||||
|
bggPhoto: item.thumbnail?.[0] || "",
|
||||||
|
toBeKnown: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function getData(): Promise<string> {
|
||||||
|
const { data } = await axios.get(
|
||||||
|
"https://boardgamegeek.com/xmlapi2/collection?username=Paris%20est%20ludique&subtype=boardgame&stats=1"
|
||||||
|
)
|
||||||
|
if (!data) {
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
setTimeout(() => resolve(), 20000)
|
||||||
|
})
|
||||||
|
return getData()
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ export class Game {
|
|||||||
|
|
||||||
duration = 0
|
duration = 0
|
||||||
|
|
||||||
type: "Ambiance" | "Famille" | "Expert" | "" = ""
|
type: "Ambiance" | "Famille" | "Expert" | "Rapide" | "Jeux à deux" | "Enfants" | "" = ""
|
||||||
|
|
||||||
poufpaf = ""
|
poufpaf = ""
|
||||||
|
|
||||||
@ -18,6 +18,8 @@ export class Game {
|
|||||||
ean = ""
|
ean = ""
|
||||||
|
|
||||||
bggPhoto = ""
|
bggPhoto = ""
|
||||||
|
|
||||||
|
toBeKnown = false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const translationGame: { [k in keyof Game]: string } = {
|
export const translationGame: { [k in keyof Game]: string } = {
|
||||||
@ -31,6 +33,7 @@ export const translationGame: { [k in keyof Game]: string } = {
|
|||||||
bggId: "bggId",
|
bggId: "bggId",
|
||||||
ean: "ean",
|
ean: "ean",
|
||||||
bggPhoto: "bggPhoto",
|
bggPhoto: "bggPhoto",
|
||||||
|
toBeKnown: "àConnaitre",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const elementName = "Game"
|
export const elementName = "Game"
|
||||||
|
@ -26,6 +26,7 @@ const mockData: Game[] = [
|
|||||||
"https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg",
|
"https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg",
|
||||||
bggId: 432,
|
bggId: 432,
|
||||||
ean: "3421272101313",
|
ean: "3421272101313",
|
||||||
|
toBeKnown: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const mockError = "Oops! Something went wrong."
|
const mockError = "Oops! Something went wrong."
|
||||||
|
Loading…
x
Reference in New Issue
Block a user