Merge branch 'master' into registering

This commit is contained in:
pikiou 2022-05-04 01:46:04 +02:00
commit f0d9cfad3d
11 changed files with 211 additions and 95 deletions

View File

@ -1,13 +1,36 @@
import { FC, useCallback, useState } from "react" import { FC, useCallback, useState } from "react"
import { useSelector } from "react-redux" import { useSelector } from "react-redux"
import classnames from "classnames" import classnames from "classnames"
import { isUserConnected, routerSelector } from "../../store/auth" import { isUserConnected, routerSelector, selectUserRoles } from "../../store/auth"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import ROLES from "../../utils/roles.constants"
const MainMenu: FC = (): JSX.Element | null => { interface MenuItemProps {
const connected = useSelector(isUserConnected) name: string
pathname: string
}
const MenuItem: FC<MenuItemProps> = ({ name, pathname }): JSX.Element => {
const router = useSelector(routerSelector) const router = useSelector(routerSelector)
const isActive = (router as any)?.location?.pathname === pathname
return (
<li className={classnames(styles.mainMenuItem, isActive ? styles.active : null)}>
<a href={pathname}>{name}</a>
</li>
)
}
interface RestrictMenuItemProps extends MenuItemProps {
role: string
}
const RestrictMenuItem: FC<RestrictMenuItemProps> = ({ name, pathname, role }): JSX.Element => {
const roles = useSelector(selectUserRoles)
return roles.includes(role) ? <MenuItem name={name} pathname={pathname} /> : <div />
}
const MainMenu: FC = (): JSX.Element => {
const connected = useSelector(isUserConnected)
const [opened, setOpened] = useState(false) const [opened, setOpened] = useState(false)
const onOpen = useCallback(() => { const onOpen = useCallback(() => {
@ -18,16 +41,7 @@ const MainMenu: FC = (): JSX.Element | null => {
setOpened(false) setOpened(false)
}, [setOpened]) }, [setOpened])
if (!connected) return null if (!connected) return <div />
function createMenuItem(name: string, pathname: string): JSX.Element {
const isActive = (router as any)?.location?.pathname === pathname
return (
<li className={classnames(styles.mainMenuItem, isActive ? styles.active : null)}>
<a href={pathname}>{name}</a>
</li>
)
}
return ( return (
<nav> <nav>
@ -35,9 +49,14 @@ const MainMenu: FC = (): JSX.Element | null => {
</button> </button>
<ul className={classnames(styles.mainMenu, opened && styles.opened)}> <ul className={classnames(styles.mainMenu, opened && styles.opened)}>
{createMenuItem("Questions", "/")} <MenuItem name="Questions" pathname="/" />
{createMenuItem("Annonces", "/annonces")} <MenuItem name="Annonces" pathname="/annonces" />
{createMenuItem("Mon profil", "/profil")} <MenuItem name="Mon profil" pathname="/profil" />
<RestrictMenuItem
role={ROLES.ASSIGNER}
name="Gestion équipes"
pathname="/team-assign"
/>
<button type="button" className={styles.close} onClick={onClose}> <button type="button" className={styles.close} onClick={onClose}>
× ×
</button> </button>

View File

@ -65,15 +65,7 @@
.mainMenuItem { .mainMenuItem {
display: inline-block; display: inline-block;
margin: 0; margin: 0 6px;
padding: 7px 6px 6px;
border: 1px solid $color-black;
border-width: 1px 1px 0;
border-radius: 10px 10px 0 0;
text-align: center;
background-color: $color-grey-light;
cursor: pointer;
color: $color-grey-dark;
@include mobile { @include mobile {
display: block; display: block;
@ -89,6 +81,22 @@
background-color: $color-black; background-color: $color-black;
} }
@include desktop {
margin: 0;
padding: 7px 6px 6px;
border-radius: 10px 10px 0 0;
text-align: center;
cursor: pointer;
color: $color-grey-dark;
border: solid $color-black;
border-width: 1px 1px 0;
background-color: $color-grey-light;
&:not(.active) a:hover {
color: $color-white;
}
}
a { a {
font-size: 0.9em; font-size: 0.9em;
font-weight: bold; font-weight: bold;
@ -100,8 +108,4 @@
padding: 10px 15px; padding: 10px 15px;
} }
} }
&:not(.active) a:hover {
color: $color-white;
}
} }

View File

@ -3,18 +3,17 @@ import ContentTitle from "../ui/Content/ContentTitle"
import withUserConnected from "../../utils/withUserConnected" import withUserConnected from "../../utils/withUserConnected"
import { fetchTeamListIfNeed } from "../../store/teamList" import { fetchTeamListIfNeed } from "../../store/teamList"
import { fetchVolunteerListIfNeed } from "../../store/volunteerList" import { fetchVolunteerListIfNeed } from "../../store/volunteerList"
import VolunteersWithTeamWishes from "./VolunteersWithTeamWishes"
import TeamsWithCandidates from "./TeamsWithCandidates" import TeamsWithCandidates from "./TeamsWithCandidates"
import withUserRole from "../../utils/withUserRole"
import ROLES from "../../utils/roles.constants"
const TeamAssignment: FC = (): JSX.Element => ( const TeamAssignment: FC = (): JSX.Element => (
<> <>
<ContentTitle title="Choix par équipes" /> <ContentTitle title="Gestion des équipes" />
<TeamsWithCandidates /> <TeamsWithCandidates />
<ContentTitle title="Choix des bénévoles" />
<VolunteersWithTeamWishes />
</> </>
) )
export default memo(withUserConnected(TeamAssignment)) export default withUserRole(ROLES.ASSIGNER, memo(withUserConnected(TeamAssignment)))
export const fetchFor = [fetchTeamListIfNeed, fetchVolunteerListIfNeed] export const fetchFor = [fetchTeamListIfNeed, fetchVolunteerListIfNeed]

View File

@ -28,7 +28,13 @@ const selectTeamsWithVolunteersCandidates = createSelector(
return { return {
id, id,
name, name,
volunteers: volunteersSelection, volunteersWithoutTeam: volunteersSelection.filter((volunteer) => !volunteer.team),
volunteersWithCurrentTeam: volunteersSelection.filter(
(volunteer) => volunteer.team === id
),
volunteersWithOtherTeam: volunteersSelection.filter(
(volunteer) => volunteer.team && volunteer.team !== id
),
} }
}) })
) )
@ -49,57 +55,93 @@ const DaysDisplay: FC<PropsDaysDisplay> = ({ dayWishes }): JSX.Element => (
</span> </span>
) )
type Props = { type TeamWithCandidatesVolunteerProps = {
volunteer: any
teamId: number teamId: number
} }
const TeamWithCandidates: FC<Props> = ({ teamId }): JSX.Element | null => { const TeamWithCandidatesVolunteer: FC<TeamWithCandidatesVolunteerProps> = ({
const teams = useSelector(selectTeamsWithVolunteersCandidates) teamId,
const currentTeam = teams.find((t) => t.id === teamId) volunteer,
}): JSX.Element => {
const { id, lastname, firstname, teamWishes, dayWishes, team } = volunteer
const [, saveTeam] = useTeamAssign() const [, saveTeam] = useTeamAssign()
const onTeamSelected = useCallback( const onTeamSelected = useCallback(
(volunteer, selectedTeamId) => { (selectedVolunteer, selectedTeamId) => {
saveTeam(volunteer, selectedTeamId) saveTeam(selectedVolunteer, selectedTeamId)
}, },
[saveTeam] [saveTeam]
) )
return (
<li key={id}>
<span className={styles.volunteerName}>
{firstname} {lastname} (<DaysDisplay dayWishes={dayWishes} />)
</span>
{teamWishes.map((teamWish: any) => {
const active = teamWish.id === team
const current = teamWish.id === teamId
return (
<button
key={teamWish.id}
type="button"
onClick={() => onTeamSelected({ id, team }, teamWish.id)}
className={classnames(
styles.teamWishButton,
current && styles.teamCurrent,
active && styles.teamActive
)}
>
{teamWish.name}
</button>
)
})}
</li>
)
}
type TeamWithCandidatesProps = {
teamId: number
}
const TeamWithCandidates: FC<TeamWithCandidatesProps> = ({ teamId }): JSX.Element => {
const teams = useSelector(selectTeamsWithVolunteersCandidates)
const currentTeam = teams.find((t) => t.id === teamId)
if (!currentTeam) return <div /> if (!currentTeam) return <div />
return ( return (
<div> <div>
<div className={styles.teamHeaderName}>Equipe {currentTeam.name}</div> <div className={styles.teamHeaderName}>Equipe {currentTeam.name}</div>
<TeamMembers teamId={teamId} /> <TeamMembers teamId={teamId} />
<div>Bénévoles intéressés ({currentTeam.volunteers.length}) :</div> <div>Bénévoles intéressés :</div>
<ul> <ul>
{currentTeam.volunteers.map( {currentTeam.volunteersWithoutTeam.map((volunteer) => (
({ id, lastname, firstname, teamWishes, dayWishes, team }) => ( <TeamWithCandidatesVolunteer
<li key={id}> key={volunteer.id}
<span className={styles.volunteerName}> teamId={teamId}
{firstname} {lastname} (<DaysDisplay dayWishes={dayWishes} />) volunteer={volunteer}
</span> />
{teamWishes.map((teamWish) => { ))}
const active = teamWish.id === team </ul>
const current = teamWish.id === teamId <ul>
return ( {currentTeam.volunteersWithCurrentTeam.map((volunteer) => (
<button <TeamWithCandidatesVolunteer
key={teamWish.id} key={volunteer.id}
type="button" teamId={teamId}
onClick={() => onTeamSelected({ id, team }, teamWish.id)} volunteer={volunteer}
className={classnames( />
styles.teamWishButton, ))}
current && styles.teamCurrent, </ul>
active && styles.teamActive <ul>
)} {currentTeam.volunteersWithOtherTeam.map((volunteer) => (
> <TeamWithCandidatesVolunteer
{teamWish.name} key={volunteer.id}
</button> teamId={teamId}
) volunteer={volunteer}
})} />
</li> ))}
)
)}
</ul> </ul>
</div> </div>
) )

View File

@ -3,7 +3,6 @@ import { useSelector } from "react-redux"
import { selectTeamList } from "../../store/teamList" import { selectTeamList } from "../../store/teamList"
import TeamWithCandidates from "./TeamWithCandidates" import TeamWithCandidates from "./TeamWithCandidates"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import withUserRole from "../../utils/withUserRole"
const TeamsWithCandidates: FC = (): JSX.Element => { const TeamsWithCandidates: FC = (): JSX.Element => {
const teams = useSelector(selectTeamList) const teams = useSelector(selectTeamList)
@ -26,4 +25,4 @@ const TeamsWithCandidates: FC = (): JSX.Element => {
) )
} }
export default withUserRole("répartiteur", memo(TeamsWithCandidates)) export default memo(TeamsWithCandidates)

View File

@ -4,6 +4,7 @@ import { createSelector } from "@reduxjs/toolkit"
import { selectVolunteerListAlphaSorted } from "../../store/volunteerList" import { selectVolunteerListAlphaSorted } from "../../store/volunteerList"
import { selectTeamList } from "../../store/teamList" import { selectTeamList } from "../../store/teamList"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import ContentTitle from "../ui/Content/ContentTitle"
const selectVolunteersWithTeamWishes = createSelector( const selectVolunteersWithTeamWishes = createSelector(
selectVolunteerListAlphaSorted, selectVolunteerListAlphaSorted,
@ -23,24 +24,52 @@ const selectVolunteersWithTeamWishes = createSelector(
})) }))
) )
const selectVolunteersWithTeam = createSelector(selectVolunteersWithTeamWishes, (volunteers) =>
volunteers.filter((volunteer) => volunteer.teamObject)
)
const selectVolunteersWithoutTeam = createSelector(selectVolunteersWithTeamWishes, (volunteers) =>
volunteers.filter((volunteer) => !volunteer.teamObject)
)
const VolunteersWithTeamWishes: FC = (): JSX.Element => { const VolunteersWithTeamWishes: FC = (): JSX.Element => {
const volunteers = useSelector(selectVolunteersWithTeamWishes) const volunteersWithTeam = useSelector(selectVolunteersWithTeam)
const volunteersWithoutTeam = useSelector(selectVolunteersWithoutTeam)
return ( return (
<div> <>
<div>Bénévoles ayant choisi des équipes ({volunteers.length}) :</div> <ContentTitle title="Choix des bénévoles" />
<ul> <div>
{volunteers.map(({ id, lastname, firstname, teamWishes, teamObject }) => ( <div>Bénévoles en attente d'équipe ({volunteersWithoutTeam.length}) :</div>
<li key={id}> <ul>
<span className={styles.volunteerName}> {volunteersWithoutTeam.map(
{firstname} {lastname}{" "} ({ id, lastname, firstname, teamWishes, teamObject }) => (
</span> <li key={id}>
<span className={styles.teamActive}>{teamObject?.name} </span> <span className={styles.volunteerName}>
<span>{teamWishes.join(", ")}</span> {firstname} {lastname}{" "}
</li> </span>
))} <span className={styles.teamActive}>{teamObject?.name} </span>
</ul> <span>{teamWishes.join(", ")}</span>
</div> </li>
)
)}
</ul>
<div>Bénévoles dans une équipe ({volunteersWithTeam.length}) :</div>
<ul>
{volunteersWithTeam.map(
({ id, lastname, firstname, teamWishes, teamObject }) => (
<li key={id}>
<span className={styles.volunteerName}>
{firstname} {lastname}{" "}
</span>
<span className={styles.teamActive}>{teamObject?.name} </span>
<span>{teamWishes.join(", ")}</span>
</li>
)
)}
</ul>
</div>
</>
) )
} }

