Add db new line support

This commit is contained in:
pikiou 2022-05-24 17:43:12 +02:00
parent da643df6a6
commit 028d703e98
11 changed files with 124 additions and 29 deletions

View File

@ -1,3 +1,4 @@
import { max } from "lodash"
import { FC, memo } from "react" import { FC, memo } from "react"
import { useSelector } from "react-redux" import { useSelector } from "react-redux"
import withUserConnected from "../../utils/withUserConnected" import withUserConnected from "../../utils/withUserConnected"
@ -9,13 +10,18 @@ import useAction from "../../utils/useAction"
import { fetchVolunteerSetIfNeed } from "../../store/volunteerSet" import { fetchVolunteerSetIfNeed } from "../../store/volunteerSet"
import { Volunteer } from "../../services/volunteers" import { Volunteer } from "../../services/volunteers"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import { fetchVolunteerAddNewIfNeed } from "../../store/volunteerAddNew"
const DbEdit: FC = (): JSX.Element => { const DbEdit: FC = (): JSX.Element => {
const volunteers = useSelector(selectVolunteerList) const volunteers = useSelector(selectVolunteerList)
const saveVolunteer = useAction(fetchVolunteerSetIfNeed) const saveVolunteer = useAction(fetchVolunteerSetIfNeed)
const addVolunteer = useAction(fetchVolunteerAddNewIfNeed)
if (!volunteers) { if (!volunteers) {
return <>No member found</> return <>No member found</>
} }
const nextId = (max(volunteers.map((v) => v.id)) || 0) + 1
const nextVolunteer = new Volunteer()
nextVolunteer.id = nextId
return ( return (
<ul className={styles.list}> <ul className={styles.list}>
{volunteers.map((volunteer: Volunteer) => ( {volunteers.map((volunteer: Volunteer) => (
@ -25,6 +31,12 @@ const DbEdit: FC = (): JSX.Element => {
volunteer={volunteer} volunteer={volunteer}
/> />
))} ))}
<MemberEdit
key={nextId}
addBefore={addVolunteer}
saveVolunteer={saveVolunteer}
volunteer={nextVolunteer}
/>
</ul> </ul>
) )
} }

View File

@ -10,16 +10,29 @@ import { toastError } from "../../store/utils"
interface Props { interface Props {
volunteer: Volunteer volunteer: Volunteer
saveVolunteer: (newVolunteer: Partial<Volunteer>) => void saveVolunteer: (newVolunteer: Partial<Volunteer>) => void
addBefore?: () => void
} }
const MemberEdit: FC<Props> = ({ volunteer, saveVolunteer }): JSX.Element => { const MemberEdit: FC<Props> = ({ volunteer, saveVolunteer, addBefore }): JSX.Element => {
const [localVolunteer, setLocalVolunteer] = useState(volunteer) const [localVolunteer, setLocalVolunteer] = useState(volunteer)
async function addAndWait() {
if (addBefore) {
addBefore()
await new Promise<void>((resolve) => {
setTimeout(() => resolve(), 1000)
})
}
}
const stringDispatch = const stringDispatch =
(propName: string) => (propName: string) =>
(e: React.ChangeEvent<HTMLInputElement>): void => { async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
saveVolunteer({ id: localVolunteer.id, [propName]: e.target.value }) const rawValue = e.target.value
setLocalVolunteer({ ...localVolunteer, [propName]: e.target.value }) const value = rawValue
await addAndWait()
saveVolunteer({ id: localVolunteer.id, [propName]: rawValue })
setLocalVolunteer({ ...localVolunteer, [propName]: value })
} }
function stringInput(id: string, value: string): JSX.Element { function stringInput(id: string, value: string): JSX.Element {
@ -40,13 +53,15 @@ const MemberEdit: FC<Props> = ({ volunteer, saveVolunteer }): JSX.Element => {
const numberDispatch = const numberDispatch =
(propName: string) => (propName: string) =>
(e: React.ChangeEvent<HTMLInputElement>): void => { async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
const value: number = +e.target.value const rawValue = e.target.value
const value: number = +rawValue
if (!isFinite(value)) { if (!isFinite(value)) {
toastError("Should be a number") toastError("Should be a number")
return return
} }
saveVolunteer({ id: localVolunteer.id, [propName]: +value }) await addAndWait()
saveVolunteer({ id: localVolunteer.id, [propName]: rawValue })
setLocalVolunteer({ ...localVolunteer, [propName]: +value }) setLocalVolunteer({ ...localVolunteer, [propName]: +value })
} }
@ -68,9 +83,11 @@ const MemberEdit: FC<Props> = ({ volunteer, saveVolunteer }): JSX.Element => {
const booleanDispatch = const booleanDispatch =
(propName: string) => (propName: string) =>
(e: React.ChangeEvent<HTMLInputElement>): void => { async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
const value: boolean = e.target.value !== "0" && e.target.value !== "" const rawValue = e.target.value
saveVolunteer({ id: localVolunteer.id, [propName]: value }) const value: boolean = rawValue !== "0" && rawValue !== ""
await addAndWait()
saveVolunteer({ id: localVolunteer.id, [propName]: rawValue })
setLocalVolunteer({ ...localVolunteer, [propName]: value }) setLocalVolunteer({ ...localVolunteer, [propName]: value })
} }
@ -112,6 +129,9 @@ const MemberEdit: FC<Props> = ({ volunteer, saveVolunteer }): JSX.Element => {
) )
} }
MemberEdit.defaultProps = {
addBefore: undefined,
}
export default withUserRole(ROLES.ADMIN, memo(withUserConnected(MemberEdit))) export default withUserRole(ROLES.ADMIN, memo(withUserConnected(MemberEdit)))
export const fetchFor = [] export const fetchFor = []

View File

@ -133,7 +133,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
) )
const { error: volunteerError, entities: volunteer } = useSelector( const { error: volunteerError, entities: volunteer } = useSelector(
(state: AppState) => state.volunteerAdd, (state: AppState) => state.volunteerPartialAdd,
shallowEqual shallowEqual
) )

