fix(frontend): apply feedback updates

This commit is contained in:
yassinedorbozgithub 2025-02-04 11:30:44 +01:00
parent 0b19f45b59
commit 658bfbc924
10 changed files with 80 additions and 70 deletions

View File

@ -43,7 +43,9 @@
"account_update_success": "Account has been updated successfully", "account_update_success": "Account has been updated successfully",
"account_disabled": "Your account has been either disabled or is pending confirmation.", "account_disabled": "Your account has been either disabled or is pending confirmation.",
"success_invitation_sent": "Invitation to join has been successfully sent.", "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", "item_delete_success": "Item has been deleted successfully",
"success_save": "Changes has been saved!", "success_save": "Changes has been saved!",
"no_result_found": "No result found", "no_result_found": "No result found",

View File

@ -43,7 +43,8 @@
"account_update_success": "Le compte a été mis à jour avec succès", "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.", "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.", "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?",
"item_delete_success": "L'élément a été supprimé avec succès", "item_delete_success": "L'élément a été supprimé avec succès",
"success_save": "Les modifications ont été enregistrées!", "success_save": "Les modifications ont été enregistrées!",
"no_result_found": "Aucun résultat trouvé", "no_result_found": "Aucun résultat trouvé",

View File

@ -6,7 +6,6 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
import CheckIcon from "@mui/icons-material/Check"; import CheckIcon from "@mui/icons-material/Check";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { Button, Grid } from "@mui/material"; import { Button, Grid } from "@mui/material";
@ -22,7 +21,7 @@ export const DialogFormButtons = <T,>({
return ( return (
<Grid <Grid
p="5px 15px" p="0.3rem 1rem"
width="100%" width="100%"
display="flex" display="flex"
justifyContent="space-between" justifyContent="space-between"

View File

@ -19,17 +19,14 @@ export const FormDialog = <T,>({
onSubmit, onSubmit,
...rest ...rest
}: FormDialogProps<T>) => { }: FormDialogProps<T>) => {
const handleClose = () => rest.onClose?.({}, "backdropClick");
return ( return (
<Dialog fullWidth {...rest}> <Dialog fullWidth {...rest}>
<DialogTitle onClose={() => rest.onClose?.({}, "backdropClick")}> <DialogTitle onClose={handleClose}>{title}</DialogTitle>
{title}
</DialogTitle>
<DialogContent>{children}</DialogContent> <DialogContent>{children}</DialogContent>
<DialogActions style={{ padding: "8px" }}> <DialogActions style={{ padding: "0.5rem" }}>
<DialogFormButtons <DialogFormButtons onCancel={handleClose} onSubmit={onSubmit} />
onCancel={() => rest.onClose?.({}, "backdropClick")}
onSubmit={onSubmit}
/>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );

View File

@ -6,9 +6,16 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
import { Button, Dialog, DialogActions, DialogContent } from "@mui/material"; import {
Button,
Dialog,
DialogActions,
DialogContent,
Grid,
} from "@mui/material";
import { FC, ReactNode } from "react"; import { FC, ReactNode } from "react";
import { useTranslate } from "@/hooks/useTranslate";
import { ConfirmOptions, DialogProps } from "@/types/common/dialogs.types"; import { ConfirmOptions, DialogProps } from "@/types/common/dialogs.types";
import { DialogTitle } from "../DialogTitle"; import { DialogTitle } from "../DialogTitle";
@ -23,6 +30,7 @@ export interface ConfirmDialogProps
extends DialogProps<ConfirmDialogPayload, boolean> {} extends DialogProps<ConfirmDialogPayload, boolean> {}
export const ConfirmDialog: FC<ConfirmDialogProps> = ({ payload, ...rest }) => { export const ConfirmDialog: FC<ConfirmDialogProps> = ({ payload, ...rest }) => {
const { t } = useTranslate();
const cancelButtonProps = useDialogLoadingButton(() => rest.onClose(false)); const cancelButtonProps = useDialogLoadingButton(() => rest.onClose(false));
const okButtonProps = useDialogLoadingButton(() => rest.onClose(true)); const okButtonProps = useDialogLoadingButton(() => rest.onClose(true));
@ -34,10 +42,11 @@ export const ConfirmDialog: FC<ConfirmDialogProps> = ({ payload, ...rest }) => {
onClose={() => rest.onClose(false)} onClose={() => rest.onClose(false)}
> >
<DialogTitle onClose={() => rest.onClose(false)}> <DialogTitle onClose={() => rest.onClose(false)}>
{payload.title} {payload.title || t("title.warning")}
</DialogTitle> </DialogTitle>
<DialogContent>{payload.msg}</DialogContent> <DialogContent>{payload.msg}</DialogContent>
<DialogActions> <DialogActions>
<Grid p="0.3rem 1rem" gap="0.5rem" display="flex">
<Button <Button
color={payload.severity || "error"} color={payload.severity || "error"}
variant="contained" variant="contained"
@ -45,11 +54,12 @@ export const ConfirmDialog: FC<ConfirmDialogProps> = ({ payload, ...rest }) => {
autoFocus autoFocus
{...okButtonProps} {...okButtonProps}
> >
{payload.okText} {payload.okText || t("label.yes")}
</Button> </Button>
<Button variant="outlined" disabled={!open} {...cancelButtonProps}> <Button variant="outlined" disabled={!open} {...cancelButtonProps}>
{payload.cancelText} {payload.cancelText || t("label.no")}
</Button> </Button>
</Grid>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );

View File

@ -11,16 +11,30 @@ import { Grid, Typography } from "@mui/material";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
export const ConfirmDialogBody = () => { export const ConfirmDialogBody = ({
mode = "click",
itemsNumber = 1,
}: {
mode?: "selection" | "click";
itemsNumber?: number;
}) => {
const { t } = useTranslate(); const { t } = useTranslate();
const DialogBodyText =
itemsNumber === 1
? t("message.item_delete_confirm", {
"0": mode === "click" ? "" : t("message.selected"),
})
: t("message.items_delete_confirm", {
"0": itemsNumber.toString(),
});
return ( return (
<Grid container gap={1}> <Grid container gap={1}>
<Grid item height="28px"> <Grid item height="1.75rem">
<ErrorIcon sx={{ fontSize: "28px" }} color="error" /> <ErrorIcon sx={{ fontSize: "1.75rem" }} color="error" />
</Grid> </Grid>
<Grid item alignSelf="center"> <Grid item alignSelf="center">
<Typography>{t("message.item_delete_confirm")}</Typography> <Typography>{DialogBodyText}</Typography>
</Grid> </Grid>
</Grid> </Grid>
); );

View File

@ -21,8 +21,8 @@ import { ComponentFormProps } from "@/types/common/dialogs.types";
export const CategoryForm: FC<ComponentFormProps<ICategory>> = ({ export const CategoryForm: FC<ComponentFormProps<ICategory>> = ({
data, data,
FormWrapper = React.Fragment, Wrapper = React.Fragment,
FormWrapperProps, WrapperProps,
...rest ...rest
}) => { }) => {
const { t } = useTranslate(); const { t } = useTranslate();
@ -84,10 +84,10 @@ export const CategoryForm: FC<ComponentFormProps<ICategory>> = ({
}, [data, reset]); }, [data, reset]);
return ( return (
<FormWrapper <Wrapper
{...FormWrapperProps} open={!!WrapperProps?.open}
onSubmit={submitAsync} onSubmit={submitAsync}
open={!!FormWrapperProps?.open} {...WrapperProps}
> >
<form onSubmit={submitAsync}> <form onSubmit={submitAsync}>
<ContentContainer> <ContentContainer>
@ -102,7 +102,7 @@ export const CategoryForm: FC<ComponentFormProps<ICategory>> = ({
</ContentItem> </ContentItem>
</ContentContainer> </ContentContainer>
</form> </form>
</FormWrapper> </Wrapper>
); );
}; };

View File

@ -27,8 +27,8 @@ export const CategoryFormDialog: FC<ComponentFormDialogProps<ICategory>> = ({
onSuccess={() => { onSuccess={() => {
rest.onClose(true); rest.onClose(true);
}} }}
FormWrapper={FormDialog} Wrapper={FormDialog}
FormWrapperProps={{ WrapperProps={{
title: payload ? t("title.edit_category") : t("title.new_category"), title: payload ? t("title.edit_category") : t("title.new_category"),
...rest, ...rest,
}} }}

View File

@ -1,11 +1,12 @@
/* /*
* Copyright © 2024 Hexastack. All rights reserved. * Copyright © 2025 Hexastack. All rights reserved.
* *
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
import FolderIcon from "@mui/icons-material/Folder"; import FolderIcon from "@mui/icons-material/Folder";
@ -24,7 +25,6 @@ import { DataGrid } from "@/app-components/tables/DataGrid";
import { useDelete } from "@/hooks/crud/useDelete"; import { useDelete } from "@/hooks/crud/useDelete";
import { useDeleteMany } from "@/hooks/crud/useDeleteMany"; import { useDeleteMany } from "@/hooks/crud/useDeleteMany";
import { useFind } from "@/hooks/crud/useFind"; import { useFind } from "@/hooks/crud/useFind";
import { useDialog } from "@/hooks/useDialog";
import { useDialogs } from "@/hooks/useDialogs"; import { useDialogs } from "@/hooks/useDialogs";
import { useHasPermission } from "@/hooks/useHasPermission"; import { useHasPermission } from "@/hooks/useHasPermission";
import { useSearch } from "@/hooks/useSearch"; import { useSearch } from "@/hooks/useSearch";
@ -42,7 +42,6 @@ import { CategoryFormDialog } from "./CategoryFormDialog";
export const Categories = () => { export const Categories = () => {
const { t } = useTranslate(); const { t } = useTranslate();
const { toast } = useToast(); const { toast } = useToast();
const deleteDialogCtl = useDialog<string>(false);
const hasPermission = useHasPermission(); const hasPermission = useHasPermission();
const { onSearch, searchPayload } = useSearch<ICategory>({ const { onSearch, searchPayload } = useSearch<ICategory>({
$iLike: ["label"], $iLike: ["label"],
@ -53,26 +52,20 @@ export const Categories = () => {
params: searchPayload, params: searchPayload,
}, },
); );
const { mutateAsync: deleteCategory } = useDelete(EntityType.CATEGORY, { const options = {
onError: (error) => { onError: (error: Error) => {
toast.error(error.message || t("message.internal_server_error")); toast.error(error.message || t("message.internal_server_error"));
}, },
onSuccess: () => { onSuccess: () => {
deleteDialogCtl.closeDialog();
setSelectedCategories([]); setSelectedCategories([]);
toast.success(t("message.item_delete_success")); toast.success(t("message.item_delete_success"));
}, },
}); };
const { mutateAsync: deleteCategories } = useDeleteMany(EntityType.CATEGORY, { const { mutate: deleteCategory } = useDelete(EntityType.CATEGORY, options);
onError: (error) => { const { mutate: deleteCategories } = useDeleteMany(
toast.error(error.message || t("message.internal_server_error")); EntityType.CATEGORY,
}, options,
onSuccess: () => { );
deleteDialogCtl.closeDialog();
setSelectedCategories([]);
toast.success(t("message.item_delete_success"));
},
});
const [selectedCategories, setSelectedCategories] = useState<string[]>([]); const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
const actionColumns = useActionColumns<ICategory>( const actionColumns = useActionColumns<ICategory>(
EntityType.CATEGORY, EntityType.CATEGORY,
@ -87,14 +80,10 @@ export const Categories = () => {
{ {
label: ActionColumnLabel.Delete, label: ActionColumnLabel.Delete,
action: async ({ id }) => { action: async ({ id }) => {
const isConfirmed = await dialogs.confirm(<ConfirmDialogBody />, { const isConfirmed = await dialogs.confirm(<ConfirmDialogBody />);
title: t("title.warning"),
okText: t("label.yes"),
cancelText: t("label.no"),
});
if (isConfirmed) { if (isConfirmed) {
await deleteCategory(id); deleteCategory(id);
} }
}, },
requires: [PermissionAction.DELETE], requires: [PermissionAction.DELETE],
@ -176,16 +165,14 @@ export const Categories = () => {
color="error" color="error"
onClick={async () => { onClick={async () => {
const isConfirmed = await dialogs.confirm( const isConfirmed = await dialogs.confirm(
<ConfirmDialogBody />, <ConfirmDialogBody
{ mode="selection"
title: t("title.warning"), itemsNumber={selectedCategories.length}
okText: t("label.yes"), />,
cancelText: t("label.no"),
},
); );
if (isConfirmed) { if (isConfirmed) {
await deleteCategories(selectedCategories); deleteCategories(selectedCategories);
} }
}} }}
> >

View File

@ -148,8 +148,8 @@ export type ComponentFormProps<T> = {
data: T | null; data: T | null;
onError?: () => void; onError?: () => void;
onSuccess?: () => void; onSuccess?: () => void;
FormWrapper?: React.FC<FormDialogProps<T>>; Wrapper?: React.FC<FormDialogProps<T>>;
FormWrapperProps?: Partial<FormDialogProps<T>>; WrapperProps?: Partial<FormDialogProps<T>>;
}; };
export interface FormButtonsProps<T> { export interface FormButtonsProps<T> {