View File

@ -8,6 +8,7 @@ import styles from "./styles.module.scss"
import { Volunteer } from "../../services/volunteers" import { Volunteer } from "../../services/volunteers"
import { Team } from "../../services/teams" import { Team } from "../../services/teams"
import withUserRole from "../../utils/withUserRole" import withUserRole from "../../utils/withUserRole"
import ROLES from "../../utils/roles.constants"
interface ExtendedVolunteer extends Volunteer { interface ExtendedVolunteer extends Volunteer {
teamObject: Team | undefined teamObject: Team | undefined
@ -31,7 +32,7 @@ type VolunteerEmailProps = {
email: string email: string
} }
const VolunteerEmail: FC<VolunteerEmailProps> = withUserRole("référent", ({ email }) => ( const VolunteerEmail: FC<VolunteerEmailProps> = withUserRole(ROLES.TEAMLEAD, ({ email }) => (
<div className={styles.volunteerEmail}>{email}</div> <div className={styles.volunteerEmail}>{email}</div>
)) ))

View File

@ -7,7 +7,9 @@ type Props = {
const Page: FC<Props> = ({ children }): JSX.Element => ( const Page: FC<Props> = ({ children }): JSX.Element => (
<div className={styles.pageWrapper}> <div className={styles.pageWrapper}>
<div className={styles.pageContent}>{children}</div> {React.Children.map(children, (child) => (
<div className={styles.pageContent}>{child}</div>
))}
</div> </div>
) )

View File

@ -6,6 +6,7 @@ import { AppThunk } from "../../store"
import { selectUserJwtToken } from "../../store/auth" import { selectUserJwtToken } from "../../store/auth"
import Page from "../../components/ui/Page/Page" import Page from "../../components/ui/Page/Page"
import { TeamAssignment, fetchForTeamAssignment } from "../../components" import { TeamAssignment, fetchForTeamAssignment } from "../../components"
import VolunteersWithTeamWishes from "../../components/TeamAssignment/VolunteersWithTeamWishes"
export type Props = RouteComponentProps export type Props = RouteComponentProps
@ -15,9 +16,14 @@ const TeamAssignmentPage: FC<Props> = (): JSX.Element => {
if (jwtToken === undefined) return <p>Loading...</p> if (jwtToken === undefined) return <p>Loading...</p>
if (jwtToken) { if (jwtToken) {
return ( return (
<Page> <>
<TeamAssignment /> <Page>
</Page> <TeamAssignment />
</Page>
<Page>
<VolunteersWithTeamWishes />
</Page>
</>
) )
} }
return <div>Besoin d'être identifié</div> return <div>Besoin d'être identifié</div>

View File

@ -1,13 +1,13 @@
@import "./variables"; @import "./variables";
@mixin desktop { @mixin desktop {
@media (min-width: 800px) { @media (min-width: 900px) {
@content; @content;
} }
} }
@mixin mobile { @mixin mobile {
@media (max-width: 799px) { @media (max-width: 899px) {
@content; @content;
} }
} }
@ -16,6 +16,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
width: 100%;
} }
@mixin page-wrapper-center { @mixin page-wrapper-center {
@ -23,8 +24,8 @@
} }
@mixin page-content-wrapper($desktopWidth: 720px) { @mixin page-content-wrapper($desktopWidth: 720px) {
margin: 10px 0; margin: 10px 4px;
padding: 10px; padding: 6px;
background-color: $color-white; background-color: $color-white;
border-radius: 15px; border-radius: 15px;
border: $border-large; border: $border-large;
@ -35,6 +36,10 @@
padding: 20px; padding: 20px;
width: $desktopWidth; width: $desktopWidth;
} }
&::after {
flex-basis: 100%;
}
} }
@mixin inner-content-wrapper() { @mixin inner-content-wrapper() {

View File

@ -0,0 +1,10 @@
type rolesType = {
[key: string]: string
}
const ROLES: rolesType = {
ASSIGNER: "répartiteur",
TEAMLEAD: "référent",
}
export default ROLES