Merge pull request #726 from Hexastack/725-refactor-attachmentthumbnail-attachmentuploader-dialogs
Some checks are pending
Build and Push Docker API Image / build-and-push (push) Waiting to run
Build and Push Docker Base Image / build-and-push (push) Waiting to run
Build and Push Docker UI Image / build-and-push (push) Waiting to run

refactor(frontend): update Attachment and AttachmentThumbnail dialogs
This commit is contained in:
Med Marrouchi 2025-02-10 14:13:18 +01:00 committed by GitHub
commit 455c59453a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 104 additions and 91 deletions

View File

@ -1,69 +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 { Button, Dialog, DialogActions, DialogContent } from "@mui/material";
import { GridEventListener } from "@mui/x-data-grid";
import { FC, useEffect, useState } from "react";
import { DialogTitle } from "@/app-components/dialogs/DialogTitle";
import { MediaLibrary } from "@/components/media-library";
import { DialogControlProps } from "@/hooks/useDialog";
import { useTranslate } from "@/hooks/useTranslate";
import { IAttachment } from "@/types/attachment.types";
export type AttachmentDialogProps = DialogControlProps<
never,
IAttachment | null
> & { accept: string };
export const AttachmentDialog: FC<AttachmentDialogProps> = ({
open,
closeDialog,
callback,
accept,
...rest
}) => {
const { t } = useTranslate();
const [selected, setSelected] = useState<IAttachment | null>(null);
const handleSelection: GridEventListener<"rowClick"> = (data) => {
setSelected(data.row);
};
useEffect(() => {
if (!open) {
setSelected(null);
}
}, [open]);
return (
<Dialog open={open} onClose={closeDialog} {...rest} fullWidth maxWidth="lg">
<DialogTitle onClose={closeDialog}>
{t("title.media_library")}
</DialogTitle>
<DialogContent>
<MediaLibrary
showTitle={false}
onSelect={handleSelection}
accept={accept}
/>
</DialogContent>
<DialogActions>
<Button
variant="contained"
disabled={!selected}
onClick={() => {
callback && callback(selected);
closeDialog();
}}
>
{t("button.select")}
</Button>
</DialogActions>
</Dialog>
);
};

View File

