Skip to content

Commit

Permalink
feat: faciliter l'ajout d'utilisateur #457
Browse files Browse the repository at this point in the history
  • Loading branch information
ocruze committed Aug 21, 2024
1 parent fc30a9a commit 37bbb63
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 133 deletions.
14 changes: 9 additions & 5 deletions assets/components/Layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fr } from "@codegouvfr/react-dsfr";
import { Breadcrumb } from "@codegouvfr/react-dsfr/Breadcrumb";
import { Breadcrumb, BreadcrumbProps } from "@codegouvfr/react-dsfr/Breadcrumb";
import { MainNavigationProps } from "@codegouvfr/react-dsfr/MainNavigation";
import { Notice, addNoticeTranslations } from "@codegouvfr/react-dsfr/Notice";
import { SkipLinks } from "@codegouvfr/react-dsfr/SkipLinks";
Expand Down Expand Up @@ -45,8 +45,9 @@ const infoBannerMsg = document.getElementById("info_banner")?.dataset?.msg ?? un
type AppLayoutProps = {
navItems?: MainNavigationProps.Item[];
documentTitle?: string;
customBreadcrumbProps?: BreadcrumbProps;
};
const AppLayout: FC<PropsWithChildren<AppLayoutProps>> = ({ children, navItems, documentTitle }) => {
const AppLayout: FC<PropsWithChildren<AppLayoutProps>> = ({ children, navItems, documentTitle, customBreadcrumbProps }) => {
useDocumentTitle(documentTitle);
const { t } = useTranslation("navItems");

Expand All @@ -62,9 +63,12 @@ const AppLayout: FC<PropsWithChildren<AppLayoutProps>> = ({ children, navItems,
});

const breadcrumbProps = useMemo(() => {
const datastoreName = datastoreQuery.data?.name;
return getBreadcrumb(route, datastoreName);
}, [route, datastoreQuery.data]);
if (customBreadcrumbProps !== undefined) {
return customBreadcrumbProps;
}

return getBreadcrumb(route, datastoreQuery.data);
}, [route, datastoreQuery.data, customBreadcrumbProps]);

navItems = useMemo(() => navItems ?? defaultNavItems(t), [navItems, t]);

Expand Down
8 changes: 5 additions & 3 deletions assets/components/Layout/DatastoreLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { BreadcrumbProps } from "@codegouvfr/react-dsfr/Breadcrumb";
import { useQuery } from "@tanstack/react-query";
import { FC, PropsWithChildren, memo, useMemo } from "react";

import api from "../../entrepot/api";
import { datastoreNavItems } from "../../config/datastoreNavItems";
import api from "../../entrepot/api";
import RQKeys from "../../modules/entrepot/RQKeys";
import AppLayout from "./AppLayout";

