refactor(frontend): update translation dialogs

This commit is contained in:
yassinedorbozgithub 2025-02-05 10:53:19 +01:00
parent 2afb813cee
commit e2a50e9dcf
5 changed files with 167 additions and 168 deletions

View File

@ -1,107 +0,0 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* 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.
* 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 {
Dialog,
DialogActions,
DialogContent,
FormLabel,
Typography,
} from "@mui/material";
import { FC, useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import DialogButtons from "@/app-components/buttons/DialogButtons";
import { DialogTitle } from "@/app-components/dialogs/DialogTitle";
import { ContentContainer } from "@/app-components/dialogs/layouts/ContentContainer";
import { ContentItem } from "@/app-components/dialogs/layouts/ContentItem";
import { useFind } from "@/hooks/crud/useFind";
import { useUpdate } from "@/hooks/crud/useUpdate";
import { DialogControlProps } from "@/hooks/useDialog";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { EntityType } from "@/services/types";
import {
ITranslation,
ITranslationAttributes,
ITranslations,
} from "@/types/translation.types";
import TranslationInput from "./TranslationInput";
export type EditTranslationDialogProps = DialogControlProps<ITranslation>;
export const EditTranslationDialog: FC<EditTranslationDialogProps> = ({
open,
data,
closeDialog,
...rest
}) => {
const { data: languages } = useFind(
{ entity: EntityType.LANGUAGE },
{
hasCount: false,
},
);
const { t } = useTranslate();
const { toast } = useToast();
const { mutateAsync: updateTranslation } = useUpdate(EntityType.TRANSLATION, {
onError: () => {
toast.error(t("message.internal_server_error"));
},
onSuccess() {
closeDialog();
toast.success(t("message.success_save"));
},
});
const { reset, control, handleSubmit } = useForm<ITranslationAttributes>({
defaultValues: data,
});
const onSubmitForm = async (params: ITranslationAttributes) => {
if (data?.id) updateTranslation({ id: data.id, params });
};
useEffect(() => {
if (open) reset(data);
}, [open, reset, data]);
return (
<Dialog open={open} fullWidth onClose={closeDialog} {...rest}>
<form onSubmit={handleSubmit(onSubmitForm)}>
<DialogTitle onClose={closeDialog}>
{t("title.update_translation")}
</DialogTitle>
<DialogContent>
<ContentItem>
<FormLabel>{t("label.original_text")}</FormLabel>
<Typography component="p">{data?.str}</Typography>
</ContentItem>
<ContentContainer>
{languages
.filter(({ isDefault }) => !isDefault)
.map((language) => (
<ContentItem key={language.code}>
<Controller
name={`translations.${
language.code as keyof ITranslations
}`}
control={control}
render={({ field }) => (
<TranslationInput field={field} language={language} />
)}
/>
</ContentItem>
))}
</ContentContainer>
</DialogContent>
<DialogActions>
<DialogButtons closeDialog={closeDialog} />
</DialogActions>
</form>
</Dialog>
);
};

View File

@ -0,0 +1,115 @@
/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* 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.
* 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 { FormLabel, Grid, Typography } from "@mui/material";
import { FC, Fragment } from "react";
import { Controller, ControllerRenderProps, useForm } from "react-hook-form";
import { ContentContainer, ContentItem } from "@/app-components/dialogs";
import { Input } from "@/app-components/inputs/Input";
import { useFind } from "@/hooks/crud/useFind";
import { useUpdate } from "@/hooks/crud/useUpdate";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { EntityType } from "@/services/types";
import { ComponentFormProps } from "@/types/common/dialogs.types";
import { ILanguage } from "@/types/language.types";
import {
ITranslation,
ITranslationAttributes,
ITranslations,
} from "@/types/translation.types";
interface TranslationInputProps {
field: ControllerRenderProps<ITranslationAttributes>;
language: ILanguage;
}
const TranslationInput: React.FC<TranslationInputProps> = ({
field,
language: { isRTL, title },
}) => (
<Input
dir={isRTL ? "rtl" : "ltr"}
label={
<Grid container dir="ltr">
<Grid>{title}</Grid>
</Grid>
}
minRows={3}
inputRef={field.ref}
multiline={true}
{...field}
/>
);
export const TranslationForm: FC<ComponentFormProps<ITranslation>> = ({
data,
Wrapper = Fragment,
WrapperProps,
...rest
}) => {
const { t } = useTranslate();
const { toast } = useToast();
const { data: languages } = useFind(
{ entity: EntityType.LANGUAGE },
{
hasCount: false,
},
);
const { mutate: updateTranslation } = useUpdate(EntityType.TRANSLATION, {
onError: (error: Error) => {
rest.onError?.();
toast.error(error || t("message.internal_server_error"));
},
onSuccess() {
rest.onSuccess?.();
toast.success(t("message.success_save"));
},
});
const { control, handleSubmit } = useForm<ITranslationAttributes>({
defaultValues: {
translations: data?.translations,
},
});
const onSubmitForm = (params: ITranslationAttributes) => {
if (data?.id) updateTranslation({ id: data.id, params });
};
return (
<Wrapper
open={!!WrapperProps?.open}
onSubmit={handleSubmit(onSubmitForm)}
{...WrapperProps}
>
<form onSubmit={handleSubmit(onSubmitForm)}>
<ContentItem>
<FormLabel>{t("label.original_text")}</FormLabel>
<Typography component="p">{data?.str}</Typography>
</ContentItem>
<ContentContainer>
{languages
.filter(({ isDefault }) => !isDefault)
.map((language) => (
<ContentItem key={language.code}>
<Controller
name={`translations.${language.code as keyof ITranslations}`}
control={control}
render={({ field }) => (
<TranslationInput field={field} language={language} />
)}
/>
</ContentItem>
))}
</ContentContainer>
</form>
</Wrapper>
);
};
TranslationForm.displayName = TranslationForm.name;

View File

@ -0,0 +1,36 @@
/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* 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.
* 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 { FC } from "react";
import { FormDialog } from "@/app-components/dialogs";
import { useTranslate } from "@/hooks/useTranslate";
import { ComponentFormDialogProps } from "@/types/common/dialogs.types";
import { ITranslation } from "@/types/translation.types";
import { TranslationForm } from "./TranslationForm";
export const TranslationFormDialog: FC<
ComponentFormDialogProps<ITranslation>
> = ({ payload, ...rest }) => {
const { t } = useTranslate();
return (
<TranslationForm
data={payload}
onSuccess={() => {
rest.onClose(true);
}}
Wrapper={FormDialog}
WrapperProps={{
title: t("title.update_translation"),
...rest,
}}
/>
);
};

View File

@ -1,42 +0,0 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* 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.
* 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 { Grid } from "@mui/material";
import React from "react";
import { ControllerRenderProps } from "react-hook-form";
import { Input } from "@/app-components/inputs/Input";
import { ILanguage } from "@/types/language.types";
import { ITranslationAttributes } from "@/types/translation.types";
interface RenderTranslationInputProps {
language: ILanguage;
field: ControllerRenderProps<ITranslationAttributes, any>;
}
const TranslationInput: React.FC<RenderTranslationInputProps> = ({
language,
field,
}) => (
<Input
inputRef={field.ref}
dir={language.isRTL ? "rtl" : "ltr"}
label={
<Grid container dir="ltr">
<Grid>{language.title}</Grid>
</Grid>
}
multiline={true}
minRows={3}
{...field}
/>
);
TranslationInput.displayName = "TranslationInput";
export default TranslationInput;

View File

@ -1,17 +1,18 @@
/*
* 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:
* 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).
*/
import { faLanguage } from "@fortawesome/free-solid-svg-icons";
import AutorenewIcon from "@mui/icons-material/Autorenew";
import { Button, Chip, Grid, Paper, Stack } from "@mui/material";
import { GridColDef } from "@mui/x-data-grid";
import { DeleteDialog } from "@/app-components/dialogs";
import { ConfirmDialogBody } from "@/app-components/dialogs";
import { FilterTextfield } from "@/app-components/inputs/FilterTextfield";
import {
ActionColumnLabel,
@ -21,7 +22,7 @@ import { DataGrid } from "@/app-components/tables/DataGrid";
import { useDelete } from "@/hooks/crud/useDelete";
import { useFind } from "@/hooks/crud/useFind";
import { useRefreshTranslations } from "@/hooks/entities/translation-hooks";
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
import { useDialogs } from "@/hooks/useDialogs";
import { useSearch } from "@/hooks/useSearch";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
@ -32,19 +33,18 @@ import { PermissionAction } from "@/types/permission.types";
import { ITranslation } from "@/types/translation.types";
import { getDateTimeFormatter } from "@/utils/date";
import { EditTranslationDialog } from "./EditTranslationDialog";
import { TranslationFormDialog } from "./TranslationFormDialog";
export const Translations = () => {
const { t } = useTranslate();
const { toast } = useToast();
const dialogs = useDialogs();
const { data: languages } = useFind(
{ entity: EntityType.LANGUAGE },
{
hasCount: false,
},
);
const editDialogCtl = useDialog<ITranslation>(false);
const deleteDialogCtl = useDialog<string>(false);
const { onSearch, searchPayload } = useSearch<ITranslation>({
$iLike: ["str"],
});
@ -54,16 +54,15 @@ export const Translations = () => {
params: searchPayload,
},
);
const { mutateAsync: deleteTranslation } = useDelete(EntityType.TRANSLATION, {
const { mutate: deleteTranslation } = useDelete(EntityType.TRANSLATION, {
onError: (error) => {
toast.error(error);
},
onSuccess() {
deleteDialogCtl.closeDialog();
toast.success(t("message.item_delete_success"));
},
});
const { mutateAsync: checkRefreshTranslations, isLoading } =
const { mutate: checkRefreshTranslations, isLoading } =
useRefreshTranslations({
onError: () => {
toast.error(t("message.internal_server_error"));
@ -78,12 +77,18 @@ export const Translations = () => {
[
{
label: ActionColumnLabel.Edit,
action: (row) => editDialogCtl.openDialog(row),
action: (row) => dialogs.open(TranslationFormDialog, row),
requires: [PermissionAction.UPDATE],
},
{
label: ActionColumnLabel.Delete,
action: (row) => deleteDialogCtl.openDialog(row.id),
action: async ({ id }) => {
const isConfirmed = await dialogs.confirm(ConfirmDialogBody);
if (isConfirmed) {
deleteTranslation(id);
}
},
requires: [PermissionAction.DELETE],
},
],
@ -164,14 +169,6 @@ export const Translations = () => {
<Grid item xs={12}>
<Paper>
<Grid padding={2} container>
<EditTranslationDialog {...getDisplayDialogs(editDialogCtl)} />
<DeleteDialog
{...deleteDialogCtl}
callback={() => {
if (deleteDialogCtl?.data)
deleteTranslation(deleteDialogCtl.data);
}}
/>
<Grid item width="100%">
<DataGrid {...dataGridProps} columns={columns} />
</Grid>