diff --git a/frontend/src/app-components/buttons/FormButtons.tsx b/frontend/src/app-components/buttons/FormButtons.tsx
index 9b277ddb..a4c56429 100644
--- a/frontend/src/app-components/buttons/FormButtons.tsx
+++ b/frontend/src/app-components/buttons/FormButtons.tsx
@@ -11,6 +11,7 @@ import CloseIcon from "@mui/icons-material/Close";
import { Button, Grid } from "@mui/material";
import { useTranslate } from "@/hooks/useTranslate";
+import { TTranslationKeys } from "@/i18n/i18n.types";
import { FormButtonsProps } from "@/types/common/dialogs.types";
export const DialogFormButtons = ({
@@ -20,6 +21,10 @@ export const DialogFormButtons = ({
confirmButtonProps,
}: FormButtonsProps) => {
const { t } = useTranslate();
+ const cancelButtonTitle = (cancelButtonProps?.value ||
+ "button.cancel") as TTranslationKeys;
+ const confirmButtonTitle = (confirmButtonProps?.value ||
+ "button.submit") as TTranslationKeys;
return (
}
{...cancelButtonProps}
>
- {t("button.cancel")}
+ {t(cancelButtonTitle)}
}
{...confirmButtonProps}
>
- {t("button.submit")}
+ {t(confirmButtonTitle)}
);
diff --git a/frontend/src/app-components/dialogs/GenericFormDialog.tsx b/frontend/src/app-components/dialogs/GenericFormDialog.tsx
index 3f4772e0..f73e0e57 100644
--- a/frontend/src/app-components/dialogs/GenericFormDialog.tsx
+++ b/frontend/src/app-components/dialogs/GenericFormDialog.tsx
@@ -8,29 +8,30 @@
import React from "react";
-import { FormDialog } from "@/app-components/dialogs";
+import { FormDialog as Wrapper } from "@/app-components/dialogs";
import { useTranslate } from "@/hooks/useTranslate";
import { TTranslationKeys } from "@/i18n/i18n.types";
import { ComponentFormDialogProps } from "@/types/common/dialogs.types";
type GenericFormDialogProps = ComponentFormDialogProps & {
Form: React.ElementType;
+ rowKey?: keyof T;
addText?: TTranslationKeys;
editText?: TTranslationKeys;
};
export const GenericFormDialog = ({
Form,
- payload,
+ rowKey,
+ payload: data,
...rest
}: GenericFormDialogProps) => {
const { t } = useTranslate();
- const translationKey = payload ? rest.editText : rest.addText;
+ const hasRow = rowKey ? data?.[rowKey] : data;
+ const translationKey = hasRow ? rest.editText : rest.addText;
return (
+
);
};
diff --git a/frontend/src/components/contents/ContentImportFormDialog.tsx b/frontend/src/components/contents/ContentImportFormDialog.tsx
new file mode 100644
index 00000000..046896ea
--- /dev/null
+++ b/frontend/src/components/contents/ContentImportFormDialog.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 { GenericFormDialog } from "@/app-components/dialogs";
+import { ComponentFormDialogProps } from "@/types/common/dialogs.types";
+
+import { ContentImportForm, ContentImportFormData } from "./ContentImportForm";
+
+export const ContentImportFormDialog = <
+ T extends ContentImportFormData = ContentImportFormData,
+>(
+ props: ComponentFormDialogProps,
+) => (
+
+ Form={ContentImportForm}
+ rowKey="row"
+ addText="button.import"
+ confirmButtonProps={{ value: "button.import" }}
+ {...props}
+ />
+);
diff --git a/frontend/src/components/contents/index.tsx b/frontend/src/components/contents/index.tsx
index 646455d7..ee639e4b 100644
--- a/frontend/src/components/contents/index.tsx
+++ b/frontend/src/components/contents/index.tsx
@@ -14,7 +14,7 @@ import { Button, Chip, Grid, Paper, Switch, Typography } from "@mui/material";
import Link from "next/link";
import { useRouter } from "next/router";
-import { DeleteDialog } from "@/app-components/dialogs";
+import { ConfirmDialogBody } from "@/app-components/dialogs";
import { FilterTextfield } from "@/app-components/inputs/FilterTextfield";
import {
ActionColumnLabel,
@@ -26,54 +26,38 @@ import { useDelete } from "@/hooks/crud/useDelete";
import { useFind } from "@/hooks/crud/useFind";
import { useGet, useGetFromCache } from "@/hooks/crud/useGet";
import { useUpdate } from "@/hooks/crud/useUpdate";
-import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
+import { useDialogs } from "@/hooks/useDialogs";
import { useHasPermission } from "@/hooks/useHasPermission";
import { useSearch } from "@/hooks/useSearch";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { PageHeader } from "@/layout/content/PageHeader";
import { EntityType, Format } from "@/services/types";
-import { IContentType } from "@/types/content-type.types";
import { IContent } from "@/types/content.types";
import { PermissionAction } from "@/types/permission.types";
import { getDateTimeFormatter } from "@/utils/date";
-import { ContentDialog } from "./ContentDialog";
-import { ContentImportDialog } from "./ContentImportDialog";
+import { ContentFormDialog } from "./ContentFormDialog";
+import { ContentImportFormDialog } from "./ContentImportFormDialog";
export const Contents = () => {
const { t } = useTranslate();
const { toast } = useToast();
const { query } = useRouter();
- // Dialog Controls
- const addDialogCtl = useDialog<{
- content?: IContent;
- contentType?: IContentType;
- }>(false);
- const editDialogCtl = useDialog<{
- content?: IContent;
- contentType?: IContentType;
- }>(false);
- const deleteDialogCtl = useDialog(false);
+ const dialogs = useDialogs();
// data fetching
const { onSearch, searchPayload } = useSearch({
$eq: [{ entity: String(query.id) }],
$iLike: ["title"],
});
- const importDialogCtl = useDialog<{
- contentType?: IContentType;
- }>(false);
const hasPermission = useHasPermission();
- const { data: contentType } = useGet(String(query.id), {
- entity: EntityType.CONTENT_TYPE,
- });
const { dataGridProps, refetch } = useFind(
{ entity: EntityType.CONTENT, format: Format.FULL },
{
params: searchPayload,
},
);
- const { mutateAsync: updateContent } = useUpdate(EntityType.CONTENT, {
+ const { mutate: updateContent } = useUpdate(EntityType.CONTENT, {
onError: (error) => {
toast.error(error.message || t("message.internal_server_error"));
},
@@ -81,9 +65,8 @@ export const Contents = () => {
toast.success(t("message.success_save"));
},
});
- const { mutateAsync: deleteContent } = useDelete(EntityType.CONTENT, {
+ const { mutate: deleteContent } = useDelete(EntityType.CONTENT, {
onSuccess: () => {
- deleteDialogCtl.closeDialog();
toast.success(t("message.item_delete_success"));
},
});
@@ -93,22 +76,25 @@ export const Contents = () => {
[
{
label: ActionColumnLabel.Edit,
- action: (content) =>
- editDialogCtl.openDialog({
- contentType,
- content,
- }),
+ action: (row) =>
+ dialogs.open(ContentFormDialog, { content: row, contentType }),
requires: [PermissionAction.UPDATE],
},
{
label: ActionColumnLabel.Delete,
- action: (content) => deleteDialogCtl.openDialog(content.id),
+ action: async ({ id }) => {
+ const isConfirmed = await dialogs.confirm(ConfirmDialogBody);
+
+ if (isConfirmed) {
+ deleteContent(id);
+ }
+ },
requires: [PermissionAction.DELETE],
},
],
t("label.operations"),
);
- const { data } = useGet(String(query.id), {
+ const { data: contentType } = useGet(String(query.id), {
entity: EntityType.CONTENT_TYPE,
});
@@ -123,7 +109,9 @@ export const Contents = () => {
}
+ chip={
+
+ }
title={t("title.content")}
>
@@ -135,7 +123,9 @@ export const Contents = () => {
}
variant="contained"
- onClick={() => addDialogCtl.openDialog({ contentType })}
+ onClick={() =>
+ dialogs.open(ContentFormDialog, { contentType })
+ }
sx={{ float: "right" }}
>
{t("button.add")}
@@ -147,7 +137,15 @@ export const Contents = () => {
}
variant="contained"
- onClick={() => importDialogCtl.openDialog({ contentType })}
+ onClick={async () => {
+ if (contentType) {
+ await dialogs.open(ContentImportFormDialog, {
+ row: null,
+ contentType,
+ });
+ refetch();
+ }
+ }}
sx={{ float: "right" }}
>
{t("button.import")}
@@ -159,21 +157,6 @@ export const Contents = () => {
-
-
- {
- refetch();
- }}
- />
- {
- if (deleteDialogCtl?.data) deleteContent(deleteDialogCtl.data);
- }}
- />
-
diff --git a/frontend/src/layout/themes/theme.ts b/frontend/src/layout/themes/theme.ts
index aa52d8af..84e9e0ef 100644
--- a/frontend/src/layout/themes/theme.ts
+++ b/frontend/src/layout/themes/theme.ts
@@ -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:
* 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 { Color, SimplePaletteColorOptions } from "@mui/material";
import { grey, teal } from "@mui/material/colors";
import { createTheme } from "@mui/material/styles";
@@ -129,7 +130,7 @@ export const theme = createTheme({
MuiDialogActions: {
styleOverrides: {
root: {
- paddingRight: "15px",
+ padding: "0.5rem",
borderTop: borderLine,
backgroundColor: COLOR_PALETTE.lighterGray,
},
@@ -159,10 +160,9 @@ export const theme = createTheme({
},
MuiDialogContent: {
styleOverrides: {
- root: { marginTop: "20px" },
+ root: { paddingTop: "15px!important" },
},
},
-
MuiTextField: {
styleOverrides: {
root: {
diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx
index fcff228c..ca640433 100644
--- a/frontend/src/pages/_app.tsx
+++ b/frontend/src/pages/_app.tsx
@@ -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:
* 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 { CssBaseline } from "@mui/material";
import { StyledEngineProvider, ThemeProvider } from "@mui/material/styles";
import type { NextPage } from "next";
@@ -74,37 +75,35 @@ const App = ({ Component, pageProps }: TAppPropsWithLayout) => {
-
- (
-
- )}
- >
-
-
-
-
-
-
-
-
-
-
- {getLayout()}
-
-
-
-
-
-
-
-
-
-
-
-
+ (
+
+ )}
+ >
+
+
+
+
+
+
+
+
+
+
+ {getLayout()}
+
+
+
+
+
+
+
+
+
+
+