From 5b8c1b01c5d03cdece64eb98ac19753865243560 Mon Sep 17 00:00:00 2001 From: Lea Renaux Date: Wed, 24 Apr 2024 10:33:40 +0200 Subject: [PATCH 1/6] add FollowInterviewerCard --- src/i18n-en.js | 6 +++ src/i18n-fr.js | 7 +++ src/pages/FollowPage.tsx | 26 +++++++++- src/ui/FollowInterviewerCard.tsx | 86 ++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/ui/FollowInterviewerCard.tsx diff --git a/src/i18n-en.js b/src/i18n-en.js index 1838e96..d424c27 100644 --- a/src/i18n-en.js +++ b/src/i18n-en.js @@ -21,4 +21,10 @@ export const messagesEn = { statesFilterLabel: 'States...', closingCauseFilterLabel: 'Closing cause...', priorityFilterLabel: 'Priority...', + searchInterviewer: 'an interviewer...', + searchOrganizationUnit: "an organization unit...", + chooseSurvey: "Choose a survey :", + chooseInterviewer: "Choose an interviewer :", + chooseOrganizationUnit: "Choose an organization unit :", + followInterviewer: "Follow an interviewer", } \ No newline at end of file diff --git a/src/i18n-fr.js b/src/i18n-fr.js index 610899a..02c6668 100644 --- a/src/i18n-fr.js +++ b/src/i18n-fr.js @@ -21,4 +21,11 @@ export const messagesFr = { statesFilterLabel: 'Etat...', closingCauseFilterLabel: 'Bilan agrégé...', priorityFilterLabel: 'Prioritaire...', + searchInterviewer: 'un enquêteur...', + searchOrganizationUnit: "un site...", + chooseSurvey: "Choisissez une enquête :", + chooseInterviewer: "Choisissez un enquêteur / une enquêtrice :", + chooseOrganizationUnit: "Choisissez un site :", + followInterviewer: "Suivre un enquêteur", + } \ No newline at end of file diff --git a/src/pages/FollowPage.tsx b/src/pages/FollowPage.tsx index d7a5e3d..c4e76cb 100644 --- a/src/pages/FollowPage.tsx +++ b/src/pages/FollowPage.tsx @@ -1,5 +1,27 @@ -import { Typography } from "@mui/material"; +import { Row } from "../ui/Row"; +import { FollowInterviewerCard } from "../ui/FollowInterviewerCard"; export const FollowPage = () => { - return Page suivre; + // TODO use real condition + const isNationalProfile = true; + + const gridTemplateColumns = isNationalProfile + ? { gridTemplateColumns: "1fr 1fr 1fr" } + : { gridTemplateColumns: "1fr 1fr" }; + + return ( + + + + ); }; diff --git a/src/ui/FollowInterviewerCard.tsx b/src/ui/FollowInterviewerCard.tsx new file mode 100644 index 0000000..a389886 --- /dev/null +++ b/src/ui/FollowInterviewerCard.tsx @@ -0,0 +1,86 @@ +import { useIntl } from "react-intl"; +import { useDebouncedState } from "../hooks/useDebouncedState"; +import Card from "@mui/material/Card"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { SearchField } from "./SearchField"; +import TableContainer from "@mui/material/TableContainer"; +import Table from "@mui/material/Table"; +import TableHead from "@mui/material/TableHead"; +import { TableBody, TableCell, TableRow } from "@mui/material"; + +const interviewersMock = [ + { + id: "1", + interviewerFirstName: "John", + interviewerLastName: "Doe", + }, + { + id: "2", + interviewerFirstName: "Marion", + interviewerLastName: "Cotillard", + }, + { + id: "3", + interviewerFirstName: "Johnny", + interviewerLastName: "Depp", + }, + { + id: "4", + interviewerFirstName: "Romain", + interviewerLastName: "Duris", + }, + { + id: "5", + interviewerFirstName: "Angélina", + interviewerLastName: "Jolie", + }, +]; + +export const FollowInterviewerCard = () => { + const intl = useIntl(); + const [search, setSearch] = useDebouncedState("", 500); + // const [page, setPage] = useState(0); + + // const handleChangePage = (_: React.MouseEvent | null, newPage: number) => { + // setPage(newPage); + // }; + + return ( + + + + {intl.formatMessage({ id: "followInterviewer" })} + + setSearch(e.target.value)} + label={intl.formatMessage({ id: "toSearchLabel" })} + placeholder={intl.formatMessage({ id: "searchInterviewer" })} + /> + + + + + + + {intl.formatMessage({ id: "chooseInterviewer" })} + + + + + {interviewersMock.map(interviewer => ( + + + {interviewer.interviewerLastName.toLocaleUpperCase()}{" "} + {interviewer.interviewerFirstName} + + + ))} + + {/* TODO use TableFooter */} +
+
+
+
+ ); +}; From f0e2d2d7dd0bd4a89775d6737ff70a22d9092576 Mon Sep 17 00:00:00 2001 From: Lea Renaux Date: Wed, 24 Apr 2024 14:49:04 +0200 Subject: [PATCH 2/6] add FollowOrganizationUnitCard and FollowSurveyCard --- src/i18n-en.js | 3 + src/i18n-fr.js | 4 +- src/pages/FollowCampaignPage.tsx | 5 ++ src/pages/FollowInterviewerPage.tsx | 5 ++ src/pages/FollowOrganizationUnitPage.tsx | 5 ++ src/pages/FollowPage.tsx | 6 ++ src/routes.tsx | 6 ++ src/ui/FollowCardHeader.tsx | 30 +++++++ src/ui/FollowInterviewerCard.tsx | 73 ++++++++++++----- src/ui/FollowOrganizationUnitCard.tsx | 100 +++++++++++++++++++++++ src/ui/FollowSurveyCard.tsx | 52 ++++++++++++ 11 files changed, 269 insertions(+), 20 deletions(-) create mode 100644 src/pages/FollowCampaignPage.tsx create mode 100644 src/pages/FollowInterviewerPage.tsx create mode 100644 src/pages/FollowOrganizationUnitPage.tsx create mode 100644 src/ui/FollowCardHeader.tsx create mode 100644 src/ui/FollowOrganizationUnitCard.tsx create mode 100644 src/ui/FollowSurveyCard.tsx diff --git a/src/i18n-en.js b/src/i18n-en.js index d424c27..ce5754e 100644 --- a/src/i18n-en.js +++ b/src/i18n-en.js @@ -27,4 +27,7 @@ export const messagesEn = { chooseInterviewer: "Choose an interviewer :", chooseOrganizationUnit: "Choose an organization unit :", followInterviewer: "Follow an interviewer", + followSurvey: 'Follow a survey', + allSurveys: 'All surveys', + followOrganizationUnit: "Follow an organization unit" } \ No newline at end of file diff --git a/src/i18n-fr.js b/src/i18n-fr.js index 02c6668..c7b40e7 100644 --- a/src/i18n-fr.js +++ b/src/i18n-fr.js @@ -27,5 +27,7 @@ export const messagesFr = { chooseInterviewer: "Choisissez un enquêteur / une enquêtrice :", chooseOrganizationUnit: "Choisissez un site :", followInterviewer: "Suivre un enquêteur", - + followSurvey: "Suivre une enquête", + allSurveys: "Ensemble des enquêtes", + followOrganizationUnit: "Suivre un site" } \ No newline at end of file diff --git a/src/pages/FollowCampaignPage.tsx b/src/pages/FollowCampaignPage.tsx new file mode 100644 index 0000000..e527f4f --- /dev/null +++ b/src/pages/FollowCampaignPage.tsx @@ -0,0 +1,5 @@ +import { Typography } from "@mui/material"; + +export const FollowCampaignPage = () => { + return page suivre enquête; +}; diff --git a/src/pages/FollowInterviewerPage.tsx b/src/pages/FollowInterviewerPage.tsx new file mode 100644 index 0000000..bd652f6 --- /dev/null +++ b/src/pages/FollowInterviewerPage.tsx @@ -0,0 +1,5 @@ +import { Typography } from "@mui/material"; + +export const FollowInterviewerPage = () => { + return page suivre enquêteur; +}; diff --git a/src/pages/FollowOrganizationUnitPage.tsx b/src/pages/FollowOrganizationUnitPage.tsx new file mode 100644 index 0000000..2486c17 --- /dev/null +++ b/src/pages/FollowOrganizationUnitPage.tsx @@ -0,0 +1,5 @@ +import { Typography } from "@mui/material"; + +export const FollowOrganizationUnitPage = () => { + return page suivre site; +}; diff --git a/src/pages/FollowPage.tsx b/src/pages/FollowPage.tsx index c4e76cb..6fb688c 100644 --- a/src/pages/FollowPage.tsx +++ b/src/pages/FollowPage.tsx @@ -1,10 +1,14 @@ import { Row } from "../ui/Row"; import { FollowInterviewerCard } from "../ui/FollowInterviewerCard"; +import { FollowSurveyCard } from "../ui/FollowSurveyCard"; +import { FollowOrganizationUnitCard } from "../ui/FollowOrganizationUnitCard"; export const FollowPage = () => { // TODO use real condition const isNationalProfile = true; + // TODO call api to get interviewers, surveys and organizationUnits + const gridTemplateColumns = isNationalProfile ? { gridTemplateColumns: "1fr 1fr 1fr" } : { gridTemplateColumns: "1fr 1fr" }; @@ -21,7 +25,9 @@ export const FollowPage = () => { px={4} py={3} > + + {isNationalProfile && } ); }; diff --git a/src/routes.tsx b/src/routes.tsx index f6d6255..92b6bb2 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -8,6 +8,9 @@ import { ClosePage } from "./pages/ClosePage"; import { NotifyPage } from "./pages/NotifyPage"; import { CollectOrganizationPage } from "./pages/CollectOrganizationPage"; import { ReassignmentPage } from "./pages/ReassignmentPage"; +import { FollowInterviewerPage } from "./pages/FollowInterviewerPage"; +import { FollowCampaignPage } from "./pages/FollowCampaignPage"; +import { FollowOrganizationUnitPage } from "./pages/FollowOrganizationUnitPage"; export const routes: RouteObject[] = [ { @@ -21,6 +24,9 @@ export const routes: RouteObject[] = [ children: [ { path: "", element: }, { path: "follow", element: }, + { path: "follow/interviewer/:id", element: }, + { path: "follow/campaign/:id", element: }, + { path: "follow/organization-unit/:id", element: }, { path: "read", element: }, { path: "close", element: }, { path: "notify", element: }, diff --git a/src/ui/FollowCardHeader.tsx b/src/ui/FollowCardHeader.tsx new file mode 100644 index 0000000..48af30b --- /dev/null +++ b/src/ui/FollowCardHeader.tsx @@ -0,0 +1,30 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { useIntl } from "react-intl"; +import { SearchField } from "./SearchField"; +import { ChangeEvent } from "react"; + +type Props = { + title: string; + placeholder: string; + onSearch: (e: ChangeEvent) => void; +}; + +export const FollowCardHeader = ({ title, placeholder, onSearch }: Props) => { + const intl = useIntl(); + + const handleChange = (e: ChangeEvent) => { + onSearch(e); + }; + + return ( + + {intl.formatMessage({ id: title })} + + + ); +}; diff --git a/src/ui/FollowInterviewerCard.tsx b/src/ui/FollowInterviewerCard.tsx index a389886..7ea7743 100644 --- a/src/ui/FollowInterviewerCard.tsx +++ b/src/ui/FollowInterviewerCard.tsx @@ -2,12 +2,15 @@ import { useIntl } from "react-intl"; import { useDebouncedState } from "../hooks/useDebouncedState"; import Card from "@mui/material/Card"; import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import { SearchField } from "./SearchField"; import TableContainer from "@mui/material/TableContainer"; import Table from "@mui/material/Table"; import TableHead from "@mui/material/TableHead"; import { TableBody, TableCell, TableRow } from "@mui/material"; +import { useState } from "react"; +import { APISchemas } from "../types/api"; +import { Link as RouterLink } from "react-router-dom"; +import { Link } from "./Link"; +import { FollowCardHeader } from "./FollowCardHeader"; const interviewersMock = [ { @@ -40,24 +43,22 @@ const interviewersMock = [ export const FollowInterviewerCard = () => { const intl = useIntl(); const [search, setSearch] = useDebouncedState("", 500); - // const [page, setPage] = useState(0); + const [page, setPage] = useState(0); - // const handleChangePage = (_: React.MouseEvent | null, newPage: number) => { - // setPage(newPage); - // }; + const handleChangePage = (_: React.MouseEvent | null, newPage: number) => { + setPage(newPage); + }; + + const filteredInterviewers = filterInterviewers({ interviewers: interviewersMock, search }); return ( - + - - {intl.formatMessage({ id: "followInterviewer" })} - - setSearch(e.target.value)} - label={intl.formatMessage({ id: "toSearchLabel" })} - placeholder={intl.formatMessage({ id: "searchInterviewer" })} + setSearch(e.target.value)} + placeholder="searchInterviewer" /> - @@ -68,11 +69,18 @@ export const FollowInterviewerCard = () => { - {interviewersMock.map(interviewer => ( + {filteredInterviewers.map(interviewer => ( - - {interviewer.interviewerLastName.toLocaleUpperCase()}{" "} - {interviewer.interviewerFirstName} + + + {interviewer.interviewerLastName?.toLocaleUpperCase()}{" "} + {interviewer.interviewerFirstName} + ))} @@ -84,3 +92,30 @@ export const FollowInterviewerCard = () => { ); }; + +type FilterInterviewerProps = { + interviewers: APISchemas["InterviewerDto"][]; + search?: string; +}; + +const filterInterviewers = ({ interviewers, search }: FilterInterviewerProps) => { + if (search) { + interviewers = interviewers.filter( + item => + item.interviewerFirstName?.toLowerCase().includes(search.toLowerCase()) || + item.interviewerLastName?.toLowerCase().includes(search.toLowerCase()) || + (item.interviewerFirstName && + item.interviewerLastName && + (item.interviewerFirstName + ?.toLowerCase() + .concat(item.interviewerLastName?.toLowerCase()) + .includes(search.split(" ").join("").toLowerCase()) || + item.interviewerLastName + ?.toLowerCase() + .concat(item.interviewerFirstName?.toLowerCase()) + .includes(search.split(" ").join("").toLowerCase()))), + ); + } + + return interviewers; +}; diff --git a/src/ui/FollowOrganizationUnitCard.tsx b/src/ui/FollowOrganizationUnitCard.tsx new file mode 100644 index 0000000..ac2bd57 --- /dev/null +++ b/src/ui/FollowOrganizationUnitCard.tsx @@ -0,0 +1,100 @@ +import { useIntl } from "react-intl"; +import { useDebouncedState } from "../hooks/useDebouncedState"; +import Card from "@mui/material/Card"; +import Stack from "@mui/material/Stack"; +import TableContainer from "@mui/material/TableContainer"; +import Table from "@mui/material/Table"; +import TableHead from "@mui/material/TableHead"; +import { TableBody, TableCell, TableRow } from "@mui/material"; +import { useState } from "react"; +import { Link as RouterLink } from "react-router-dom"; +import { Link } from "./Link"; +import { FollowCardHeader } from "./FollowCardHeader"; + +const OUMock = [ + { + id: "1", + label: "Paris", + }, + { + id: "2", + label: "Metz", + }, + { + id: "3", + + label: "Lille", + }, + { + id: "4", + + label: "Amiens", + }, +]; + +export const FollowOrganizationUnitCard = () => { + const intl = useIntl(); + const [search, setSearch] = useDebouncedState("", 500); + const [page, setPage] = useState(0); + + const handleChangePage = (_: React.MouseEvent | null, newPage: number) => { + setPage(newPage); + }; + + const filteredOU = filterOrganizationUnits({ organizationUnits: OUMock, search }); + + return ( + + + setSearch(e.target.value)} + placeholder="searchOrganizationUnit" + /> + +
+ + + + {intl.formatMessage({ id: "chooseOrganizationUnit" })} + + + + + {filteredOU.map(OU => ( + + + + {OU.label} + + + + ))} + + {/* TODO use TableFooter */} +
+
+
+
+ ); +}; + +type FilterOUProps = { + organizationUnits: { id: string; label: string }[]; + search?: string; +}; + +const filterOrganizationUnits = ({ organizationUnits, search }: FilterOUProps) => { + if (search) { + organizationUnits = organizationUnits.filter(item => + item.label.toLowerCase().includes(search.toLowerCase()), + ); + } + + return organizationUnits; +}; diff --git a/src/ui/FollowSurveyCard.tsx b/src/ui/FollowSurveyCard.tsx new file mode 100644 index 0000000..3b5f87a --- /dev/null +++ b/src/ui/FollowSurveyCard.tsx @@ -0,0 +1,52 @@ +import { useIntl } from "react-intl"; +import Card from "@mui/material/Card"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; +import { Link } from "./Link"; +import { Link as RouterLink } from "react-router-dom"; + +const surveysMock = [ + { + id: "1", + label: "Logement", + }, + { + id: "2", + label: "Autonomie", + }, + { + id: "3", + label: "TIC", + }, +]; + +export const FollowSurveyCard = () => { + const intl = useIntl(); + + return ( + + + {intl.formatMessage({ id: "followSurvey" })} + + + {intl.formatMessage({ id: "chooseSurvey" })} + + {surveysMock.map(survey => ( + + {survey.label} + + ))} + + {intl.formatMessage({ id: "allSurveys" })} + + + + ); +}; From 7bc3f9264e4fc71668842794915a04e66184ddab Mon Sep 17 00:00:00 2001 From: Lea Renaux Date: Thu, 25 Apr 2024 07:53:17 +0200 Subject: [PATCH 3/6] sort items in follow cards --- src/ui/FollowInterviewerCard.tsx | 29 ++++++++++++++++++++++++++- src/ui/FollowOrganizationUnitCard.tsx | 2 +- src/ui/FollowSurveyCard.tsx | 2 ++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/ui/FollowInterviewerCard.tsx b/src/ui/FollowInterviewerCard.tsx index 7ea7743..2e76d06 100644 --- a/src/ui/FollowInterviewerCard.tsx +++ b/src/ui/FollowInterviewerCard.tsx @@ -98,6 +98,13 @@ type FilterInterviewerProps = { search?: string; }; +/* filterInterviewers : + conditions enable to filter when user search by: + - firstName + - lastName + - firstName follow by lastName + - lastName follow by firstName +*/ const filterInterviewers = ({ interviewers, search }: FilterInterviewerProps) => { if (search) { interviewers = interviewers.filter( @@ -117,5 +124,25 @@ const filterInterviewers = ({ interviewers, search }: FilterInterviewerProps) => ); } - return interviewers; + return interviewers.sort((i1, i2) => { + const nameI1 = getInterviewerName({ + lastName: i1.interviewerLastName, + firstName: i1.interviewerFirstName, + }); + + const nameI2 = getInterviewerName({ + lastName: i2.interviewerLastName, + firstName: i2.interviewerFirstName, + }); + + return nameI1.trim().localeCompare(nameI2.trim()); + }); +}; + +const getInterviewerName = ({ lastName, firstName }: { lastName?: string; firstName?: string }) => { + if (lastName) { + return firstName ? lastName.concat(firstName) : lastName; + } else { + return firstName ?? ""; + } }; diff --git a/src/ui/FollowOrganizationUnitCard.tsx b/src/ui/FollowOrganizationUnitCard.tsx index ac2bd57..322f990 100644 --- a/src/ui/FollowOrganizationUnitCard.tsx +++ b/src/ui/FollowOrganizationUnitCard.tsx @@ -96,5 +96,5 @@ const filterOrganizationUnits = ({ organizationUnits, search }: FilterOUProps) = ); } - return organizationUnits; + return organizationUnits.sort((ou1, ou2) => ou1.label.localeCompare(ou2.label)); }; diff --git a/src/ui/FollowSurveyCard.tsx b/src/ui/FollowSurveyCard.tsx index 3b5f87a..4b821c6 100644 --- a/src/ui/FollowSurveyCard.tsx +++ b/src/ui/FollowSurveyCard.tsx @@ -24,6 +24,8 @@ const surveysMock = [ export const FollowSurveyCard = () => { const intl = useIntl(); + surveysMock.sort((su1, su2) => su1.label.localeCompare(su2.label)); + return ( From 8543b60328f08b479557e53277f4b2ff096b6b71 Mon Sep 17 00:00:00 2001 From: Lea Renaux Date: Fri, 26 Apr 2024 11:15:55 +0200 Subject: [PATCH 4/6] add follow single page header --- src/i18n-en.js | 10 +++- src/i18n-fr.js | 10 +++- src/pages/FollowCampaignPage.tsx | 58 ++++++++++++++++++- src/pages/FollowPage.tsx | 6 +- src/theme.tsx | 38 ++++++++++++ src/ui/Breadcrumbs.tsx | 53 +++++++++++++++++ src/ui/PageTab.tsx | 21 +++++++ src/ui/follow/FollowCampaignProgress.tsx | 46 +++++++++++++++ src/ui/{ => follow}/FollowCardHeader.tsx | 2 +- src/ui/{ => follow}/FollowInterviewerCard.tsx | 6 +- .../FollowOrganizationUnitCard.tsx | 4 +- src/ui/follow/FollowSinglePageHeader.tsx | 39 +++++++++++++ src/ui/{ => follow}/FollowSurveyCard.tsx | 2 +- src/ui/follow/SurveyGlobalFollow.tsx | 11 ++++ 14 files changed, 292 insertions(+), 14 deletions(-) create mode 100644 src/ui/Breadcrumbs.tsx create mode 100644 src/ui/PageTab.tsx create mode 100644 src/ui/follow/FollowCampaignProgress.tsx rename src/ui/{ => follow}/FollowCardHeader.tsx (94%) rename src/ui/{ => follow}/FollowInterviewerCard.tsx (96%) rename src/ui/{ => follow}/FollowOrganizationUnitCard.tsx (96%) create mode 100644 src/ui/follow/FollowSinglePageHeader.tsx rename src/ui/{ => follow}/FollowSurveyCard.tsx (97%) create mode 100644 src/ui/follow/SurveyGlobalFollow.tsx diff --git a/src/i18n-en.js b/src/i18n-en.js index ce5754e..e77fd1d 100644 --- a/src/i18n-en.js +++ b/src/i18n-en.js @@ -29,5 +29,13 @@ export const messagesEn = { followInterviewer: "Follow an interviewer", followSurvey: 'Follow a survey', allSurveys: 'All surveys', - followOrganizationUnit: "Follow an organization unit" + followOrganizationUnit: "Follow an organization unit", + followCampaignBreacrumb: "Follow survey", + survey: "Survey", + progress: "Progress", + collect: "Collect", + reminders: "Reminders", + provisionalStatus: "Provisional status", + closedSU: "Closed SU", + terminatedSU: "Terminated SU" } \ No newline at end of file diff --git a/src/i18n-fr.js b/src/i18n-fr.js index c7b40e7..cb3192a 100644 --- a/src/i18n-fr.js +++ b/src/i18n-fr.js @@ -29,5 +29,13 @@ export const messagesFr = { followInterviewer: "Suivre un enquêteur", followSurvey: "Suivre une enquête", allSurveys: "Ensemble des enquêtes", - followOrganizationUnit: "Suivre un site" + followOrganizationUnit: "Suivre un site", + followCampaignBreacrumb: "Suivre enquête", + survey: "Enquête", + progress: "Avancement", + collect: "Collecte", + reminders: "Relances", + provisionalStatus: "Statut provisoire", + closedSU: "UE cloturées", + terminatedSU: "UE terminées" } \ No newline at end of file diff --git a/src/pages/FollowCampaignPage.tsx b/src/pages/FollowCampaignPage.tsx index e527f4f..980de1a 100644 --- a/src/pages/FollowCampaignPage.tsx +++ b/src/pages/FollowCampaignPage.tsx @@ -1,5 +1,59 @@ -import { Typography } from "@mui/material"; +import { useIntl } from "react-intl"; +import { FollowSinglePageHeader } from "../ui/follow/FollowSinglePageHeader"; +import { SyntheticEvent, useState } from "react"; +import Tabs from "@mui/material/Tabs"; +import Stack from "@mui/material/Stack"; +import { PageTab } from "../ui/PageTab"; +import { FollowCampaignProgress } from "../ui/follow/FollowCampaignProgress"; + +// TODO remove +const labelMock = "Logement"; + +enum Tab { + progress = "progress", + collect = "collect", + reminders = "reminders", + provisionalStatus = "provisionalStatus", + closedSU = "closedSU", + terminatedSU = "terminatedSU", +} export const FollowCampaignPage = () => { - return page suivre enquête; + const intl = useIntl(); + const [currentTab, setCurrentTab] = useState(Tab.progress); + const handleChange = (_: SyntheticEvent, newValue: Tab) => { + setCurrentTab(newValue); + }; + + const breadcrumbs = [ + { href: "/follow", title: "goToFollowPage" }, + intl.formatMessage({ id: "followCampaignBreacrumb" }), + labelMock, + ]; + + return ( + <> + + + {Object.keys(Tab).map(k => ( + + ))} + + + {currentTab === Tab.progress && } + {currentTab === Tab.collect && <>tab collecte} + {currentTab === Tab.reminders && <>tab relances} + {currentTab === Tab.provisionalStatus && <>tab statut provisoire} + {currentTab === Tab.closedSU && <>tab UE cloturées} + {currentTab === Tab.terminatedSU && <>tab UE terminées} + + + ); }; diff --git a/src/pages/FollowPage.tsx b/src/pages/FollowPage.tsx index 6fb688c..1ef718d 100644 --- a/src/pages/FollowPage.tsx +++ b/src/pages/FollowPage.tsx @@ -1,7 +1,7 @@ import { Row } from "../ui/Row"; -import { FollowInterviewerCard } from "../ui/FollowInterviewerCard"; -import { FollowSurveyCard } from "../ui/FollowSurveyCard"; -import { FollowOrganizationUnitCard } from "../ui/FollowOrganizationUnitCard"; +import { FollowInterviewerCard } from "../ui/follow/FollowInterviewerCard"; +import { FollowSurveyCard } from "../ui/follow/FollowSurveyCard"; +import { FollowOrganizationUnitCard } from "../ui/follow/FollowOrganizationUnitCard"; export const FollowPage = () => { // TODO use real condition diff --git a/src/theme.tsx b/src/theme.tsx index 6826824..b6a7f3d 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -119,6 +119,7 @@ declare module "@mui/material/Paper" { declare module "@mui/material/Tab" { interface TabPropsClassesOverrides { search: true; + cardTab: true; } } @@ -148,6 +149,7 @@ const typography = { headlineLarge: { fontSize: 32, lineHeight: "40px", + fontWeight: 400, }, headlineMedium: { fontSize: 28, @@ -323,6 +325,42 @@ export const theme = createTheme({ }, }, }, + { + props: { classes: "cardTab" }, + style: { + borderTopLeftRadius: "16px !important", + borderTopRightRadius: "16px !important", + paddingLeft: 40, + paddingRight: 40, + backgroundColor: "#EFEFEF", + border: "none", + ...typography.titleSmall, + textTransform: "none", + "&:hover": { + backgroundColor: "#EFEFEF", + }, + ":nth-of-type(2)": { + left: -20, + }, + ":nth-of-type(3)": { + left: -40, + }, + ":nth-of-type(4)": { + left: -60, + }, + ":nth-of-type(5)": { + left: -80, + }, + "&.Mui-selected": { + position: "relative", + zIndex: 2, + background: "white", + "&:hover": { + background: "white", + }, + }, + }, + }, ], }, MuiPaper: { diff --git a/src/ui/Breadcrumbs.tsx b/src/ui/Breadcrumbs.tsx new file mode 100644 index 0000000..c3a653d --- /dev/null +++ b/src/ui/Breadcrumbs.tsx @@ -0,0 +1,53 @@ +import MuiBreadcrumbs from "@mui/material/Breadcrumbs"; +import Box from "@mui/material/Box"; +import Link from "@mui/material/Link"; +import { NavLink } from "react-router-dom"; +import { useIntl } from "react-intl"; + +export type BreadcrumbsItem = { href: string; title: string } | string; + +type Props = { + items: BreadcrumbsItem[]; +}; + +export function Breadcrumbs({ items }: Readonly) { + return ( + + {items.map(item => ( + + ))} + + ); +} + +function getKey(item: BreadcrumbsItem) { + if (typeof item === "string") { + return item; + } + return item.href; +} + +function BreadcrumbsItem({ item }: Readonly<{ item: BreadcrumbsItem }>) { + const intl = useIntl(); + if (typeof item === "string") { + return ( + + {item} + + ); + } + + return ( + + {intl.formatMessage({ id: item.title })} + + ); +} diff --git a/src/ui/PageTab.tsx b/src/ui/PageTab.tsx new file mode 100644 index 0000000..e9f24e3 --- /dev/null +++ b/src/ui/PageTab.tsx @@ -0,0 +1,21 @@ +import Tab, { TabProps } from "@mui/material/Tab"; + +type Props = { + label: string; +} & TabProps; + +export const PageTab = ({ label, ...props }: Props) => { + return ( + + ); +}; diff --git a/src/ui/follow/FollowCampaignProgress.tsx b/src/ui/follow/FollowCampaignProgress.tsx new file mode 100644 index 0000000..fd0806a --- /dev/null +++ b/src/ui/follow/FollowCampaignProgress.tsx @@ -0,0 +1,46 @@ +import { useIntl } from "react-intl"; +import { SyntheticEvent, useState } from "react"; +import Tabs from "@mui/material/Tabs"; +import Stack from "@mui/material/Stack"; +import { SurveyGlobalFollow } from "./SurveyGlobalFollow"; +import { Tab as MuiTab } from "@mui/material"; + +// TODO change tabs +enum Tab { + progress = "progress", + collect = "collect", + reminders = "reminders", + provisionalStatus = "provisionalStatus", + closedSU = "closedSU", +} + +export const FollowCampaignProgress = () => { + const intl = useIntl(); + const [currentTab, setCurrentTab] = useState(Tab.progress); + const handleChange = (_: SyntheticEvent, newValue: Tab) => { + setCurrentTab(newValue); + }; + + return ( + <> + + {Object.keys(Tab).map(k => ( + + ))} + + + + {/* todo changes components and tabs */} + {currentTab === Tab.progress && } + {currentTab === Tab.collect && } + {currentTab === Tab.reminders && } + {currentTab === Tab.provisionalStatus && } + {currentTab === Tab.closedSU && } + + + ); +}; diff --git a/src/ui/FollowCardHeader.tsx b/src/ui/follow/FollowCardHeader.tsx similarity index 94% rename from src/ui/FollowCardHeader.tsx rename to src/ui/follow/FollowCardHeader.tsx index 48af30b..4424608 100644 --- a/src/ui/FollowCardHeader.tsx +++ b/src/ui/follow/FollowCardHeader.tsx @@ -1,7 +1,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import { useIntl } from "react-intl"; -import { SearchField } from "./SearchField"; +import { SearchField } from "../SearchField"; import { ChangeEvent } from "react"; type Props = { diff --git a/src/ui/FollowInterviewerCard.tsx b/src/ui/follow/FollowInterviewerCard.tsx similarity index 96% rename from src/ui/FollowInterviewerCard.tsx rename to src/ui/follow/FollowInterviewerCard.tsx index 2e76d06..a0e42f7 100644 --- a/src/ui/FollowInterviewerCard.tsx +++ b/src/ui/follow/FollowInterviewerCard.tsx @@ -1,5 +1,5 @@ import { useIntl } from "react-intl"; -import { useDebouncedState } from "../hooks/useDebouncedState"; +import { useDebouncedState } from "../../hooks/useDebouncedState"; import Card from "@mui/material/Card"; import Stack from "@mui/material/Stack"; import TableContainer from "@mui/material/TableContainer"; @@ -7,9 +7,9 @@ import Table from "@mui/material/Table"; import TableHead from "@mui/material/TableHead"; import { TableBody, TableCell, TableRow } from "@mui/material"; import { useState } from "react"; -import { APISchemas } from "../types/api"; +import { APISchemas } from "../../types/api"; import { Link as RouterLink } from "react-router-dom"; -import { Link } from "./Link"; +import { Link } from "../Link"; import { FollowCardHeader } from "./FollowCardHeader"; const interviewersMock = [ diff --git a/src/ui/FollowOrganizationUnitCard.tsx b/src/ui/follow/FollowOrganizationUnitCard.tsx similarity index 96% rename from src/ui/FollowOrganizationUnitCard.tsx rename to src/ui/follow/FollowOrganizationUnitCard.tsx index 322f990..cb86c72 100644 --- a/src/ui/FollowOrganizationUnitCard.tsx +++ b/src/ui/follow/FollowOrganizationUnitCard.tsx @@ -1,5 +1,5 @@ import { useIntl } from "react-intl"; -import { useDebouncedState } from "../hooks/useDebouncedState"; +import { useDebouncedState } from "../../hooks/useDebouncedState"; import Card from "@mui/material/Card"; import Stack from "@mui/material/Stack"; import TableContainer from "@mui/material/TableContainer"; @@ -8,7 +8,7 @@ import TableHead from "@mui/material/TableHead"; import { TableBody, TableCell, TableRow } from "@mui/material"; import { useState } from "react"; import { Link as RouterLink } from "react-router-dom"; -import { Link } from "./Link"; +import { Link } from "../Link"; import { FollowCardHeader } from "./FollowCardHeader"; const OUMock = [ diff --git a/src/ui/follow/FollowSinglePageHeader.tsx b/src/ui/follow/FollowSinglePageHeader.tsx new file mode 100644 index 0000000..b10c9ad --- /dev/null +++ b/src/ui/follow/FollowSinglePageHeader.tsx @@ -0,0 +1,39 @@ +import { useNavigate } from "react-router-dom"; +import { Row } from "../Row"; +import Stack from "@mui/material/Stack"; +import { Box, Divider, IconButton, Typography } from "@mui/material"; +import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; +import { Breadcrumbs, BreadcrumbsItem } from "../Breadcrumbs"; +import { useIntl } from "react-intl"; + +type Props = { + category: string; + label: string; + breadcrumbs: BreadcrumbsItem[]; +}; + +export const FollowSinglePageHeader = ({ category, label, breadcrumbs }: Props) => { + const navigate = useNavigate(); + const intl = useIntl(); + + return ( + + + + + navigate(-1)}> + + + + {intl.formatMessage({ id: category })} - + + {label} + + + + + + + + ); +}; diff --git a/src/ui/FollowSurveyCard.tsx b/src/ui/follow/FollowSurveyCard.tsx similarity index 97% rename from src/ui/FollowSurveyCard.tsx rename to src/ui/follow/FollowSurveyCard.tsx index 4b821c6..d1d1504 100644 --- a/src/ui/FollowSurveyCard.tsx +++ b/src/ui/follow/FollowSurveyCard.tsx @@ -3,7 +3,7 @@ import Card from "@mui/material/Card"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; -import { Link } from "./Link"; +import { Link } from "../Link"; import { Link as RouterLink } from "react-router-dom"; const surveysMock = [ diff --git a/src/ui/follow/SurveyGlobalFollow.tsx b/src/ui/follow/SurveyGlobalFollow.tsx new file mode 100644 index 0000000..a34ceab --- /dev/null +++ b/src/ui/follow/SurveyGlobalFollow.tsx @@ -0,0 +1,11 @@ +import { Card, Typography } from "@mui/material"; +import { useIntl } from "react-intl"; + +export const SurveyGlobalFollow = () => { + const intl = useIntl(); + return ( + + {intl.formatMessage({ id: "followSurvey" })} + + ); +}; From 405edbb9f9750e4584d4609d6658f0db9f999e38 Mon Sep 17 00:00:00 2001 From: Lea Renaux Date: Tue, 30 Apr 2024 11:26:56 +0200 Subject: [PATCH 5/6] add header in follow interviewer & OU page --- src/i18n-en.js | 7 +- src/i18n-fr.js | 9 ++- src/pages/FollowInterviewerPage.tsx | 69 +++++++++++++++++- src/pages/FollowOrganizationUnitPage.tsx | 53 +++++++++++++- src/pages/FollowPage.tsx | 2 - src/ui/LoadingCell.tsx | 13 ++++ src/ui/follow/FollowCardHeader.tsx | 2 +- src/ui/follow/FollowInterviewerCard.tsx | 71 +++++++----------- src/ui/follow/FollowOrganizationUnitCard.tsx | 76 ++++++++++---------- 9 files changed, 208 insertions(+), 94 deletions(-) create mode 100644 src/ui/LoadingCell.tsx diff --git a/src/i18n-en.js b/src/i18n-en.js index e77fd1d..a2df1ef 100644 --- a/src/i18n-en.js +++ b/src/i18n-en.js @@ -37,5 +37,10 @@ export const messagesEn = { reminders: "Reminders", provisionalStatus: "Provisional status", closedSU: "Closed SU", - terminatedSU: "Terminated SU" + terminatedSU: "Terminated SU", + interviewer: "Interviewer", + followInterviewerBreacrumb: "Follow interviewer", + lastSynchronization: "Last synchronization", + followOrganizationUnitBreacrumb: "Follow organization unit", + organizationUnit: "OU" } \ No newline at end of file diff --git a/src/i18n-fr.js b/src/i18n-fr.js index cb3192a..848ed23 100644 --- a/src/i18n-fr.js +++ b/src/i18n-fr.js @@ -37,5 +37,12 @@ export const messagesFr = { reminders: "Relances", provisionalStatus: "Statut provisoire", closedSU: "UE cloturées", - terminatedSU: "UE terminées" + terminatedSU: "UE terminées", + interviewer: "Enquêteur", + followInterviewerBreacrumb: "Suivre enquêteur", + lastSynchronization: "Dernière synchronization", + followOrganizationUnitBreacrumb: "Suivre site", + organizationUnit: "DEM" + + } \ No newline at end of file diff --git a/src/pages/FollowInterviewerPage.tsx b/src/pages/FollowInterviewerPage.tsx index bd652f6..9f0fa35 100644 --- a/src/pages/FollowInterviewerPage.tsx +++ b/src/pages/FollowInterviewerPage.tsx @@ -1,5 +1,70 @@ -import { Typography } from "@mui/material"; +import { CircularProgress, Stack, Tabs } from "@mui/material"; +import { SyntheticEvent, useState } from "react"; +import { useIntl } from "react-intl"; +import { FollowSinglePageHeader } from "../ui/follow/FollowSinglePageHeader"; +import { PageTab } from "../ui/PageTab"; +import { Row } from "../ui/Row"; +import { useFetchQuery } from "../hooks/useFetchQuery"; +import { useParams } from "react-router-dom"; + +enum Tab { + progress = "progress", + collect = "collect", + lastSynchronization = "lastSynchronization", +} export const FollowInterviewerPage = () => { - return page suivre enquêteur; + const { id } = useParams(); + const intl = useIntl(); + + const [currentTab, setCurrentTab] = useState(Tab.progress); + + const { data: interviewer } = useFetchQuery("/api/interviewer/{id}", { + urlParams: { + id: id!, + }, + }); + + if (!interviewer) { + return ( + + + + ); + } + + const label = `${interviewer.lastName?.toLocaleUpperCase() ?? ""} ${interviewer.firstName ?? ""}`; + + const handleChange = (_: SyntheticEvent, newValue: Tab) => { + setCurrentTab(newValue); + }; + + const breadcrumbs = [ + { href: "/follow", title: "goToFollowPage" }, + intl.formatMessage({ id: "followInterviewerBreacrumb" }), + label, + ]; + + return ( + <> + + + {Object.keys(Tab).map(k => ( + + ))} + + + {currentTab === Tab.progress && <>tab avancement} + {currentTab === Tab.collect && <>tab collecte} + {currentTab === Tab.lastSynchronization && <>tab dernière synchro} + + + ); }; diff --git a/src/pages/FollowOrganizationUnitPage.tsx b/src/pages/FollowOrganizationUnitPage.tsx index 2486c17..998000a 100644 --- a/src/pages/FollowOrganizationUnitPage.tsx +++ b/src/pages/FollowOrganizationUnitPage.tsx @@ -1,5 +1,54 @@ -import { Typography } from "@mui/material"; +import { SyntheticEvent, useState } from "react"; +import { useIntl } from "react-intl"; +import { FollowSinglePageHeader } from "../ui/follow/FollowSinglePageHeader"; +import Tabs from "@mui/material/Tabs"; +import { PageTab } from "../ui/PageTab"; +import Stack from "@mui/material/Stack"; + +// TODO remove +const labelMock = "Paris"; + +enum Tab { + progress = "progress", + collect = "collect", +} export const FollowOrganizationUnitPage = () => { - return page suivre site; + const intl = useIntl(); + const [currentTab, setCurrentTab] = useState(Tab.progress); + const handleChange = (_: SyntheticEvent, newValue: Tab) => { + setCurrentTab(newValue); + }; + + const breadcrumbs = [ + { href: "/follow", title: "goToFollowPage" }, + intl.formatMessage({ id: "followOrganizationUnitBreacrumb" }), + labelMock, + ]; + + return ( + <> + + + {Object.keys(Tab).map(k => ( + + ))} + + + {currentTab === Tab.progress && <>tab avancement} + {currentTab === Tab.collect && <>tab collecte} + + + ); }; diff --git a/src/pages/FollowPage.tsx b/src/pages/FollowPage.tsx index 1ef718d..766e231 100644 --- a/src/pages/FollowPage.tsx +++ b/src/pages/FollowPage.tsx @@ -7,8 +7,6 @@ export const FollowPage = () => { // TODO use real condition const isNationalProfile = true; - // TODO call api to get interviewers, surveys and organizationUnits - const gridTemplateColumns = isNationalProfile ? { gridTemplateColumns: "1fr 1fr 1fr" } : { gridTemplateColumns: "1fr 1fr" }; diff --git a/src/ui/LoadingCell.tsx b/src/ui/LoadingCell.tsx new file mode 100644 index 0000000..cea8079 --- /dev/null +++ b/src/ui/LoadingCell.tsx @@ -0,0 +1,13 @@ +import CircularProgress from "@mui/material/CircularProgress"; +import TableCell from "@mui/material/TableCell"; +import TableRow from "@mui/material/TableRow"; + +export const LoadingCell = ({ columnLength }: { columnLength: number }) => { + return ( + + + + + + ); +}; diff --git a/src/ui/follow/FollowCardHeader.tsx b/src/ui/follow/FollowCardHeader.tsx index 4424608..3b796ff 100644 --- a/src/ui/follow/FollowCardHeader.tsx +++ b/src/ui/follow/FollowCardHeader.tsx @@ -18,7 +18,7 @@ export const FollowCardHeader = ({ title, placeholder, onSearch }: Props) => { }; return ( - + {intl.formatMessage({ id: title })} { const intl = useIntl(); const [search, setSearch] = useDebouncedState("", 500); const [page, setPage] = useState(0); + const { data: interviewers, isLoading } = useFetchQuery("/api/interviewers", { method: "get" }); + const handleChangePage = (_: React.MouseEvent | null, newPage: number) => { setPage(newPage); }; - const filteredInterviewers = filterInterviewers({ interviewers: interviewersMock, search }); + const filteredInterviewers = filterInterviewers({ interviewers: interviewers ?? [], search }); return ( - + setSearch(e.target.value)} @@ -63,27 +40,31 @@ export const FollowInterviewerCard = () => { - + {intl.formatMessage({ id: "chooseInterviewer" })} - {filteredInterviewers.map(interviewer => ( - - - - {interviewer.interviewerLastName?.toLocaleUpperCase()}{" "} - {interviewer.interviewerFirstName} - - - - ))} + {isLoading ? ( + + ) : ( + filteredInterviewers.map(interviewer => ( + + + + {interviewer.interviewerLastName?.toLocaleUpperCase()}{" "} + {interviewer.interviewerFirstName} + + + + )) + )} {/* TODO use TableFooter */}
diff --git a/src/ui/follow/FollowOrganizationUnitCard.tsx b/src/ui/follow/FollowOrganizationUnitCard.tsx index cb86c72..c27eb65 100644 --- a/src/ui/follow/FollowOrganizationUnitCard.tsx +++ b/src/ui/follow/FollowOrganizationUnitCard.tsx @@ -10,42 +10,29 @@ import { useState } from "react"; import { Link as RouterLink } from "react-router-dom"; import { Link } from "../Link"; import { FollowCardHeader } from "./FollowCardHeader"; +import { useFetchQuery } from "../../hooks/useFetchQuery"; -const OUMock = [ - { - id: "1", - label: "Paris", - }, - { - id: "2", - label: "Metz", - }, - { - id: "3", - - label: "Lille", - }, - { - id: "4", - - label: "Amiens", - }, -]; +import { APISchemas } from "../../types/api"; +import { LoadingCell } from "../LoadingCell"; export const FollowOrganizationUnitCard = () => { const intl = useIntl(); const [search, setSearch] = useDebouncedState("", 500); const [page, setPage] = useState(0); + const { data: organizationUnits, isLoading } = useFetchQuery("/api/organization-units", { + method: "get", + }); + const handleChangePage = (_: React.MouseEvent | null, newPage: number) => { setPage(newPage); }; - const filteredOU = filterOrganizationUnits({ organizationUnits: OUMock, search }); + const filteredOU = filterOrganizationUnits({ organizationUnits: organizationUnits ?? [], search }); return ( - + setSearch(e.target.value)} @@ -55,26 +42,30 @@ export const FollowOrganizationUnitCard = () => { - + {intl.formatMessage({ id: "chooseOrganizationUnit" })} - {filteredOU.map(OU => ( - - - - {OU.label} - - - - ))} + {isLoading ? ( + + ) : ( + filteredOU.map(OU => ( + + + + {OU.label} + + + + )) + )} {/* TODO use TableFooter */}
@@ -85,16 +76,21 @@ export const FollowOrganizationUnitCard = () => { }; type FilterOUProps = { - organizationUnits: { id: string; label: string }[]; + organizationUnits: APISchemas["OrganizationUnitContextDto"][]; search?: string; }; const filterOrganizationUnits = ({ organizationUnits, search }: FilterOUProps) => { if (search) { organizationUnits = organizationUnits.filter(item => - item.label.toLowerCase().includes(search.toLowerCase()), + item.label?.toLowerCase().includes(search.toLowerCase()), ); } - return organizationUnits.sort((ou1, ou2) => ou1.label.localeCompare(ou2.label)); + return organizationUnits.sort((ou1, ou2) => { + if (!ou1.label || !ou2.label) { + return 1; + } + return ou1.label.localeCompare(ou2.label); + }); }; From b2dbe37dcbf3f55911c12aa253e073ba547c095b Mon Sep 17 00:00:00 2001 From: Lea Renaux Date: Fri, 3 May 2024 12:30:26 +0200 Subject: [PATCH 6/6] wip oidc in wrappedRender and add cardTab style --- src/WrappedRender.tsx | 26 +++++++-- src/functions/api.ts | 19 ++++++- src/functions/oidc.ts | 3 +- src/pages/FollowCampaignPage.tsx | 19 +++---- src/pages/FollowInterviewerPage.tsx | 19 +++---- src/pages/FollowOrganizationUnitPage.tsx | 17 ++---- src/theme.tsx | 54 ++++++++++-------- src/ui/follow/FollowCampaignProgress.tsx | 7 ++- .../FollowOrganizationUnitCard.test.tsx | 55 +++++++++++++++++++ src/ui/follow/FollowSinglePageHeader.tsx | 24 +++++++- 10 files changed, 178 insertions(+), 65 deletions(-) create mode 100644 src/ui/follow/FollowOrganizationUnitCard.test.tsx diff --git a/src/WrappedRender.tsx b/src/WrappedRender.tsx index cbb9c43..873a7f5 100644 --- a/src/WrappedRender.tsx +++ b/src/WrappedRender.tsx @@ -5,15 +5,31 @@ import { messagesFr } from "./i18n-fr"; import { IntlProvider } from "react-intl"; import { ReactElement } from "react"; import { SonorTheme } from "./theme"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { AuthProvider } from "./hooks/useAuth"; const router = createBrowserRouter(routes); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000, + refetchOnWindowFocus: false, + retry: false, + }, + }, +}); + export const WrappedRender = (children: ReactElement) => { return render( - - - {children} - - , + + + + + {children} + + + + , ); }; diff --git a/src/functions/api.ts b/src/functions/api.ts index 3159ee3..431c799 100644 --- a/src/functions/api.ts +++ b/src/functions/api.ts @@ -1,7 +1,20 @@ -import type { APIPaths, APIRequests, APIResponse } from "../types/api.ts"; +import type { APIPaths, APIRequests, APIResponse, APIMethods, APIRequest } from "../types/api.ts"; const baseURL = import.meta.env.VITE_API_ENDPOINT; +const mockApiRequest = new Map any>() + +export function mockRequest< +Path extends APIPaths, +Method extends APIMethods, +>(path: Path, method: Method, cb: (options: APIRequest) => APIResponse) { + mockApiRequest.set(`${method} ${path}`, cb as any) +} + +export function clearMockRequest() { + mockApiRequest.clear() +} + export async function fetchAPI< Path extends APIPaths, Options extends APIRequests & { signal?: AbortSignal; headers?: Record }, @@ -46,6 +59,10 @@ export async function fetchAPI< } } } + const requestKey = `${fetchOptions.method?.toLowerCase()} ${path}` + if (mockApiRequest.has(requestKey)) { + return (mockApiRequest.get('requestKey') as any)(options) as APIResponse + } const response = await fetch(url.toString(), fetchOptions); if (response.status === 204) { return null as any; diff --git a/src/functions/oidc.ts b/src/functions/oidc.ts index 5e56ec0..01a06a9 100644 --- a/src/functions/oidc.ts +++ b/src/functions/oidc.ts @@ -26,7 +26,8 @@ export const createAppOidc = () => { if (isOidc) { return !import.meta.env.VITE_OIDC_ISSUER ? createMockReactOidc({ - isUserInitiallyLoggedIn: false, + isUserInitiallyLoggedIn: true, + mockedTokens: { decodedIdToken: { inseegroupedefaut: ["gr"], diff --git a/src/pages/FollowCampaignPage.tsx b/src/pages/FollowCampaignPage.tsx index 980de1a..554b363 100644 --- a/src/pages/FollowCampaignPage.tsx +++ b/src/pages/FollowCampaignPage.tsx @@ -20,8 +20,8 @@ enum Tab { export const FollowCampaignPage = () => { const intl = useIntl(); - const [currentTab, setCurrentTab] = useState(Tab.progress); - const handleChange = (_: SyntheticEvent, newValue: Tab) => { + const [currentTab, setCurrentTab] = useState(Tab.progress); + const handleChange = (_: SyntheticEvent, newValue: string) => { setCurrentTab(newValue); }; @@ -33,19 +33,18 @@ export const FollowCampaignPage = () => { return ( <> - - {Object.keys(Tab).map(k => ( ))} - + + {currentTab === Tab.progress && } {currentTab === Tab.collect && <>tab collecte} diff --git a/src/pages/FollowInterviewerPage.tsx b/src/pages/FollowInterviewerPage.tsx index 9f0fa35..00bf1be 100644 --- a/src/pages/FollowInterviewerPage.tsx +++ b/src/pages/FollowInterviewerPage.tsx @@ -17,7 +17,7 @@ export const FollowInterviewerPage = () => { const { id } = useParams(); const intl = useIntl(); - const [currentTab, setCurrentTab] = useState(Tab.progress); + const [currentTab, setCurrentTab] = useState(Tab.progress); const { data: interviewer } = useFetchQuery("/api/interviewer/{id}", { urlParams: { @@ -35,7 +35,7 @@ export const FollowInterviewerPage = () => { const label = `${interviewer.lastName?.toLocaleUpperCase() ?? ""} ${interviewer.firstName ?? ""}`; - const handleChange = (_: SyntheticEvent, newValue: Tab) => { + const handleChange = (_: SyntheticEvent, newValue: string) => { setCurrentTab(newValue); }; @@ -47,19 +47,18 @@ export const FollowInterviewerPage = () => { return ( <> - - {Object.keys(Tab).map(k => ( ))} - + + {currentTab === Tab.progress && <>tab avancement} {currentTab === Tab.collect && <>tab collecte} diff --git a/src/pages/FollowOrganizationUnitPage.tsx b/src/pages/FollowOrganizationUnitPage.tsx index 998000a..eccee54 100644 --- a/src/pages/FollowOrganizationUnitPage.tsx +++ b/src/pages/FollowOrganizationUnitPage.tsx @@ -1,7 +1,7 @@ import { SyntheticEvent, useState } from "react"; import { useIntl } from "react-intl"; import { FollowSinglePageHeader } from "../ui/follow/FollowSinglePageHeader"; -import Tabs from "@mui/material/Tabs"; + import { PageTab } from "../ui/PageTab"; import Stack from "@mui/material/Stack"; @@ -15,8 +15,8 @@ enum Tab { export const FollowOrganizationUnitPage = () => { const intl = useIntl(); - const [currentTab, setCurrentTab] = useState(Tab.progress); - const handleChange = (_: SyntheticEvent, newValue: Tab) => { + const [currentTab, setCurrentTab] = useState(Tab.progress); + const handleChange = (_: SyntheticEvent, newValue: string) => { setCurrentTab(newValue); }; @@ -32,19 +32,14 @@ export const FollowOrganizationUnitPage = () => { category={"organizationUnit"} label={labelMock} breadcrumbs={breadcrumbs} - /> - {Object.keys(Tab).map(k => ( ))} - +
+ {currentTab === Tab.progress && <>tab avancement} {currentTab === Tab.collect && <>tab collecte} diff --git a/src/theme.tsx b/src/theme.tsx index b6a7f3d..d2988eb 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -328,36 +328,42 @@ export const theme = createTheme({ { props: { classes: "cardTab" }, style: { - borderTopLeftRadius: "16px !important", - borderTopRightRadius: "16px !important", - paddingLeft: 40, - paddingRight: 40, + padding: "10px 40px", backgroundColor: "#EFEFEF", - border: "none", - ...typography.titleSmall, - textTransform: "none", - "&:hover": { - backgroundColor: "#EFEFEF", - }, - ":nth-of-type(2)": { - left: -20, - }, - ":nth-of-type(3)": { - left: -40, - }, - ":nth-of-type(4)": { - left: -60, - }, - ":nth-of-type(5)": { - left: -80, - }, + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + zIndex: 1, + overflow: "visible", + "&.Mui-selected": { - position: "relative", - zIndex: 2, + zIndex: 3, background: "white", "&:hover": { background: "white", }, + + "&::before": { + content: "''", + position: "absolute", + bottom: 0, + width: 15, + height: 15, + background: + "#FFF radial-gradient(circle at top left, #EFEFEF 0px, #EFEFEF 70%, transparent 71%, transparent 100%) no-repeat", + left: -15, + zIndex: 5, + }, + "&::after": { + content: "''", + position: "absolute", + bottom: 0, + width: 15, + height: 15, + background: + "#FFF radial-gradient(circle at top right, #EFEFEF 0px, #EFEFEF 70%, transparent 71%, transparent 100%) no-repeat", + right: -15, + zIndex: 5, + }, }, }, }, diff --git a/src/ui/follow/FollowCampaignProgress.tsx b/src/ui/follow/FollowCampaignProgress.tsx index fd0806a..8d8b4b6 100644 --- a/src/ui/follow/FollowCampaignProgress.tsx +++ b/src/ui/follow/FollowCampaignProgress.tsx @@ -26,7 +26,12 @@ export const FollowCampaignProgress = () => { {Object.keys(Tab).map(k => ( diff --git a/src/ui/follow/FollowOrganizationUnitCard.test.tsx b/src/ui/follow/FollowOrganizationUnitCard.test.tsx new file mode 100644 index 0000000..e80c1b0 --- /dev/null +++ b/src/ui/follow/FollowOrganizationUnitCard.test.tsx @@ -0,0 +1,55 @@ +import userEvent from "@testing-library/user-event"; +import { WrappedRender } from "../../WrappedRender"; +import { FollowOrganizationUnitCard } from "./FollowOrganizationUnitCard"; +import { act, screen, waitFor } from "@testing-library/react"; +import { clearMockRequest, mockRequest } from "../../functions/api"; + +describe("FollowOrganizationUnitCard component", () => { + beforeEach(() => { + vi.resetModules(); + vi.clearAllMocks(); + mockRequest("/api/organization-units", "get", () => [ + { + id: "DR75", + label: "Ile de France", + type: "LOCAL" as const, + users: [], + }, + { + id: "DR51", + label: "Champagne-Ardenne", + type: "LOCAL" as const, + users: [], + }, + ]); + }); + + afterEach(() => clearMockRequest()); + + it("should render FollowOrganizationUnitCard component", () => { + WrappedRender(); + + expect(screen.getByText("Suivre un site")).toBeInTheDocument(); + expect(screen.getByRole("textbox")).toBeInTheDocument(); + expect(screen.getByText("Champagne-Ardenne")).toBeInTheDocument(); + + expect(screen.getByText("Ile de France")).toBeInTheDocument(); + }); + + it("should display search result", async () => { + await act(async () => WrappedRender()); + + userEvent.type(screen.getByRole("textbox"), "france"); + + expect(screen.getByText("Ile de France")).toBeInTheDocument(); + await waitFor(() => expect(screen.queryByText("Champagne-Ardenne")).not.toBeInTheDocument()); + }); + + it("should display sorted OU", async () => { + await act(async () => WrappedRender()); + + const links = screen.getAllByRole("link"); + expect(links[0]).toContainHTML("Champagne-Ardenne"); + expect(links[1]).toContainHTML("Ile de France"); + }); +}); diff --git a/src/ui/follow/FollowSinglePageHeader.tsx b/src/ui/follow/FollowSinglePageHeader.tsx index b10c9ad..c579fad 100644 --- a/src/ui/follow/FollowSinglePageHeader.tsx +++ b/src/ui/follow/FollowSinglePageHeader.tsx @@ -1,18 +1,29 @@ import { useNavigate } from "react-router-dom"; import { Row } from "../Row"; import Stack from "@mui/material/Stack"; -import { Box, Divider, IconButton, Typography } from "@mui/material"; +import { Box, Divider, IconButton, Tabs, Typography } from "@mui/material"; import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; import { Breadcrumbs, BreadcrumbsItem } from "../Breadcrumbs"; import { useIntl } from "react-intl"; +import { ReactNode, SyntheticEvent } from "react"; type Props = { category: string; label: string; breadcrumbs: BreadcrumbsItem[]; + currentTab: string; + onChange: (_: SyntheticEvent, newValue: string) => void; + children: ReactNode; }; -export const FollowSinglePageHeader = ({ category, label, breadcrumbs }: Props) => { +export const FollowSinglePageHeader = ({ + category, + label, + breadcrumbs, + currentTab, + onChange, + children, +}: Props) => { const navigate = useNavigate(); const intl = useIntl(); @@ -34,6 +45,15 @@ export const FollowSinglePageHeader = ({ category, label, breadcrumbs }: Props) + + {children} + ); };