Add /emprunts and better BGG games parsing

This commit is contained in:
pikiou
2022-09-24 03:49:23 +02:00
parent 72a633ae4f
commit 2a1d2ef49b
72 changed files with 884 additions and 34 deletions

View File

@@ -8,7 +8,7 @@ import {
useVolunteerKnowledge,
} from "../../store/volunteerKnowledgeSet"
const BoxList: React.FC = (): JSX.Element | null => {
const KnowledgeBoxList: React.FC = (): JSX.Element | null => {
const detailedBoxes = useSelector(selectSortedUniqueDetailedBoxes)
const [volunteerKnowledge, saveVolunteerKnowledge] = useVolunteerKnowledge()
const [showUnknownOnly, setShowUnknownOnly] = useState(false)
@@ -53,6 +53,6 @@ const BoxList: React.FC = (): JSX.Element | null => {
)
}
export default memo(BoxList)
export default memo(KnowledgeBoxList)
export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerKnowledgeSetIfNeed]

View File

@@ -0,0 +1,156 @@
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)

View File

@@ -0,0 +1,56 @@
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"
const LoanBoxList: React.FC = (): JSX.Element | null => {
const detailedBoxes = 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) &&
!volunteerLoan.playable.includes(box.gameId) &&
!volunteerLoan.giftable.includes(box.gameId) &&
!volunteerLoan.noOpinion.includes(box.gameId))
)
return (
<div className={styles.loanThings}>
<label className={styles.showUnknownOnlyLabel}>
<input
type="checkbox"
name="showUnknownOnly"
onChange={onShowUnknownOnly}
checked={showUnknownOnly}
/>{" "}
Uniquement les non-renseignés
</label>
<ul className={styles.boxList}>
{boxesToShow.map((detailedBox: any) => (
<BoxItem
detailedBox={detailedBox}
volunteerLoan={volunteerLoan}
saveVolunteerLoan={saveVolunteerLoan}
key={detailedBox.id}
/>
))}
</ul>
</div>
)
}
export default memo(LoanBoxList)
export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerLoanSetIfNeed]

View File

@@ -0,0 +1,57 @@
import classnames from "classnames"
import React from "react"
import styles from "./styles.module.scss"
const LoanIntro: React.FC = (): JSX.Element => (
<div className={styles.loanThings}>
<h1>Emprunt et tri des jeux</h1>
<p>
Lors du brunch des bénévoles ayant contribué au dernier festival, des boîtes de jeux en
trop dans la ludothèque seront données, et des boîtes qui passe l'année dans la cave
entre deux éditions seront prêtées pour 4 mois.
</p>
<p>
Pour chaque jeu, active le bouton :<br />
<button
type="button"
className={classnames(styles.loanButton, styles.loanable, styles.loanableCheckbox)}
>
&nbsp;
</button>{" "}
si tu veux l'emprunter lors du brunch ou d'un rdv bénévoles mensuel
<br />
<button
type="button"
className={classnames(styles.loanButton, styles.playable, styles.playableCheckbox)}
>
&nbsp;
</button>{" "}
si tu penses que les visiteurs du festival (ou même les bénévoles) doivent pouvoir y
jouer
<br />
<button
type="button"
className={classnames(styles.loanButton, styles.giftable, styles.giftableCheckbox)}
>
&nbsp;
</button>{" "}
si tu aimerais le récupérer comme cadeau lors du brunch (ou rdv bénévole si tu ne peux
pas venir)
<br />
<button
type="button"
className={classnames(
styles.loanButton,
styles.noOpinion,
styles.noOpinionCheckbox
)}
>
&nbsp;
</button>{" "}
si tu n'as pas d'avis sur ce jeu
<br />
</p>
</div>
)
export default LoanIntro

View File

