fix(frontend): introduce state mode feature

This commit is contained in:
yassinedorbozgithub
2025-01-26 09:15:07 +01:00
parent dbf9d52a11
commit 21c4bd97e6
6 changed files with 74 additions and 78 deletions

View File

@@ -43,8 +43,9 @@
"account_update_success": "Account has been updated successfully",
"account_disabled": "Your account has been either disabled or is pending confirmation.",
"success_invitation_sent": "Invitation to join has been successfully sent.",
"item_delete_confirm": "Are you sure you want to delete this item?",
"item_delete_confirm": "Are you sure you want to delete this {{0}} item?",
"items_delete_confirm": "Are you sure you want to delete those {{0}} selected items?",
"selected": "selected",
"item_delete_success": "Item has been deleted successfully",
"success_save": "Changes has been saved!",
"no_result_found": "No result found",

View File

@@ -43,8 +43,9 @@
"account_update_success": "Le compte a été mis à jour avec succès",
"account_disabled": "Votre compte a été désactivé ou est en attente de confirmation.",
"success_invitation_sent": "L'invitation a été envoyée avec succès.",
"item_delete_confirm": "Êtes-vous sûr de bien vouloir supprimer cet élément?",
"item_delete_confirm": "Êtes-vous sûr de bien vouloir supprimer cet élément {{0}}?",
"items_delete_confirm": "Êtes-vous sûr de bien vouloir supprimer ces {{0}} éléments sélectionnés?",
"selected": "sélectionné",
"item_delete_success": "L'élément a été supprimé avec succès",
"success_save": "Les modifications ont été enregistrées!",
"no_result_found": "Aucun résultat trouvé",

View File