type DatastoreLayoutProps = {
datastoreId: string;
documentTitle?: string;
customBreadcrumbProps?: BreadcrumbProps;
};
const DatastoreLayout: FC<PropsWithChildren<DatastoreLayoutProps>> = ({ datastoreId, documentTitle, children }) => {
const DatastoreLayout: FC<PropsWithChildren<DatastoreLayoutProps>> = ({ datastoreId, documentTitle, customBreadcrumbProps, children }) => {
const datastoreQuery = useQuery({
queryKey: RQKeys.datastore(datastoreId),
queryFn: ({ signal }) => api.datastore.get(datastoreId, { signal }),
Expand All @@ -20,7 +22,7 @@ const DatastoreLayout: FC<PropsWithChildren<DatastoreLayoutProps>> = ({ datastor
const navItems = useMemo(() => datastoreNavItems(datastoreQuery.data), [datastoreQuery.data]);

return (
<AppLayout navItems={navItems} documentTitle={documentTitle}>
<AppLayout navItems={navItems} documentTitle={documentTitle} customBreadcrumbProps={customBreadcrumbProps}>
{children}
</AppLayout>
);
Expand Down
2 changes: 1 addition & 1 deletion assets/config/datastoreNavItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const datastoreNavItems = (currentDatastore?: Datastore): MainNavigationP
});
navItems.push({
text: t("members"),
linkProps: routes.members_list({ datastoreId: currentDatastore._id }).link,
linkProps: routes.members_list({ communityId: currentDatastore.community._id }).link,
});
navItems.push({
text: t("manage_storage"),
Expand Down
17 changes: 10 additions & 7 deletions assets/entrepot/api/community.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { UserRightsResponseDto } from "../../@types/app";
import type { CommunityDetailResponseDto, CommunityUserResponseDto } from "../../@types/entrepot";
import SymfonyRouting from "../../modules/Routing";

import { jsonFetch } from "../../modules/jsonFetch";
import { CommunityDetailResponseDto, CommunityUserResponseDto } from "../../@types/entrepot";
import { UserRightsResponseDto } from "../../@types/app";

const get = (communityId: string) => {
const get = (communityId: string, otherOptions: RequestInit = {}) => {
const url = SymfonyRouting.generate("cartesgouvfr_api_community_get", { communityId });
return jsonFetch<CommunityDetailResponseDto>(url);
return jsonFetch<CommunityDetailResponseDto>(url, {
...otherOptions,
});
};

const getMembers = (communityId: string) => {
const getMembers = (communityId: string, otherOptions: RequestInit = {}) => {
const url = SymfonyRouting.generate("cartesgouvfr_api_community_members", { communityId });
return jsonFetch<CommunityUserResponseDto[]>(url);
return jsonFetch<CommunityUserResponseDto[]>(url, {
...otherOptions,
});
};

/**
Expand Down
85 changes: 44 additions & 41 deletions assets/entrepot/pages/communities/AddMember.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
import { FC, useCallback, useState } from "react";
import { createPortal } from "react-dom";
import { fr } from "@codegouvfr/react-dsfr";
import { TranslationFunction } from "i18nifty/typeUtils/TranslationFunction";
import { ComponentKey, Translations, declareComponentKeys, getTranslation, useTranslation } from "../../../i18n/i18n";
import Input from "@codegouvfr/react-dsfr/Input";
import Alert from "@codegouvfr/react-dsfr/Alert";
import Checkbox from "@codegouvfr/react-dsfr/Checkbox";
import Input from "@codegouvfr/react-dsfr/Input";
import { createModal } from "@codegouvfr/react-dsfr/Modal";
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import { regex } from "../../../utils";
import { getRights, rightTypes } from "./UserRights";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { UserRightsResponseDto } from "../../../@types/app";
import { TranslationFunction } from "i18nifty/typeUtils/TranslationFunction";
import { FC, useCallback } from "react";
import { createPortal } from "react-dom";
import { useForm } from "react-hook-form";
import * as yup from "yup";

import type { UserRightsResponseDto } from "../../../@types/app";
import LoadingIcon from "../../../components/Utils/LoadingIcon";
import { ComponentKey, Translations, declareComponentKeys, getTranslation, useTranslation } from "../../../i18n/i18n";
import RQKeys from "../../../modules/entrepot/RQKeys";
import { CartesApiException } from "../../../modules/jsonFetch";
import api from "../../api";
import Alert from "@codegouvfr/react-dsfr/Alert";
import { routes } from "../../../router/router";
import { regex } from "../../../utils";
import api from "../../api";
import { getRights, rightTypes } from "./UserRights";

const addMemberModal = createModal({
id: "add-member-modal",
isOpenedByDefault: false,
});

type AddMemberProps = {
datastoreId: string;
communityId?: string;
communityId: string;
communityMemberIds: string[];
userId?: string;
};

const { t: tCommon } = getTranslation("Common");
const { t: translateRights } = getTranslation("Rights");

const AddMember: FC<AddMemberProps> = ({ datastoreId, communityId, communityMemberIds, userId }) => {
const AddMember: FC<AddMemberProps> = ({ communityId, communityMemberIds, userId }) => {
const { t } = useTranslation({ AddMember });

const schema = (t: TranslationFunction<"AddMember", ComponentKey>) => {
Expand All @@ -60,52 +62,50 @@ const AddMember: FC<AddMemberProps> = ({ datastoreId, communityId, communityMemb
handleSubmit,
} = useForm({ mode: "onSubmit", defaultValues: { user_rights: getRights() }, resolver: yupResolver(schema(t)) });

const [error, setError] = useState<CartesApiException | undefined>(undefined);

const resetAll = useCallback(() => {
setError(undefined);
reset({ user_id: "", user_rights: getRights() });
addMemberModal.close();
}, [reset]);

const queryClient = useQueryClient();

const { isPending, mutate } = useMutation<UserRightsResponseDto | undefined, CartesApiException, object>({
const addMemberMutation = useMutation<UserRightsResponseDto | undefined, CartesApiException, object>({
mutationFn: (datas) => {
if (communityId) return api.community.updateMember(communityId, datas);
return Promise.resolve(undefined);
},
onSuccess: () => {
resetAll();
routes.members_list({ datastoreId }).push(); // Suppression du parametre userId de la requete
queryClient.refetchQueries({ queryKey: ["community", "members", communityId] });
},
onError: (error) => {
setError(error);
routes.members_list({ communityId }).push(); // Suppression du parametre userId de la requete
queryClient.refetchQueries({ queryKey: RQKeys.community_members(communityId) });
},
});

const resetAll = useCallback(() => {
addMemberMutation.reset();
reset({ user_id: "", user_rights: getRights() });
addMemberModal.close();
}, [reset, addMemberMutation]);

// Annulation
const handleOnCancel = () => {
resetAll();
routes.members_list({ datastoreId }).push();
routes.members_list({ communityId }).push();
};

// Ajout de l'utilisateur
const onSubmit = () => {
const values = getFormValues();
values["user_creation"] = true;
mutate(values);
addMemberMutation.mutate(values);
};

let title = t("add_user_title");
if (isPending) {
title += `&nbsp;${t("running")}&nbsp;<i class="fr-icon-refresh-line frx-icon-spin" />`;
}

return createPortal(
<addMemberModal.Component
title={<span dangerouslySetInnerHTML={{ __html: title }} />}
title={
addMemberMutation.isPending === true ? (
<>
{t("running")} <LoadingIcon />
</>
) : (
t("add_user_title")
)
}
buttons={[
{
children: tCommon("cancel"),
Expand All @@ -120,8 +120,11 @@ const AddMember: FC<AddMemberProps> = ({ datastoreId, communityId, communityMemb
priority: "primary",
},
]}
concealingBackdrop={true}
>
{error && <Alert severity={"error"} title={tCommon("error")} description={error?.message} className={fr.cx("fr-my-3w")} />}
{addMemberMutation.error && (
<Alert severity={"error"} title={tCommon("error")} description={addMemberMutation.error?.message} className={fr.cx("fr-my-3w")} />
)}
<Input
label={t("user_id")}
state={errors.user_id ? "error" : "default"}
Expand All @@ -148,7 +151,7 @@ const AddMember: FC<AddMemberProps> = ({ datastoreId, communityId, communityMemb
);
};

export { addMemberModal, AddMember };
export { AddMember, addMemberModal };

// traductions
export const { i18n } = declareComponentKeys<
Expand All @@ -164,7 +167,7 @@ export const AddMemberFrTranslations: Translations<"fr">["AddMember"] = {
id_mandatory: "L’identifiant est obligatoire",
id_must_be_uuid: "L’Identifiant doit être un UUID",
already_member: ({ userId }) => `l’utilisateur ${userId} est déjà membre de cet espace de travail`,
running: "en cours ...",
running: "Ajout d’utilisateur en cours ...",
};

export const AddMemberEnTranslations: Translations<"en">["AddMember"] = {
Expand All @@ -174,5 +177,5 @@ export const AddMemberEnTranslations: Translations<"en">["AddMember"] = {
id_mandatory: "Identifier is mandatory",
id_must_be_uuid: "Identifier must be an UUID",
already_member: ({ userId }) => `User ${userId} is already a member of this community`,
running: "running ...",
running: "Ajout d’utilisateur running ...",
};
Loading

0 comments on commit 37bbb63

Please sign in to comment.