diff --git a/.env b/.env
index 23f8d03..0d26d85 100644
--- a/.env
+++ b/.env
@@ -3,3 +3,5 @@ VITE_AUTH_TYPE=anonymous
VITE_OIDC_CLIENT_ID=
VITE_OIDC_ISSUER=https://localhost:8000
VITE_IDENTITY_PROVIDER=
+VITE_ADMIN_LDAP_ROLE=
+VITE_USER_LDAP_ROLE=
diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts
index bb1c19c..2eb1a3f 100644
--- a/src/hooks/useAuth.ts
+++ b/src/hooks/useAuth.ts
@@ -13,7 +13,11 @@ export const useAccessToken = (): string => {
};
export const useUser = () => {
- return useOidc({ assertUserLoggedIn: true }).oidcTokens.decodedIdToken;
+ return {
+ decodedToken: useOidc({ assertUserLoggedIn: true }).oidcTokens.decodedIdToken,
+ isAdminLdap: useHasRole(import.meta.env.VITE_ADMIN_LDAP_ROLE),
+ isUserLdap: useHasRole(import.meta.env.VITE_USER_LDAP_ROLE),
+ };
};
export const useLogout = () => {
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
deleted file mode 100644
index 904b1b1..0000000
--- a/src/pages/Settings.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export function Settings() {
- return
Settings coming
;
-}
diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx
new file mode 100644
index 0000000..9efa7dd
--- /dev/null
+++ b/src/pages/SettingsPage.tsx
@@ -0,0 +1,72 @@
+import { Box, Divider, Tabs } from "@mui/material";
+import { PageTab } from "../ui/PageTab";
+import { useState, SyntheticEvent } from "react";
+import { SettingsHeader } from "../ui/Settings/SettingsHeader";
+import { SettingsHabilitationsCard } from "../ui/Settings/SettingsHabilitationsCard";
+import { Breadcrumbs } from "../ui/Breadcrumbs.tsx";
+
+enum Tab {
+ Habilitations = "Habilitations",
+ Communications = "Communications",
+ NewSource = "NewSource",
+}
+
+const TabNames = {
+ [Tab.Habilitations]: "Gestion des habilitations",
+ [Tab.Communications]: "Communications",
+ [Tab.NewSource]: "Nouvelle Source",
+};
+
+export function SettingsPage() {
+ const [currentTab, setCurrentTab] = useState(Tab.Habilitations);
+ const handleChange = (_: SyntheticEvent, newValue: Tab) => {
+ setCurrentTab(newValue);
+ };
+ const breadcrumbs = [
+ { href: "/", title: "Accueil" },
+ { href: "/settings", title: "Réglages" },
+ TabNames[currentTab],
+ ];
+ return (
+ <>
+
+
+
+ {Object.keys(Tab).map(k => (
+
+ ))}
+
+
+
+
+
+
+
+ >
+ );
+}
+
+function SettingsTab({ tab }: { tab: Tab }) {
+ if (tab === Tab.Habilitations) {
+ return ;
+ }
+
+ return;
+}
diff --git a/src/pages/SurveyPage.tsx b/src/pages/SurveyPage.tsx
index fad8ccf..d36b123 100644
--- a/src/pages/SurveyPage.tsx
+++ b/src/pages/SurveyPage.tsx
@@ -93,13 +93,13 @@ export function SurveyPage() {
-
+
>
);
}
-function SurveyUnitTab({
+function SurveyTab({
survey,
onSave,
tab,
diff --git a/src/routes.tsx b/src/routes.tsx
index ec24e7c..0efe0f8 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -5,7 +5,7 @@ import { Layout } from "./ui/Layout";
import { PageError } from "./ui/PageError";
import { SurveyPage } from "./pages/SurveyPage";
import { ContactPage } from "./pages/ContactPage";
-import { Settings } from "./pages/Settings";
+import { SettingsPage } from "./pages/SettingsPage.tsx";
import { SearchContacts } from "./pages/Search/SearchContacts.tsx";
import { SearchSurveys } from "./pages/Search/SearchSurveys.tsx";
import { SearchSurveyUnits } from "./pages/Search/SearchSurveyUnits.tsx";
@@ -33,8 +33,8 @@ export const routes: RouteObject[] = [
},
{ path: "surveys/:id", element: },
{ path: "contacts/:id", element: },
+ { path: "settings", element: },
{ path: "survey-units/:id", element: },
- { path: "reglages", element: },
{ path: "", element: },
],
},
diff --git a/src/theme.tsx b/src/theme.tsx
index d2b0493..363d106 100644
--- a/src/theme.tsx
+++ b/src/theme.tsx
@@ -49,6 +49,7 @@ declare module "@mui/material/styles" {
bodyLarge: CSSProperties;
bodyMedium: CSSProperties;
bodySmall: CSSProperties;
+ robotoLarge: CSSProperties;
}
interface TypographyVariantsOptions {
@@ -67,6 +68,7 @@ declare module "@mui/material/styles" {
bodyLarge?: CSSProperties;
bodyMedium?: CSSProperties;
bodySmall?: CSSProperties;
+ robotoLarge?: CSSProperties;
}
}
@@ -87,6 +89,7 @@ declare module "@mui/material/Typography" {
bodyLarge: true;
bodyMedium: true;
bodySmall: true;
+ robotoLarge: true;
}
}
@@ -115,6 +118,11 @@ declare module "@mui/material/Paper" {
disabled: true;
}
}
+declare module "@mui/material/Chip" {
+ interface ChipPropsVariantOverrides {
+ role: true;
+ }
+}
declare module "@mui/material/Tab" {
interface TabPropsClassesOverrides {
@@ -206,6 +214,13 @@ const typography = {
lineHeight: "16px",
letterSpacing: 0.4,
},
+ robotoLarge: {
+ lineHeight: "24px",
+ fontSize: "16px",
+ letterSpacing: "0.15px",
+ fontWeight: 400,
+ fontFamily: "Roboto",
+ },
};
const palette = {
background: {
@@ -389,11 +404,27 @@ export const theme = createTheme({
},
MuiInputLabel: {
styleOverrides: {
- sizeSmall: {
- ...typography.bodyMedium,
+ root: {
+ ...typography.bodyLarge,
},
},
},
+ MuiChip: {
+ variants: [
+ {
+ props: { variant: "role" },
+ style: {
+ fontWeight: 600,
+ lineHeight: "18px",
+ letterSpacing: "0.1px",
+ fontSize: "14px",
+ textTransform: "capitalize",
+ backgroundColor: "#EBEBEB",
+ height: "34px",
+ },
+ },
+ ],
+ },
MuiInputBase: {
styleOverrides: {
root: {
diff --git a/src/types/api.ts b/src/types/api.ts
index 4312a17..3a7b275 100644
--- a/src/types/api.ts
+++ b/src/types/api.ts
@@ -7,7 +7,17 @@ export type APISchemas = {
/* Format: date-time */
timestamp?: string
}
- UserDto: { identifier: string; role?: string }
+ UserDto: {
+ identifier: string
+ role?: string
+ name?: string
+ firstName?: string
+ organization?: string
+ accreditedSources?: Array
+ /* Format: date-time */
+ creationDate?: string
+ creationAuthor?: string
+ }
SurveyDto: {
id: string
sourceId: string
@@ -340,17 +350,17 @@ export type APISchemas = {
}
OnGoingDto: { ongoing?: boolean }
PageableObject: {
- /* Format: int64 */
- offset?: number
- sort?: APISchemas["SortObject"]
/* Format: int32 */
pageNumber?: number
/* Format: int32 */
pageSize?: number
- unpaged?: boolean
+ /* Format: int64 */
+ offset?: number
+ sort?: APISchemas["SortObject"]
paged?: boolean
+ unpaged?: boolean
}
- SortObject: { empty?: boolean; sorted?: boolean; unsorted?: boolean }
+ SortObject: { sorted?: boolean; empty?: boolean; unsorted?: boolean }
UserPage: {
content?: Array
pageable?: APISchemas["PageableObject"]
@@ -608,7 +618,7 @@ export type APISchemas = {
empty?: boolean
}
PeriodDto: { value?: string; label?: string; period?: string }
- PeriodicityDto: { value: string; label: string }
+ PeriodicityDto: { value?: string; label?: string }
EligibleDto: { eligible?: string }
OwnerPage: {
content?: Array
@@ -748,6 +758,7 @@ export type APISchemas = {
totalPages?: number
first?: boolean
last?: boolean
+ pageable?: APISchemas["PageableObject"]
/* Format: int32 */
size?: number
content?: Array
@@ -756,7 +767,6 @@ export type APISchemas = {
sort?: APISchemas["SortObject"]
/* Format: int32 */
numberOfElements?: number
- pageable?: APISchemas["PageableObject"]
empty?: boolean
}
HealthcheckDto: { status?: string }
@@ -779,7 +789,7 @@ export type APISchemas = {
empty?: boolean
}
ContactFirstLoginDto: {
- identifier: string
+ identifier?: string
externalId?: string
civility?: "Female" | "Male" | "Undefined"
lastName?: string
@@ -1107,7 +1117,7 @@ export type APIEndpoints = {
}
}
"/api/contacts/{id}": {
- responses: { get: APISchemas["ContactFirstLoginDto"]; put: APISchemas["ContactDto"]; delete: null }
+ responses: { get: null; put: APISchemas["ContactDto"]; delete: null }
requests:
| { method?: "get"; urlParams: { id: string } }
| {
@@ -1240,6 +1250,10 @@ export type APIEndpoints = {
responses: { get: null }
requests: { method?: "get"; urlParams: { id: string } }
}
+ "/api/users/v2": {
+ responses: { get: Array }
+ requests: { method?: "get" }
+ }
"/api/temp/moog/campaigns/{idCampaign}/monitoring/progress": {
responses: { get: APISchemas["JSONCollectionWrapperMoogProgressDto"] }
requests: { method?: "get"; urlParams: { idCampaign: string } }
diff --git a/src/ui/Header.tsx b/src/ui/Header.tsx
index bb4c323..b3a1cb0 100644
--- a/src/ui/Header.tsx
+++ b/src/ui/Header.tsx
@@ -8,7 +8,7 @@ import { useUser, useLogout } from "../hooks/useAuth.ts";
import packageInfo from "../../package.json";
export function Header() {
- const { preferred_username } = useUser();
+ const { decodedToken, isAdminLdap, isUserLdap } = useUser();
const logout = useLogout();
return (
@@ -20,16 +20,28 @@ export function Header() {
Platine
- Collecte
+ Gestion
v{packageInfo.version}
-
- {preferred_username}
-
+
+
+
+
+ Bienvenue, {decodedToken.preferred_username.toUpperCase()}
+
+
+ {isAdminLdap
+ ? "Vous avez le profil Administrateur"
+ : isUserLdap
+ ? "Vous avez le profil Utilisateur"
+ : "Vous n'avez aucune habilitation annuaire"}
+
+
+
-
+
);
}
diff --git a/src/ui/Settings/RoleChip.tsx b/src/ui/Settings/RoleChip.tsx
new file mode 100644
index 0000000..fd94e71
--- /dev/null
+++ b/src/ui/Settings/RoleChip.tsx
@@ -0,0 +1,27 @@
+import { Chip } from "@mui/material";
+
+type Props = {
+ role: string;
+};
+
+export const roleColorsWidth = [
+ { role: "administrateur", color: "#442F99", width: "145px" },
+ { role: "responsable", color: "#0C8C87", width: "127px" },
+ { role: "gestionnaire", color: "#3C75A2", width: "129px" },
+ { role: "assistance", color: "#5A1741", width: "113px" },
+];
+
+export const RoleChip = ({ role }: Props) => {
+ const color = role ? roleColorsWidth.find(r => r.role === role.toLowerCase())?.color : "black";
+ const width = role ? roleColorsWidth.find(r => r.role === role.toLowerCase())?.width : undefined;
+ return (
+
+ );
+};
diff --git a/src/ui/Settings/SettingsHabilitationsCard.tsx b/src/ui/Settings/SettingsHabilitationsCard.tsx
new file mode 100644
index 0000000..aefb3da
--- /dev/null
+++ b/src/ui/Settings/SettingsHabilitationsCard.tsx
@@ -0,0 +1,205 @@
+import {
+ Card,
+ Stack,
+ Divider,
+ Typography,
+ Paper,
+ Table,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ TableBody,
+ TextField,
+ CircularProgress,
+ Button,
+ TableFooter,
+ TablePagination,
+ InputAdornment,
+ CardContent,
+} from "@mui/material";
+import { useFetchQuery } from "../../hooks/useFetchQuery";
+import { SettingsHabilitationsMenu } from "./SettingsHabilitationsMenu";
+import { RoleChip } from "./RoleChip";
+import { Row } from "../Row";
+import { ChangeEvent, useEffect, useState } from "react";
+import AddCircleOutlineOutlinedIcon from "@mui/icons-material/AddCircleOutlineOutlined";
+import { format } from "date-fns";
+import SearchIcon from "@mui/icons-material/Search";
+import { APISchemas } from "../../types/api";
+
+interface Column {
+ id: string;
+ label: string;
+ minWidth?: string;
+ format?: (value: number) => string;
+}
+
+const columns: readonly Column[] = [
+ { id: "id", label: "Idep", minWidth: "95px" },
+ { id: "name", label: "Nom", minWidth: "95px" },
+ { id: "firstName", label: "Prénom", minWidth: "95px" },
+ { id: "Organisation", label: "Organisation", minWidth: "150px" },
+ { id: "role", label: "Rôle", minWidth: "120px" },
+ { id: "pilotRights", label: "Droits Pilotage", minWidth: "50px" },
+ { id: "ldapRights", label: "Droits Annuaire", minWidth: "50px" },
+ { id: "accreditedSources", label: "Source", minWidth: "100px" },
+ { id: "creation", label: "Date de création", minWidth: "120px" },
+ { id: "actions", label: "Actions", minWidth: "80px" },
+];
+
+export const SettingsHabilitationsCard = () => {
+ const { data: users /*, refetch*/ } = useFetchQuery("/api/users/v2");
+ const [searchList, setSearchList] = useState>([]);
+ const [rowsPerPage, setRowsPerPage] = useState(10);
+ const [pageNumber, setPageNumber] = useState(0);
+
+ useEffect(() => {
+ if (users) setSearchList(users);
+ }, [users]);
+
+ const handleChangePage = (_: React.MouseEvent | null, newPage: number) => {
+ setPageNumber(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event: React.ChangeEvent) => {
+ setRowsPerPage(parseInt(event.target.value, 10));
+ setPageNumber(0);
+ };
+
+ function filterValues(e: ChangeEvent): void {
+ const filteredList = users!.filter(u =>
+ e.target.value.length > 0
+ ? u.identifier.toLowerCase().includes(e.target.value.toLowerCase()) ||
+ u.name?.toLowerCase().includes(e.target.value.toLowerCase()) ||
+ u.role?.toLowerCase().includes(e.target.value.toLowerCase())
+ : u,
+ );
+ setSearchList(filteredList);
+ setPageNumber(0);
+ }
+
+ const sortedUsers = searchList.sort((a, b) => (a.identifier > b.identifier ? 1 : -1));
+ if (!users || !searchList) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ {" "}
+ {"Gestion des habilitations des utilisateurs INSEE"}
+
+
+
+
+
+
+
+ ),
+ }}
+ onChange={e => filterValues(e)}
+ />
+
+
+ }
+ >
+ Ajouter un utilisateur
+
+
+
+
+
+
+ {columns.map(column => (
+
+ {column.label}
+
+ ))}
+
+
+
+ {sortedUsers
+ ?.slice(rowsPerPage * pageNumber, rowsPerPage * (pageNumber + 1))
+ .map(user => (
+
+ {user.identifier}
+ {user.name}
+ {user.firstName}
+ {user.organization}
+
+ {user.role ? : null}
+
+ {"not provided"}
+ {"not provided"}
+ {user.accreditedSources?.join()}
+
+ {user.creationDate ? `Le ${format(user.creationDate, "dd/MM/yyyy")}` : ""}{" "}
+ {`par
+ ${user.creationAuthor}`}
+
+
+
+
+
+ ))}
+
+
+
+
+ `${page.from}-${page.to === -1 ? page.count : page.to} sur ${
+ page.count
+ } entités affichées`
+ }
+ count={searchList.length}
+ rowsPerPage={rowsPerPage}
+ page={pageNumber}
+ onPageChange={handleChangePage}
+ onRowsPerPageChange={handleChangeRowsPerPage}
+ />
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/ui/Settings/SettingsHabilitationsMenu.tsx b/src/ui/Settings/SettingsHabilitationsMenu.tsx
new file mode 100644
index 0000000..0442a13
--- /dev/null
+++ b/src/ui/Settings/SettingsHabilitationsMenu.tsx
@@ -0,0 +1,59 @@
+import { IconButton, Typography } from "@mui/material";
+import { useState } from "react";
+import MoreVertIcon from "@mui/icons-material/MoreVert";
+import Menu from "@mui/material/Menu";
+import MenuItem from "@mui/material/MenuItem";
+import { APISchemas } from "../../types/api";
+
+type Props = {
+ user: APISchemas["UserDto"];
+};
+
+const options = [
+ "Suppression des droits annuaire",
+ "Suppression des droits Pilotage",
+ "Modification du rôle dans Pilotage",
+ "Ajout/suppression de sources dans Pilotage",
+];
+
+export const SettingsHabilitationsMenu = ({ user }: Props) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+ const handleClick = (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget);
+ };
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+ console.log(user);
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/src/ui/Settings/SettingsHeader.tsx b/src/ui/Settings/SettingsHeader.tsx
new file mode 100644
index 0000000..448daba
--- /dev/null
+++ b/src/ui/Settings/SettingsHeader.tsx
@@ -0,0 +1,25 @@
+import { Stack, IconButton, Typography } from "@mui/material";
+import { useNavigate } from "react-router-dom";
+import { Row } from "../Row";
+import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
+import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
+
+export const SettingsHeader = () => {
+ const navigate = useNavigate();
+
+ return (
+
+
+ navigate(-1)}>
+
+
+
+
+
+ {"Réglages"}
+
+
+
+
+ );
+};
diff --git a/src/ui/Survey/SurveyCreateCampaignCard.tsx b/src/ui/Survey/SurveyCreateCampaignCard.tsx
index b256a8b..940b4d6 100644
--- a/src/ui/Survey/SurveyCreateCampaignCard.tsx
+++ b/src/ui/Survey/SurveyCreateCampaignCard.tsx
@@ -30,7 +30,7 @@ export const SurveyCreateCampaignCard = ({ survey }: Props) => {
const existingPeriods = campaigns ? campaigns.map(c => c.period?.toString()) : [];
const periodicity = existingPeriods[0]?.substring(0, 1);
const selectoptions = periods
- ?.map(p => p.label!)
+ ?.map(p => p.value!)
.filter(p => p.substring(0, 1) === periodicity && !existingPeriods?.includes(p))
.sort((a, b) => (a > b ? 1 : -1));
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index 4c741ad..f230803 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -5,6 +5,8 @@ interface ImportMetaEnv {
readonly VITE_OIDC_CLIENT_ID: string;
readonly VITE_OIDC_ISSUER: string;
readonly VITE_IDENTITY_PROVIDER: string;
+ readonly VITE_ADMIN_LDAP_ROLE: string;
+ readonly VITE_USER_LDAP_ROLE: string;
}
interface ImportMeta {