@@ -26,40 +26,30 @@ import { IEntityMapTypes } from "@/types/base.types";
export type DeleteDialogProps<T extends string = string> = DialogControl<T>;
export const DeleteDialog = <T extends string = string>({
open,
closeDialog: closeFunction,
datum,
data: ids,
callback,
entity = EntityType.ATTACHMENT,
setData,
onDeleteError = () => {},
onDeleteSuccess = () => {},
...rest
}: DeleteDialogProps<T> & {
entity?: keyof IEntityMapTypes;
onDeleteError?: (error: Error) => void;
onDeleteSuccess?: (data?: unknown) => void;
}) => {
const { t } = useTranslate();
const getItemsFromData = (data: unknown) => (Array.isArray(data) ? data : []);
const hasMultipleItems = (data: unknown): boolean =>
getItemsFromData(data).length > 1;
const onSuccess = (data: unknown) => {
setData?.(undefined);
onDeleteSuccess(data);
const options = {
onError: onDeleteError,
onSuccess: (data: unknown) => {
rest.setData?.(undefined);
onDeleteSuccess(data);
},
};
const { mutateAsync: deleteOne } = useDelete(entity, {
onError: onDeleteError,
onSuccess,
});
const { mutateAsync: deleteMany } = useDeleteMany(entity, {
onError: onDeleteError,
onSuccess,
});
const { mutateAsync: deleteOne } = useDelete(entity, options);
const { mutateAsync: deleteMany } = useDeleteMany(entity, options);
return (
<Dialog open={open} fullWidth onClose={closeFunction}>
<DialogTitle onClose={closeFunction}>{t("title.warning")}</DialogTitle>
<Dialog open={rest.open} fullWidth onClose={rest.closeDialog}>
<DialogTitle onClose={rest.closeDialog}>{t("title.warning")}</DialogTitle>
<DialogContent>
<Grid container gap={1}>
<Grid item height="28px">
@@ -68,12 +58,15 @@ export const DeleteDialog = <T extends string = string>({
<Grid item alignSelf="center">
<Typography>
{t(
`${
hasMultipleItems(ids)
? "message.items_delete_confirm"
: "message.item_delete_confirm"
}`,
{ "0": getItemsFromData(ids).length.toString() },
`message.item${
(ids?.length || 0) > 1 ? "s" : ""
}_delete_confirm`,
{
"0":
ids?.length === 1
? t("message.selected")
: ids?.length.toString(),
},
)}
</Typography>
</Grid>
@@ -84,19 +77,19 @@ export const DeleteDialog = <T extends string = string>({
color="error"
variant="contained"
onClick={async () => {
if (callback) {
callback(ids);
if (rest.callback) {
rest.callback(ids);
} else if (Array.isArray(ids)) {
await deleteMany(ids);
} else if (datum) {
await deleteOne(datum);
} else if (rest.datum) {
await deleteOne(rest.datum);
}
}}
autoFocus
>
{t("button.yes")}
</Button>
<Button variant="outlined" onClick={closeFunction}>
<Button variant="outlined" onClick={rest.closeDialog}>
{t("button.no")}
</Button>
</DialogActions>

View File

@@ -15,55 +15,51 @@ import {
MenuItem,
Select,
} from "@mui/material";
import { FC, useState } from "react";
import { DialogTitle } from "@/app-components/dialogs/DialogTitle";
import { DialogControl } from "@/hooks/useDialog";
import { useTranslate } from "@/hooks/useTranslate";
import { ICategory } from "@/types/category.types";
export interface MoveDialogProps
extends Omit<DialogControl<string>, "callback"> {
export interface MoveDialogProps<T = never>
extends Omit<DialogControl<T>, "callback"> {
categories: ICategory[];
callback?: (newCategoryId?: string) => Promise<void>;
openDialog: (data?: string) => void;
callback?: (selectedCategoryId?: T, ids?: T[]) => Promise<void>;
}
export const MoveDialog: FC<MoveDialogProps> = ({
open,
callback,
closeDialog,
categories,
}: MoveDialogProps) => {
export const MoveDialog = <T,>({
data: ids,
datum: selectedCategoryId,
...rest
}: MoveDialogProps<T>) => {
const { t } = useTranslate();
const [selectedCategoryId, setSelectedCategoryId] = useState<string>("");
const handleMove = async () => {
if (selectedCategoryId && callback) {
await callback(selectedCategoryId);
closeDialog();
if (selectedCategoryId && rest.callback) {
await rest.callback(selectedCategoryId, ids);
rest.closeDialog();
}
};
return (
<Dialog open={open} fullWidth onClose={closeDialog}>
<DialogTitle onClose={closeDialog}>
<Dialog open={rest.open} fullWidth onClose={rest.closeDialog}>
<DialogTitle onClose={rest.closeDialog}>
{t("message.select_category")}
</DialogTitle>
<DialogContent>
<Grid container direction="column" gap={2}>
<Grid item>
<Select
value={selectedCategoryId}
onChange={(e) => setSelectedCategoryId(e.target.value as string)}
value={selectedCategoryId || ""}
onChange={(e) => rest.setDatum?.(e.target.value as T)}
fullWidth
displayEmpty
>
<MenuItem value="" disabled>
{t("label.category")}
</MenuItem>
{categories.map((category) => (
<MenuItem key={category.id} value={category.id}>
{category.label}
{rest.categories.map(({ id, label }) => (
<MenuItem key={id} value={id}>
{label}
</MenuItem>
))}
</Select>
@@ -78,7 +74,7 @@ export const MoveDialog: FC<MoveDialogProps> = ({
>
{t("button.move")}
</Button>
<Button variant="outlined" onClick={closeDialog}>
<Button variant="outlined" onClick={rest.closeDialog}>
{t("button.cancel")}
</Button>
</DialogActions>

View File

@@ -75,7 +75,7 @@ const Diagrams = () => {
const [canvas, setCanvas] = useState<JSX.Element | undefined>();
const [selectedBlockId, setSelectedBlockId] = useState<string | undefined>();
const deleteDialogCtl = useDialog<string>(false);
const moveDialogCtl = useDialog<string>(false);
const moveDialogCtl = useDialog<string>(false, "datumAndData");
const addCategoryDialogCtl = useDialog<ICategory>(false);
const { mutateAsync: updateBlocks } = useUpdateMany(EntityType.BLOCK);
const {
@@ -203,8 +203,7 @@ const Diagrams = () => {
setter: setSelectedBlockId,
updateFn: updateBlock,
onRemoveNode: (ids, next) => {
deleteDialogCtl.setData?.(ids);
deleteDialogCtl.openDialog();
deleteDialogCtl.openDialog(ids);
deleteCallbackRef.current = next;
},
onDbClickNode: (event, id) => {
@@ -446,8 +445,7 @@ const Diagrams = () => {
});
engine?.repaintCanvas();
};
deleteDialogCtl.setData?.(ids);
deleteDialogCtl.openDialog();
deleteDialogCtl.openDialog(ids);
}
};
const handleMoveButton = () => {
@@ -455,8 +453,7 @@ const Diagrams = () => {
const ids = selectedEntities?.map((model) => model.getID());
if (ids && selectedEntities) {
moveDialogCtl.setData?.(ids);
moveDialogCtl.openDialog();
moveDialogCtl.openDialog(ids);
}
};
const onDelete = async (ids: string[]) => {
@@ -473,14 +470,12 @@ const Diagrams = () => {
cleanupAfterDeletion();
};
const onMove = async (newCategoryId?: string) => {
const onMove = async (newCategoryId?: string, ids?: string[]) => {
if (!newCategoryId) {
return;
}
const ids = moveDialogCtl?.data;
if (ids?.length && Array.isArray(ids)) {
if (Array.isArray(ids) && ids?.length) {
await updateBlocks({ ids, payload: { category: newCategoryId } });
queryClient.invalidateQueries({

View File

@@ -15,24 +15,33 @@ type TCloseDialog = <E extends React.MouseEvent | Event | Object>(
reason?: "backdropClick" | "escapeKeyDown",
) => void;
type TFnVoid<T> = (data?: T) => void;
type TStatesMode = "datumOrData" | "datumAndData";
export type DialogControl<T = never> = DialogProps & {
data?: T[];
datum?: T;
setData?: TFnVoid<T[]>;
setDatum?: TFnVoid<T>;
callback?: (data?: T | T[]) => Promise<void>;
openDialog: TFnVoid<T>;
openDialog: TFnVoid<T | T[]>;
closeDialog: TCloseDialog;
};
export const useDialog = <T,>(initialState: boolean): DialogControl<T> => {
export const useDialog = <T,>(
initialState: boolean,
statesMode: TStatesMode = "datumOrData",
): DialogControl<T> => {
const [open, setOpen] = useState(initialState);
const [data, setData] = useState<T[] | undefined>(undefined);
const [datum, setDatum] = useState<T | undefined>(undefined);
const openDialog: TFnVoid<T> = (datum) => {
setDatum(datum);
if (datum) {
setData(undefined);
const [data, setData] = useState<T[] | undefined>();
const [datum, setDatum] = useState<T | undefined>();
const openDialog: TFnVoid<T | T[]> = (value) => {
if (statesMode === "datumOrData") {
if (value) {
setData(Array.isArray(value) ? value : undefined);
setDatum(Array.isArray(value) ? undefined : value);
}
} else if (statesMode === "datumAndData") {
setData(Array.isArray(value) ? value : data);
setDatum(Array.isArray(value) ? datum : value);
}
setOpen(true);
};
@@ -47,6 +56,7 @@ export const useDialog = <T,>(initialState: boolean): DialogControl<T> => {
data,
datum,
setData,
setDatum,
openDialog,
closeDialog,
};
@@ -54,6 +64,6 @@ export const useDialog = <T,>(initialState: boolean): DialogControl<T> => {
export const getDisplayDialogs = <T,>({
open,
closeDialog,
datum,
closeDialog,
}: DialogControl<T>): DialogControlProps<T> => ({ open, datum, closeDialog });