mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-09-11 13:56:29 +02:00
Add db edit feature for admin
This commit is contained in:
34
src/components/Admin/DbEdit.tsx
Normal file
34
src/components/Admin/DbEdit.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { FC, memo } from "react"
|
||||
import { useSelector } from "react-redux"
|
||||
import withUserConnected from "../../utils/withUserConnected"
|
||||
import { fetchVolunteerListIfNeed, selectVolunteerList } from "../../store/volunteerList"
|
||||
import withUserRole from "../../utils/withUserRole"
|
||||
import ROLES from "../../utils/roles.constants"
|
||||
import MemberEdit from "./MemberEdit"
|
||||
import useAction from "../../utils/useAction"
|
||||
import { fetchVolunteerSetIfNeed } from "../../store/volunteerSet"
|
||||
import { Volunteer } from "../../services/volunteers"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
const DbEdit: FC = (): JSX.Element => {
|
||||
const volunteers = useSelector(selectVolunteerList)
|
||||
const saveVolunteer = useAction(fetchVolunteerSetIfNeed)
|
||||
if (!volunteers) {
|
||||
return <>No member found</>
|
||||
}
|
||||
return (
|
||||
<ul className={styles.list}>
|
||||
{volunteers.map((volunteer: Volunteer) => (
|
||||
<MemberEdit
|
||||
key={volunteer.id}
|
||||
saveVolunteer={saveVolunteer}
|
||||
volunteer={volunteer}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default withUserRole(ROLES.ADMIN, memo(withUserConnected(DbEdit)))
|
||||
|
||||
export const fetchFor = [fetchVolunteerListIfNeed]
|
31
src/components/Admin/GameDetailsUpdate.tsx
Normal file
31
src/components/Admin/GameDetailsUpdate.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { FC, memo, useCallback } from "react"
|
||||
import { useSelector } from "react-redux"
|
||||
import withUserConnected from "../../utils/withUserConnected"
|
||||
import withUserRole from "../../utils/withUserRole"
|
||||
import ROLES from "../../utils/roles.constants"
|
||||
import { fetchGameDetailsUpdate } from "../../store/gameDetailsUpdate"
|
||||
import { selectUserJwtToken } from "../../store/auth"
|
||||
import useAction from "../../utils/useAction"
|
||||
import styles from "./styles.module.scss"
|
||||
import FormButton from "../Form/FormButton/FormButton"
|
||||
|
||||
const GameDetailsUpdate: FC = (): JSX.Element => {
|
||||
const jwtToken = useSelector(selectUserJwtToken)
|
||||
const save = useAction(fetchGameDetailsUpdate)
|
||||
|
||||
const onSubmit = useCallback(async () => {
|
||||
await save(jwtToken)
|
||||
}, [save, jwtToken])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.formButtons}>
|
||||
<FormButton onClick={onSubmit}>Ok, noté</FormButton>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default withUserRole(ROLES.ADMIN, memo(withUserConnected(GameDetailsUpdate)))
|
||||
|
||||
export const fetchFor = [fetchGameDetailsUpdate]
|
117
src/components/Admin/MemberEdit.tsx
Normal file
117
src/components/Admin/MemberEdit.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { isFinite } from "lodash"
|
||||
import { FC, memo, useState } from "react"
|
||||
import withUserConnected from "../../utils/withUserConnected"
|
||||
import withUserRole from "../../utils/withUserRole"
|
||||
import ROLES from "../../utils/roles.constants"
|
||||
import { Volunteer } from "../../services/volunteers"
|
||||
import styles from "./styles.module.scss"
|
||||
import { toastError } from "../../store/utils"
|
||||
|
||||
interface Props {
|
||||
volunteer: Volunteer
|
||||
saveVolunteer: (newVolunteer: Partial<Volunteer>) => void
|
||||
}
|
||||
|
||||
const MemberEdit: FC<Props> = ({ volunteer, saveVolunteer }): JSX.Element => {
|
||||
const [localVolunteer, setLocalVolunteer] = useState(volunteer)
|
||||
|
||||
const stringDispatch =
|
||||
(propName: string) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
saveVolunteer({ id: localVolunteer.id, [propName]: e.target.value })
|
||||
setLocalVolunteer({ ...localVolunteer, [propName]: e.target.value })
|
||||
}
|
||||
|
||||
function stringInput(id: string, value: string): JSX.Element {
|
||||
return (
|
||||
<div key={id} className={styles.inputContainer}>
|
||||
<span className={styles.inputDesc}>{id}</span>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
id={id}
|
||||
value={value}
|
||||
onChange={stringDispatch(id)}
|
||||
className={styles.stringInput}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const numberDispatch =
|
||||
(propName: string) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const value: number = +e.target.value
|
||||
if (!isFinite(value)) {
|
||||
toastError("Should be a number")
|
||||
return
|
||||
}
|
||||
saveVolunteer({ id: localVolunteer.id, [propName]: +value })
|
||||
setLocalVolunteer({ ...localVolunteer, [propName]: +value })
|
||||
}
|
||||
|
||||
function numberInput(id: string, value: number): JSX.Element {
|
||||
return (
|
||||
<div key={id} className={styles.inputContainer}>
|
||||
<span className={styles.inputDesc}>{id}</span>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
id={id}
|
||||
value={value}
|
||||
onChange={numberDispatch(id)}
|
||||
className={styles.numberInput}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const booleanDispatch =
|
||||
(propName: string) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const value: boolean = e.target.value !== "0" && e.target.value !== ""
|
||||
saveVolunteer({ id: localVolunteer.id, [propName]: value })
|
||||
setLocalVolunteer({ ...localVolunteer, [propName]: value })
|
||||
}
|
||||
|
||||
function booleanInput(id: string, value: boolean): JSX.Element {
|
||||
return (
|
||||
<div key={id} className={styles.inputContainer}>
|
||||
<span className={styles.inputDesc}>{id}</span>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
id={id}
|
||||
value={value ? "X" : ""}
|
||||
onChange={booleanDispatch(id)}
|
||||
className={styles.booleanInput}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const volunteerDefault = new Volunteer()
|
||||
const typeHandler: { [id: string]: (id: string, value: any) => JSX.Element } = {
|
||||
string: stringInput,
|
||||
number: numberInput,
|
||||
boolean: booleanInput,
|
||||
}
|
||||
const keys = Object.keys(volunteerDefault) as (keyof Volunteer)[]
|
||||
|
||||
return (
|
||||
<li className={styles.item} key={volunteer.id}>
|
||||
{keys.map((key) => {
|
||||
const valueType = typeof volunteerDefault[key]
|
||||
const value = localVolunteer[key]
|
||||
return (
|
||||
typeHandler[valueType as string]?.(key, value as any) ||
|
||||
stringInput(key, value as any)
|
||||
)
|
||||
})}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default withUserRole(ROLES.ADMIN, memo(withUserConnected(MemberEdit)))
|
||||
|
||||
export const fetchFor = []
|
39
src/components/Admin/styles.module.scss
Executable file
39
src/components/Admin/styles.module.scss
Executable file
@@ -0,0 +1,39 @@
|
||||
@import "../../theme/variables";
|
||||
@import "../../theme/mixins";
|
||||
|
||||
.title {
|
||||
padding-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.formButtons {
|
||||
margin-top: 10px;
|
||||
padding: 5px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.stringInput {
|
||||
width: 5em;
|
||||
}
|
||||
.numberInput {
|
||||
width: 3em;
|
||||
}
|
||||
.booleanInput {
|
||||
width: 1.5em;
|
||||
}
|
||||
.inputDesc {
|
||||
font-size: small;
|
||||
}
|
@@ -29,8 +29,6 @@ describe("<List />", () => {
|
||||
"5": {
|
||||
id: 5,
|
||||
title: "6 qui prend!",
|
||||
author: "Wolfgang Kramer",
|
||||
editor: "(uncredited) , Design Edge , B",
|
||||
playersMin: 2,
|
||||
playersMax: 10,
|
||||
duration: 45,
|
||||
|
@@ -10,7 +10,7 @@ const KnowledgeIntro: React.FC = (): JSX.Element => (
|
||||
</p>
|
||||
<p>
|
||||
OK signifie que tu peux expliquer le jeu, avec au maximum un coup d'oeil aux règles sur
|
||||
un nombre de cartes à distribuer, ou un nombre de PV déclancheur de fin de partie.
|
||||
un nombre de cartes à distribuer, ou un nombre de PV déclencheur de fin de partie.
|
||||
<br />
|
||||
Bof signifie que tu seras plus utile que la lecture des règles.
|
||||
</p>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { memo, useEffect, useState } from "react"
|
||||
import { memo, useEffect, useState } from "react"
|
||||
import { useSelector, shallowEqual } from "react-redux"
|
||||
import { toast } from "react-toastify"
|
||||
import _ from "lodash"
|
||||
@@ -11,6 +11,12 @@ import { fetchVolunteerPartialAdd } from "../../store/volunteerPartialAdd"
|
||||
import FormButton from "../Form/FormButton/FormButton"
|
||||
import { validEmail } from "../../utils/standardization"
|
||||
import { toastError } from "../../store/utils"
|
||||
import {
|
||||
sendBooleanRadioboxDispatch,
|
||||
sendTextareaDispatch,
|
||||
sendRadioboxDispatch,
|
||||
sendTextDispatch,
|
||||
} from "../input.utils"
|
||||
import {
|
||||
fetchMiscMeetingDateListIfNeed,
|
||||
selectMiscMeetingDateList,
|
||||
@@ -57,26 +63,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||
}, [changingBackground, setChangingBackground])
|
||||
const transitionClass = (i: number) => animations[changingBackground][i - 1]
|
||||
|
||||
const sendTextDispatch =
|
||||
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatchSetter(e.target.value)
|
||||
|
||||
const sendTextareaDispatch =
|
||||
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
dispatchSetter(e.target.value)
|
||||
|
||||
const sendBooleanRadioboxDispatch =
|
||||
(dispatchSetter: React.Dispatch<React.SetStateAction<boolean>>, isYes: boolean) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatchSetter(isYes ? !!e.target.value : !e.target.value)
|
||||
|
||||
const sendRadioboxDispatch =
|
||||
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatchSetter(e.target.value)
|
||||
|
||||
const onSubmit = () => {
|
||||
if (!validEmail(email)) {
|
||||
toastError("Cet email est invalid ><")
|
||||
|
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { render } from "@testing-library/react"
|
||||
import { MemoryRouter } from "react-router-dom"
|
||||
import { volunteerExample } from "../../../services/volunteers"
|
||||
|
||||
import VolunteerSet from "../index"
|
||||
|
||||
describe("<SetVolunteer />", () => {
|
||||
it("renders", () => {
|
||||
const dispatch = jest.fn()
|
||||
const tree = render(
|
||||
<MemoryRouter>
|
||||
<VolunteerSet dispatch={dispatch} volunteer={volunteerExample} />
|
||||
</MemoryRouter>
|
||||
).container.firstChild
|
||||
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
})
|
@@ -1,51 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<SetVolunteer /> renders 1`] = `
|
||||
<section
|
||||
class="VolunteerList"
|
||||
>
|
||||
<h2>
|
||||
Modifier un volunteer
|
||||
</h2>
|
||||
<form>
|
||||
<label
|
||||
for="postFirstname"
|
||||
>
|
||||
Prénom:
|
||||
<input
|
||||
id="postFirstname"
|
||||
name="postFirstname"
|
||||
type="text"
|
||||
value="Aupeix"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postName"
|
||||
>
|
||||
Nom:
|
||||
<input
|
||||
id="postName"
|
||||
name="postName"
|
||||
type="text"
|
||||
value="Amélie"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postAdult"
|
||||
>
|
||||
Majeur:
|
||||
<input
|
||||
id="postAdult"
|
||||
name="postAdult"
|
||||
type="text"
|
||||
value="1"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Save changes
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
`;
|
@@ -1,88 +0,0 @@
|
||||
import React, { useState, memo } from "react"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
import { AppDispatch } from "../../store"
|
||||
|
||||
import { fetchVolunteerSet } from "../../store/volunteerSet"
|
||||
import { Volunteer } from "../../services/volunteers"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
interface Props {
|
||||
dispatch: AppDispatch
|
||||
volunteer: Volunteer
|
||||
}
|
||||
|
||||
const VolunteerSet = ({ dispatch, volunteer }: Props) => {
|
||||
const [firstname, setFirstname] = useState(volunteer.firstname)
|
||||
const [lastname, setName] = useState(volunteer.lastname)
|
||||
const [adult, setAdult] = useState(volunteer.adult)
|
||||
|
||||
const onFirstnameChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setFirstname(e.target.value)
|
||||
const onNameChanged = (e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)
|
||||
const onAdultChanged = (e: React.ChangeEvent<HTMLInputElement>) => setAdult(+e.target.value)
|
||||
|
||||
const onSavePostClicked = () => {
|
||||
if (firstname && lastname) {
|
||||
dispatch(
|
||||
fetchVolunteerSet({
|
||||
...volunteer,
|
||||
firstname,
|
||||
lastname,
|
||||
adult,
|
||||
})
|
||||
)
|
||||
} else {
|
||||
toast.warning("Il faut au moins préciser un prenom et un nom", {
|
||||
position: "top-center",
|
||||
autoClose: 6000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
return (
|
||||
<section className={styles.VolunteerList}>
|
||||
<h2>Modifier un volunteer</h2>
|
||||
<form>
|
||||
<label htmlFor="postFirstname">
|
||||
Prénom:
|
||||
<input
|
||||
type="text"
|
||||
id="postFirstname"
|
||||
name="postFirstname"
|
||||
value={firstname}
|
||||
onChange={onFirstnameChanged}
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="postName">
|
||||
Nom:
|
||||
<input
|
||||
type="text"
|
||||
id="postName"
|
||||
name="postName"
|
||||
value={lastname}
|
||||
onChange={onNameChanged}
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="postAdult">
|
||||
Majeur:
|
||||
<input
|
||||
type="text"
|
||||
id="postAdult"
|
||||
name="postAdult"
|
||||
value={adult}
|
||||
onChange={onAdultChanged}
|
||||
/>
|
||||
</label>
|
||||
<button type="button" onClick={onSavePostClicked}>
|
||||
Save changes
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default memo(VolunteerSet)
|
@@ -1,17 +0,0 @@
|
||||
@import "../../theme/variables";
|
||||
|
||||
.VolunteerList {
|
||||
color: $color-white;
|
||||
|
||||
ul {
|
||||
padding-left: 17px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
import AnnouncementLink from "./AnnouncementLink"
|
||||
import DbEdit, { fetchFor as fetchForDbEdit } from "./Admin/DbEdit"
|
||||
import GameDetailsUpdate, { fetchFor as fetchForGameDetailsUpdate } from "./Admin/GameDetailsUpdate"
|
||||
import Board, { fetchFor as fetchForBoard } from "./VolunteerBoard/Board"
|
||||
import DayWishesForm, {
|
||||
fetchFor as fetchForDayWishesForm,
|
||||
@@ -20,11 +22,14 @@ import TeamWishesForm, {
|
||||
} from "./VolunteerBoard/TeamWishesForm/TeamWishesForm"
|
||||
import VolunteerList from "./VolunteerList"
|
||||
import VolunteerInfo from "./VolunteerInfo"
|
||||
import VolunteerSet from "./VolunteerSet"
|
||||
import WishAdd from "./WishAdd"
|
||||
|
||||
export {
|
||||
AnnouncementLink,
|
||||
DbEdit,
|
||||
fetchForDbEdit,
|
||||
GameDetailsUpdate,
|
||||
fetchForGameDetailsUpdate,
|
||||
Board,
|
||||
fetchForBoard,
|
||||
BoxList,
|
||||
@@ -48,6 +53,5 @@ export {
|
||||
fetchForTeamWishesForm,
|
||||
VolunteerInfo,
|
||||
VolunteerList,
|
||||
VolunteerSet,
|
||||
WishAdd,
|
||||
}
|
||||
|
22
src/components/input.utils.ts
Executable file
22
src/components/input.utils.ts
Executable file
@@ -0,0 +1,22 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import React from "react"
|
||||
|
||||
export const sendTextDispatch =
|
||||
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatchSetter(e.target.value)
|
||||
|
||||
export const sendTextareaDispatch =
|
||||
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
dispatchSetter(e.target.value)
|
||||
|
||||
export const sendBooleanRadioboxDispatch =
|
||||
(dispatchSetter: React.Dispatch<React.SetStateAction<boolean>>, isYes: boolean) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatchSetter(isYes ? !!e.target.value : !e.target.value)
|
||||
|
||||
export const sendRadioboxDispatch =
|
||||
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
dispatchSetter(e.target.value)
|
Reference in New Issue
Block a user