@@ -0,0 +1,292 @@
@import "../../theme/variables";
@import "../../theme/mixins";
.showUnknownOnlyLabel {
text-align: left;
display: inline-block;
margin-bottom: 10px;
width: 280px;
}
.boxList {
padding: 0;
list-style: none;
}
.boxItem {
padding: 10px 0;
}
.photoContainer {
height: 50px;
width: 73px;
position: relative;
display: inline-block;
vertical-align: middle;
}
.photo {
height: 50px;
max-width: 73px;
position: absolute;
left: 0;
}
.a371 {
background: url("../../app/img/gameImages/371.png") no-repeat;
}
.a888 {
background: url("../../app/img/gameImages/888.jpg") no-repeat;
}
.a918 {
background: url("../../app/img/gameImages/918.jpg") no-repeat;
}
.a992 {
background: url("../../app/img/gameImages/992.jpg") no-repeat;
}
.a993 {
background: url("../../app/img/gameImages/993.jpg") no-repeat;
}
.a994 {
background: url("../../app/img/gameImages/994.png") no-repeat;
}
.a995 {
background: url("../../app/img/gameImages/995.jpg") no-repeat;
}
.a996 {
background: url("../../app/img/gameImages/996.png") no-repeat;
}
.a997 {
background: url("../../app/img/gameImages/997.jpg") no-repeat;
}
.a998 {
background: url("../../app/img/gameImages/998.jpg") no-repeat;
}
.a999 {
background: url("../../app/img/gameImages/999.png") no-repeat;
}
.a1000 {
background: url("../../app/img/gameImages/1000.jpg") no-repeat;
}
.a1001 {
background: url("../../app/img/gameImages/1001.png") no-repeat;
}
.a1002 {
background: url("../../app/img/gameImages/1002.jpg") no-repeat;
}
.a1003 {
background: url("../../app/img/gameImages/1003.jpg") no-repeat;
}
.a1004 {
background: url("../../app/img/gameImages/1004.png") no-repeat;
}
.a1005 {
background: url("../../app/img/gameImages/1005.png") no-repeat;
}
.a1007 {
background: url("../../app/img/gameImages/1007.png") no-repeat;
}
.a1008 {
background: url("../../app/img/gameImages/1008.png") no-repeat;
}
.a1009 {
background: url("../../app/img/gameImages/1009.png") no-repeat;
}
.a1010 {
background: url("../../app/img/gameImages/1010.jpg") no-repeat;
}
.a1011 {
background: url("../../app/img/gameImages/1011.jpg") no-repeat;
}
.a1012 {
background: url("../../app/img/gameImages/1012.jpg") no-repeat;
}
.a1013 {
background: url("../../app/img/gameImages/1013.jpg") no-repeat;
}
.a1014 {
background: url("../../app/img/gameImages/1014.jpg") no-repeat;
}
.a1015 {
background: url("../../app/img/gameImages/1015.jpg") no-repeat;
}
.a1016 {
background: url("../../app/img/gameImages/1016.jpg") no-repeat;
}
.a1017 {
background: url("../../app/img/gameImages/1017.jpg") no-repeat;
}
.a1018 {
background: url("../../app/img/gameImages/1018.jpg") no-repeat;
}
.a1019 {
background: url("../../app/img/gameImages/1019.jpg") no-repeat;
}
.a1020 {
background: url("../../app/img/gameImages/1020.jpg") no-repeat;
}
.a1021 {
background: url("../../app/img/gameImages/1021.jpg") no-repeat;
}
.a1022 {
background: url("../../app/img/gameImages/1022.jpg") no-repeat;
}
.a1023 {
background: url("../../app/img/gameImages/1023.jpg") no-repeat;
}
.a1024 {
background: url("../../app/img/gameImages/1024.jpg") no-repeat;
}
.a1025 {
background: url("../../app/img/gameImages/1025.jpg") no-repeat;
}
.a1026 {
background: url("../../app/img/gameImages/1026.jpg") no-repeat;
}
.a1027 {
background: url("../../app/img/gameImages/1027.jpg") no-repeat;
}
.a1028 {
background: url("../../app/img/gameImages/1028.jpg") no-repeat;
}
.a1029 {
background: url("../../app/img/gameImages/1029.jpeg") no-repeat;
}
.a1030 {
background: url("../../app/img/gameImages/1030.jpeg") no-repeat;
}
.a1031 {
background: url("../../app/img/gameImages/1031.jpg") no-repeat;
}
.a1032 {
background: url("../../app/img/gameImages/1032.png") no-repeat;
}
.alternateBox {
vertical-align: middle;
display: inline-block;
width: inherit;
height: inherit;
background-size: contain;
}
.titleContainer {
height: 50px;
width: 198px;
position: relative;
display: inline-block;
vertical-align: middle;
}
.shorterTitle {
width: 168px;
}
.title {
height: 50px;
font-weight: bold;
display: flex;
align-items: center;
}
.poufpaf {
width: 30px;
height: 30px;
position: relative;
display: inline-block;
background: url("../../app/img/poufpaf.png");
background-size: contain;
vertical-align: middle;
}
.noPoufpaf {
display: none;
}
.loanList {
@include clear-ul-style;
width: 266px;
display: inline-block;
text-align: center;
vertical-align: middle;
}
.loanItem {
display: inline-block;
margin: 3px;
}
.loanButton {
margin: 0;
padding: 4px 0 3px;
border: 0;
border-radius: 0;
width: 60px;
text-align: center;
color: $color-grey-medium;
cursor: pointer;
&.active {
color: $color-yellow;
}
}
.loanCheckbox {
vertical-align: middle;
}
.loanCheckboxImg {
vertical-align: middle;
display: inline-block;
width: 2em;
height: 1.4em;
}
.loanThings {
position: relative;
}
.loanThings .loanable {
background-color: $color-loanable;
&.active {
background-color: $color-active-loanable;
}
}
.loanableCheckbox {
background: url("../../app/img/loanable.svg") no-repeat center center;
background-size: contain;
}
.loanThings .playable {
background-color: $color-playable;
&.active {
background-color: $color-active-playable;
}
}
.playableCheckbox {
background: url("../../app/img/playable.svg") no-repeat center center;
background-size: contain;
}
.loanThings .giftable {
background-color: $color-giftable;
&.active {
background-color: $color-active-giftable;
}
}
.giftableCheckbox {
background: url("../../app/img/giftable.svg") no-repeat center center;
background-size: contain;
}
.loanThings .noOpinion {
background-color: $color-no-opinion;
&.active {
background-color: $color-active-no-opinion;
}
}
.noOpinionCheckbox {
background: url("../../app/img/noOpinion.svg") no-repeat center center;
background-size: contain;
}

View File

@@ -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="Emprunts" pathname="/emprunts" />
{/* <MenuItem name="Mes connaissances" pathname="/connaissances" /> */}
<RestrictMenuItem
role={ROLES.ASSIGNER}

View File

@@ -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 LoginForm from "./LoginForm"
import BoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/BoxList"
import KnowledgeBoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/KnowledgeBoxList"
import KnowledgeCard, { fetchFor as fetchForKnowledgeCard } from "./Knowledge/KnowledgeCard"
import KnowledgeIntro from "./Knowledge/KnowledgeIntro"
import Asks, { fetchFor as fetchForAsks } from "./Asks"
@@ -33,16 +35,19 @@ export {
fetchForGameDetailsUpdate,
Board,
fetchForBoard,
BoxList,
fetchForKnowledge,
KnowledgeCard,
fetchForKnowledgeCard,
DayWishesForm,
fetchForDayWishesForm,
ErrorBoundary,
GameList,
KnowledgeBoxList,
fetchForKnowledge,
KnowledgeIntro,
Loading,
LoanBoxList,
fetchForLoan,
LoanIntro,
LoginForm,
Asks,
fetchForAsks,