mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-08 08:34:20 +02:00
Update sign up message and available meetup location
This commit is contained in:
parent
2a1d2ef49b
commit
e6a049d0d1
@ -40,6 +40,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "yarn dev:build && nodemon ./public/server",
|
||||
"ser": "yarn dev:build && node ./public/server",
|
||||
"dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts",
|
||||
"local-start": "cross-env LOCAL=true yarn build && node ./public/server",
|
||||
"start": "node ./public/server",
|
||||
|
@ -6,7 +6,7 @@ import { AskWelcome } from "./AskWelcome"
|
||||
import { AskBrunch, fetchFor as fetchForBrunch } from "./AskBrunch"
|
||||
import { AskRetex, fetchFor as fetchForRetex } from "./AskRetex"
|
||||
import { AskDiscord, fetchFor as fetchForDiscord } from "./AskDiscord"
|
||||
// import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
|
||||
import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
|
||||
// import { AskHosting, fetchFor as fetchForHosting } from "./AskHosting"
|
||||
// import { AskMeals, fetchFor as fetchForMeals } from "./AskMeals"
|
||||
// import { AskPersonalInfo, fetchFor as fetchForPersonalInfo } from "./AskPersonalInfo"
|
||||
@ -26,7 +26,7 @@ const Asks = (): JSX.Element | null => {
|
||||
AskRetex(asks, 3)
|
||||
AskDiscord(asks, 5)
|
||||
|
||||
// AskDayWishes(asks, 10)
|
||||
AskDayWishes(asks, 10)
|
||||
// AskTeamWishes(asks, 11)
|
||||
// AskParticipationDetails(asks, 12)
|
||||
// AskPersonalInfo(asks, 15)
|
||||
@ -69,7 +69,7 @@ export const fetchFor = [
|
||||
...fetchForBrunch,
|
||||
...fetchForRetex,
|
||||
...fetchForDiscord,
|
||||
// ...fetchForDayWishes,
|
||||
...fetchForDayWishes,
|
||||
// ...fetchForHosting,
|
||||
// ...fetchForMeals,
|
||||
// ...fetchForTeamWishes,
|
||||
|
@ -37,7 +37,7 @@ const KnowledgeBoxList: React.FC = (): JSX.Element | null => {
|
||||
onChange={onShowUnknownOnly}
|
||||
checked={showUnknownOnly}
|
||||
/>{" "}
|
||||
Uniquement les non-renseignés
|
||||
Afficher uniquement les non-renseignés
|
||||
</label>
|
||||
<ul className={styles.boxList}>
|
||||
{boxesToShow.map((detailedBox: any) => (
|
||||
|
@ -5,7 +5,6 @@
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.boxList {
|
||||
|
@ -1,156 +0,0 @@
|
||||
import { cloneDeep, pull, sortBy, uniq } from "lodash"
|
||||
import React, { memo, useCallback, useEffect, useState } from "react"
|
||||
import classnames from "classnames"
|
||||
import styles from "./styles.module.scss"
|
||||
import { DetailedBox } from "../../services/boxes"
|
||||
import { VolunteerLoan, VolunteerLoanWithoutId } from "../../services/volunteers"
|
||||
|
||||
interface Props {
|
||||
detailedBox: DetailedBox
|
||||
volunteerLoan: VolunteerLoan | undefined
|
||||
saveVolunteerLoan: (newVolunteerLoan: VolunteerLoan) => void
|
||||
}
|
||||
|
||||
const BoxItem: React.FC<Props> = ({
|
||||
detailedBox,
|
||||
volunteerLoan,
|
||||
saveVolunteerLoan,
|
||||
}): JSX.Element => {
|
||||
type ChoiceValue = keyof VolunteerLoanWithoutId
|
||||
const [loanable, setLoanable] = useState(false)
|
||||
const [playable, setPlayable] = useState(false)
|
||||
const [giftable, setGiftable] = useState(false)
|
||||
const [noOpinion, setNoOpinion] = useState(false)
|
||||
const { gameId, bggPhoto, title, bggId, poufpaf, bggIdAlternative } = detailedBox
|
||||
const isBggPhoto = !/^[0-9]+\.[a-zA-Z]+$/.test(bggPhoto)
|
||||
const loanChoices: { name: string; value: ChoiceValue }[] = [
|
||||
{ name: "Empruntable", value: "loanable" },
|
||||
{ name: "Jouable", value: "playable" },
|
||||
{ name: "Offrable", value: "giftable" },
|
||||
{ name: "SansAvis", value: "noOpinion" },
|
||||
]
|
||||
const loanValues = {
|
||||
loanable,
|
||||
playable,
|
||||
giftable,
|
||||
noOpinion,
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const loanSetters = {
|
||||
loanable: setLoanable,
|
||||
playable: setPlayable,
|
||||
giftable: setGiftable,
|
||||
noOpinion: setNoOpinion,
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!volunteerLoan) return
|
||||
setLoanable(volunteerLoan.loanable.includes(gameId))
|
||||
setPlayable(volunteerLoan.playable.includes(gameId))
|
||||
setGiftable(volunteerLoan.giftable.includes(gameId))
|
||||
setNoOpinion(volunteerLoan.noOpinion.includes(gameId))
|
||||
}, [gameId, volunteerLoan])
|
||||
|
||||
const onChoiceClick = useCallback(
|
||||
(value: ChoiceValue) => {
|
||||
if (!volunteerLoan) return
|
||||
const adding = !volunteerLoan[value].includes(gameId)
|
||||
const newVolunteerLoan: VolunteerLoan = cloneDeep(volunteerLoan)
|
||||
if (adding) {
|
||||
newVolunteerLoan[value] = sortBy(uniq([...newVolunteerLoan[value], gameId]))
|
||||
} else {
|
||||
pull(newVolunteerLoan[value], gameId)
|
||||
}
|
||||
|
||||
if (adding && value === "noOpinion") {
|
||||
pull(newVolunteerLoan.loanable, gameId)
|
||||
pull(newVolunteerLoan.playable, gameId)
|
||||
pull(newVolunteerLoan.giftable, gameId)
|
||||
setLoanable(volunteerLoan.loanable.includes(gameId))
|
||||
setPlayable(volunteerLoan.playable.includes(gameId))
|
||||
setGiftable(volunteerLoan.giftable.includes(gameId))
|
||||
setNoOpinion(true)
|
||||
} else {
|
||||
pull(newVolunteerLoan.noOpinion, gameId)
|
||||
setNoOpinion(false)
|
||||
loanSetters[value](adding)
|
||||
}
|
||||
saveVolunteerLoan(newVolunteerLoan)
|
||||
},
|
||||
[volunteerLoan, gameId, saveVolunteerLoan, loanSetters]
|
||||
)
|
||||
|
||||
const onCheckboxChange = useCallback(() => {
|
||||
// Do nothing
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<li className={styles.boxItem}>
|
||||
<div className={styles.photoContainer}>
|
||||
{isBggPhoto && <img className={styles.photo} src={bggPhoto} alt="" />}
|
||||
{!isBggPhoto && (
|
||||
<div
|
||||
className={classnames(
|
||||
styles.alternateBox,
|
||||
styles[`a${bggPhoto.replace(/\..*/, "")}`]
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={classnames(styles.titleContainer, poufpaf && styles.shorterTitle)}>
|
||||
<a
|
||||
href={
|
||||
bggId > 0
|
||||
? `https://boardgamegeek.com/boardgame/${bggId}`
|
||||
: bggIdAlternative
|
||||
}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.title}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href={`https://sites.google.com/site/poufpafpasteque/fiches_alpha/${poufpaf}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className={poufpaf ? styles.poufpaf : styles.noPoufpaf}> </div>
|
||||
</a>
|
||||
<ul className={styles.loanList}>
|
||||
{loanChoices.map(({ name, value }) => (
|
||||
<li key={value} className={styles.loanItem}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChoiceClick(value)}
|
||||
className={classnames(
|
||||
styles.loanButton,
|
||||
styles[value],
|
||||
loanValues[value] && styles.active
|
||||
)}
|
||||
data-message={name}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.loanCheckbox}
|
||||
checked={loanValues[value]}
|
||||
onChange={() => onCheckboxChange()}
|
||||
/>
|
||||
<div
|
||||
className={classnames(
|
||||
styles.loanCheckboxImg,
|
||||
styles[`${value}Checkbox`]
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(BoxItem)
|
164
src/components/Loan/DetailedGameItem.tsx
Normal file
164
src/components/Loan/DetailedGameItem.tsx
Normal file
@ -0,0 +1,164 @@
|
||||
// import { cloneDeep, pull, sortBy, uniq } from "lodash"
|
||||
import { toast } from "react-toastify"
|
||||
import React, { memo, useCallback, useEffect, useState } from "react"
|
||||
import classnames from "classnames"
|
||||
import styles from "./styles.module.scss"
|
||||
import { DetailedBox } from "../../services/boxes"
|
||||
import { VolunteerLoan, VolunteerLoanWithoutId } from "../../services/volunteers"
|
||||
|
||||
interface Props {
|
||||
detailedGame: DetailedBox
|
||||
volunteerLoan: VolunteerLoan | undefined
|
||||
saveVolunteerLoan: (newVolunteerLoan: VolunteerLoan) => void
|
||||
}
|
||||
|
||||
const DetailedGameItem: React.FC<Props> = ({
|
||||
detailedGame,
|
||||
volunteerLoan,
|
||||
saveVolunteerLoan,
|
||||
}): JSX.Element => {
|
||||
type ChoiceValue = keyof VolunteerLoanWithoutId
|
||||
const [loanable, setLoanable] = useState(false)
|
||||
const [playable, setPlayable] = useState(false)
|
||||
const [giftable, setGiftable] = useState(false)
|
||||
const [noOpinion, setNoOpinion] = useState(false)
|
||||
const { gameId, bggPhoto, title, bggId, poufpaf, bggIdAlternative } = detailedGame
|
||||
const isBggPhoto = !/^[0-9]+\.[a-zA-Z]+$/.test(bggPhoto)
|
||||
const loanChoices: { name: string; value: ChoiceValue }[] = [
|
||||
{ name: "Empruntable", value: "loanable" },
|
||||
{ name: "Jouable", value: "playable" },
|
||||
{ name: "Offrable", value: "giftable" },
|
||||
{ name: "SansAvis", value: "noOpinion" },
|
||||
]
|
||||
const loanValues = {
|
||||
loanable,
|
||||
playable,
|
||||
giftable,
|
||||
noOpinion,
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!volunteerLoan) return
|
||||
setLoanable(volunteerLoan.loanable.includes(gameId))
|
||||
setPlayable(volunteerLoan.playable.includes(gameId))
|
||||
setGiftable(volunteerLoan.giftable.includes(gameId))
|
||||
setNoOpinion(volunteerLoan.noOpinion.includes(gameId))
|
||||
}, [gameId, volunteerLoan])
|
||||
|
||||
const onChoiceClick = useCallback(
|
||||
(_value: ChoiceValue) => {
|
||||
toast.warning("Il n'est plus possible de changer ses choix", {
|
||||
position: "top-center",
|
||||
autoClose: 500,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
})
|
||||
},
|
||||
[]
|
||||
|
||||
// (value: ChoiceValue) => {
|
||||
// if (!volunteerLoan) return
|
||||
// const adding = !volunteerLoan[value].includes(gameId)
|
||||
// const newVolunteerLoan: VolunteerLoan = cloneDeep(volunteerLoan)
|
||||
// if (adding) {
|
||||
// newVolunteerLoan[value] = sortBy(uniq([...newVolunteerLoan[value], gameId]))
|
||||
// } else {
|
||||
// pull(newVolunteerLoan[value], gameId)
|
||||
// }
|
||||
|
||||
// if (adding && value === "noOpinion") {
|
||||
// pull(newVolunteerLoan.loanable, gameId)
|
||||
// pull(newVolunteerLoan.playable, gameId)
|
||||
// pull(newVolunteerLoan.giftable, gameId)
|
||||
// } else {
|
||||
// pull(newVolunteerLoan.noOpinion, gameId)
|
||||
// }
|
||||
// saveVolunteerLoan(newVolunteerLoan)
|
||||
// },
|
||||
// [volunteerLoan, gameId, saveVolunteerLoan]
|
||||
)
|
||||
|
||||
// Just to keep saveVolunteerLoan necessary to prevent errors while changes are prevented
|
||||
if (!saveVolunteerLoan) {
|
||||
console.log("")
|
||||
}
|
||||
|
||||
const onCheckboxChange = useCallback(() => {
|
||||
// Do nothing
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<li className={classnames(styles.boxItem, !volunteerLoan && styles.hide)}>
|
||||
<div className={styles.photoContainer}>
|
||||
{isBggPhoto && <img className={styles.photo} src={bggPhoto} alt="" />}
|
||||
{!isBggPhoto && (
|
||||
<div
|
||||
className={classnames(
|
||||
styles.alternateBox,
|
||||
styles[`a${bggPhoto.replace(/\..*/, "")}`]
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={classnames(styles.titleContainer, poufpaf && styles.shorterTitle)}>
|
||||
<a
|
||||
href={
|
||||
bggId > 0
|
||||
? `https://boardgamegeek.com/boardgame/${bggId}`
|
||||
: bggIdAlternative
|
||||
}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.title}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href={`https://sites.google.com/site/poufpafpasteque/fiches_alpha/${poufpaf}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className={poufpaf ? styles.poufpaf : styles.noPoufpaf}> </div>
|
||||
</a>
|
||||
<ul className={styles.loanList}>
|
||||
{loanChoices.map(({ name, value }) => (
|
||||
<li key={value} className={styles.loanItem}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChoiceClick(value)}
|
||||
className={classnames(
|
||||
styles.loanButton,
|
||||
styles[value],
|
||||
loanValues[value] && styles.active
|
||||
)}
|
||||
data-message={name}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.loanCheckbox}
|
||||
checked={loanValues[value]}
|
||||
onChange={() => onCheckboxChange()}
|
||||
/>
|
||||
<div
|
||||
className={classnames(
|
||||
styles.loanCheckboxImg,
|
||||
styles[`${value}Checkbox`]
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(DetailedGameItem)
|
64
src/components/Loan/GamesWithVolunteersItem.tsx
Normal file
64
src/components/Loan/GamesWithVolunteersItem.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React, { memo } from "react"
|
||||
import classnames from "classnames"
|
||||
import styles from "./styles.module.scss"
|
||||
import { GameWithVolunteers } from "../../services/games"
|
||||
import { gameTitleExactCategory } from "../../store/utils"
|
||||
|
||||
interface Props {
|
||||
gameWithLoans: GameWithVolunteers
|
||||
}
|
||||
|
||||
const LoanGamesItem: React.FC<Props> = ({ gameWithLoans }): JSX.Element => {
|
||||
const { bggPhoto, title, bggId, poufpaf, bggIdAlternative, volunteerNicknames, boxCount } =
|
||||
gameWithLoans
|
||||
const isBggPhoto = !/^[0-9]+\.[a-zA-Z]+$/.test(bggPhoto)
|
||||
|
||||
return (
|
||||
<li className={styles.boxItem}>
|
||||
<div className={styles.photoContainer}>
|
||||
{isBggPhoto && <img className={styles.photo} src={bggPhoto} alt="" />}
|
||||
{!isBggPhoto && (
|
||||
<div
|
||||
className={classnames(
|
||||
styles.alternateBox,
|
||||
styles[`a${bggPhoto.replace(/\..*/, "")}`]
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={classnames(styles.titleContainer, poufpaf && styles.shorterTitle)}>
|
||||
<a
|
||||
href={
|
||||
bggId > 0
|
||||
? `https://boardgamegeek.com/boardgame/${bggId}`
|
||||
: bggIdAlternative
|
||||
}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.title}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href={`https://sites.google.com/site/poufpafpasteque/fiches_alpha/${poufpaf}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className={poufpaf ? styles.poufpaf : styles.noPoufpaf}> </div>
|
||||
</a>
|
||||
<div className={styles.boxCount}>
|
||||
{boxCount > 1 ? `${boxCount} boîtes` : ""}
|
||||
<br />
|
||||
{gameTitleExactCategory({ title })}
|
||||
</div>
|
||||
<div className={styles.volunteerList}>
|
||||
Bénévoles intéressés: {volunteerNicknames.join(", ")}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(LoanGamesItem)
|
@ -1,23 +1,21 @@
|
||||
import React, { memo, useState } from "react"
|
||||
import { useSelector } from "react-redux"
|
||||
import styles from "./styles.module.scss"
|
||||
import BoxItem from "./BoxItem"
|
||||
import { fetchBoxListIfNeed, selectSortedUniqueDetailedBoxes } from "../../store/boxList"
|
||||
import { fetchVolunteerLoanSetIfNeed, useVolunteerLoan } from "../../store/volunteerLoanSet"
|
||||
import { DetailedBox } from "../../services/boxes"
|
||||
import DetailedGameItem from "./DetailedGameItem"
|
||||
|
||||
const LoanBoxList: React.FC = (): JSX.Element | null => {
|
||||
const detailedBoxes = useSelector(selectSortedUniqueDetailedBoxes)
|
||||
const Loaning: React.FC = (): JSX.Element | null => {
|
||||
const detailedBoxes: DetailedBox[] = useSelector(selectSortedUniqueDetailedBoxes)
|
||||
const [volunteerLoan, saveVolunteerLoan] = useVolunteerLoan()
|
||||
const [showUnknownOnly, setShowUnknownOnly] = useState(false)
|
||||
|
||||
const onShowUnknownOnly = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setShowUnknownOnly(e.target.checked)
|
||||
|
||||
if (!detailedBoxes || detailedBoxes.length === 0) return null
|
||||
|
||||
const boxesToShow = detailedBoxes.filter(
|
||||
(box) =>
|
||||
!box ||
|
||||
!showUnknownOnly ||
|
||||
!volunteerLoan ||
|
||||
(!volunteerLoan.loanable.includes(box.gameId) &&
|
||||
@ -28,22 +26,22 @@ const LoanBoxList: React.FC = (): JSX.Element | null => {
|
||||
|
||||
return (
|
||||
<div className={styles.loanThings}>
|
||||
<label className={styles.showUnknownOnlyLabel}>
|
||||
<label className={styles.showCheckboxLabel}>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="showUnknownOnly"
|
||||
onChange={onShowUnknownOnly}
|
||||
checked={showUnknownOnly}
|
||||
/>{" "}
|
||||
Uniquement les non-renseignés
|
||||
Afficher uniquement les non-renseignés
|
||||
</label>
|
||||
<ul className={styles.boxList}>
|
||||
{boxesToShow.map((detailedBox: any) => (
|
||||
<BoxItem
|
||||
detailedBox={detailedBox}
|
||||
{boxesToShow.map((game) => (
|
||||
<DetailedGameItem
|
||||
detailedGame={game}
|
||||
volunteerLoan={volunteerLoan}
|
||||
saveVolunteerLoan={saveVolunteerLoan}
|
||||
key={detailedBox.id}
|
||||
key={game.id}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
@ -51,6 +49,6 @@ const LoanBoxList: React.FC = (): JSX.Element | null => {
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(LoanBoxList)
|
||||
export default memo(Loaning)
|
||||
|
||||
export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerLoanSetIfNeed]
|
@ -2,7 +2,7 @@ import classnames from "classnames"
|
||||
import React from "react"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
const LoanIntro: React.FC = (): JSX.Element => (
|
||||
const LoaningIntro: React.FC = (): JSX.Element => (
|
||||
<div className={styles.loanThings}>
|
||||
<h1>Emprunt et tri des jeux</h1>
|
||||
<p>
|
||||
@ -54,4 +54,4 @@ const LoanIntro: React.FC = (): JSX.Element => (
|
||||
</div>
|
||||
)
|
||||
|
||||
export default LoanIntro
|
||||
export default LoaningIntro
|
46
src/components/Loan/Loans.tsx
Normal file
46
src/components/Loan/Loans.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React, { memo } from "react"
|
||||
import { useSelector } from "react-redux"
|
||||
import styles from "./styles.module.scss"
|
||||
import {
|
||||
fetchGameWithVolunteersListIfNeed,
|
||||
selectSortedUniqueGamesWithVolunteers,
|
||||
} from "../../store/gameWithVolunteersList"
|
||||
import { GameWithVolunteers } from "../../services/games"
|
||||
import GamesWithVolunteersItem from "./GamesWithVolunteersItem"
|
||||
|
||||
const Loans: React.FC = (): JSX.Element | null => {
|
||||
const gameWithLoans: GameWithVolunteers[] = useSelector(selectSortedUniqueGamesWithVolunteers)
|
||||
|
||||
const loanGames = gameWithLoans.filter((game) => game.toLoan)
|
||||
// const giftGames = gameWithLoans.filter(game => !game.toLoan)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>Jeux que tu veux emprunter</h3>
|
||||
{loanGames.length > 0 && (
|
||||
<ul className={styles.boxList}>
|
||||
{loanGames.map((game) => (
|
||||
<GamesWithVolunteersItem gameWithLoans={game} key={game.id} />
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{loanGames.length === 0 && <>Aucun jeu.</>}
|
||||
|
||||
{/* {giftGames.length > 0 && <>
|
||||
<h3>Jeux que tu voudrais récupérer définitivement</h3>
|
||||
<ul className={styles.boxList}>
|
||||
{giftGames.map(game => (
|
||||
<GamesWithVolunteersItem
|
||||
gameWithLoans={game}
|
||||
key={game.id}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</>} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Loans)
|
||||
|
||||
export const fetchFor = [fetchGameWithVolunteersListIfNeed]
|
14
src/components/Loan/LoansIntro.tsx
Normal file
14
src/components/Loan/LoansIntro.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from "react"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
const LoansIntro: React.FC = (): JSX.Element => (
|
||||
<div className={styles.loanThings}>
|
||||
<h1>Jeux à emprunter</h1>
|
||||
<p>
|
||||
Si un jeu qui t'intéresse est emprunté par un autre bénévole, son nom sera affiché ici.
|
||||
Vous pourrez peut-être envisager d'y jouer ensemble !
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default LoansIntro
|
@ -1,11 +1,14 @@
|
||||
@import "../../theme/variables";
|
||||
@import "../../theme/mixins";
|
||||
|
||||
.showUnknownOnlyLabel {
|
||||
.showCheckboxLabel {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.boxList {
|
||||
@ -200,6 +203,22 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.boxCount {
|
||||
height: 50px;
|
||||
width: 73px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.volunteerList {
|
||||
height: 50px;
|
||||
width: 198px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.loanList {
|
||||
@include clear-ul-style;
|
||||
|
||||
|
@ -52,6 +52,7 @@ const MainMenu: FC = (): JSX.Element => {
|
||||
<MenuItem name="Questions" pathname="/" />
|
||||
<MenuItem name="Annonces" pathname="/annonces" />
|
||||
<MenuItem name="Mon profil" pathname="/profil" />
|
||||
<MenuItem name="Emprunter" pathname="/emprunter" />
|
||||
<MenuItem name="Emprunts" pathname="/emprunts" />
|
||||
{/* <MenuItem name="Mes connaissances" pathname="/connaissances" /> */}
|
||||
<RestrictMenuItem
|
||||
|
@ -8,7 +8,6 @@ import styles from "./styles.module.scss"
|
||||
|
||||
import { fetchPostulantAdd } from "../../store/postulantAdd"
|
||||
import { AppDispatch, AppState } from "../../store"
|
||||
import { fetchVolunteerPartialAdd } from "../../store/volunteerPartialAdd"
|
||||
import FormButton from "../Form/FormButton/FormButton"
|
||||
import { validEmail } from "../../utils/standardization"
|
||||
import { toastError } from "../../store/utils"
|
||||
@ -38,24 +37,21 @@ const animations = [
|
||||
[styles.imgTransitionShow, styles.imgTransitionAbouToShow],
|
||||
]
|
||||
const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
const [potentialVolunteer, setPotentialVolunteer] = useState(true)
|
||||
const [firstname, setFirstname] = useState("")
|
||||
const [lastname, setLastname] = useState("")
|
||||
const [email, setEmail] = useState("")
|
||||
const [mobile, setMobile] = useState("")
|
||||
const [alreadyVolunteer, setAlreadyVolunteer] = useState(false)
|
||||
const [comment, setComment] = useState("")
|
||||
const [alreadyCame, setAlreadyCame] = useState(true)
|
||||
const [firstMeeting, setFirstMeeting] = useState("")
|
||||
const [commentFirstMeeting, setCommentFirstMeeting] = useState("")
|
||||
const [canHelpBefore, setCanHelpBefore] = useState("")
|
||||
const [howToContact, setHowToContact] = useState("Email")
|
||||
const [sending, setSending] = useState(false)
|
||||
const [changingBackground, setChangingBackground] = useState(0)
|
||||
|
||||
const meetingDateList = useSelector(selectMiscMeetingDateList)
|
||||
|
||||
const enableRegistering = false
|
||||
const enableRegistering = true
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
@ -83,7 +79,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
return
|
||||
}
|
||||
|
||||
if (potentialVolunteer) {
|
||||
dispatch(
|
||||
fetchPostulantAdd({
|
||||
potential: true,
|
||||
@ -98,32 +93,15 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
comment,
|
||||
})
|
||||
)
|
||||
} else {
|
||||
dispatch(
|
||||
fetchVolunteerPartialAdd({
|
||||
firstname,
|
||||
lastname,
|
||||
email,
|
||||
mobile,
|
||||
howToContact,
|
||||
canHelpBefore,
|
||||
})
|
||||
)
|
||||
dispatch(
|
||||
fetchPostulantAdd({
|
||||
potential: false,
|
||||
firstname,
|
||||
lastname,
|
||||
email,
|
||||
mobile,
|
||||
howToContact,
|
||||
alreadyCame,
|
||||
firstMeeting,
|
||||
commentFirstMeeting: firstMeeting ? "" : commentFirstMeeting,
|
||||
comment,
|
||||
})
|
||||
)
|
||||
}
|
||||
// dispatch(
|
||||
// fetchVolunteerPartialAdd({
|
||||
// firstname,
|
||||
// lastname,
|
||||
// email,
|
||||
// mobile,
|
||||
// howToContact,
|
||||
// })
|
||||
// )
|
||||
|
||||
setSending(true)
|
||||
}
|
||||
@ -141,11 +119,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
let sendSuccess
|
||||
let sendError
|
||||
let sendingElement
|
||||
if (
|
||||
!postulantError &&
|
||||
!_.isEmpty(postulant) &&
|
||||
(potentialVolunteer || (!volunteerError && !_.isEmpty(volunteer)))
|
||||
) {
|
||||
if (!postulantError && !_.isEmpty(postulant)) {
|
||||
if (sending) {
|
||||
setSending(false)
|
||||
}
|
||||
@ -181,11 +155,11 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
<dd>
|
||||
<p>
|
||||
Un festival en plein air dédié aux <b>jeux de société modernes</b> sous toutes
|
||||
leurs formes. Les samedi 2 et dimanche 3 juillet 2022 !
|
||||
leurs formes. Les samedi 24 et dimanche 25 juin 2023 !
|
||||
</p>
|
||||
<p>
|
||||
En 2019 lors de la dernière édition, ce sont <b>16 000</b> joueurs qui se sont
|
||||
réunis sous 300 chapiteaux et 2 000 tables.
|
||||
En 2022, ce sont <b>18 000</b> visiteurs qui sont venus sous 300 chapiteaux et 2
|
||||
000 tables.
|
||||
</p>
|
||||
<p>
|
||||
Les 2 jours que durent le festival sont entièrement dédiés à ce que le public{" "}
|
||||
@ -211,7 +185,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
toute l'année ^^
|
||||
</p>
|
||||
<p>
|
||||
Pendant le festival de 2019, nous étions <b>187 bénévoles</b> organisés en
|
||||
Pendant le festival de 2022, nous étions <b>196 bénévoles</b> organisés en
|
||||
équipes qui chouchoutent les visiteurs en les accueillant, en s'assurant
|
||||
que tout se passe bien, ou encore en expliquant des règles de jeux.
|
||||
</p>
|
||||
@ -224,9 +198,9 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
<p>
|
||||
La majorité d'entre nous sommes bénévoles les <b>samedi et dimanche</b>, mais
|
||||
certains bénévoles ne sont pas disponibles les deux jours. On leur demande alors
|
||||
d'aider à la mise en place jeudi ou vendredi, ou au rangement le lundi, à la
|
||||
place d'un des jours du weekend. Bref, chacun participe comme il peut mais deux
|
||||
jours minimum !
|
||||
d'aider à la mise en place mercredi, jeudi ou vendredi, ou au rangement le
|
||||
lundi, à la place d'un des jours du weekend. Bref, chacun participe comme il
|
||||
peut mais <b>deux jours minimum</b> !
|
||||
</p>
|
||||
<p>
|
||||
Le samedi soir quand les visiteurs sont partis, nous prolongeons la fête en
|
||||
@ -238,8 +212,8 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
|
||||
{!enableRegistering && (
|
||||
<dt>
|
||||
L'inscription est clôturée pour l'édition 2022, mais si l'expérience vous tente,
|
||||
remplissez le formulaire suivant pour devenir bénévole à PeL 2023 !<br />
|
||||
L'inscription est clôturée pour l'édition 2023, mais si l'expérience te tente,
|
||||
remplis le formulaire suivant pour devenir bénévole à PeL 2024 !<br />
|
||||
Dès septembre on se rencontrera sur Paris en petits groupes pour discuter du
|
||||
festival, du bénévolat et surtout faire connaissance :)
|
||||
<br />
|
||||
@ -248,98 +222,15 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
|
||||
{enableRegistering && (
|
||||
<dt>
|
||||
Si l'expérience vous tente, remplissez le formulaire suivant pour devenir
|
||||
bénévole !<br />
|
||||
Vous pouvez aussi juste nous rencontrer avant de vous décider à devenir
|
||||
bénévole, on comprend qu'un saut pareil dans l'inconnu soit difficile.
|
||||
<br />
|
||||
Dans les deux cas, venez rencontrer une poignée d'entre nous dans un bar/resto
|
||||
près de Châtelet ! :) Sur inscription uniquement...
|
||||
Si tu as envie de donner le sourire à des milliers de gens, remplis le
|
||||
formulaire suivant pour rencontrer une poignée d'entre nous dans un bar/resto
|
||||
près de Châtelet, avant de te décider ou non à devenir bénévole :)
|
||||
<br />
|
||||
</dt>
|
||||
)}
|
||||
</dl>
|
||||
)
|
||||
|
||||
const potentialVolunteerQuestion = enableRegistering && (
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.leftCol}>
|
||||
<div className={styles.multipleChoiceTitle}>Je veux devenir bénévole :</div>
|
||||
</div>
|
||||
<div className={styles.rightCol}>
|
||||
{["Tout de suite !", "Peut-être après une rencontre avec des bénévoles"].map(
|
||||
(option) => (
|
||||
<label className={styles.longAnswerLabel} key={option}>
|
||||
<input
|
||||
type="radio"
|
||||
name="potentialVolunteer"
|
||||
onChange={sendBooleanRadioboxDispatch(
|
||||
setPotentialVolunteer,
|
||||
option !== "Tout de suite !"
|
||||
)}
|
||||
checked={potentialVolunteer === (option !== "Tout de suite !")}
|
||||
/>{" "}
|
||||
{option}
|
||||
</label>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const alreadyVolunteerQuestion = !potentialVolunteer && (
|
||||
<>
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.leftCol}>
|
||||
<div className={styles.multipleChoiceTitle}>J'ai déjà été bénévole à PeL</div>
|
||||
</div>
|
||||
<div className={styles.rightCol}>
|
||||
<div className={styles.rightColContainer}>
|
||||
{["Oui", "Non"].map((option) => (
|
||||
<label className={styles.shortAnswerLabel} key={option}>
|
||||
<input
|
||||
type="radio"
|
||||
name="alreadyVolunteer"
|
||||
onChange={sendBooleanRadioboxDispatch(
|
||||
setAlreadyVolunteer,
|
||||
option === "Oui"
|
||||
)}
|
||||
checked={alreadyVolunteer === (option === "Oui")}
|
||||
/>{" "}
|
||||
{option}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{alreadyVolunteer && (
|
||||
<dl className={styles.registerIntro}>
|
||||
<dd>
|
||||
<p>Dans ce cas pourquoi t'inscris-tu ici ? ^^</p>
|
||||
<p>
|
||||
Si tu te rappelles de l'email que tu avais utilisé à ta dernière
|
||||
inscription sur le site Force Orange des bénévoles (même sur l'ancienne
|
||||
version) tu peux{" "}
|
||||
<a href="/sidentifier" target="_blank" rel="noreferrer">
|
||||
t'identifier ici
|
||||
</a>{" "}
|
||||
avec ton ancien mot de passe, ou en{" "}
|
||||
<a href="/sinscrire" target="_blank" rel="noreferrer">
|
||||
demander un nouveau ici
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Autrement, si tu as changé d'email, mieux vaut nous le communiquer à
|
||||
benevoles@parisestludique.fr en précisant bien tes nom et prénom :)
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
const commentQuestion = (
|
||||
<dl className={styles.inputWrapper}>
|
||||
<dd className={styles.commentWrapper}>
|
||||
@ -448,10 +339,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
const meeting = enableRegistering && (
|
||||
<>
|
||||
<dl className={styles.registerIntro}>
|
||||
{!potentialVolunteer && <dt>Faisons connaissance !</dt>}
|
||||
{potentialVolunteer && (
|
||||
<dt>Se rencontrer avant de se décider à devenir bénévole ?</dt>
|
||||
)}
|
||||
<dt>Faisons connaissance !</dt>
|
||||
<dd>
|
||||
<p>
|
||||
On organise des rencontres entre de nouvelles personnes comme toi, et des
|
||||
@ -461,23 +349,13 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
<p>
|
||||
Ces rencontres ont lieu à 19h dans un bar/resto calme à Châtelet, le{" "}
|
||||
<a
|
||||
href="https://goo.gl/maps/N5NYWDF66vNQDFMh8"
|
||||
href="https://g.page/lerhinocerosparis?share"
|
||||
id="sfmMap"
|
||||
key="sfmMap"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Street Food Market
|
||||
</a>
|
||||
, ou à une soirée festive à 2 pas du lieu du festival, aux{" "}
|
||||
<a
|
||||
href="https://www.captainturtle.fr/aperos-petanque-paris/"
|
||||
id="petanque"
|
||||
key="petanque"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
apéros de la pétanque
|
||||
Rhinocéros
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
@ -535,11 +413,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
name="commentFirstMeeting"
|
||||
id="commentFirstMeeting"
|
||||
className={styles.inputWrapper}
|
||||
placeholder={
|
||||
potentialVolunteer
|
||||
? "Mince. Quelles dates t'arrangeraient ? Ou si c'est plus simple, quels jours sont à éviter ? Est-ce trop loin de chez toi ? Préfères-tu nous rencontrer en visio ?"
|
||||
: "Ce n'est pas obligé mais ça aurait été top ! Manques-tu de temps ? Préfères-tu une autre date ? Est-ce trop loin de chez toi ? Préfères-tu nous rencontrer en visio ?"
|
||||
}
|
||||
placeholder="Mince. Quelles dates t'arrangeraient ? Ou si c'est plus simple, quels jours sont à éviter ? Est-ce trop loin de chez toi ? Préfères-tu nous rencontrer en visio ?"
|
||||
value={commentFirstMeeting}
|
||||
onChange={sendTextareaDispatch(setCommentFirstMeeting)}
|
||||
/>
|
||||
@ -549,79 +423,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
</>
|
||||
)
|
||||
|
||||
const helpBefore = enableRegistering && !potentialVolunteer && (
|
||||
<>
|
||||
<dl className={styles.registerIntro}>
|
||||
<dt>Bénévolat en amont du festival</dt>
|
||||
<dd>
|
||||
<p>
|
||||
En tant que bénévole, tu fais selon tes envies, tes disponibilités, ton
|
||||
énergie. Si personne ne veut faire quelque chose de primordial pour le
|
||||
festival, on paye quelqu'un de l'extérieur. Par exemple le transport+montage
|
||||
des tentes+tables, ou la sécurité de nuit sont délégués à des prestataires.
|
||||
Et si ce quelque chose n'est pas primordiale et que personne ne veut s'en
|
||||
occuper, bah tant pis on le fait pas ^^
|
||||
</p>
|
||||
<p>
|
||||
Après on essaye de faire plein de choses sans aide extérieure. Pour le
|
||||
plaisir de collaborer à un projet entre bénévoles parfois devenus amis, pour
|
||||
acquérir de nouvelles compétences, parce que chaque économie d'argent fait
|
||||
baisser le prix d'entrée au festival et contribue à le rendre plus
|
||||
accessible.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.leftCol}>
|
||||
<div className={styles.multipleChoiceTitle}>
|
||||
Bref, as-tu le temps et l'envie de voir si tu peux aider en amont du
|
||||
festival ?
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.rightCol}>
|
||||
<div className={styles.rightColContainer}>
|
||||
{[
|
||||
{ value: "oui", desc: "Oui" },
|
||||
{ value: "non", desc: "Non" },
|
||||
{ value: "", desc: "Ne sais pas" },
|
||||
].map((option) => (
|
||||
<label className={styles.shortAnswerLabel} key={option.value}>
|
||||
<input
|
||||
type="radio"
|
||||
name="canHelpBefore"
|
||||
value={option.value}
|
||||
onChange={sendRadioboxDispatch(setCanHelpBefore)}
|
||||
checked={canHelpBefore === option.value}
|
||||
/>{" "}
|
||||
{option.desc}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<dl className={styles.registerIntro}>
|
||||
<dd>
|
||||
{canHelpBefore === "oui" && (
|
||||
<p>
|
||||
Génial ! Quand tu auras fini de t'inscrire et que tu seras identifié sur
|
||||
le site, nous t'en parlerons plus en détail.
|
||||
</p>
|
||||
)}
|
||||
{canHelpBefore === "non" && (
|
||||
<p>
|
||||
Aucun souci tu nous seras d'une aide précieuse le jour J c'est déjà
|
||||
énorme !
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
Si tu changes d'avis, il sera possible de revenir sur cette décision dans
|
||||
ton profil sur le site.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</>
|
||||
)
|
||||
|
||||
const pelMember = enableRegistering && (
|
||||
<>
|
||||
<dl className={styles.registerIntro}>
|
||||
@ -744,9 +545,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
const submitButton = (
|
||||
<>
|
||||
<div className={styles.buttonWrapper}>
|
||||
<FormButton onClick={onSubmit} disabled={!potentialVolunteer && alreadyVolunteer}>
|
||||
Envoyer
|
||||
</FormButton>
|
||||
<FormButton onClick={onSubmit}>Envoyer</FormButton>
|
||||
</div>
|
||||
<div className={styles.formReactions}>
|
||||
{sendingElement}
|
||||
@ -759,21 +558,13 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
return (
|
||||
<form>
|
||||
{intro}
|
||||
{potentialVolunteerQuestion}
|
||||
{alreadyVolunteerQuestion}
|
||||
|
||||
{(potentialVolunteer || !alreadyVolunteer) && (
|
||||
<>
|
||||
{enableRegistering && commentQuestion}
|
||||
{cameAsVisitor}
|
||||
{meeting}
|
||||
{helpBefore}
|
||||
{pelMember}
|
||||
{nameMobileEmail}
|
||||
{!enableRegistering && commentQuestion}
|
||||
{howToContact !== "Aucun" && submitButton}
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FC, memo } from "react"
|
||||
// import DayWishes from "./DayWishes/DayWishes"
|
||||
// import DayWishesFormModal from "./DayWishesForm/DayWishesFormModal"
|
||||
import DayWishes from "./DayWishes/DayWishes"
|
||||
import DayWishesFormModal from "./DayWishesForm/DayWishesFormModal"
|
||||
// import Hosting from "./Hosting/Hosting"
|
||||
// import HostingFormModal from "./HostingForm/HostingFormModal"
|
||||
// import Meals from "./Meals/Meals"
|
||||
@ -12,7 +12,7 @@ import { FC, memo } from "react"
|
||||
// import VolunteerTeam from "./VolunteerTeam/VolunteerTeam"
|
||||
import withUserConnected from "../../utils/withUserConnected"
|
||||
import ContentTitle from "../ui/Content/ContentTitle"
|
||||
// import { fetchFor as fetchForDayWishesForm } from "./DayWishesForm/DayWishesForm"
|
||||
import { fetchFor as fetchForDayWishesForm } from "./DayWishesForm/DayWishesForm"
|
||||
// import { fetchFor as fetchForHostingForm } from "./HostingForm/HostingForm"
|
||||
// import { fetchFor as fetchForMealsForm } from "./MealsForm/MealsForm"
|
||||
// import { fetchFor as fetchForParticipationDetailsForm } from "./ParticipationDetailsForm/ParticipationDetailsForm"
|
||||
@ -39,9 +39,9 @@ const Board: FC = (): JSX.Element => {
|
||||
{retex && <BrunchFormModal />}
|
||||
{retex && <Retex />}
|
||||
{retex && <RetexFormModal />}
|
||||
{/* <DayWishes />
|
||||
<DayWishes />
|
||||
<DayWishesFormModal />
|
||||
<ParticipationDetails />
|
||||
{/* <ParticipationDetails />
|
||||
<ParticipationDetailsFormModal />
|
||||
<TeamWishes />
|
||||
<TeamWishesFormModal />
|
||||
@ -60,7 +60,7 @@ export const fetchFor = [
|
||||
...fetchForPersonalInfoForm,
|
||||
...fetchForRetexForm,
|
||||
...fetchForBrunchForm,
|
||||
// ...fetchForDayWishesForm,
|
||||
...fetchForDayWishesForm,
|
||||
// ...fetchForHostingForm,
|
||||
// ...fetchForMealsForm,
|
||||
// ...fetchForParticipationDetailsForm,
|
||||
|
@ -27,7 +27,10 @@ const Brunch: FC<Props> = (): JSX.Element | null => {
|
||||
)}
|
||||
{question1 === 0 && <>Je ne viendrai pas au brunch </>}
|
||||
{question1 > 0 && (
|
||||
<>Je viendrai au brunch{question1 > 1 && <> avec {question1} personne(s)</>}</>
|
||||
<>
|
||||
Je viendrai au brunch
|
||||
{question1 > 1 && <> avec {+question1 - 1} personne(s)</>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.editButton}>
|
||||
|
@ -18,22 +18,22 @@ const DayWishes: FC = (): JSX.Element | null => {
|
||||
<div className={styles.title}>Mes présences</div>
|
||||
{participation === "non" && (
|
||||
<div className={styles.participationLabel}>
|
||||
Je <b>ne participerai pas</b> à PeL 2022 :(
|
||||
Je <b>ne participerai pas</b> à PeL 2023 :(
|
||||
</div>
|
||||
)}
|
||||
{participation === "oui" && (
|
||||
<div className={styles.participationLabel}>
|
||||
Je <b className={styles.yesParticipation}>participerai</b> à PeL 2022 !
|
||||
Je <b className={styles.yesParticipation}>participerai</b> à PeL 2023 !
|
||||
</div>
|
||||
)}
|
||||
{participation === "peut-etre" && (
|
||||
<div className={styles.participationLabel}>
|
||||
Je <b>ne sais pas encore</b> si je participerai à PeL 2022
|
||||
Je <b>ne sais pas encore</b> si je participerai à PeL 2023
|
||||
</div>
|
||||
)}
|
||||
{participation === "inconnu" && (
|
||||
<div className={styles.lineEmpty}>
|
||||
Participation à PeL 2022{" "}
|
||||
Participation à PeL 2023{" "}
|
||||
<span className={styles.lineEmpty}>non renseignées</span>
|
||||
</div>
|
||||
)}
|
||||
|
@ -72,7 +72,7 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
|
||||
<div className={styles.leftCol}>
|
||||
<div className={styles.participationTitle}>
|
||||
Si les conditions sanitaires te le permettent, souhaites-tu être bénévole à
|
||||
PeL 2022 ?
|
||||
PeL les 24 & 25 juin 2023 ?
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.rightCol}>
|
||||
@ -124,6 +124,8 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
|
||||
<div className={styles.leftCol}>
|
||||
<div className={styles.dayWishesTitle}>
|
||||
Quels jours viendras-tu ?<br />
|
||||
Il est tôt pour le dire, tu peux y repondre plus tard sur la page profil.
|
||||
<br />
|
||||
(Minimum 2 jours dont l'un sera samedi ou dimanche, idéalement samedi{" "}
|
||||
<b>et</b> dimanche ^^)
|
||||
</div>
|
||||
|
@ -8,8 +8,10 @@ import DayWishesForm, {
|
||||
import ErrorBoundary from "./ErrorBoundary"
|
||||
import GameList from "./GameList"
|
||||
import Loading from "./Loading"
|
||||
import LoanBoxList, { fetchFor as fetchForLoan } from "./Loan/LoanBoxList"
|
||||
import LoanIntro from "./Loan/LoanIntro"
|
||||
import LoaningIntro from "./Loan/LoaningIntro"
|
||||
import Loaning, { fetchFor as fetchForLoaning } from "./Loan/Loaning"
|
||||
import LoansIntro from "./Loan/LoansIntro"
|
||||
import Loans, { fetchFor as fetchForLoans } from "./Loan/Loans"
|
||||
import LoginForm from "./LoginForm"
|
||||
import KnowledgeBoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/KnowledgeBoxList"
|
||||
import KnowledgeCard, { fetchFor as fetchForKnowledgeCard } from "./Knowledge/KnowledgeCard"
|
||||
@ -45,9 +47,12 @@ export {
|
||||
fetchForKnowledge,
|
||||
KnowledgeIntro,
|
||||
Loading,
|
||||
LoanBoxList,
|
||||
fetchForLoan,
|
||||
LoanIntro,
|
||||
LoaningIntro,
|
||||
Loaning,
|
||||
fetchForLoaning,
|
||||
LoansIntro,
|
||||
Loans,
|
||||
fetchForLoans,
|
||||
LoginForm,
|
||||
Asks,
|
||||
fetchForAsks,
|
||||
|
33
src/pages/Loaning/LoaningPage.tsx
Normal file
33
src/pages/Loaning/LoaningPage.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { FC, memo } from "react"
|
||||
import { useSelector } from "react-redux"
|
||||
import { RouteComponentProps } from "react-router-dom"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import { AppThunk } from "../../store"
|
||||
import styles from "./styles.module.scss"
|
||||
import { LoaningIntro, Loaning, fetchForLoaning } from "../../components"
|
||||
import { selectUserJwtToken } from "../../store/auth"
|
||||
|
||||
export type Props = RouteComponentProps
|
||||
|
||||
const LoaningPage: 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 (
|
||||
<div className={styles.loaningPage}>
|
||||
<div className={styles.loaningContent}>
|
||||
<Helmet title="LoaningPage" />
|
||||
<LoaningIntro />
|
||||
<Loaning />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch server-side data here
|
||||
export const loadData = (): AppThunk[] => [...fetchForLoaning.map((f) => f())]
|
||||
|
||||
export default memo(LoaningPage)
|
16
src/pages/Loaning/index.tsx
Executable file
16
src/pages/Loaning/index.tsx
Executable file
@ -0,0 +1,16 @@
|
||||
import loadable from "@loadable/component"
|
||||
|
||||
import { Loading, ErrorBoundary } from "../../components"
|
||||
import { Props, loadData } from "./LoaningPage"
|
||||
|
||||
const Loaning = loadable(() => import("./LoaningPage"), {
|
||||
fallback: <Loading />,
|
||||
})
|
||||
|
||||
export default (props: Props): JSX.Element => (
|
||||
<ErrorBoundary>
|
||||
<Loaning {...props} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
||||
export { loadData }
|
@ -1,9 +1,13 @@
|
||||
@import "../../theme/mixins";
|
||||
|
||||
.loanPage {
|
||||
.loaningPage {
|
||||
@include page-wrapper-center;
|
||||
}
|
||||
|
||||
.loanContent {
|
||||
.loaningContent {
|
||||
@include page-content-wrapper(700px);
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
@ -5,29 +5,29 @@ import { Helmet } from "react-helmet"
|
||||
|
||||
import { AppThunk } from "../../store"
|
||||
import styles from "./styles.module.scss"
|
||||
import { LoanBoxList, LoanIntro, fetchForLoan } from "../../components"
|
||||
import { LoansIntro, Loans, fetchForLoans } from "../../components"
|
||||
import { selectUserJwtToken } from "../../store/auth"
|
||||
|
||||
export type Props = RouteComponentProps
|
||||
|
||||
const LoanPage: FC<Props> = (): JSX.Element => {
|
||||
const LoansPage: 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 (
|
||||
<div className={styles.loanPage}>
|
||||
<div className={styles.loanContent}>
|
||||
<Helmet title="LoanPage" />
|
||||
<LoanIntro />
|
||||
<LoanBoxList />
|
||||
<div className={styles.loaningPage}>
|
||||
<div className={styles.loaningContent}>
|
||||
<Helmet title="LoansPage" />
|
||||
<LoansIntro />
|
||||
<Loans />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch server-side data here
|
||||
export const loadData = (): AppThunk[] => [...fetchForLoan.map((f) => f())]
|
||||
export const loadData = (): AppThunk[] => [...fetchForLoans.map((f) => f())]
|
||||
|
||||
export default memo(LoanPage)
|
||||
export default memo(LoansPage)
|
@ -1,15 +1,15 @@
|
||||
import loadable from "@loadable/component"
|
||||
|
||||
import { Loading, ErrorBoundary } from "../../components"
|
||||
import { Props, loadData } from "./LoanPage"
|
||||
import { Props, loadData } from "./LoansPage"
|
||||
|
||||
const Loan = loadable(() => import("./LoanPage"), {
|
||||
const Loans = loadable(() => import("./LoansPage"), {
|
||||
fallback: <Loading />,
|
||||
})
|
||||
|
||||
export default (props: Props): JSX.Element => (
|
||||
<ErrorBoundary>
|
||||
<Loan {...props} />
|
||||
<Loans {...props} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
13
src/pages/Loans/styles.module.scss
Executable file
13
src/pages/Loans/styles.module.scss
Executable file
@ -0,0 +1,13 @@
|
||||
@import "../../theme/mixins";
|
||||
|
||||
.loaningPage {
|
||||
@include page-wrapper-center;
|
||||
}
|
||||
|
||||
.loaningContent {
|
||||
@include page-content-wrapper(700px);
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
@ -10,7 +10,8 @@ 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 AsyncLoan, { loadData as loadLoanData } from "../pages/Loan"
|
||||
import AsyncLoans, { loadData as loadLoansData } from "../pages/Loans"
|
||||
import AsyncLoaning, { loadData as loadLoaningData } from "../pages/Loaning"
|
||||
import AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards"
|
||||
import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams"
|
||||
import AsyncBoard, { loadData as loadBoardData } from "../pages/Board"
|
||||
@ -47,8 +48,13 @@ export default [
|
||||
},
|
||||
{
|
||||
path: "/emprunts",
|
||||
component: AsyncLoan,
|
||||
loadData: loadLoanData,
|
||||
component: AsyncLoans,
|
||||
loadData: loadLoansData,
|
||||
},
|
||||
{
|
||||
path: "/emprunter",
|
||||
component: AsyncLoaning,
|
||||
loadData: loadLoaningData,
|
||||
},
|
||||
{
|
||||
path: "/fiches",
|
||||
|
@ -16,7 +16,7 @@ export const detailedBoxListGet = expressAccessor.get(async (list) => {
|
||||
const toBeAsked: DetailedBox[] = []
|
||||
|
||||
gameList.forEach((game) => {
|
||||
const box: Box | undefined = list.find((g) => g.gameId === game.id)
|
||||
const box: Box | undefined = list.find((b) => b.gameId === game.id)
|
||||
if ((box && box.unplayable) || (!box && !game.toBeKnown)) {
|
||||
return
|
||||
}
|
||||
|
@ -1,8 +1,24 @@
|
||||
import axios from "axios"
|
||||
import { Parser } from "xml2js"
|
||||
import { assign, cloneDeep, find } from "lodash"
|
||||
import {
|
||||
assign,
|
||||
cloneDeep,
|
||||
every,
|
||||
find,
|
||||
groupBy,
|
||||
last,
|
||||
mapValues,
|
||||
reduce,
|
||||
some,
|
||||
sortBy,
|
||||
} from "lodash"
|
||||
import ExpressAccessors from "./expressAccessors"
|
||||
import { Game, GameWithoutId, translationGame } from "../../services/games"
|
||||
import { getSheet } from "./accessors"
|
||||
import { Game, GameWithoutId, GameWithVolunteers, translationGame } from "../../services/games"
|
||||
import { translationVolunteer, Volunteer, VolunteerWithoutId } from "../../services/volunteers"
|
||||
import { translationBox, Box, BoxWithoutId } from "../../services/boxes"
|
||||
import { getUniqueNickname } from "./tools"
|
||||
import { gameTitleCategory, gameTitleExactCategory, gameTitleOrder } from "../../store/utils"
|
||||
|
||||
const expressAccessor = new ExpressAccessors<GameWithoutId, Game>(
|
||||
"Games",
|
||||
@ -10,7 +26,7 @@ const expressAccessor = new ExpressAccessors<GameWithoutId, Game>(
|
||||
translationGame
|
||||
)
|
||||
|
||||
export const gameListGet = expressAccessor.listGet()
|
||||
// export const gameListGet = expressAccessor.listGet()
|
||||
// export const gameGet = expressAccessor.get()
|
||||
// export const gameAdd = expressAccessor.add()
|
||||
// export const gameSet = expressAccessor.set()
|
||||
@ -18,7 +34,7 @@ export const gameListGet = expressAccessor.listGet()
|
||||
export const gameDetailsUpdate = expressAccessor.listSet(
|
||||
async (list, _body, _id, _roles, request) => {
|
||||
request.setTimeout(500000)
|
||||
const newList = cloneDeep(list)
|
||||
const newList: Game[] = cloneDeep(list)
|
||||
|
||||
const data = await getData()
|
||||
const parser = new Parser()
|
||||
@ -98,3 +114,214 @@ async function getData(): Promise<string> {
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export const gameListGet = expressAccessor.get(async (list) => {
|
||||
const boxSheet = await getSheet<BoxWithoutId, Box>("Boxes", new Box(), translationBox)
|
||||
|
||||
const boxList = await boxSheet.getList()
|
||||
if (!boxList) {
|
||||
throw Error("Unable to load boxList")
|
||||
}
|
||||
|
||||
const toBeAsked: Game[] = []
|
||||
|
||||
list.forEach((game) => {
|
||||
const box: Box | undefined = boxList.find((b) => b.gameId === game.id)
|
||||
if (!box) {
|
||||
return
|
||||
}
|
||||
toBeAsked.push({
|
||||
id: game.id,
|
||||
title: game.title,
|
||||
bggId: game.bggId,
|
||||
bggIdAlternative: game.bggIdAlternative,
|
||||
bggPhoto: game.bggPhoto,
|
||||
poufpaf: game.poufpaf,
|
||||
playersMin: game.playersMin,
|
||||
playersMax: game.playersMax,
|
||||
duration: game.duration,
|
||||
type: game.type,
|
||||
} as Game)
|
||||
})
|
||||
|
||||
return toBeAsked
|
||||
})
|
||||
|
||||
export const gameWithVolunteersListGet = expressAccessor.get(
|
||||
async (list, _body, id): Promise<GameWithVolunteers[]> => {
|
||||
if (id <= 0) {
|
||||
throw Error(`L'accès est réservé aux utilisateurs identifiés`)
|
||||
}
|
||||
const volunteerSheet = await getSheet<VolunteerWithoutId, Volunteer>(
|
||||
"Volunteers",
|
||||
new Volunteer(),
|
||||
translationVolunteer
|
||||
)
|
||||
const volunteerList = await volunteerSheet.getList()
|
||||
if (!volunteerList) {
|
||||
throw Error("Unable to load volunteers")
|
||||
}
|
||||
|
||||
const currentVolunteer: Volunteer | undefined = volunteerList.find((v) => v.id === id)
|
||||
if (!currentVolunteer) {
|
||||
throw Error(`Unknown volunteer ${id}`)
|
||||
}
|
||||
|
||||
const boxSheet = await getSheet<BoxWithoutId, Box>("Boxes", new Box(), translationBox)
|
||||
const boxList = await boxSheet.getList()
|
||||
if (!boxList) {
|
||||
throw Error("Unable to load boxes")
|
||||
}
|
||||
|
||||
const giftGameIds = list
|
||||
.filter((game) =>
|
||||
every(
|
||||
volunteerList,
|
||||
(v) => !v.loanable.includes(game.id) && !v.playable.includes(game.id)
|
||||
)
|
||||
)
|
||||
.map((game) => game.id)
|
||||
|
||||
const gamesToLoan = list
|
||||
.filter((game) => currentVolunteer.loanable.includes(game.id))
|
||||
.map((game) => ({
|
||||
...cloneDeep(game),
|
||||
volunteerNicknames: volunteerToNicknameList(
|
||||
volunteerList.filter((v) => v.loanable.includes(game.id)),
|
||||
volunteerList
|
||||
),
|
||||
toLoan: true,
|
||||
boxCount: boxList.filter((b) => b.gameId === game.id).length,
|
||||
}))
|
||||
|
||||
const gamesToGift = list
|
||||
.filter(
|
||||
(game) =>
|
||||
giftGameIds.includes(game.id) && currentVolunteer.loanable.includes(game.id)
|
||||
)
|
||||
.map((game) => ({
|
||||
...cloneDeep(game),
|
||||
volunteerNicknames: volunteerToNicknameList(
|
||||
volunteerList.filter((v) => v.loanable.includes(game.id)),
|
||||
volunteerList
|
||||
),
|
||||
toLoan: false,
|
||||
boxCount: boxList.filter((b) => b.gameId === game.id).length,
|
||||
}))
|
||||
|
||||
return [...gamesToLoan, ...gamesToGift]
|
||||
}
|
||||
)
|
||||
|
||||
function volunteerToNicknameList(volunteers: Volunteer[], allVolunteers: Volunteer[]): string[] {
|
||||
return volunteers
|
||||
.map((v) => getUniqueNickname(allVolunteers, v))
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
}
|
||||
|
||||
export const gamesToGiveListGet = expressAccessor.get(async (list): Promise<string[]> => {
|
||||
const volunteerSheet = await getSheet<VolunteerWithoutId, Volunteer>(
|
||||
"Volunteers",
|
||||
new Volunteer(),
|
||||
translationVolunteer
|
||||
)
|
||||
const volunteerList = await volunteerSheet.getList()
|
||||
if (!volunteerList) {
|
||||
throw Error("Unable to load volunteers")
|
||||
}
|
||||
|
||||
const boxSheet = await getSheet<BoxWithoutId, Box>("Boxes", new Box(), translationBox)
|
||||
const boxList = await boxSheet.getList()
|
||||
if (!boxList) {
|
||||
throw Error("Unable to load boxes")
|
||||
}
|
||||
|
||||
volunteerList[105].playable = []
|
||||
|
||||
const giftGameTitles = list
|
||||
.filter((game) =>
|
||||
every(
|
||||
volunteerList,
|
||||
(v) => !v.loanable.includes(game.id) && !v.playable.includes(game.id)
|
||||
)
|
||||
)
|
||||
.map((game) => game.title)
|
||||
|
||||
return giftGameTitles
|
||||
})
|
||||
type GameCategory = { start: string; end: string; titles: string[] }
|
||||
export const gameTitleOrderCategories = expressAccessor.get(
|
||||
async (list): Promise<GameCategory[]> => {
|
||||
const volunteerSheet = await getSheet<VolunteerWithoutId, Volunteer>(
|
||||
"Volunteers",
|
||||
new Volunteer(),
|
||||
translationVolunteer
|
||||
)
|
||||
const volunteerList = await volunteerSheet.getList()
|
||||
if (!volunteerList) {
|
||||
throw Error("Unable to load volunteers")
|
||||
}
|
||||
|
||||
const boxSheet = await getSheet<BoxWithoutId, Box>("Boxes", new Box(), translationBox)
|
||||
const boxList = await boxSheet.getList()
|
||||
if (!boxList) {
|
||||
throw Error("Unable to load boxes")
|
||||
}
|
||||
|
||||
volunteerList[105].playable = []
|
||||
|
||||
const giftGameIds = list
|
||||
.filter((game) =>
|
||||
every(
|
||||
volunteerList,
|
||||
(v) => !v.loanable.includes(game.id) && !v.playable.includes(game.id)
|
||||
)
|
||||
)
|
||||
.map((game) => game.id)
|
||||
|
||||
const gamesToLoan = sortBy(
|
||||
list.filter((game) => !giftGameIds.includes(game.id)).map((game) => cloneDeep(game)),
|
||||
gameTitleOrder
|
||||
)
|
||||
|
||||
const exaustiveCats = mapValues(groupBy(gamesToLoan, gameTitleCategory), (gameList) =>
|
||||
gameList.map((game) => game.title)
|
||||
)
|
||||
|
||||
const cats: GameCategory[] = reduce(
|
||||
Object.entries(exaustiveCats),
|
||||
(res, [exaustiveCat, catTitles]) => {
|
||||
const prevCat = last(res)
|
||||
const prevCount = prevCat?.titles.length || 0
|
||||
if (prevCount === 0 || prevCount + catTitles.length > 10) {
|
||||
const newCat = {
|
||||
start: exaustiveCat,
|
||||
end: exaustiveCat,
|
||||
titles: [...catTitles],
|
||||
}
|
||||
res.push(newCat)
|
||||
} else if (prevCat !== undefined) {
|
||||
prevCat.end = exaustiveCat
|
||||
catTitles.forEach((title) => prevCat.titles.push(title))
|
||||
}
|
||||
return res
|
||||
},
|
||||
[] as GameCategory[]
|
||||
)
|
||||
|
||||
const catList: string[] = cats.map((cat) => `${cat.start}-${cat.end}`)
|
||||
|
||||
console.log(catList.join("\n"))
|
||||
|
||||
const availableGiftIds = giftGameIds.filter((id) =>
|
||||
some(volunteerList, (v: Volunteer) => v.giftable.includes(id))
|
||||
)
|
||||
console.log("availableGiftIds", availableGiftIds)
|
||||
|
||||
gamesToLoan.forEach((g) => gameTitleExactCategory(g))
|
||||
|
||||
return cats
|
||||
}
|
||||
)
|
||||
|
||||
// _.mapValues(list, cat => cat.length)
|
||||
|
@ -1,9 +1,10 @@
|
||||
// eslint-disable-next-line max-classes-per-file
|
||||
import path from "path"
|
||||
import _ from "lodash"
|
||||
import _, { assign } from "lodash"
|
||||
import { promises as fs } from "fs"
|
||||
import { Volunteer } from "../../services/volunteers"
|
||||
import { Postulant } from "../../services/postulants"
|
||||
import { Retex } from "../../services/retex"
|
||||
|
||||
const DB_PATH = path.resolve(process.cwd(), "access/db.json")
|
||||
const DB_TO_LOAD_PATH = path.resolve(process.cwd(), "access/dbToLoad.json")
|
||||
@ -275,6 +276,11 @@ function anonimizedDb(_s: States): States {
|
||||
v.comment = v.id % 3 === 0 ? "Bonjour, j'adore l'initiative!" : ""
|
||||
})
|
||||
}
|
||||
if (s.Retex) {
|
||||
;(s.Retex as Retex[]).forEach((r) => {
|
||||
assign(r, new Retex(), { id: r.id, dayWishes: r.dayWishes })
|
||||
})
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
|
16
src/server/gsheets/tools.ts
Normal file
16
src/server/gsheets/tools.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { remove } from "lodash"
|
||||
import { Volunteer } from "../../services/volunteers"
|
||||
|
||||
export 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
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import path from "path"
|
||||
import * as fs from "fs"
|
||||
import { assign, cloneDeep, max, omit, pick, remove } from "lodash"
|
||||
import { assign, cloneDeep, max, omit, pick } from "lodash"
|
||||
import bcrypt from "bcrypt"
|
||||
import sgMail from "@sendgrid/mail"
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
} from "../../services/volunteers"
|
||||
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
|
||||
import { getJwt } from "../secure"
|
||||
import { getUniqueNickname } from "./tools"
|
||||
|
||||
const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>(
|
||||
"Volunteers",
|
||||
@ -577,17 +578,3 @@ export const volunteerLoanSet = expressAccessor.set(async (list, body, id) => {
|
||||
} as VolunteerLoan,
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -19,7 +19,13 @@ import certbotRouter from "../routes/certbot"
|
||||
import { hasSecret, secure } from "./secure"
|
||||
import { announcementListGet } from "./gsheets/announcements"
|
||||
import { detailedBoxListGet } from "./gsheets/boxes"
|
||||
import { gameListGet, gameDetailsUpdate } from "./gsheets/games"
|
||||
import {
|
||||
gameListGet,
|
||||
gameDetailsUpdate,
|
||||
gameWithVolunteersListGet,
|
||||
gamesToGiveListGet,
|
||||
gameTitleOrderCategories,
|
||||
} from "./gsheets/games"
|
||||
import { postulantAdd } from "./gsheets/postulants"
|
||||
import { teamListGet } from "./gsheets/teams"
|
||||
import {
|
||||
@ -98,6 +104,8 @@ app.get(
|
||||
app.get("/GameDetailsUpdate", gameDetailsUpdate)
|
||||
app.get("/BoxDetailedListGet", detailedBoxListGet)
|
||||
app.get("/GameListGet", gameListGet)
|
||||
app.get("/GamesToGiveListGet", gamesToGiveListGet)
|
||||
app.get("/GameTitleOrderCategories", gameTitleOrderCategories)
|
||||
app.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
|
||||
app.get("/WishListGet", wishListGet)
|
||||
app.post("/WishAdd", wishAdd)
|
||||
@ -109,6 +117,7 @@ app.get("/VolunteerListGet", secure as RequestHandler, volunteerListGet)
|
||||
|
||||
// Secured APIs
|
||||
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
|
||||
app.get("/GameWithVolunteersListGet", secure as RequestHandler, gameWithVolunteersListGet)
|
||||
app.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation)
|
||||
app.post("/RetexSet", secure as RequestHandler, retexSet)
|
||||
app.get("/TeamListGet", teamListGet)
|
||||
|
@ -90,6 +90,37 @@ export default class ServiceAccessors<
|
||||
}
|
||||
}
|
||||
|
||||
securedCustomListGet<InputElements extends Array<any>, OutputType>(
|
||||
apiName: string
|
||||
): (
|
||||
jwt: string,
|
||||
...params: InputElements
|
||||
) => Promise<{
|
||||
data?: any
|
||||
error?: Error
|
||||
}> {
|
||||
interface ElementGetResponse {
|
||||
data?: any
|
||||
error?: Error
|
||||
}
|
||||
return async (jwt: string, ...params: InputElements): Promise<ElementGetResponse> => {
|
||||
try {
|
||||
const auth = { headers: { Authorization: `Bearer ${jwt}` } }
|
||||
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
|
||||
const { data } = await axios.get(
|
||||
`${config.API_URL}/${this.elementName}${apiName}`,
|
||||
{ ...fullAxiosConfig, params }
|
||||
)
|
||||
if (data.error) {
|
||||
throw Error(data.error)
|
||||
}
|
||||
return { data } as { data: OutputType }
|
||||
} catch (error) {
|
||||
return { error: error as Error }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
add(): (volunteerWithoutId: ElementNoId) => Promise<{
|
||||
data?: Element
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
export class Game {
|
||||
id = 0
|
||||
|
||||
@ -26,6 +27,14 @@ export class Game {
|
||||
toBeKnown = false
|
||||
}
|
||||
|
||||
export class GameWithVolunteers extends Game {
|
||||
volunteerNicknames: string[] = []
|
||||
|
||||
toLoan = true
|
||||
|
||||
boxCount = 0
|
||||
}
|
||||
|
||||
export const translationGame: { [k in keyof Game]: string } = {
|
||||
id: "id",
|
||||
title: "titre",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import ServiceAccessors from "./accessors"
|
||||
import { elementName, Game, GameWithoutId } from "./games"
|
||||
import { elementName, Game, GameWithVolunteers, GameWithoutId } from "./games"
|
||||
|
||||
const serviceAccessors = new ServiceAccessors<GameWithoutId, Game>(elementName)
|
||||
|
||||
@ -9,3 +9,8 @@ export const gameListGet = serviceAccessors.listGet()
|
||||
// export const gameSet = serviceAccessors.set()
|
||||
|
||||
export const gameDetailsUpdate = serviceAccessors.securedCustomGet<[], Game[]>("DetailsUpdate")
|
||||
|
||||
export const gameWithVolunteersListGet = serviceAccessors.securedCustomListGet<
|
||||
[],
|
||||
GameWithVolunteers[]
|
||||
>("WithVolunteersListGet")
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PayloadAction, createSlice, createEntityAdapter, createSelector } from "@reduxjs/toolkit"
|
||||
import { sortedUniqBy, sortBy } from "lodash"
|
||||
|
||||
import { StateRequest, toastError, elementListFetch } from "./utils"
|
||||
import { StateRequest, toastError, elementListFetch, gameTitleOrder } from "./utils"
|
||||
import { DetailedBox } from "../services/boxes"
|
||||
import { AppThunk, AppState, EntitiesRequest } from "."
|
||||
import { detailedBoxListGet } from "../services/boxesAccessors"
|
||||
@ -59,9 +59,10 @@ export const selectBoxList = createSelector(
|
||||
}
|
||||
)
|
||||
|
||||
export const selectSortedUniqueDetailedBoxes = createSelector(selectBoxList, (boxes) =>
|
||||
sortedUniqBy(sortBy(boxes, "title"), "title")
|
||||
)
|
||||
export const selectSortedUniqueDetailedBoxes = createSelector(selectBoxList, (boxes) => {
|
||||
const validBoxes = boxes.filter((box) => box) as DetailedBox[]
|
||||
return sortedUniqBy(sortBy(validBoxes, gameTitleOrder), gameTitleOrder)
|
||||
})
|
||||
|
||||
export const selectContainerSortedDetailedBoxes = createSelector(selectBoxList, (boxes) =>
|
||||
sortBy(boxes, "container")
|
||||
|
73
src/store/gameWithVolunteersList.ts
Normal file
73
src/store/gameWithVolunteersList.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { PayloadAction, createSlice, createEntityAdapter, createSelector } from "@reduxjs/toolkit"
|
||||
import { sortedUniqBy, sortBy } from "lodash"
|
||||
|
||||
import { StateRequest, toastError, elementListFetch, gameTitleOrder } from "./utils"
|
||||
import { GameWithVolunteers } from "../services/games"
|
||||
import { AppThunk, AppState, EntitiesRequest } from "."
|
||||
import { gameWithVolunteersListGet } from "../services/gamesAccessors"
|
||||
|
||||
const gameAdapter = createEntityAdapter<GameWithVolunteers>()
|
||||
|
||||
export const initialState = gameAdapter.getInitialState({
|
||||
readyStatus: "idle",
|
||||
} as StateRequest)
|
||||
|
||||
const gameWithVolunteersList = createSlice({
|
||||
name: "gameWithVolunteersList",
|
||||
initialState,
|
||||
reducers: {
|
||||
getRequesting: (state) => {
|
||||
state.readyStatus = "request"
|
||||
},
|
||||
getSuccess: (state, { payload }: PayloadAction<GameWithVolunteers[]>) => {
|
||||
state.readyStatus = "success"
|
||||
gameAdapter.setAll(state, payload)
|
||||
},
|
||||
getFailure: (state, { payload }: PayloadAction<string>) => {
|
||||
state.readyStatus = "failure"
|
||||
state.error = payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default gameWithVolunteersList.reducer
|
||||
export const { getRequesting, getSuccess, getFailure } = gameWithVolunteersList.actions
|
||||
|
||||
export const fetchGameWithVolunteersList = elementListFetch(
|
||||
gameWithVolunteersListGet,
|
||||
getRequesting,
|
||||
getSuccess,
|
||||
getFailure,
|
||||
(error: Error) => toastError(`Erreur lors du chargement des jeux JAV: ${error.message}`)
|
||||
)
|
||||
|
||||
const shouldFetchGameWithVolunteersList = (state: AppState) =>
|
||||
state.gameWithVolunteersList.readyStatus !== "success"
|
||||
|
||||
export const fetchGameWithVolunteersListIfNeed = (): AppThunk => (dispatch, getState) => {
|
||||
const { jwt } = getState().auth
|
||||
if (shouldFetchGameWithVolunteersList(getState()))
|
||||
return dispatch(fetchGameWithVolunteersList(jwt))
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const selectGameWithVolunteersListState = (
|
||||
state: AppState
|
||||
): EntitiesRequest<GameWithVolunteers> => state.gameWithVolunteersList
|
||||
|
||||
export const selectGameWithVolunteersList = createSelector(
|
||||
selectGameWithVolunteersListState,
|
||||
({ ids, entities, readyStatus }) => {
|
||||
if (readyStatus !== "success") return []
|
||||
return ids.map((id) => entities[id])
|
||||
}
|
||||
)
|
||||
|
||||
export const selectSortedUniqueGamesWithVolunteers = createSelector(
|
||||
selectGameWithVolunteersList,
|
||||
(games) => {
|
||||
const gameWithVolunteers = games.filter((game) => game) as GameWithVolunteers[]
|
||||
return sortedUniqBy(sortBy(gameWithVolunteers, gameTitleOrder), gameTitleOrder)
|
||||
}
|
||||
)
|
@ -5,6 +5,7 @@ import announcementList from "./announcementList"
|
||||
import auth from "./auth"
|
||||
import boxList from "./boxList"
|
||||
import gameList from "./gameList"
|
||||
import gameWithVolunteersList from "./gameWithVolunteersList"
|
||||
import gameDetailsUpdate from "./gameDetailsUpdate"
|
||||
import miscDiscordInvitation from "./miscDiscordInvitation"
|
||||
import miscMeetingDateList from "./miscMeetingDateList"
|
||||
@ -39,6 +40,7 @@ export default (history: History) => ({
|
||||
auth,
|
||||
boxList,
|
||||
gameList,
|
||||
gameWithVolunteersList,
|
||||
gameDetailsUpdate,
|
||||
miscDiscordInvitation,
|
||||
miscMeetingDateList,
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
ActionCreatorWithPayload,
|
||||
ThunkDispatch,
|
||||
} from "@reduxjs/toolkit"
|
||||
import { find } from "lodash"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
import { AppState, AppThunk } from "."
|
||||
@ -143,3 +144,129 @@ export function elementValueFetch<Element>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function gameTitleOrder(boxOrGame: { title: string }): string {
|
||||
return boxOrGame.title
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.toLowerCase()
|
||||
.replace(/^[^a-z0-9]+/, "")
|
||||
.replace(/^(le |la |les |l'|the )/, "")
|
||||
.replace(/[^a-z0-9]/g, "")
|
||||
}
|
||||
|
||||
export function gameTitleCategory(boxOrGame: { title: string }, length = 3): string {
|
||||
return gameTitleOrder(boxOrGame)
|
||||
.substring(0, length)
|
||||
.replace(/(?<=[0-9]).+/, "")
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
export function gameTitleExactCategory(boxOrGame: { title: string }): string {
|
||||
const cats = [
|
||||
["1", "6"],
|
||||
["7", "AB"],
|
||||
["AC", "AK"],
|
||||
["ALA", "ALO"],
|
||||
["ALP", "AP"],
|
||||
["AQ", "ARG"],
|
||||
["ARH", "AT"],
|
||||
["AU", "AV"],
|
||||
["AW", "BAR"],
|
||||
["BAS", "BER"],
|
||||
["BES", "BLI"],
|
||||
["BLJ", "BRE"],
|
||||
["BRF", "CAC"],
|
||||
["CAD", "CAP"],
|
||||
["CAQ", "CEM"],
|
||||
["CEN", "CHIN"],
|
||||
["CHJ", "CK"],
|
||||
["CL"],
|
||||
["COA", "COL"],
|
||||
["COM"],
|
||||
["CON", "COP"],
|
||||
["COQ", "CRA"],
|
||||
["CRB", "DEB"],
|
||||
["DEC", "DES"],
|
||||
["DET", "DIC"],
|
||||
["DID", "DOM"],
|
||||
["DON", "DR"],
|
||||
["DS", "ELG"],
|
||||
["ELH", "EVE"],
|
||||
["EVD", "FAM"],
|
||||
["FAN", "FIC"],
|
||||
["FID", "FOL"],
|
||||
["FOM", "FUM"],
|
||||
["FUN", "GEE"],
|
||||
["GED", "GLA"],
|
||||
["GLB", "GQ"],
|
||||
["GR", "HAN"],
|
||||
["HAO", "HIM"],
|
||||
["HIN", "IL"],
|
||||
["IM", "IS"],
|
||||
["IT", "JE"],
|
||||
["JD", "KAB"],
|
||||
["KAC", "KEM"],
|
||||
["KEN", "KILL"],
|
||||
["KIM", "KOD"],
|
||||
["KOE", "LAB"],
|
||||
["LAC", "LIC"],
|
||||
["LID", "LIP"],
|
||||
["LIQ", "LOQ"],
|
||||
["LOR", "LZ"],
|
||||
["MAA", "MAN"],
|
||||
["MAO", "MED"],
|
||||
["MEE", "MIM"],
|
||||
["MINE", "MOR"],
|
||||
["MOS", "MYS"],
|
||||
["MYT", "NE"],
|
||||
["ND", "ODD"],
|
||||
["ODE", "ONE"],
|
||||
["ONF", "OU"],
|
||||
["OV", "PAN"],
|
||||
["PAO", "PEL"],
|
||||
["PEM", "PIO"],
|
||||
["PIP", "PLZ"],
|
||||
["PO", "PRI"],
|
||||
["PRJ", "PZ"],
|
||||
["QI", "RAT"],
|
||||
["RAU", "RI"],
|
||||
["RJ", "ROO"],
|
||||
["ROP", "SAL"],
|
||||
["SAM", "SEI"],
|
||||
["SEJ", "SIC"],
|
||||
["SID", "SL"],
|
||||
["SM", "SO"],
|
||||
["SPA", "SPX"],
|
||||
["SPY", "STE"],
|
||||
["STD", "SUN"],
|
||||
["SUO", "TAF"],
|
||||
["TAG", "TD"],
|
||||
["TE", "TIC"],
|
||||
["TID", "TIL"],
|
||||
["TIM"],
|
||||
["TIN", "TO"],
|
||||
["TP", "TRO"],
|
||||
["TRP", "UNE"],
|
||||
["UND", "VEM"],
|
||||
["VEN", "WAT"],
|
||||
["WAU", "WH"],
|
||||
["WI", "YE"],
|
||||
["YOGA", "ZOO"],
|
||||
]
|
||||
|
||||
const gameCat = gameTitleCategory(boxOrGame, 4)
|
||||
|
||||
const foundCat = find(cats, (cat) =>
|
||||
cat[1]
|
||||
? gameCat.substring(0, cat[0].length) >= cat[0] &&
|
||||
gameCat.substring(0, cat[1].length) <= `${cat[1]}`
|
||||
: gameCat.substring(0, cat[0].length) === cat[0]
|
||||
)
|
||||
|
||||
if (!foundCat) {
|
||||
console.log("no game found for ", foundCat, boxOrGame.title)
|
||||
}
|
||||
|
||||
return foundCat ? `${foundCat[0]}-${foundCat[1]}` : gameCat
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user