diff --git a/src/app/img/gameImages/1000.jpg b/src/app/img/gameImages/1000.jpg new file mode 100644 index 0000000..4e0cea2 Binary files /dev/null and b/src/app/img/gameImages/1000.jpg differ diff --git a/src/app/img/gameImages/1001.png b/src/app/img/gameImages/1001.png new file mode 100644 index 0000000..9e70197 Binary files /dev/null and b/src/app/img/gameImages/1001.png differ diff --git a/src/app/img/gameImages/1002.jpg b/src/app/img/gameImages/1002.jpg new file mode 100644 index 0000000..f8399cc Binary files /dev/null and b/src/app/img/gameImages/1002.jpg differ diff --git a/src/app/img/gameImages/1003.jpg b/src/app/img/gameImages/1003.jpg new file mode 100644 index 0000000..1dbfa76 Binary files /dev/null and b/src/app/img/gameImages/1003.jpg differ diff --git a/src/app/img/gameImages/1004.png b/src/app/img/gameImages/1004.png new file mode 100644 index 0000000..29ef770 Binary files /dev/null and b/src/app/img/gameImages/1004.png differ diff --git a/src/app/img/gameImages/1005.png b/src/app/img/gameImages/1005.png new file mode 100644 index 0000000..ec51afb Binary files /dev/null and b/src/app/img/gameImages/1005.png differ diff --git a/src/app/img/gameImages/1007.png b/src/app/img/gameImages/1007.png new file mode 100644 index 0000000..e527e71 Binary files /dev/null and b/src/app/img/gameImages/1007.png differ diff --git a/src/app/img/gameImages/1008.png b/src/app/img/gameImages/1008.png new file mode 100644 index 0000000..c535d9e Binary files /dev/null and b/src/app/img/gameImages/1008.png differ diff --git a/src/app/img/gameImages/1009.png b/src/app/img/gameImages/1009.png new file mode 100644 index 0000000..d28d53c Binary files /dev/null and b/src/app/img/gameImages/1009.png differ diff --git a/src/app/img/gameImages/1010.jpg b/src/app/img/gameImages/1010.jpg new file mode 100644 index 0000000..5da6778 Binary files /dev/null and b/src/app/img/gameImages/1010.jpg differ diff --git a/src/app/img/gameImages/1011.jpg b/src/app/img/gameImages/1011.jpg new file mode 100644 index 0000000..0d93617 Binary files /dev/null and b/src/app/img/gameImages/1011.jpg differ diff --git a/src/app/img/gameImages/1012.jpg b/src/app/img/gameImages/1012.jpg new file mode 100644 index 0000000..9da4575 Binary files /dev/null and b/src/app/img/gameImages/1012.jpg differ diff --git a/src/app/img/gameImages/1013.jpg b/src/app/img/gameImages/1013.jpg new file mode 100644 index 0000000..579e3c3 Binary files /dev/null and b/src/app/img/gameImages/1013.jpg differ diff --git a/src/app/img/gameImages/1014.jpg b/src/app/img/gameImages/1014.jpg new file mode 100644 index 0000000..6daf4e3 Binary files /dev/null and b/src/app/img/gameImages/1014.jpg differ diff --git a/src/app/img/gameImages/1015.jpg b/src/app/img/gameImages/1015.jpg new file mode 100644 index 0000000..44c1ef0 Binary files /dev/null and b/src/app/img/gameImages/1015.jpg differ diff --git a/src/app/img/gameImages/1016.jpg b/src/app/img/gameImages/1016.jpg new file mode 100644 index 0000000..26d98be Binary files /dev/null and b/src/app/img/gameImages/1016.jpg differ diff --git a/src/app/img/gameImages/1017.jpg b/src/app/img/gameImages/1017.jpg new file mode 100644 index 0000000..d81719a Binary files /dev/null and b/src/app/img/gameImages/1017.jpg differ diff --git a/src/app/img/gameImages/1018.jpg b/src/app/img/gameImages/1018.jpg new file mode 100644 index 0000000..93d13ce Binary files /dev/null and b/src/app/img/gameImages/1018.jpg differ diff --git a/src/app/img/gameImages/1019.jpg b/src/app/img/gameImages/1019.jpg new file mode 100644 index 0000000..7de32e2 Binary files /dev/null and b/src/app/img/gameImages/1019.jpg differ diff --git a/src/app/img/gameImages/1020.jpg b/src/app/img/gameImages/1020.jpg new file mode 100644 index 0000000..381b3d8 Binary files /dev/null and b/src/app/img/gameImages/1020.jpg differ diff --git a/src/app/img/gameImages/1021.jpg b/src/app/img/gameImages/1021.jpg new file mode 100644 index 0000000..6c823ab Binary files /dev/null and b/src/app/img/gameImages/1021.jpg differ diff --git a/src/app/img/gameImages/1022.jpg b/src/app/img/gameImages/1022.jpg new file mode 100644 index 0000000..e83e842 Binary files /dev/null and b/src/app/img/gameImages/1022.jpg differ diff --git a/src/app/img/gameImages/1023.jpg b/src/app/img/gameImages/1023.jpg new file mode 100644 index 0000000..ef880b5 Binary files /dev/null and b/src/app/img/gameImages/1023.jpg differ diff --git a/src/app/img/gameImages/1024.jpg b/src/app/img/gameImages/1024.jpg new file mode 100644 index 0000000..bd8a4ee Binary files /dev/null and b/src/app/img/gameImages/1024.jpg differ diff --git a/src/app/img/gameImages/1025.jpg b/src/app/img/gameImages/1025.jpg new file mode 100644 index 0000000..cd5da5b Binary files /dev/null and b/src/app/img/gameImages/1025.jpg differ diff --git a/src/app/img/gameImages/1026.jpg b/src/app/img/gameImages/1026.jpg new file mode 100644 index 0000000..9c8dd1e Binary files /dev/null and b/src/app/img/gameImages/1026.jpg differ diff --git a/src/app/img/gameImages/1027.jpg b/src/app/img/gameImages/1027.jpg new file mode 100644 index 0000000..e425aad Binary files /dev/null and b/src/app/img/gameImages/1027.jpg differ diff --git a/src/app/img/gameImages/1028.jpg b/src/app/img/gameImages/1028.jpg new file mode 100644 index 0000000..684b57a Binary files /dev/null and b/src/app/img/gameImages/1028.jpg differ diff --git a/src/app/img/gameImages/1029.jpeg b/src/app/img/gameImages/1029.jpeg new file mode 100644 index 0000000..6b291a1 Binary files /dev/null and b/src/app/img/gameImages/1029.jpeg differ diff --git a/src/app/img/gameImages/1030.jpeg b/src/app/img/gameImages/1030.jpeg new file mode 100644 index 0000000..e5182ef Binary files /dev/null and b/src/app/img/gameImages/1030.jpeg differ diff --git a/src/app/img/gameImages/1031.jpg b/src/app/img/gameImages/1031.jpg new file mode 100644 index 0000000..086fee3 Binary files /dev/null and b/src/app/img/gameImages/1031.jpg differ diff --git a/src/app/img/gameImages/1032.png b/src/app/img/gameImages/1032.png new file mode 100644 index 0000000..ebe9440 Binary files /dev/null and b/src/app/img/gameImages/1032.png differ diff --git a/src/app/img/gameImages/371.png b/src/app/img/gameImages/371.png new file mode 100644 index 0000000..fb173bb Binary files /dev/null and b/src/app/img/gameImages/371.png differ diff --git a/src/app/img/gameImages/888.jpg b/src/app/img/gameImages/888.jpg new file mode 100644 index 0000000..090ea19 Binary files /dev/null and b/src/app/img/gameImages/888.jpg differ diff --git a/src/app/img/gameImages/918.jpg b/src/app/img/gameImages/918.jpg new file mode 100644 index 0000000..be606c0 Binary files /dev/null and b/src/app/img/gameImages/918.jpg differ diff --git a/src/app/img/gameImages/992.jpg b/src/app/img/gameImages/992.jpg new file mode 100644 index 0000000..332003e Binary files /dev/null and b/src/app/img/gameImages/992.jpg differ diff --git a/src/app/img/gameImages/993.jpg b/src/app/img/gameImages/993.jpg new file mode 100644 index 0000000..e84a330 Binary files /dev/null and b/src/app/img/gameImages/993.jpg differ diff --git a/src/app/img/gameImages/994.png b/src/app/img/gameImages/994.png new file mode 100644 index 0000000..e743c00 Binary files /dev/null and b/src/app/img/gameImages/994.png differ diff --git a/src/app/img/gameImages/995.jpg b/src/app/img/gameImages/995.jpg new file mode 100644 index 0000000..8a54721 Binary files /dev/null and b/src/app/img/gameImages/995.jpg differ diff --git a/src/app/img/gameImages/996.png b/src/app/img/gameImages/996.png new file mode 100644 index 0000000..3833213 Binary files /dev/null and b/src/app/img/gameImages/996.png differ diff --git a/src/app/img/gameImages/997.jpg b/src/app/img/gameImages/997.jpg new file mode 100644 index 0000000..335c7b7 Binary files /dev/null and b/src/app/img/gameImages/997.jpg differ diff --git a/src/app/img/gameImages/998.jpg b/src/app/img/gameImages/998.jpg new file mode 100644 index 0000000..d4376d6 Binary files /dev/null and b/src/app/img/gameImages/998.jpg differ diff --git a/src/app/img/gameImages/999.png b/src/app/img/gameImages/999.png new file mode 100644 index 0000000..8cdf95d Binary files /dev/null and b/src/app/img/gameImages/999.png differ diff --git a/src/app/img/giftable.svg b/src/app/img/giftable.svg new file mode 100644 index 0000000..30fec50 --- /dev/null +++ b/src/app/img/giftable.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/app/img/loanable small.svg b/src/app/img/loanable small.svg new file mode 100644 index 0000000..0e0ef59 --- /dev/null +++ b/src/app/img/loanable small.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/app/img/loanable.svg b/src/app/img/loanable.svg new file mode 100644 index 0000000..8cd23e2 --- /dev/null +++ b/src/app/img/loanable.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/img/noOpinion.svg b/src/app/img/noOpinion.svg new file mode 100644 index 0000000..ecbc6d1 --- /dev/null +++ b/src/app/img/noOpinion.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/img/playable.svg b/src/app/img/playable.svg new file mode 100644 index 0000000..8a38860 --- /dev/null +++ b/src/app/img/playable.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/components/Knowledge/BoxList.tsx b/src/components/Knowledge/KnowledgeBoxList.tsx similarity index 95% rename from src/components/Knowledge/BoxList.tsx rename to src/components/Knowledge/KnowledgeBoxList.tsx index 51c7784..5035ce6 100644 --- a/src/components/Knowledge/BoxList.tsx +++ b/src/components/Knowledge/KnowledgeBoxList.tsx @@ -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] diff --git a/src/components/Loan/BoxItem.tsx b/src/components/Loan/BoxItem.tsx new file mode 100644 index 0000000..644c993 --- /dev/null +++ b/src/components/Loan/BoxItem.tsx @@ -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 = ({ + 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 ( +
  • +
    + {isBggPhoto && } + {!isBggPhoto && ( +
    + {" "} +
    + )} +
    +
    + 0 + ? `https://boardgamegeek.com/boardgame/${bggId}` + : bggIdAlternative + } + target="_blank" + rel="noreferrer" + className={styles.title} + > + {title} + +
    + +
    +
    + +
  • + ) +} + +export default memo(BoxItem) diff --git a/src/components/Loan/LoanBoxList.tsx b/src/components/Loan/LoanBoxList.tsx new file mode 100644 index 0000000..116e9d3 --- /dev/null +++ b/src/components/Loan/LoanBoxList.tsx @@ -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) => + 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 ( +
    + +
      + {boxesToShow.map((detailedBox: any) => ( + + ))} +
    +
    + ) +} + +export default memo(LoanBoxList) + +export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerLoanSetIfNeed] diff --git a/src/components/Loan/LoanIntro.tsx b/src/components/Loan/LoanIntro.tsx new file mode 100644 index 0000000..c23bc82 --- /dev/null +++ b/src/components/Loan/LoanIntro.tsx @@ -0,0 +1,57 @@ +import classnames from "classnames" +import React from "react" +import styles from "./styles.module.scss" + +const LoanIntro: React.FC = (): JSX.Element => ( +
    +

    Emprunt et tri des jeux

    +

    + 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. +

    +

    + Pour chaque jeu, active le bouton :
    + {" "} + si tu veux l'emprunter lors du brunch ou d'un rdv bénévoles mensuel +
    + {" "} + si tu penses que les visiteurs du festival (ou même les bénévoles) doivent pouvoir y + jouer +
    + {" "} + si tu aimerais le récupérer comme cadeau lors du brunch (ou rdv bénévole si tu ne peux + pas venir) +
    + {" "} + si tu n'as pas d'avis sur ce jeu +
    +

    +
    +) + +export default LoanIntro diff --git a/src/components/Loan/styles.module.scss b/src/components/Loan/styles.module.scss new file mode 100755 index 0000000..bb05bac --- /dev/null +++ b/src/components/Loan/styles.module.scss @@ -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; +} diff --git a/src/components/Navigation/MainMenu.tsx b/src/components/Navigation/MainMenu.tsx index 9affa03..6ab488b 100644 --- a/src/components/Navigation/MainMenu.tsx +++ b/src/components/Navigation/MainMenu.tsx @@ -52,6 +52,7 @@ const MainMenu: FC = (): JSX.Element => { + {/* */} = (): JSX.Element => {
    - +
    ) diff --git a/src/pages/Loan/LoanPage.tsx b/src/pages/Loan/LoanPage.tsx new file mode 100644 index 0000000..b7fe0ed --- /dev/null +++ b/src/pages/Loan/LoanPage.tsx @@ -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 { LoanBoxList, LoanIntro, fetchForLoan } from "../../components" +import { selectUserJwtToken } from "../../store/auth" + +export type Props = RouteComponentProps + +const LoanPage: FC = (): JSX.Element => { + const jwtToken = useSelector(selectUserJwtToken) + if (jwtToken === undefined) return

    Loading...

    + if (!jwtToken) { + return
    Besoin d'être identifié
    + } + return ( +
    +
    + + + +
    +
    + ) +} + +// Fetch server-side data here +export const loadData = (): AppThunk[] => [...fetchForLoan.map((f) => f())] + +export default memo(LoanPage) diff --git a/src/pages/Loan/index.tsx b/src/pages/Loan/index.tsx new file mode 100755 index 0000000..c7b61dc --- /dev/null +++ b/src/pages/Loan/index.tsx @@ -0,0 +1,16 @@ +import loadable from "@loadable/component" + +import { Loading, ErrorBoundary } from "../../components" +import { Props, loadData } from "./LoanPage" + +const Loan = loadable(() => import("./LoanPage"), { + fallback: , +}) + +export default (props: Props): JSX.Element => ( + + + +) + +export { loadData } diff --git a/src/pages/Loan/styles.module.scss b/src/pages/Loan/styles.module.scss new file mode 100755 index 0000000..db1afb2 --- /dev/null +++ b/src/pages/Loan/styles.module.scss @@ -0,0 +1,9 @@ +@import "../../theme/mixins"; + +.loanPage { + @include page-wrapper-center; +} + +.loanContent { + @include page-content-wrapper(700px); +} diff --git a/src/routes/index.ts b/src/routes/index.ts index af39210..402bf65 100755 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -10,6 +10,7 @@ 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 AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards" import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams" import AsyncBoard, { loadData as loadBoardData } from "../pages/Board" @@ -44,6 +45,11 @@ export default [ component: AsyncKnowledge, loadData: loadKnowledgeData, }, + { + path: "/emprunts", + component: AsyncLoan, + loadData: loadLoanData, + }, { path: "/fiches", component: AsyncKnowledgeCards, diff --git a/src/server/gsheets/boxes.ts b/src/server/gsheets/boxes.ts index d99b500..659830f 100644 --- a/src/server/gsheets/boxes.ts +++ b/src/server/gsheets/boxes.ts @@ -24,9 +24,10 @@ export const detailedBoxListGet = expressAccessor.get(async (list) => { id: box?.id || 10000 + game.id, gameId: game.id, title: game.title, + bggId: game.bggId, + bggIdAlternative: game.bggIdAlternative, bggPhoto: game.bggPhoto, poufpaf: game.poufpaf, - bggId: game.bggId, container: box?.container || "Non stocké", playersMin: game.playersMin, playersMax: game.playersMax, diff --git a/src/server/gsheets/games.ts b/src/server/gsheets/games.ts index 1699f29..c70a9ba 100644 --- a/src/server/gsheets/games.ts +++ b/src/server/gsheets/games.ts @@ -1,6 +1,6 @@ import axios from "axios" import { Parser } from "xml2js" -import { assign, cloneDeep, find, maxBy, some } from "lodash" +import { assign, cloneDeep, find } from "lodash" import ExpressAccessors from "./expressAccessors" import { Game, GameWithoutId, translationGame } from "../../services/games" @@ -26,28 +26,41 @@ export const gameDetailsUpdate = expressAccessor.listSet( newList.forEach((game, index, arr) => { const box = find(parsed.items.item, (item: any) => game.bggId === +item.$.objectid) - if (box && game.bggPhoto === "") { - assign(arr[index], xmlToGame(game.id, box), { - ean: arr[index].ean, - type: arr[index].type, + if (box) { + if (game.bggPhoto === "" || game.duration === 0) { + assign(arr[index], xmlToGame(game.id, box), { + title: arr[index].title, + ean: arr[index].ean || 0, + type: arr[index].type, + }) + } + } else { + assign(arr[index], { + bggId: 0, + playersMin: 0, + playersMax: 0, + duration: 0, + ean: 0, + toBeKnown: true, }) } }) - 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) - ) + // // Add to DB game that were only present on BGG + // 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)) - }) + // let id = maxBy(newList, "id")?.id || 0 + // newGames.forEach((item: any) => { + // id += 1 + // newList.push(xmlToGame(id, item)) + // }) return { toDatabase: newList, @@ -59,13 +72,15 @@ export const gameDetailsUpdate = expressAccessor.listSet( function xmlToGame(id: number, item: any): Game { return { id, - title: item.name?.[0]._ || "", + title: "", + bggId: +item.$.objectid || 0, + bggIdAlternative: "", + bggTitle: 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, diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts index 46e3c31..76aa05c 100644 --- a/src/server/gsheets/volunteers.ts +++ b/src/server/gsheets/volunteers.ts @@ -20,6 +20,7 @@ import { VolunteerKnowledge, VolunteerDetailedKnowledge, VolunteerPersonalInfo, + VolunteerLoan, } from "../../services/volunteers" import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization" import { getJwt } from "../secure" @@ -552,6 +553,31 @@ export const volunteerDetailedKnowledgeList = expressAccessor.get(async (list) = }) }) +export const volunteerLoanSet = expressAccessor.set(async (list, body, id) => { + const requestedId = +body[0] || id + const volunteer: Volunteer | undefined = list.find((v) => v.id === requestedId) + if (!volunteer) { + throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`) + } + const loan = body[1] as VolunteerLoan + const newVolunteer: Volunteer = cloneDeep(volunteer) + if (loan?.loanable !== undefined) newVolunteer.loanable = loan.loanable + if (loan?.playable !== undefined) newVolunteer.playable = loan.playable + if (loan?.giftable !== undefined) newVolunteer.giftable = loan.giftable + if (loan?.noOpinion !== undefined) newVolunteer.noOpinion = loan.noOpinion + + return { + toDatabase: newVolunteer, + toCaller: { + id: newVolunteer.id, + loanable: newVolunteer.loanable, + playable: newVolunteer.playable, + giftable: newVolunteer.giftable, + noOpinion: newVolunteer.noOpinion, + } as VolunteerLoan, + } +}) + function getUniqueNickname(list: Volunteer[], volunteer: Volunteer): string { const lastnameList = list .filter((v) => v.firstname === volunteer.firstname) diff --git a/src/server/index.ts b/src/server/index.ts index d1d3580..d70b449 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -39,6 +39,7 @@ import { volunteerKnowledgeSet, volunteerAddNew, volunteerDetailedKnowledgeList, + volunteerLoanSet, } from "./gsheets/volunteers" import { wishListGet, wishAdd } from "./gsheets/wishes" import config from "../config" @@ -119,6 +120,7 @@ app.post( secure as RequestHandler, volunteerDetailedKnowledgeList ) +app.post("/VolunteerLoanSet", secure as RequestHandler, volunteerLoanSet) app.post( "/VolunteerParticipationDetailsSet", secure as RequestHandler, diff --git a/src/services/boxes.ts b/src/services/boxes.ts index 5bc42ad..ffc3639 100644 --- a/src/services/boxes.ts +++ b/src/services/boxes.ts @@ -38,12 +38,14 @@ export class DetailedBox { title = new Game().title + bggId = new Game().bggId + + bggIdAlternative = new Game().bggIdAlternative + bggPhoto = new Game().bggPhoto poufpaf = new Game().poufpaf - bggId = new Game().bggId - playersMin = new Game().playersMin playersMax = new Game().playersMax diff --git a/src/services/games.ts b/src/services/games.ts index 278eb03..f21c0fb 100644 --- a/src/services/games.ts +++ b/src/services/games.ts @@ -3,6 +3,12 @@ export class Game { title = "" + bggId = 0 + + bggIdAlternative = "" + + bggTitle = "" + playersMin = 0 playersMax = 0 @@ -13,8 +19,6 @@ export class Game { poufpaf = "" - bggId = 0 - ean = "" bggPhoto = "" @@ -25,12 +29,14 @@ export class Game { export const translationGame: { [k in keyof Game]: string } = { id: "id", title: "titre", + bggId: "bggId", + bggIdAlternative: "bggIdAlternative", + bggTitle: "titreBgg", playersMin: "minJoueurs", playersMax: "maxJoueurs", duration: "duree", type: "type", poufpaf: "poufpaf", - bggId: "bggId", ean: "ean", bggPhoto: "bggPhoto", toBeKnown: "àConnaitre", diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts index e05cfa7..0cf42c5 100644 --- a/src/services/volunteers.ts +++ b/src/services/volunteers.ts @@ -58,6 +58,14 @@ export class Volunteer implements VolunteerPartial { niet: number[] = [] + loanable: number[] = [] + + playable: number[] = [] + + giftable: number[] = [] + + noOpinion: number[] = [] + needsHosting = false canHostCount = 0 @@ -99,6 +107,10 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = { ok: "OK", bof: "Bof", niet: "Niet", + loanable: "empruntable", + playable: "jouable", + giftable: "offrable", + noOpinion: "sansAvis", needsHosting: "besoinHébergement", canHostCount: "nombreHébergés", distanceToFestival: "distanceAuFestival", @@ -148,6 +160,10 @@ export const volunteerExample: Volunteer = { ok: [5, 7, 24, 26, 31, 38, 50, 52, 54, 58], bof: [9, 12, 16, 27, 34, 35, 36], niet: [13, 18, 19, 23, 47, 53, 59, 67], + loanable: [5, 7], + playable: [34, 35, 36], + giftable: [13, 67], + noOpinion: [3, 4], needsHosting: false, canHostCount: 0, distanceToFestival: 0, @@ -247,3 +263,12 @@ export interface VolunteerDetailedKnowledge { niet: Volunteer["niet"] dayWishes: Volunteer["dayWishes"] } + +export type VolunteerLoanWithoutId = Omit +export interface VolunteerLoan { + id: Volunteer["id"] + loanable: Volunteer["loanable"] + playable: Volunteer["playable"] + giftable: Volunteer["giftable"] + noOpinion: Volunteer["noOpinion"] +} diff --git a/src/services/volunteersAccessors.ts b/src/services/volunteersAccessors.ts index 2a0d869..228c976 100644 --- a/src/services/volunteersAccessors.ts +++ b/src/services/volunteersAccessors.ts @@ -13,6 +13,7 @@ import { VolunteerKnowledge, VolunteerMeals, VolunteerPersonalInfo, + VolunteerLoan, } from "./volunteers" const serviceAccessors = new ServiceAccessors(elementName) @@ -64,3 +65,6 @@ export const volunteerKnowledgeSet = export const volunteerDetailedKnowledgeList = serviceAccessors.securedCustomPost<[number]>( "DetailedKnowledgeListGet" ) + +export const volunteerLoanSet = + serviceAccessors.securedCustomPost<[number, Partial]>("LoanSet") diff --git a/src/store/__tests__/javGameList.ts b/src/store/__tests__/javGameList.ts index 92de828..185fff1 100644 --- a/src/store/__tests__/javGameList.ts +++ b/src/store/__tests__/javGameList.ts @@ -17,6 +17,9 @@ const mockData: Game[] = [ { id: 5, title: "6 qui prend!", + bggId: 432, + bggIdAlternative: "", + bggTitle: "6 nimmt!", playersMin: 2, playersMax: 10, duration: 45, @@ -24,7 +27,6 @@ const mockData: Game[] = [ poufpaf: "0-9-2/6-qui-prend-6-nimmt", bggPhoto: "https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg", - bggId: 432, ean: "3421272101313", toBeKnown: false, }, diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index bf73836..46acc4d 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -20,6 +20,7 @@ import volunteerForgot from "./volunteerForgot" import volunteerHostingSet from "./volunteerHostingSet" import volunteerMealsSet from "./volunteerMealsSet" import volunteerList from "./volunteerList" +import volunteerLoanSet from "./volunteerLoanSet" import volunteerLogin from "./volunteerLogin" import volunteerKnowledgeSet from "./volunteerKnowledgeSet" import volunteerDetailedKnowledgeList from "./volunteerDetailedKnowledgeList" @@ -53,6 +54,7 @@ export default (history: History) => ({ volunteerHostingSet, volunteerMealsSet, volunteerList, + volunteerLoanSet, volunteerLogin, volunteerKnowledgeSet, volunteerDetailedKnowledgeList, diff --git a/src/store/volunteerLoanSet.ts b/src/store/volunteerLoanSet.ts new file mode 100644 index 0000000..3fd0f59 --- /dev/null +++ b/src/store/volunteerLoanSet.ts @@ -0,0 +1,85 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit" +import { shallowEqual, useSelector } from "react-redux" +import { useCallback } from "react" +import { StateRequest, toastError, elementFetch } from "./utils" +import { VolunteerLoan } from "../services/volunteers" +import { AppThunk, AppState } from "." +import { volunteerLoanSet } from "../services/volunteersAccessors" +import useAction from "../utils/useAction" +import { selectUserJwtToken } from "./auth" + +type StateVolunteerLoanSet = { + entity?: VolunteerLoan +} & StateRequest + +export const initialState: StateVolunteerLoanSet = { + readyStatus: "idle", +} + +const volunteerLoanSetSlice = createSlice({ + name: "volunteerLoanSet", + initialState, + reducers: { + getRequesting: (_) => ({ + readyStatus: "request", + }), + getSuccess: (_, { payload }: PayloadAction) => ({ + readyStatus: "success", + entity: payload, + }), + getFailure: (_, { payload }: PayloadAction) => ({ + readyStatus: "failure", + error: payload, + }), + }, +}) + +export default volunteerLoanSetSlice.reducer +export const { getRequesting, getSuccess, getFailure } = volunteerLoanSetSlice.actions + +export const fetchVolunteerLoanSet = elementFetch( + volunteerLoanSet, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors du chargement des emprunts: ${error.message}`) +) + +const shouldFetchVolunteerLoanSet = (state: AppState, id: number) => + state.volunteerLoanSet?.readyStatus !== "success" || + (state.volunteerLoanSet?.entity && state.volunteerLoanSet?.entity?.id !== id) + +export const fetchVolunteerLoanSetIfNeed = + (id = 0, loan: Partial = {}): AppThunk => + (dispatch, getState) => { + let jwt = "" + + if (!id) { + ;({ jwt, id } = getState().auth) + } + + if (shouldFetchVolunteerLoanSet(getState(), id)) + return dispatch(fetchVolunteerLoanSet(jwt, id, loan)) + + return null + } + +type SetFunction = (newVolunteerLoan: VolunteerLoan) => void + +export const useVolunteerLoan = (): [VolunteerLoan | undefined, SetFunction] => { + const save = useAction(fetchVolunteerLoanSet) + const jwtToken = useSelector(selectUserJwtToken) + const volunteerLoan = useSelector( + (state: AppState) => state.volunteerLoanSet?.entity, + shallowEqual + ) + + const saveVolunteerLoan: SetFunction = useCallback( + (newVolunteerLoan) => { + save(jwtToken, 0, newVolunteerLoan) + }, + [save, jwtToken] + ) + + return [volunteerLoan, saveVolunteerLoan] +} diff --git a/src/theme/variables.scss b/src/theme/variables.scss index 2135300..dc112a1 100755 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -19,6 +19,15 @@ $color-active-bof: rgb(180, 124, 19); $color-niet: rgb(233, 215, 217); $color-active-niet: rgb(158, 17, 41); +$color-no-opinion: rgb(232, 223, 235); +$color-active-no-opinion: rgb(170, 87, 196); +$color-loanable: rgb(215, 233, 217); +$color-active-loanable: rgb(50, 107, 38); +$color-playable: rgb(231, 227, 214); +$color-active-playable: rgb(175, 139, 73); +$color-giftable: rgb(233, 215, 217); +$color-active-giftable: rgb(160, 70, 85); + $border-large: 4px solid $color-black; $border-thin: 2px solid $color-black;