diff --git a/package-lock.json b/package-lock.json index 15de32237a17a5d4fd4ecc7be6eced5d740791a2..082678053912b821f1d6530ec04b5985c9e97e37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@tanstack/react-query": "^5.17.12", "@types/node": "^20.11.3", + "axios": "^1.6.5", "bootstrap": "^5.3.2", "react": "^18.2.0", "react-bootstrap": "^2.9.2", @@ -2790,6 +2791,11 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -2802,6 +2808,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3173,6 +3189,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3334,6 +3361,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3881,6 +3916,25 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -3890,6 +3944,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5443,6 +5510,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -5937,6 +6023,11 @@ "react": ">=0.14.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 8ea1a649480157f47ce014626a87091a6db6cc80..e4b2c06a0da3108fb9824f51d18800c37f037c75 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@tanstack/react-query": "^5.17.12", "@types/node": "^20.11.3", + "axios": "^1.6.5", "bootstrap": "^5.3.2", "react": "^18.2.0", "react-bootstrap": "^2.9.2", diff --git a/src/App.tsx b/src/App.tsx index d8c16b60415000ebb3037cda29f9158f5716740a..d46c4ad3e229e9da40ac0ff1b34e9e3550129cd7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const queryClient = new QueryClient(); function App() { - const [selection, setSelection] = useState(0); + const [selection, setSelection] = useState(1); return ( <QueryClientProvider client={queryClient}> diff --git a/src/components/Formulaire.tsx b/src/components/Formulaire.tsx index 8fcc33fd3ec63cd73d4bc3e33b4edd604f0e3797..943dec4d0110e73e0c93f5a15e147f9e4da45cd4 100644 --- a/src/components/Formulaire.tsx +++ b/src/components/Formulaire.tsx @@ -1,13 +1,12 @@ -import { useQuery } from "@tanstack/react-query"; -import { Button, Col, Container, Form, Row } from "react-bootstrap"; +import { Col, Container, Form, Row } from "react-bootstrap"; +import FormulaireArret from "./Formulaires/Formulaire-Arret"; type FormulaireProps = { selection: number; }; -const URL = `https://hackathon-login.osc-fr1.scalingo.io`; - const Formulaire = ({ selection }: FormulaireProps) => { + const getFormsGroups = (selection: number) => { switch (selection) { case 0: @@ -19,7 +18,7 @@ const Formulaire = ({ selection }: FormulaireProps) => { <Form.Label>Latitude</Form.Label> <Form.Control type="text" - placeholder="47,264" + defaultValue="47,264" pattern="-?\d+(,\d+)?(\.\d+)?" step="any" required @@ -33,7 +32,7 @@ const Formulaire = ({ selection }: FormulaireProps) => { <Form.Label>Longitude</Form.Label> <Form.Control type="text" - placeholder="-1,585" + defaultValue="-1,585" pattern="-?\d+(,\d+)?(\.\d+)?" step="any" required @@ -46,11 +45,7 @@ const Formulaire = ({ selection }: FormulaireProps) => { </> ); case 1: - return ( - <> - <h1>Liste de tous les arrĂȘts</h1> - </> - ); + return <FormulaireArret />; case 2: return ( <> @@ -93,31 +88,10 @@ const Formulaire = ({ selection }: FormulaireProps) => { } }; - const { isPending, isError, error, data } = useQuery({ - queryKey: ["arrets"], - queryFn: async () => { - const result = await fetch(`${URL}/get_all_stations/`); - return result.json(); - }, - }); - - if (isPending) return <p>Loading...</p>; - else if (isError) - return ( - <p> - Error fetching data: {error.name} - {error.message} - </p> - ); - return ( <Container> <Row> - <Form onSubmit={(e) => { e.preventDefault(); console.log(data) }}> {getFormsGroups(selection)} - <Button className="mt-3" variant="primary" type="submit"> - Submit - </Button> - </Form> </Row> </Container> ); diff --git a/src/components/Formulaires/Formulaire-Arret.tsx b/src/components/Formulaires/Formulaire-Arret.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7597899d2aa557ac4aa44c2c7bc8d98704b1469a --- /dev/null +++ b/src/components/Formulaires/Formulaire-Arret.tsx @@ -0,0 +1,88 @@ +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { getStops, getStopsPaginated } from "../../utils"; +import { useState } from "react"; +import { Button, Col, Form, Spinner } from "react-bootstrap"; + +const FormulaireArret = () => { + const [page, setPage] = useState(1); + const [filter, setFilter] = useState(""); + const [showResult, setShowResult] = useState(false); + + const { data: listArrets, isFetched } = useQuery({ + queryKey: ["arrets"], + queryFn: async () => getStops(), + }); + + const { isPending, isError, error, data } = useQuery({ + queryKey: ["arrets", { page, filter }], + placeholderData: keepPreviousData, + enabled: isFetched, + queryFn: () => getStopsPaginated(listArrets!, page, filter), + }); + + return ( + <> + <Form + onSubmit={(e) => { + e.preventDefault(); + setShowResult(true); + }} + > + <h1>Liste de tous les arrĂȘts</h1> + <Form.Group className="mb-3" controlId="ArretSearchBar"> + <Form.Label>Barre de recherche</Form.Label> + <Form.Control + type="text" + placeholder="Ecrire ici pour filtrer" + onChange={(e) => { + setPage(1); + setFilter(e.target.value); + }} + /> + </Form.Group> + <Button className="mt-1" variant="primary" type="submit"> + Submit + </Button> + <Button + className="mt-1" + variant="primary" + type="reset" + onClick={() => setShowResult(false)} + > + Clear + </Button> + </Form> + {!showResult ? ( + <></> + ) : isPending ? ( + <Spinner animation="border" role="status"> + <span className="visually-hidden">Loading...</span> + </Spinner> + ) : isError ? ( + <p> + An error has occured: {error.name} - {error.message} + </p> + ) : ( + <Col md={4} className="mt-3"> + {data.arrets.map((arret) => ( + <p key={arret.id}>{arret.libelle}</p> + ))} + <Button + disabled={data.previousPage === undefined} + onClick={() => setPage(page - 1)} + > + Previous + </Button> + <Button + disabled={data.nextPage === undefined} + onClick={() => setPage(page + 1)} + > + Next + </Button> + </Col> + )} + </> + ); +}; + +export default FormulaireArret; diff --git a/src/models/Arret.model.ts b/src/models/Arret.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..329c12af7a912adfe7e0680d4043ac33d03acd1a --- /dev/null +++ b/src/models/Arret.model.ts @@ -0,0 +1,12 @@ +export type Arret = + { + id: number, + "codeLieu": string, + "libelle": string, + "distance": string, + "ligne": [ + { + "numLigne": string + } + ] + } \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..41ee55304745d2242a3855b9b075854d92ebaaa7 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,26 @@ +import axios from "axios"; +import { Arret } from "./models/Arret.model"; + +const URL = `https://hackathon-login.osc-fr1.scalingo.io`; + +export const getPaginated = (data: Array<object>, page: number) => { + const hasNext = page * 10 < data.length; + const startIndex = (page - 1) * 10; + const endIndex = startIndex + 10; + return { + nextPage: hasNext ? page + 1 : undefined, + previousPage: page > 1 ? page - 1 : undefined, + arrets: data.slice(startIndex, endIndex) + } +} + +export const getStopsPaginated = (data: Arret[], page: number, filter: string = '') => { + const json = data.filter((arret) => arret.libelle.includes(filter)); + const paginated = getPaginated(json, page); + return { ...paginated, arrets: paginated.arrets as Arret[] } +} + +export const getStops = async () => { + const result = await axios.get<Omit<Arret, "id">[]>(`${URL}/get_all_stations/`); + return result.data.map((arret, i) => { return { ...arret, id: i } as Arret }); +} \ No newline at end of file