Add TeamWishes form and display components to Board

This commit is contained in:
memeriau 2022-02-09 23:36:53 +01:00
parent 733c4fc04f
commit 6e22136737
13 changed files with 437 additions and 6 deletions

View File

@ -15,6 +15,7 @@
bottom: auto;
right: auto;
padding: 15px;
max-height: 80vh;
outline: 0;
background-color: $color-white;
border-radius: 15px;

View File

@ -3,6 +3,8 @@ import DayWishes from "./DayWishes/DayWishes"
import DayWishesFormModal from "./DayWishesForm/DayWishesFormModal"
import ParticipationDetails from "./ParticipationDetails/ParticipationDetails"
import ParticipationDetailsFormModal from "./ParticipationDetailsForm/ParticipationDetailsFormModal"
import TeamWishes from "./TeamWishes/TeamWishes"
import TeamWishesFormModal from "./TeamWishesForm/TeamWishesFormModal"
import withUserConnected from "../../utils/withUserConnected"
const Board: FC = (): JSX.Element => (
@ -11,6 +13,8 @@ const Board: FC = (): JSX.Element => (
<DayWishesFormModal />
<ParticipationDetails />
<ParticipationDetailsFormModal />
<TeamWishes />
<TeamWishesFormModal />
</div>
)

View File

@ -22,9 +22,7 @@ const DayWishes: FC = (): JSX.Element | null => {
{comment && (
<div className={styles.commentLine}>
<span className={styles.commentLineTitle}>Mon commentaire :</span>
<span className={styles.commentLineText}>
{get(userWishes, "dayWishesComment", "")}
</span>
<span className={styles.commentLineText}>{comment}</span>
</div>
)}
<div className={styles.editButton}>

View File

@ -9,7 +9,7 @@ type Props = {
afterSubmit?: () => void | undefined
}
const ParticipationDetailsForm: FC<Props> = (): JSX.Element | null => {
const ParticipationDetails: FC<Props> = (): JSX.Element | null => {
const [participationDetails] = useUserParticipationDetails()
const age = get(participationDetails, "age", "")
const tShirtSize = get(participationDetails, "teeshirtSize", "")
@ -44,4 +44,4 @@ const ParticipationDetailsForm: FC<Props> = (): JSX.Element | null => {
)
}
export default memo(ParticipationDetailsForm)
export default memo(ParticipationDetails)

View File

@ -0,0 +1,51 @@
import { FC, memo, useCallback } from "react"
import { useSelector } from "react-redux"
import get from "lodash/get"
import styles from "./styles.module.scss"
import { displayModal, MODAL_IDS } from "../../../store/ui"
import useAction from "../../../utils/useAction"
import { useUserTeamWishes } from "../teamWishes.utils"
import { selectTeamList } from "../../../store/teamList"
type Props = {
afterSubmit?: () => void | undefined
}
const TeamWishes: FC<Props> = (): JSX.Element | null => {
const teams = useSelector(selectTeamList)
const [teamWishesData] = useUserTeamWishes()
const teamWishesString = get(teamWishesData, "teamWishes", [])
.map((id: number): string =>
get(
teams.find((team) => team && team.id === id),
"name",
""
)
)
.filter((name: string) => name)
.join(", ")
const comment = get(teamWishesData, "teamWishesComment", "")
const execDisplayModal = useAction(displayModal)
const onEdit = useCallback(() => execDisplayModal(MODAL_IDS.TEAMWISHES), [execDisplayModal])
return (
<div className={styles.root}>
<div className={styles.title}>Mes choix d&apos;équipes</div>
{teamWishesString && <span>{teamWishesString}</span>}
{!teamWishesString && <span className={styles.lineEmpty}>Non renseignés</span>}
{comment && (
<div className={styles.commentLine}>
<span className={styles.commentLineTitle}>Mon commentaire :</span>
<span className={styles.commentLineText}>{comment}</span>
</div>
)}
<div className={styles.editButton}>
<button type="button" onClick={onEdit}>
Modifier
</button>
</div>
</div>
)
}
export default memo(TeamWishes)

View File

@ -0,0 +1,44 @@
@import "../../../theme/variables";
@import "../../../theme/mixins";
.root {
@include inner-content-wrapper();
position: relative;
padding-right: 130px;
}
.title {
padding-bottom: 5px;
font-weight: bold;
}
.line {
margin: 2px 0;
}
.lineEmpty {
color: $color-red;
font-style: italic;
}
.commentLine {
span {
display: inline-block;
}
}
.commentLineTitle {
padding-right: 5px;
}
.commentLineText {
font-style: italic;
}
.editButton {
@include vertical-center();
position: absolute;
right: 20px;
}

View File

@ -0,0 +1,94 @@
import { FC, memo, useCallback, useEffect, useRef, useState } from "react"
import { useSelector } from "react-redux"
import get from "lodash/get"
import set from "lodash/set"
import classnames from "classnames"
import styles from "./styles.module.scss"
import { useUserTeamWishes } from "../teamWishes.utils"
import { selectTeamList } from "../../../store/teamList"
import useSelection from "../useSelection"
type Props = {
afterSubmit?: () => void | undefined
}
const TeamWishesForm: FC<Props> = ({ afterSubmit }): JSX.Element | null => {
const teams = useSelector(selectTeamList)
const { addToSelection, toggleToSelection, isInSelection } = useSelection()
const commentRef = useRef<HTMLTextAreaElement | null>(null)
const [userWishes, saveWishes] = useUserTeamWishes()
const [extendedTeam, setExtendedTeam] = useState<number | null>(null)
useEffect(() => {
if (!userWishes) return
addToSelection(...get(userWishes, "teamWishes", []))
set(commentRef, "current.value", get(userWishes, "teamWishesComment", ""))
}, [userWishes, addToSelection])
const onTeamClick = useCallback((id) => toggleToSelection(id), [toggleToSelection])
const onExtendClick = useCallback(
(id) => setExtendedTeam(extendedTeam === id ? null : id),
[extendedTeam, setExtendedTeam]
)
console.log("extendedTeam", extendedTeam)
const onSubmit = useCallback(() => {
const teamWishesComment = get(commentRef, "current.value", "")
const teamWishes = teams
.map((team) => team && team.id)
.filter((id) => id && isInSelection(id))
saveWishes({ teamWishes, teamWishesComment })
if (afterSubmit) afterSubmit()
}, [teams, isInSelection, saveWishes, afterSubmit])
return (
<div className={styles.root}>
<div className={styles.title}>Mes choix d&apos;équipes</div>
<ul className={styles.teamList}>
{teams.map((team: any) => (
<li
key={team.id}
className={classnames(
styles.teamLine,
isInSelection(team.id) && styles.active,
extendedTeam === team.id && styles.extended
)}
>
<button
type="button"
onClick={() => onTeamClick(team.id)}
className={styles.teamButton}
>
{team.name}
</button>
<button
type="button"
onClick={() => onExtendClick(team.id)}
className={styles.extendButton}
>
Détails
</button>
<div className={styles.teamDescription}>{team.description}</div>
</li>
))}
</ul>
<div className={styles.commentWrapper}>
<label htmlFor="day-choice-comment">Un commentaire, une précision ?</label>
<textarea id="day-choice-comment" ref={commentRef} />
</div>
<div className={styles.buttonWrapper}>
<button type="submit" onClick={onSubmit}>
Enregistrer
</button>
</div>
</div>
)
}
TeamWishesForm.defaultProps = {
afterSubmit: undefined,
}
export default memo(TeamWishesForm)

View File

@ -0,0 +1,18 @@
import { FC, memo, useCallback } from "react"
import { hideModal, MODAL_IDS } from "../../../store/ui"
import Modal from "../../Modal/Modal"
import useAction from "../../../utils/useAction"
import TeamWishesForm from "./TeamWishesForm"
const TeamWishesFormModal: FC = (): JSX.Element => {
const execHideModal = useAction(hideModal)
const afterFormSubmit = useCallback(() => execHideModal(), [execHideModal])
return (
<Modal modalId={MODAL_IDS.TEAMWISHES}>
<TeamWishesForm afterSubmit={afterFormSubmit} />
</Modal>
)
}
export default memo(TeamWishesFormModal)

View File

@ -0,0 +1,100 @@
@import "../../../theme/variables";
@import "../../../theme/mixins";
.root {
width: 470px;
}
.title {
padding: 4px;
font-weight: bold;
text-align: center;
}
.inputWrapper {
margin: 10px 0;
label {
display: inline-block;
width: 170px;
}
input {
width: 300px;
border: 1px solid $color-grey-medium;
outline: 0;
}
}
.teamList {
@include clear-ul-style;
}
.teamLine {
position: relative;
margin: 4px 0;
background-color: $color-grey-lighter;
}
.teamButton {
display: block;
padding: 4px 6px;
background: none;
color: $color-grey-medium;
border-radius: 0;
width: 100%;
text-align: left;
.active & {
background-color: $color-black;
color: $color-yellow;
}
}
.extendButton {
position: absolute;
top: 2px;
right: 4px;
padding: 2px;
background: none;
color: $color-grey-dark;
border-radius: 0;
font-size: 0.9em;
font-weight: normal;
.active & {
color: $color-grey-light;
}
}
.teamDescription {
display: none;
padding: 2px 4px 6px 8px;
color: $color-grey-dark;
font-size: 0.95em;
.extended & {
display: block;
}
}
.commentWrapper {
margin: 6px 0;
label {
display: block;
padding: 6px 0 2px 4px;
}
textarea {
width: 100%;
height: 50px;
padding: 5px;
border: 1px solid $color-grey-light;
background-color: $color-grey-lighter;
outline: 0;
}
}
.buttonWrapper {
margin-bottom: 10px;
text-align: center;
}

View File

@ -0,0 +1,29 @@
import { useCallback } from "react"
import { shallowEqual, useSelector } from "react-redux"
import useAction from "../../utils/useAction"
import { selectUserJwtToken } from "../../store/auth"
import { AppState } from "../../store"
import { fetchVolunteerTeamWishesSet } from "../../store/volunteerTeamWishesSet"
export const useUserTeamWishes = (): [any, any] => {
const save = useAction(fetchVolunteerTeamWishesSet)
const jwtToken = useSelector(selectUserJwtToken)
const userTeamWishes = useSelector(
(state: AppState) => state.volunteerTeamWishesSet?.entity,
shallowEqual
)
const saveTeamWishes = useCallback(
({ teamWishes, teamWishesComment }) => {
if (!userTeamWishes) return
save(jwtToken, 0, {
id: userTeamWishes.id,
teamWishes,
teamWishesComment,
})
},
[userTeamWishes, save, jwtToken]
)
return [userTeamWishes, saveTeamWishes]
}

View File

@ -0,0 +1,87 @@
import { useCallback, useMemo, useReducer } from "react"
type valueType = string | number
type selectionType = {
[key: string]: boolean
}
type State = {
selection: selectionType
}
type Action = { type: "add"; payload: valueType[] } | { type: "toggle"; payload: valueType }
interface selectionHook {
addToSelection: (...values: valueType[]) => void
toggleToSelection: (value: valueType) => void
isInSelection: (value: valueType) => boolean
}
const initialState: State = {
selection: {},
}
const buildIndex = (value: valueType) => `item_${value}`
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "add": {
const values = action.payload
return {
selection: values.reduce(
(acc: selectionType, value: valueType) => ({
...acc,
[buildIndex(value)]: true,
}),
state.selection
),
}
}
case "toggle": {
const value = action.payload
const index = buildIndex(value)
return {
selection: {
...state.selection,
[index]: !state.selection[index],
},
}
}
default:
return state
}
}
const useSelection = (): selectionHook => {
const [state, dispatch] = useReducer(reducer, initialState)
const addToSelection = useCallback(
(...values: valueType[]) => {
dispatch({ type: "add", payload: values })
},
[dispatch]
)
const toggleToSelection = useCallback(
(value: valueType) => {
dispatch({ type: "toggle", payload: value })
},
[dispatch]
)
const isInSelection = useCallback(
(value: valueType) => {
const index = buildIndex(value)
return state.selection[index]
},
[state.selection]
)
return useMemo(
() => ({ addToSelection, toggleToSelection, isInSelection }),
[addToSelection, toggleToSelection, isInSelection]
)
}
export default useSelection

View File

@ -3,11 +3,13 @@ import { RouteComponentProps } from "react-router-dom"
import { useSelector } from "react-redux"
import { AppThunk } from "../../store"
import { fetchVolunteerDayWishesSetIfNeed } from "../../store/volunteerDayWishesSet"
import { selectUserJwtToken } from "../../store/auth"
import Page from "../../components/Page/Page"
import Board from "../../components/VolunteerBoard/Board"
import { fetchVolunteerDayWishesSetIfNeed } from "../../store/volunteerDayWishesSet"
import { fetchVolunteerParticipationDetailsSetIfNeed } from "../../store/volunteerParticipationDetailsSet"
import { fetchVolunteerTeamWishesSetIfNeed } from "../../store/volunteerTeamWishesSet"
import { fetchTeamListIfNeed } from "../../store/teamList"
export type Props = RouteComponentProps
@ -29,6 +31,8 @@ const BoardPage: FC<Props> = (): JSX.Element => {
export const loadData = (): AppThunk[] => [
fetchVolunteerDayWishesSetIfNeed(),
fetchVolunteerParticipationDetailsSetIfNeed(),
fetchVolunteerTeamWishesSetIfNeed(),
fetchTeamListIfNeed(),
]
export default memo(BoardPage)

View File

@ -29,4 +29,5 @@ export const selectActiveModalId = createSelector(selectUiData, (ui) => ui.modal
export const MODAL_IDS = {
DAYWISHES: "DAYWISHES",
PARTICIPATIONDETAILS: "PARTICIPATIONDETAILS",
TEAMWISHES: "TEAMWISHES",
}