@ -0,0 +1,53 @@
/*
* 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 { GridEventListener } from "@mui/x-data-grid";
import { FC, Fragment, useState } from "react";
import { MediaLibrary } from "@/components/media-library";
import { IAttachment } from "@/types/attachment.types";
import { ComponentFormProps } from "@/types/common/dialogs.types";
export type AttachmentFormData = {
row?: undefined;
accept?: string;
onChange?: (data?: IAttachment | null) => void;
};
export const AttachmentForm: FC<ComponentFormProps<AttachmentFormData>> = ({
data,
Wrapper = Fragment,
WrapperProps,
...rest
}) => {
const [selected, setSelected] = useState<IAttachment | null>(null);
const handleSelection: GridEventListener<"rowClick"> = (data) => {
setSelected(data.row);
};
return (
<Wrapper
open={!!WrapperProps?.open}
onSubmit={() => {
data?.onChange?.(selected);
rest.onSuccess?.();
}}
{...WrapperProps}
confirmButtonProps={{
...WrapperProps?.confirmButtonProps,
disabled: !selected,
}}
>
<MediaLibrary
showTitle={false}
onSelect={handleSelection}
accept={data?.accept}
/>
</Wrapper>
);
};

View File

@ -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 { AttachmentForm, AttachmentFormData } from "./AttachmentForm";
export const AttachmentFormDialog = <
T extends AttachmentFormData = AttachmentFormData,
>(
props: ComponentFormDialogProps<T>,
) => (
<GenericFormDialog<T>
Form={AttachmentForm}
rowKey="row"
addText="title.media_library"
confirmButtonProps={{ value: "button.select" }}
{...props}
/>
);

View File

@ -23,7 +23,7 @@ import { FC } from "react";
import { useDelete } from "@/hooks/crud/useDelete";
import { useGet } from "@/hooks/crud/useGet";
import { useDialog } from "@/hooks/useDialog";
import { useDialogs } from "@/hooks/useDialogs";
import useFormattedFileSize from "@/hooks/useFormattedFileSize";
import { useHasPermission } from "@/hooks/useHasPermission";
import { useToast } from "@/hooks/useToast";
@ -32,7 +32,7 @@ import { EntityType } from "@/services/types";
import { IAttachment } from "@/types/attachment.types";
import { PermissionAction } from "@/types/permission.types";
import { DeleteDialog } from "../dialogs";
import { ConfirmDialogBody } from "../dialogs";
const AttachmentPreview = ({
attachment,
@ -85,8 +85,8 @@ const AttachmentThumbnail: FC<AttachmentThumbnailProps> = ({
});
const { toast } = useToast();
const { t } = useTranslate();
const deleteDialogCtl = useDialog<string>(false);
const { mutateAsync: deleteAttachment } = useDelete(EntityType.ATTACHMENT, {
const dialogs = useDialogs();
const { mutate: deleteAttachment } = useDelete(EntityType.ATTACHMENT, {
onError: () => {
toast.error(t("message.internal_server_error"));
},
@ -121,12 +121,6 @@ const AttachmentThumbnail: FC<AttachmentThumbnailProps> = ({
hasPermission(EntityType.ATTACHMENT, PermissionAction.DELETE) &&
onChange ? (
<>
<DeleteDialog
{...deleteDialogCtl}
callback={() => {
deleteAttachment(attachment.id);
}}
/>
<CardActions sx={{ justifyContent: "center", flex: "1 1 50%" }}>
<Button
color="primary"
@ -145,8 +139,14 @@ const AttachmentThumbnail: FC<AttachmentThumbnailProps> = ({
color="secondary"
variant="contained"
startIcon={<DeleteOutlineOutlinedIcon />}
onClick={(e) => {
deleteDialogCtl.openDialog();
onClick={async (e) => {
const isConfirmed = await dialogs.confirm(
ConfirmDialogBody,
);
if (isConfirmed) {
deleteAttachment(attachment.id);
}
e.preventDefault();
e.stopPropagation();
}}

View File

@ -6,20 +6,19 @@
* 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 CloudUploadIcon from "@mui/icons-material/CloudUpload";
import FolderCopyIcon from "@mui/icons-material/FolderCopy";
import { Box, Button, Divider, Grid, styled, Typography } from "@mui/material";
import { ChangeEvent, DragEvent, FC, useState } from "react";
import { useUpload } from "@/hooks/crud/useUpload";
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
import { useDialogs } from "@/hooks/useDialogs";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { EntityType } from "@/services/types";
import { AttachmentResourceRef, IAttachment } from "@/types/attachment.types";
import { AttachmentDialog } from "./AttachmentDialog";
import { AttachmentFormDialog } from "./AttachmentFormDialog";
import AttachmentThumbnail from "./AttachmentThumbnail";
const FileUploadLabel = styled("label")(
@ -82,6 +81,7 @@ const AttachmentUploader: FC<FileUploadProps> = ({
undefined,
);
const { t } = useTranslate();
const dialogs = useDialogs();
const [isDragOver, setIsDragOver] = useState<boolean>(false);
const { toast } = useToast();
const { mutateAsync: uploadAttachment } = useUpload(EntityType.ATTACHMENT, {
@ -95,7 +95,6 @@ const AttachmentUploader: FC<FileUploadProps> = ({
onUploadComplete && onUploadComplete();
},
});
const libraryDialogCtl = useDialog<never>(false);
const stopDefaults = (e: DragEvent) => {
e.stopPropagation();
e.preventDefault();
@ -139,11 +138,6 @@ const AttachmentUploader: FC<FileUploadProps> = ({
return (
<Grid>
<AttachmentDialog
{...getDisplayDialogs(libraryDialogCtl)}
callback={onChange}
accept={accept}
/>
<Grid container>
<Grid item xs={enableMediaLibrary ? 5 : 12}>
<HiddenInput
@ -208,7 +202,16 @@ const AttachmentUploader: FC<FileUploadProps> = ({
startIcon={<FolderCopyIcon />}
variant="contained"
color="primary"
onClick={() => libraryDialogCtl.openDialog()}
onClick={() =>
dialogs.open(
AttachmentFormDialog,
{
accept,
onChange,
},
{ maxWidth: "xl" },
)
}
>
{t("button.media_library")}
</Button>