mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-08 08:34:20 +02:00
Adds redux list add example
This commit is contained in:
parent
2616b109d7
commit
1ddb710a6c
@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import * as _ from "lodash"
|
||||
import path from "path"
|
||||
import { promises as fs } from "fs"
|
||||
@ -329,6 +330,20 @@ class Test {
|
||||
tictactoe: boolean[] = []
|
||||
}
|
||||
|
||||
// class Membre {
|
||||
// membreId = 0
|
||||
|
||||
// nom = ""
|
||||
|
||||
// prenom = ""
|
||||
|
||||
// mail = ""
|
||||
|
||||
// telephone = ""
|
||||
|
||||
// photo = ""
|
||||
// }
|
||||
|
||||
// Can't run it on every test, it requires private access to a google sheet
|
||||
async function testGSheetAPi(): Promise<void> {
|
||||
const dataset: Test[] = [
|
||||
@ -368,6 +383,15 @@ async function testGSheetAPi(): Promise<void> {
|
||||
},
|
||||
]
|
||||
|
||||
// console.log("Lecture des Membres...")
|
||||
// const datasetMembresLu = await getList<Membre>("Membres", new Membre())
|
||||
// if (!datasetMembresLu) {
|
||||
// console.log("ECHEC de la lecture des membres", datasetMembresLu)
|
||||
// return
|
||||
// }
|
||||
// console.log("Extraction des membres réussie")
|
||||
// await fs.writeFile("membres.json", JSON.stringify(datasetMembresLu))
|
||||
|
||||
console.log("Test d'écriture...")
|
||||
const resultatEcriture = await setList<Test>("Tests de l'API", dataset)
|
||||
if (!resultatEcriture) {
|
||||
|
@ -99,6 +99,7 @@
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-config": "^5.1.1",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-toastify": "^8.1.0",
|
||||
"readline": "^1.3.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { Link } from "react-router-dom"
|
||||
import { RouteConfig, renderRoutes } from "react-router-config"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { ToastContainer } from "react-toastify"
|
||||
|
||||
import logo from "../static/logo.svg"
|
||||
import config from "../config"
|
||||
// Import your global styles here
|
||||
import "normalize.css/normalize.css"
|
||||
import styles from "./styles.module.scss"
|
||||
import "react-toastify/dist/ReactToastify.css"
|
||||
|
||||
interface Route {
|
||||
route: { routes: RouteConfig[] }
|
||||
@ -24,6 +26,7 @@ const App = ({ route }: Route): JSX.Element => (
|
||||
<hr />
|
||||
{/* Child routes won't render without this */}
|
||||
{renderRoutes(route.routes)}
|
||||
<ToastContainer />
|
||||
</div>
|
||||
)
|
||||
|
||||
|
20
src/components/AddEnvie/__tests__/AddEnvie.tsx
Normal file
20
src/components/AddEnvie/__tests__/AddEnvie.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { render } from "@testing-library/react"
|
||||
import { MemoryRouter } from "react-router-dom"
|
||||
|
||||
import AddEnvie from "../index"
|
||||
|
||||
describe("<AddEnvie />", () => {
|
||||
it("renders", () => {
|
||||
const dispatch = jest.fn()
|
||||
const tree = render(
|
||||
<MemoryRouter>
|
||||
<AddEnvie dispatch={dispatch} />
|
||||
</MemoryRouter>
|
||||
).container.firstChild
|
||||
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
})
|
@ -0,0 +1,69 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<AddEnvie /> renders 1`] = `
|
||||
<section
|
||||
class="EnvieList"
|
||||
>
|
||||
<h2>
|
||||
Ajouter une nouvelle envie
|
||||
</h2>
|
||||
<form>
|
||||
<label
|
||||
for="postDomaine"
|
||||
>
|
||||
Domaine:
|
||||
<input
|
||||
id="postDomaine"
|
||||
name="postDomaine"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEnvies"
|
||||
>
|
||||
Envies:
|
||||
<textarea
|
||||
id="postEnvies"
|
||||
name="postEnvies"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postPrecisions"
|
||||
>
|
||||
Precisions:
|
||||
<textarea
|
||||
id="postPrecisions"
|
||||
name="postPrecisions"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEquipes"
|
||||
>
|
||||
Equipes:
|
||||
<input
|
||||
id="postEquipes"
|
||||
name="postEquipes"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postDateAjout"
|
||||
>
|
||||
DateAjout:
|
||||
<input
|
||||
id="postDateAjout"
|
||||
name="postDateAjout"
|
||||
type="date"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Save Post
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
`;
|
117
src/components/AddEnvie/index.tsx
Normal file
117
src/components/AddEnvie/index.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import React, { useState, memo } from "react"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
import { AppDispatch } from "../../store"
|
||||
|
||||
import { postEnvie } from "../../store/envieAdd"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
interface Props {
|
||||
dispatch: AppDispatch
|
||||
}
|
||||
|
||||
const AddEnvie = ({ dispatch }: Props) => {
|
||||
const [domaine, setDomaine] = useState("")
|
||||
const [envies, setEnvies] = useState("")
|
||||
const [precisions, setPrecisions] = useState("")
|
||||
const [equipes, setEquipes] = useState([""])
|
||||
const [dateAjout, setDateAjout] = useState("")
|
||||
|
||||
const onDomaineChanged = (e: React.ChangeEvent<HTMLInputElement>) => setDomaine(e.target.value)
|
||||
const onEnviesChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => setEnvies(e.target.value)
|
||||
const onPrecisionsChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
setPrecisions(e.target.value)
|
||||
const onEquipesChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setEquipes(e.target.value.split(/, ?/))
|
||||
const onDateAjoutChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setDateAjout(e.target.value)
|
||||
|
||||
const onSavePostClicked = () => {
|
||||
if (domaine && envies) {
|
||||
dispatch(
|
||||
postEnvie({
|
||||
domaine,
|
||||
envies,
|
||||
precisions,
|
||||
equipes,
|
||||
dateAjout,
|
||||
})
|
||||
)
|
||||
|
||||
setDomaine("")
|
||||
setEnvies("")
|
||||
setPrecisions("")
|
||||
setEquipes([""])
|
||||
setDateAjout("")
|
||||
} else {
|
||||
toast.warning("Il faut au moins préciser un domaine et l'envie", {
|
||||
position: "top-center",
|
||||
autoClose: 6000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
return (
|
||||
<section className={styles.EnvieList}>
|
||||
<h2>Ajouter une nouvelle envie</h2>
|
||||
<form>
|
||||
<label htmlFor="postDomaine">
|
||||
Domaine:
|
||||
<input
|
||||
type="text"
|
||||
id="postDomaine"
|
||||
name="postDomaine"
|
||||
value={domaine}
|
||||
onChange={onDomaineChanged}
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="postEnvies">
|
||||
Envies:
|
||||
<textarea
|
||||
id="postEnvies"
|
||||
name="postEnvies"
|
||||
value={envies}
|
||||
onChange={onEnviesChanged}
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="postPrecisions">
|
||||
Precisions:
|
||||
<textarea
|
||||
id="postPrecisions"
|
||||
name="postPrecisions"
|
||||
value={precisions}
|
||||
onChange={onPrecisionsChanged}
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="postEquipes">
|
||||
Equipes:
|
||||
<input
|
||||
type="text"
|
||||
id="postEquipes"
|
||||
name="postEquipes"
|
||||
value={equipes.join(", ")}
|
||||
onChange={onEquipesChanged}
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="postDateAjout">
|
||||
DateAjout:
|
||||
<input
|
||||
type="date"
|
||||
id="postDateAjout"
|
||||
name="postDateAjout"
|
||||
value={dateAjout}
|
||||
onChange={onDateAjoutChanged}
|
||||
/>
|
||||
</label>
|
||||
<button type="button" onClick={onSavePostClicked}>
|
||||
Save Post
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default memo(AddEnvie)
|
17
src/components/AddEnvie/styles.module.scss
Normal file
17
src/components/AddEnvie/styles.module.scss
Normal file
@ -0,0 +1,17 @@
|
||||
@import "../../theme/variables";
|
||||
|
||||
.jav-game-list {
|
||||
color: $color-white;
|
||||
|
||||
ul {
|
||||
padding-left: 17px;
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
@ -3,5 +3,6 @@ import JeuxJavList from "./JeuxJavList"
|
||||
import Info from "./Info"
|
||||
import ErrorBoundary from "./ErrorBoundary"
|
||||
import Loading from "./Loading"
|
||||
import AddEnvie from "./AddEnvie"
|
||||
|
||||
export { List, JeuxJavList, Info, ErrorBoundary, Loading }
|
||||
export { List, JeuxJavList, Info, ErrorBoundary, Loading, AddEnvie }
|
||||
|
@ -1,25 +1,33 @@
|
||||
import { Request, Response, NextFunction } from "express"
|
||||
import { getList, setList } from "./utils"
|
||||
import { Envie } from "../services/envies"
|
||||
import { getList, add } from "./utils"
|
||||
import { Envie, EnvieWithoutId } from "../services/envies"
|
||||
|
||||
export const getEnvieList = async (
|
||||
_request: Request,
|
||||
response: Response,
|
||||
_next: NextFunction
|
||||
): Promise<void> => {
|
||||
const list = await getList<Envie>("Envies d'aider", new Envie())
|
||||
if (list) {
|
||||
response.status(200).json(list)
|
||||
try {
|
||||
const list = await getList<Envie>("Envies d'aider", new Envie())
|
||||
if (list) {
|
||||
response.status(200).json(list)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
response.status(400).json(e)
|
||||
}
|
||||
}
|
||||
|
||||
export const setEnvieList = async (
|
||||
export const addEnvie = async (
|
||||
request: Request,
|
||||
response: Response,
|
||||
_next: NextFunction
|
||||
): Promise<void> => {
|
||||
const success = await setList<Envie>("Envies d'aider", request.body)
|
||||
if (success) {
|
||||
response.status(200).json()
|
||||
try {
|
||||
const envie = await add<EnvieWithoutId, Envie>("Envies d'aider", "envieId", request.body)
|
||||
if (envie) {
|
||||
response.status(200).json(envie)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
response.status(400).json(e)
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,13 @@ export const getJeuxJavList = async (
|
||||
response: Response,
|
||||
_next: NextFunction
|
||||
): Promise<void> => {
|
||||
const list = await getList<JeuxJav>("Jeux JAV", new JeuxJav())
|
||||
if (list) {
|
||||
response.status(200).json(list)
|
||||
try {
|
||||
const list = await getList<JeuxJav>("Jeux JAV", new JeuxJav())
|
||||
if (list) {
|
||||
response.status(200).json(list)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
response.status(400).json(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,7 @@ export async function getList<Element extends object>(
|
||||
const rows = (await sheet.getRows()) as StringifiedElement[]
|
||||
const elements: Element[] = []
|
||||
if (!rows[0]) {
|
||||
// TODO: Report format error to database maintainers
|
||||
return []
|
||||
throw new Error(`No column types defined in sheet ${sheetName}`)
|
||||
}
|
||||
const types = _.pick(rows[0], Object.keys(specimen)) as Record<keyof Element, string>
|
||||
rows.shift()
|
||||
@ -47,7 +46,7 @@ export async function setList<Element extends object>(
|
||||
// Load sheet into an array of objects
|
||||
const rows = await sheet.getRows()
|
||||
if (!rows[0]) {
|
||||
return undefined
|
||||
throw new Error(`No column types defined in sheet ${sheetName}`)
|
||||
}
|
||||
const types = _.pick(rows[0], Object.keys(elements[0] || {})) as Record<keyof Element, string>
|
||||
|
||||
@ -58,10 +57,6 @@ export async function setList<Element extends object>(
|
||||
const row = rows[rowid]
|
||||
const stringifiedRow = stringifyElement(element, types)
|
||||
|
||||
if (stringifiedRow === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (!row) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await sheet.addRow(stringifiedRow)
|
||||
@ -94,6 +89,39 @@ export async function setList<Element extends object>(
|
||||
return true
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export async function add<ElementNoId extends object, Element extends ElementNoId>(
|
||||
sheetName: string,
|
||||
idFieldName: string,
|
||||
partialElement: Partial<ElementNoId>
|
||||
): Promise<Element | undefined> {
|
||||
if (!partialElement) {
|
||||
return undefined
|
||||
}
|
||||
const sheet = await getGSheet(sheetName)
|
||||
|
||||
// Load sheet into an array of objects
|
||||
const rows = await sheet.getRows()
|
||||
if (!rows[0]) {
|
||||
throw new Error(`No column types defined in sheet ${sheetName}`)
|
||||
}
|
||||
const types = {
|
||||
[idFieldName]: "number",
|
||||
...(_.pick(rows[0], Object.keys(partialElement || {})) as Record<keyof Element, string>),
|
||||
}
|
||||
|
||||
// Create full element
|
||||
rows.shift()
|
||||
const highestId = rows.reduce((id: number, row) => Math.max(id, +row[idFieldName] || 0), 0)
|
||||
const element = { [idFieldName]: highestId + 1, ...partialElement } as Element
|
||||
|
||||
// Add element
|
||||
const stringifiedRow = stringifyElement(element, types)
|
||||
await sheet.addRow(stringifiedRow)
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
async function getGSheet(sheetName: string): Promise<GoogleSpreadsheetWorksheet> {
|
||||
const doc = new GoogleSpreadsheet("1pMMKcYx6NXLOqNn6pLHJTPMTOLRYZmSNg2QQcAu7-Pw")
|
||||
const creds = await fs.readFile(CRED_PATH)
|
||||
@ -108,13 +136,10 @@ function parseElement<Element extends object>(
|
||||
rawElement: Record<keyof Element, string>,
|
||||
types: Record<keyof Element, string>,
|
||||
specimen: Element
|
||||
): Element | undefined {
|
||||
): Element {
|
||||
const fullElement = _.reduce(
|
||||
types,
|
||||
(element: any, type: string, prop: string) => {
|
||||
if (element === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const rawProp: string = rawElement[prop as keyof Element]
|
||||
switch (type) {
|
||||
case "string":
|
||||
@ -136,14 +161,13 @@ function parseElement<Element extends object>(
|
||||
element[prop] = new Date(+matchDate[3], +matchDate[2] - 1, +matchDate[1])
|
||||
break
|
||||
}
|
||||
return undefined // TODO: Report format error to database maintainers
|
||||
break
|
||||
throw new Error(`Unable to read date from ${rawProp}`)
|
||||
|
||||
default:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const matchArrayType = type.match(/^(number|string|boolean|date)\[([^\]]+)\]$/)
|
||||
if (!matchArrayType) {
|
||||
return undefined
|
||||
throw new Error(`Unknown array type for ${type}`)
|
||||
}
|
||||
if (!rawProp) {
|
||||
element[prop] = []
|
||||
@ -189,10 +213,11 @@ function parseElement<Element extends object>(
|
||||
return true
|
||||
})
|
||||
if (!rightFormat) {
|
||||
return undefined
|
||||
throw new Error(`One array item is not a date in ${rawProp}`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown array type ${arrayType}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -207,17 +232,10 @@ function parseElement<Element extends object>(
|
||||
function stringifyElement<Element extends object>(
|
||||
element: Element,
|
||||
types: Record<keyof Element, string>
|
||||
): Record<keyof Element, string> | undefined {
|
||||
const rawElement: Record<keyof Element, string> | undefined = _.reduce(
|
||||
): Record<keyof Element, string> {
|
||||
const rawElement: Record<keyof Element, string> = _.reduce(
|
||||
types,
|
||||
(
|
||||
stringifiedElement: Record<keyof Element, string> | undefined,
|
||||
type: string,
|
||||
prop: string
|
||||
) => {
|
||||
if (stringifiedElement === undefined) {
|
||||
return undefined
|
||||
}
|
||||
(stringifiedElement: Record<keyof Element, string>, type: string, prop: string) => {
|
||||
const value = element[prop as keyof Element]
|
||||
switch (type) {
|
||||
case "string":
|
||||
@ -233,22 +251,16 @@ function stringifyElement<Element extends object>(
|
||||
break
|
||||
|
||||
case "date":
|
||||
if (value instanceof Date) {
|
||||
stringifiedElement[prop as keyof Element] = `${value.getDate()}/${
|
||||
value.getMonth() + 1
|
||||
}/${value.getFullYear()}`
|
||||
break
|
||||
} else {
|
||||
console.error("Wrong date format in stringifyElement")
|
||||
return undefined // TODO: Report format error to database maintainers
|
||||
}
|
||||
stringifiedElement[prop as keyof Element] = stringifiedDate(value)
|
||||
break
|
||||
|
||||
default:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const matchArrayType = type.match(/^(number|string|boolean|date)\[([^\]]+)\]$/)
|
||||
if (!matchArrayType || !_.isArray(value)) {
|
||||
console.error("Unknown matchArrayType or not an array in stringifyElement")
|
||||
return undefined
|
||||
throw new Error(
|
||||
"Unknown matchArrayType or not an array in stringifyElement"
|
||||
)
|
||||
}
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const arrayType = matchArrayType[1]
|
||||
@ -258,7 +270,7 @@ function stringifyElement<Element extends object>(
|
||||
switch (arrayType) {
|
||||
case "string":
|
||||
if (!_.every(value, _.isString)) {
|
||||
return undefined
|
||||
throw new Error(`Each date of ${value} is not a string`)
|
||||
}
|
||||
stringifiedElement[prop as keyof Element] = formulaSafe(
|
||||
value.join(delimiter)
|
||||
@ -267,14 +279,14 @@ function stringifyElement<Element extends object>(
|
||||
|
||||
case "number":
|
||||
if (!_.every(value, _.isNumber)) {
|
||||
return undefined
|
||||
throw new Error(`Each date of ${value} is not a number`)
|
||||
}
|
||||
stringifiedElement[prop as keyof Element] = value.join(delimiter)
|
||||
break
|
||||
|
||||
case "boolean":
|
||||
if (!_.every(value, _.isBoolean)) {
|
||||
return undefined
|
||||
throw new Error(`Each date of ${value} is not a boolean`)
|
||||
}
|
||||
stringifiedElement[prop as keyof Element] = _.map(value, (val) =>
|
||||
val ? "X" : ""
|
||||
@ -283,7 +295,7 @@ function stringifyElement<Element extends object>(
|
||||
|
||||
case "date":
|
||||
if (!_.every(value, _.isDate)) {
|
||||
return undefined
|
||||
throw new Error(`Each date of ${value} is not a date`)
|
||||
}
|
||||
stringifiedElement[prop as keyof Element] = _.map(
|
||||
value,
|
||||
@ -293,7 +305,7 @@ function stringifyElement<Element extends object>(
|
||||
break
|
||||
|
||||
default:
|
||||
return undefined
|
||||
throw new Error(`Unknown array type ${arrayType}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,4 +321,19 @@ function formulaSafe(value: string): string {
|
||||
return value.replace(/^=+/, "")
|
||||
}
|
||||
|
||||
function stringifiedDate(value: unknown): string {
|
||||
let date: Date
|
||||
if (value instanceof Date) {
|
||||
date = value
|
||||
} else if (typeof value === "string") {
|
||||
try {
|
||||
date = new Date(value)
|
||||
} catch (e) {
|
||||
throw new Error("Wrong date string format in stringifyElement")
|
||||
}
|
||||
} else {
|
||||
throw new Error("Wrong date format in stringifyElement")
|
||||
}
|
||||
return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`
|
||||
}
|
||||
export { SCOPES }
|
||||
|
@ -6,7 +6,7 @@ import { Helmet } from "react-helmet"
|
||||
import { AppState, AppThunk } from "../../store"
|
||||
import { fetchJeuxJavListIfNeed } from "../../store/jeuxJavList"
|
||||
import { fetchEnvieListIfNeed } from "../../store/envieList"
|
||||
import { JeuxJavList } from "../../components"
|
||||
import { JeuxJavList, AddEnvie } from "../../components"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
export type Props = RouteComponentProps
|
||||
@ -31,16 +31,20 @@ function useList(stateToProp: (state: AppState) => any, fetchDataIfNeed: () => A
|
||||
}
|
||||
}
|
||||
|
||||
const Home: FC<Props> = (): JSX.Element => (
|
||||
<div className={styles.Home}>
|
||||
<Helmet title="Home" />
|
||||
{/* {useList((state: AppState) => state.envieList, fetchEnvieListifNeed)()} */}
|
||||
{useList((state: AppState) => state.jeuxJavList, fetchJeuxJavListIfNeed)()}
|
||||
{/* <button type="button" onClick={() => setList([{id: 3, joueurs: 4, duree: 5, description: "abcd"}])}>
|
||||
const Home: FC<Props> = (): JSX.Element => {
|
||||
const dispatch = useDispatch()
|
||||
return (
|
||||
<div className={styles.Home}>
|
||||
<Helmet title="Home" />
|
||||
<AddEnvie dispatch={dispatch} />
|
||||
{/* {useList((state: AppState) => state.envieList, fetchEnvieListifNeed)()} */}
|
||||
{useList((state: AppState) => state.jeuxJavList, fetchJeuxJavListIfNeed)()}
|
||||
{/* <button type="button" onClick={() => setList([{id: 3, joueurs: 4, duree: 5, description: "abcd"}])}>
|
||||
Set list!
|
||||
</button> */}
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch server-side data here
|
||||
export const loadData = (): AppThunk[] => [
|
||||
|
@ -4,6 +4,71 @@ exports[`<Home /> renders an error if loading failed 1`] = `
|
||||
<div
|
||||
class="Home"
|
||||
>
|
||||
<section
|
||||
class="EnvieList"
|
||||
>
|
||||
<h2>
|
||||
Ajouter une nouvelle envie
|
||||
</h2>
|
||||
<form>
|
||||
<label
|
||||
for="postDomaine"
|
||||
>
|
||||
Domaine:
|
||||
<input
|
||||
id="postDomaine"
|
||||
name="postDomaine"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEnvies"
|
||||
>
|
||||
Envies:
|
||||
<textarea
|
||||
id="postEnvies"
|
||||
name="postEnvies"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postPrecisions"
|
||||
>
|
||||
Precisions:
|
||||
<textarea
|
||||
id="postPrecisions"
|
||||
name="postPrecisions"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEquipes"
|
||||
>
|
||||
Equipes:
|
||||
<input
|
||||
id="postEquipes"
|
||||
name="postEquipes"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postDateAjout"
|
||||
>
|
||||
DateAjout:
|
||||
<input
|
||||
id="postDateAjout"
|
||||
name="postDateAjout"
|
||||
type="date"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Save Post
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
<p>
|
||||
Oops, Failed to load list!
|
||||
</p>
|
||||
@ -14,6 +79,71 @@ exports[`<Home /> renders the <List /> if loading was successful 1`] = `
|
||||
<div
|
||||
class="Home"
|
||||
>
|
||||
<section
|
||||
class="EnvieList"
|
||||
>
|
||||
<h2>
|
||||
Ajouter une nouvelle envie
|
||||
</h2>
|
||||
<form>
|
||||
<label
|
||||
for="postDomaine"
|
||||
>
|
||||
Domaine:
|
||||
<input
|
||||
id="postDomaine"
|
||||
name="postDomaine"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEnvies"
|
||||
>
|
||||
Envies:
|
||||
<textarea
|
||||
id="postEnvies"
|
||||
name="postEnvies"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postPrecisions"
|
||||
>
|
||||
Precisions:
|
||||
<textarea
|
||||
id="postPrecisions"
|
||||
name="postPrecisions"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEquipes"
|
||||
>
|
||||
Equipes:
|
||||
<input
|
||||
id="postEquipes"
|
||||
name="postEquipes"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postDateAjout"
|
||||
>
|
||||
DateAjout:
|
||||
<input
|
||||
id="postDateAjout"
|
||||
name="postDateAjout"
|
||||
type="date"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Save Post
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
<div
|
||||
class="JeuxJavList"
|
||||
>
|
||||
@ -36,6 +166,71 @@ exports[`<Home /> renders the loading status if data invalid 1`] = `
|
||||
<div
|
||||
class="Home"
|
||||
>
|
||||
<section
|
||||
class="EnvieList"
|
||||
>
|
||||
<h2>
|
||||
Ajouter une nouvelle envie
|
||||
</h2>
|
||||
<form>
|
||||
<label
|
||||
for="postDomaine"
|
||||
>
|
||||
Domaine:
|
||||
<input
|
||||
id="postDomaine"
|
||||
name="postDomaine"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEnvies"
|
||||
>
|
||||
Envies:
|
||||
<textarea
|
||||
id="postEnvies"
|
||||
name="postEnvies"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postPrecisions"
|
||||
>
|
||||
Precisions:
|
||||
<textarea
|
||||
id="postPrecisions"
|
||||
name="postPrecisions"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEquipes"
|
||||
>
|
||||
Equipes:
|
||||
<input
|
||||
id="postEquipes"
|
||||
name="postEquipes"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postDateAjout"
|
||||
>
|
||||
DateAjout:
|
||||
<input
|
||||
id="postDateAjout"
|
||||
name="postDateAjout"
|
||||
type="date"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Save Post
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
<p>
|
||||
Loading...
|
||||
</p>
|
||||
@ -46,6 +241,71 @@ exports[`<Home /> renders the loading status if requesting data 1`] = `
|
||||
<div
|
||||
class="Home"
|
||||
>
|
||||
<section
|
||||
class="EnvieList"
|
||||
>
|
||||
<h2>
|
||||
Ajouter une nouvelle envie
|
||||
</h2>
|
||||
<form>
|
||||
<label
|
||||
for="postDomaine"
|
||||
>
|
||||
Domaine:
|
||||
<input
|
||||
id="postDomaine"
|
||||
name="postDomaine"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEnvies"
|
||||
>
|
||||
Envies:
|
||||
<textarea
|
||||
id="postEnvies"
|
||||
name="postEnvies"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postPrecisions"
|
||||
>
|
||||
Precisions:
|
||||
<textarea
|
||||
id="postPrecisions"
|
||||
name="postPrecisions"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postEquipes"
|
||||
>
|
||||
Equipes:
|
||||
<input
|
||||
id="postEquipes"
|
||||
name="postEquipes"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
for="postDateAjout"
|
||||
>
|
||||
DateAjout:
|
||||
<input
|
||||
id="postDateAjout"
|
||||
name="postDateAjout"
|
||||
type="date"
|
||||
value=""
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Save Post
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
<p>
|
||||
Loading...
|
||||
</p>
|
||||
|
@ -11,7 +11,7 @@ import devServer from "./devServer"
|
||||
import ssr from "./ssr"
|
||||
|
||||
import { getJeuxJavList } from "../gsheets/jeuxJav"
|
||||
import { getEnvieList, setEnvieList } from "../gsheets/envies"
|
||||
import { getEnvieList, addEnvie } from "../gsheets/envies"
|
||||
import config from "../config"
|
||||
|
||||
const app = express()
|
||||
@ -32,9 +32,10 @@ app.use(express.static(path.resolve(process.cwd(), "public")))
|
||||
if (__DEV__) devServer(app)
|
||||
|
||||
// Google Sheets requests
|
||||
app.use(express.json())
|
||||
app.get("/JeuxJav", getJeuxJavList)
|
||||
app.get("/GetList", getEnvieList)
|
||||
app.post("/SetList", setEnvieList)
|
||||
app.get("/GetEnvieList", getEnvieList)
|
||||
app.post("/AddEnvie", addEnvie)
|
||||
|
||||
// Use React server-side rendering middleware
|
||||
app.get("*", ssr)
|
||||
|
@ -3,32 +3,40 @@ import axios from "axios"
|
||||
import config from "../config"
|
||||
|
||||
export class Envie {
|
||||
envieId = 0
|
||||
|
||||
domaine = ""
|
||||
|
||||
envies = ""
|
||||
|
||||
precisions = ""
|
||||
|
||||
equipes = []
|
||||
equipes: string[] = []
|
||||
|
||||
dateAjout = new Date(0)
|
||||
dateAjout = ""
|
||||
}
|
||||
export type EnvieWithoutId = Omit<Envie, "envieId">
|
||||
|
||||
export interface EnviesResponse {
|
||||
export interface GetEnvieListResponse {
|
||||
data?: Envie[]
|
||||
error?: Error
|
||||
}
|
||||
export const getEnvieList = async (): Promise<EnviesResponse> => {
|
||||
export const getEnvieList = async (): Promise<GetEnvieListResponse> => {
|
||||
try {
|
||||
const { data } = await axios.get(`${config.API_URL}/GetList`)
|
||||
const { data } = await axios.get(`${config.API_URL}/GetEnvieList`)
|
||||
return { data }
|
||||
} catch (error) {
|
||||
return { error: error as Error }
|
||||
}
|
||||
}
|
||||
export const setEnvieList = async (list: Envie[]): Promise<EnviesResponse> => {
|
||||
|
||||
export interface AddEnvieResponse {
|
||||
data?: Envie
|
||||
error?: Error
|
||||
}
|
||||
export const addEnvie = async (envieWithoutId: EnvieWithoutId): Promise<AddEnvieResponse> => {
|
||||
try {
|
||||
const { data } = await axios.post(`${config.API_URL}/SetList`, list)
|
||||
const { data } = await axios.post(`${config.API_URL}/AddEnvie`, envieWithoutId)
|
||||
return { data }
|
||||
} catch (error) {
|
||||
return { error: error as Error }
|
||||
|
70
src/store/envieAdd.ts
Normal file
70
src/store/envieAdd.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
import { Envie, EnvieWithoutId, addEnvie } from "../services/envies"
|
||||
import { AppThunk } from "."
|
||||
|
||||
interface EnvieRequest {
|
||||
readyStatus: string
|
||||
items: Envie | null
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export const initialState: EnvieRequest = {
|
||||
readyStatus: "invalid",
|
||||
items: null,
|
||||
error: null,
|
||||
}
|
||||
|
||||
const envieList = createSlice({
|
||||
name: "addEnvie",
|
||||
initialState,
|
||||
reducers: {
|
||||
getRequesting: (state: EnvieRequest) => {
|
||||
state.readyStatus = "request"
|
||||
},
|
||||
getSuccess: (state, { payload }: PayloadAction<Envie>) => {
|
||||
state.readyStatus = "success"
|
||||
state.items = payload
|
||||
},
|
||||
getFailure: (state, { payload }: PayloadAction<string>) => {
|
||||
state.readyStatus = "failure"
|
||||
state.error = payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default envieList.reducer
|
||||
export const { getRequesting, getSuccess, getFailure } = envieList.actions
|
||||
|
||||
export const postEnvie =
|
||||
(envieWithoutId: EnvieWithoutId): AppThunk =>
|
||||
async (dispatch) => {
|
||||
dispatch(getRequesting())
|
||||
|
||||
const { error, data } = await addEnvie(envieWithoutId)
|
||||
|
||||
if (error) {
|
||||
dispatch(getFailure(error.message))
|
||||
toast.error(`Erreur lors de l'ajout: ${error.message}`, {
|
||||
position: "top-center",
|
||||
autoClose: 6000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
})
|
||||
} else {
|
||||
dispatch(getSuccess(data as Envie))
|
||||
toast.success("Envie ajoutée !", {
|
||||
position: "top-center",
|
||||
autoClose: 3000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
})
|
||||
}
|
||||
}
|
@ -3,23 +3,23 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
||||
import { Envie, getEnvieList } from "../services/envies"
|
||||
import { AppThunk, AppState } from "."
|
||||
|
||||
interface EnvieList {
|
||||
interface EnvieListRequest {
|
||||
readyStatus: string
|
||||
items: Envie[]
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export const initialState: EnvieList = {
|
||||
export const initialState: EnvieListRequest = {
|
||||
readyStatus: "invalid",
|
||||
items: [],
|
||||
error: null,
|
||||
}
|
||||
|
||||
const envieList = createSlice({
|
||||
name: "envieList",
|
||||
name: "getEnvieList",
|
||||
initialState,
|
||||
reducers: {
|
||||
getRequesting: (state: EnvieList) => {
|
||||
getRequesting: (state: EnvieListRequest) => {
|
||||
state.readyStatus = "request"
|
||||
},
|
||||
getSuccess: (state, { payload }: PayloadAction<Envie[]>) => {
|
||||
|
@ -4,7 +4,7 @@ import { JeuxJav, getJeuxJavList } from "../services/jeuxJav"
|
||||
import { AppThunk, AppState } from "."
|
||||
|
||||
interface JeuxJavList {
|
||||
readyStatus: string
|
||||
readyStatus: string // TODO Change it to: "invalid" | "request" | "success" | "failure"
|
||||
items: JeuxJav[]
|
||||
error: string | null
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user