View File

@ -1,4 +1,4 @@
import { assign, cloneDeep, omit, pick } from "lodash" import { assign, cloneDeep, max, omit, pick } from "lodash"
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
import sgMail from "@sendgrid/mail" import sgMail from "@sendgrid/mail"
@ -31,6 +31,26 @@ export const volunteerListGet = expressAccessor.get(async (list, _body, id) => {
return list return list
}) })
export const volunteerAddNew = expressAccessor.add(async (list, _body, _id, roles) => {
if (!roles.includes("admin")) {
throw Error(`À moins d'être admin, on ne peut pas modifier n'importe quel utilisateur`)
}
const id = (max(list.map((v) => v.id)) || 0) + 1
const password = generatePassword()
const passwordHash = await bcrypt.hash(password, 10)
const newVolunteer: Volunteer = new Volunteer()
newVolunteer.id = id
newVolunteer.password1 = passwordHash
newVolunteer.password2 = passwordHash
newVolunteer.firstname = password
return {
toDatabase: newVolunteer,
toCaller: newVolunteer,
}
})
export const volunteerSet = expressAccessor.set(async (list, body, _id, roles) => { export const volunteerSet = expressAccessor.set(async (list, body, _id, roles) => {
if (!roles.includes("admin")) { if (!roles.includes("admin")) {
throw Error(`À moins d'être admin, on ne peut pas modifier n'importe quel utilisateur`) throw Error(`À moins d'être admin, on ne peut pas modifier n'importe quel utilisateur`)

View File

@ -35,6 +35,7 @@ import {
volunteerTeamAssignSet, volunteerTeamAssignSet,
volunteerListGet, volunteerListGet,
volunteerKnowledgeSet, volunteerKnowledgeSet,
volunteerAddNew,
} from "./gsheets/volunteers" } from "./gsheets/volunteers"
import { wishListGet, wishAdd } from "./gsheets/wishes" import { wishListGet, wishAdd } from "./gsheets/wishes"
import config from "../config" import config from "../config"
@ -114,6 +115,7 @@ app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishe
app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet) app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet)
// Admin only // Admin only
app.post("/VolunteerAddNew", secure as RequestHandler, volunteerAddNew)
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet) app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
app.get("/GameDetailsUpdate", secure as RequestHandler, gameDetailsUpdate) app.get("/GameDetailsUpdate", secure as RequestHandler, gameDetailsUpdate)

View File

@ -19,6 +19,7 @@ export const volunteerDiscordIdGet = serviceAccessors.securedCustomGet<
[number], [number],
VolunteerDiscordId VolunteerDiscordId
>("DiscordId") >("DiscordId")
export const volunteerAddNew = serviceAccessors.securedCustomPost<[]>("AddNew")
export const volunteerPartialAdd = serviceAccessors.customPost<[Partial<Volunteer>]>("PartialAdd") export const volunteerPartialAdd = serviceAccessors.customPost<[Partial<Volunteer>]>("PartialAdd")
export const volunteerSet = serviceAccessors.securedCustomPost<[Partial<Volunteer>]>("Set") export const volunteerSet = serviceAccessors.securedCustomPost<[Partial<Volunteer>]>("Set")

View File

@ -11,7 +11,7 @@ import miscMeetingDateList from "./miscMeetingDateList"
import postulantAdd from "./postulantAdd" import postulantAdd from "./postulantAdd"
import teamList from "./teamList" import teamList from "./teamList"
import ui from "./ui" import ui from "./ui"
import volunteerAdd from "./volunteerPartialAdd" import volunteerPartialAdd from "./volunteerPartialAdd"
import volunteerAsksSet from "./volunteerAsksSet" import volunteerAsksSet from "./volunteerAsksSet"
import volunteerDayWishesSet from "./volunteerDayWishesSet" import volunteerDayWishesSet from "./volunteerDayWishesSet"
import volunteerDiscordId from "./volunteerDiscordId" import volunteerDiscordId from "./volunteerDiscordId"
@ -39,7 +39,7 @@ export default (history: History) => ({
postulantAdd, postulantAdd,
teamList, teamList,
ui, ui,
volunteerAdd, volunteerPartialAdd,
volunteerAsksSet, volunteerAsksSet,
volunteerDayWishesSet, volunteerDayWishesSet,
volunteerDiscordId, volunteerDiscordId,

View File

@ -64,8 +64,8 @@ export function elementFetch<Element, ServiceInput extends Array<any>>(
} }
} }
export function elementAddFetch<Element>( export function elementAddFetch<Element, ServiceInput extends Array<any>>(
elementAddService: (elementWithoutId: Omit<Element, "id">) => Promise<{ elementAddService: (...idArgs: ServiceInput) => Promise<{
data?: Element | undefined data?: Element | undefined
error?: Error | undefined error?: Error | undefined
}>, }>,
@ -74,12 +74,12 @@ export function elementAddFetch<Element>(
getFailure: ActionCreatorWithPayload<string, string>, getFailure: ActionCreatorWithPayload<string, string>,
errorMessage?: (error: Error) => void, errorMessage?: (error: Error) => void,
successMessage?: () => void successMessage?: () => void
): (elementWithoutId: Omit<Element, "id">) => AppThunk { ): (...idArgs: ServiceInput) => AppThunk {
return (elementWithoutId: Omit<Element, "id">): AppThunk => return (...idArgs: ServiceInput): AppThunk =>
async (dispatch) => { async (dispatch) => {
dispatch(getRequesting()) dispatch(getRequesting())
const { error, data } = await elementAddService(elementWithoutId) const { error, data } = await elementAddService(...idArgs)
if (error) { if (error) {
dispatch(getFailure(error.message)) dispatch(getFailure(error.message))

View File

@ -0,0 +1,45 @@
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
import { StateRequest, elementAddFetch, toastError } from "./utils"
import { Volunteer } from "../services/volunteers"
import { volunteerAddNew } from "../services/volunteersAccessors"
import { AppThunk } from "."
const volunteerAdapter = createEntityAdapter<Volunteer>()
const volunteerAddNewSlice = createSlice({
name: "volunteerAddNew",
initialState: volunteerAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<Volunteer>) => {
state.readyStatus = "success"
volunteerAdapter.setOne(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default volunteerAddNewSlice.reducer
export const { getRequesting, getSuccess, getFailure } = volunteerAddNewSlice.actions
export const fetchVolunteerAddNew = elementAddFetch(
volunteerAddNew,
getRequesting,
getSuccess,
getFailure,
() => toastError("Erreur d'ajout !"),
() => null
)
export const fetchVolunteerAddNewIfNeed = (): AppThunk => (dispatch, getState) => {
const { jwt } = getState().auth
return dispatch(fetchVolunteerAddNew(jwt))
}

View File

@ -1,13 +1,13 @@
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
import { StateRequest, elementAddFetch } from "./utils" import { StateRequest, elementAddFetch, toastError } from "./utils"
import { Volunteer } from "../services/volunteers" import { Volunteer } from "../services/volunteers"
import { volunteerPartialAdd } from "../services/volunteersAccessors" import { volunteerPartialAdd } from "../services/volunteersAccessors"
const volunteerAdapter = createEntityAdapter<Volunteer>() const volunteerAdapter = createEntityAdapter<Volunteer>()
const volunteerPartialAddSlice = createSlice({ const volunteerPartialAddSlice = createSlice({
name: "volunteerAdd", name: "volunteerPartialAdd",
initialState: volunteerAdapter.getInitialState({ initialState: volunteerAdapter.getInitialState({
readyStatus: "idle", readyStatus: "idle",
} as StateRequest), } as StateRequest),
@ -34,6 +34,6 @@ export const fetchVolunteerPartialAdd = elementAddFetch(
getRequesting, getRequesting,
getSuccess, getSuccess,
getFailure, getFailure,
() => null, () => toastError("Erreur d'inscription !"),
() => null () => null
) )

View File

@ -41,19 +41,14 @@ export const fetchVolunteerSet = elementFetch(
() => toastSuccess("Bénévole modifié !") () => toastSuccess("Bénévole modifié !")
) )
const shouldFetchVolunteerSet = (_state: AppState) => true
export const fetchVolunteerSetIfNeed = export const fetchVolunteerSetIfNeed =
(newPartialVolunteer: Partial<Volunteer>): AppThunk => (newPartialVolunteer: Partial<Volunteer>): AppThunk =>
(dispatch, getState) => { (dispatch, getState) => {
const { jwt } = getState().auth const { jwt } = getState().auth
if (shouldFetchVolunteerSet(getState())) return dispatch(fetchVolunteerSet(jwt, newPartialVolunteer))
return dispatch(fetchVolunteerSet(jwt, newPartialVolunteer))
return null
} }
export const selectVolunteerSet = createSelector( export const selectVolunteerSet = createSelector(
(state: AppState) => state, (state: AppState) => state,
(state): string | undefined => state.volunteerSet?.entity?.discordId (state): Volunteer | undefined => state.volunteerSet?.entity
) )