fix(frontend): align data grid toggle action

This commit is contained in:
Mohamed Marrouchi 2024-09-29 21:48:40 +01:00
parent b735d5ebd1
commit 357da9baf2
6 changed files with 124 additions and 82 deletions

View File

@ -42,7 +42,6 @@ export class UserStub extends BaseSchema {
@Prop({ @Prop({
type: String, type: String,
unique: true,
required: true, required: true,
}) })
password: string; password: string;

View File

@ -6,11 +6,12 @@
* 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 { styled } from "@mui/material"; import { useTheme } from "@mui/material";
import { import {
DataGridProps, DataGridProps,
gridClasses, gridClasses,
GridColDef, GridColDef,
GridValidRowModel,
DataGrid as MuiDataGrid, DataGrid as MuiDataGrid,
} from "@mui/x-data-grid"; } from "@mui/x-data-grid";
@ -18,22 +19,34 @@ import { renderHeader } from "./columns/renderHeader";
import { styledPaginationSlots } from "./DataGridStyledPagination"; import { styledPaginationSlots } from "./DataGridStyledPagination";
import { NoDataOverlay } from "./NoDataOverlay"; import { NoDataOverlay } from "./NoDataOverlay";
const StyledDataGrid = styled(MuiDataGrid)(({ theme }) => ({ export const StyledDataGrid = <T extends GridValidRowModel = any>(
"& .MuiDataGrid-overlayWrapper": { props: DataGridProps<T>,
height: "fit-content", ) => {
}, const theme = useTheme();
const { sx, ...otherProps } = props;
[`& .${gridClasses.row}`]: { return (
"&:hover": { <MuiDataGrid
backgroundColor: theme.palette.background.default, {...otherProps}
"@media (hover: none)": { sx={{
backgroundColor: theme.palette.background.default, "& .MuiDataGrid-overlayWrapper": {
}, height: "fit-content",
}, },
}, [`& .${gridClasses.row}`]: {
})); "&:hover": {
backgroundColor: theme.palette.background.default,
"@media (hover: none)": {
backgroundColor: theme.palette.background.default,
},
},
},
...sx,
}}
/>
);
};
export const DataGrid = ({ export const DataGrid = <T extends GridValidRowModel = any>({
columns, columns,
rows = [], rows = [],
autoHeight = true, autoHeight = true,
@ -46,8 +59,8 @@ export const DataGrid = ({
showColumnVerticalBorder = false, showColumnVerticalBorder = false,
sx = { border: "none" }, sx = { border: "none" },
...rest ...rest
}: DataGridProps) => { }: DataGridProps<T>) => {
const styledColumns: GridColDef[] = columns.map((col) => ({ const styledColumns: GridColDef<T>[] = columns.map((col) => ({
disableColumnMenu: true, disableColumnMenu: true,
renderHeader, renderHeader,
headerAlign: "left", headerAlign: "left",
@ -56,7 +69,7 @@ export const DataGrid = ({
})); }));
return ( return (
<StyledDataGrid <StyledDataGrid<T>
autoHeight={autoHeight} autoHeight={autoHeight}
disableRowSelectionOnClick={disableRowSelectionOnClick} disableRowSelectionOnClick={disableRowSelectionOnClick}
slots={slots} slots={slots}

View File

@ -9,7 +9,7 @@
import { faAlignLeft } from "@fortawesome/free-solid-svg-icons"; import { faAlignLeft } from "@fortawesome/free-solid-svg-icons";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { Button, Chip, Grid, Paper, Typography } from "@mui/material"; import { Button, Chip, Grid, Paper, Switch, Typography } from "@mui/material";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -24,13 +24,14 @@ import { renderHeader } from "@/app-components/tables/columns/renderHeader";
import { DataGrid } from "@/app-components/tables/DataGrid"; import { DataGrid } from "@/app-components/tables/DataGrid";
import { useDelete } from "@/hooks/crud/useDelete"; import { useDelete } from "@/hooks/crud/useDelete";
import { useFind } from "@/hooks/crud/useFind"; import { useFind } from "@/hooks/crud/useFind";
import { useGet } from "@/hooks/crud/useGet"; import { useGet, useGetFromCache } from "@/hooks/crud/useGet";
import { useUpdate } from "@/hooks/crud/useUpdate";
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog"; import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
import { useHasPermission } from "@/hooks/useHasPermission"; import { useHasPermission } from "@/hooks/useHasPermission";
import { useSearch } from "@/hooks/useSearch"; import { useSearch } from "@/hooks/useSearch";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { PageHeader } from "@/layout/content/PageHeader"; import { PageHeader } from "@/layout/content/PageHeader";
import { EntityType } from "@/services/types"; import { EntityType, Format } from "@/services/types";
import { IContentType } from "@/types/content-type.types"; import { IContentType } from "@/types/content-type.types";
import { IContent } from "@/types/content.types"; import { IContent } from "@/types/content.types";
import { PermissionAction } from "@/types/permission.types"; import { PermissionAction } from "@/types/permission.types";
@ -62,17 +63,26 @@ export const Contents = () => {
entity: EntityType.CONTENT_TYPE, entity: EntityType.CONTENT_TYPE,
}); });
const { dataGridProps } = useFind( const { dataGridProps } = useFind(
{ entity: EntityType.CONTENT }, { entity: EntityType.CONTENT, format: Format.FULL },
{ {
params: searchPayload, params: searchPayload,
}, },
); );
const { mutateAsync: updateContent } = useUpdate(EntityType.CONTENT, {
onError: (error) => {
toast.error(error.message || t("message.internal_server_error"));
},
onSuccess() {
toast.success(t("message.success_save"));
},
});
const { mutateAsync: deleteContent } = useDelete(EntityType.CONTENT, { const { mutateAsync: deleteContent } = useDelete(EntityType.CONTENT, {
onSuccess: () => { onSuccess: () => {
deleteDialogCtl.closeDialog(); deleteDialogCtl.closeDialog();
toast.success(t("message.item_delete_success")); toast.success(t("message.item_delete_success"));
}, },
}); });
const getEntityFromCache = useGetFromCache(EntityType.CONTENT_TYPE);
const actionColumns = useActionColumns<IContent>( const actionColumns = useActionColumns<IContent>(
EntityType.CONTENT, EntityType.CONTENT,
[ [
@ -108,7 +118,7 @@ export const Contents = () => {
<PageHeader <PageHeader
icon={faAlignLeft} icon={faAlignLeft}
chip={<Chip label={data?.name} variant="title" />} chip={<Chip label={data?.name} size="medium" variant="title" />}
title={t("title.content")} title={t("title.content")}
> >
<Grid justifyContent="flex-end" gap={1} container alignItems="center"> <Grid justifyContent="flex-end" gap={1} container alignItems="center">
@ -143,7 +153,7 @@ export const Contents = () => {
<Grid padding={2} container> <Grid padding={2} container>
<Grid item width="100%"> <Grid item width="100%">
<DataGrid <DataGrid<IContent>
{...dataGridProps} {...dataGridProps}
disableColumnFilter disableColumnFilter
showCellVerticalBorder={false} showCellVerticalBorder={false}
@ -155,25 +165,39 @@ export const Contents = () => {
field: "entity", field: "entity",
headerName: t("label.entity"), headerName: t("label.entity"),
flex: 1, flex: 1,
valueGetter: (row) => row["name"], valueGetter: (row: IContent) => {
const contentType = getEntityFromCache(row.id);
return contentType?.name;
},
}, },
{ {
maxWidth: 120,
field: "status", field: "status",
headerName: t("label.status"), headerName: t("label.status"),
resizable: false, disableColumnMenu: true,
renderHeader,
headerAlign: "left",
renderCell: (params) => ( renderCell: (params) => (
<Grid container> <Switch
<Grid item xs={12}> checked={params.value}
<Chip color="primary"
label={t( inputProps={{ "aria-label": "primary checkbox" }}
params.row.status disabled={
? "label.enabled" !hasPermission(
: "label.disabled", EntityType.CONTENT,
)} PermissionAction.UPDATE,
variant={params.row.status ? "enabled" : "disabled"} )
/> }
</Grid> onChange={() => {
</Grid> updateContent({
id: params.row.id,
params: {
status: !params.row.status,
},
});
}}
/>
), ),
}, },
{ {

View File

@ -107,6 +107,9 @@ export const ContextVars = () => {
checked={params.value} checked={params.value}
color="primary" color="primary"
inputProps={{ "aria-label": "primary checkbox" }} inputProps={{ "aria-label": "primary checkbox" }}
disabled={
!hasPermission(EntityType.CONTEXT_VAR, PermissionAction.UPDATE)
}
onChange={() => { onChange={() => {
updateContextVar({ updateContextVar({
id: params.row.id, id: params.row.id,

View File

@ -8,7 +8,7 @@
import { Flag } from "@mui/icons-material"; import { Flag } from "@mui/icons-material";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import { Button, Grid, Paper } from "@mui/material"; import { Button, Grid, Paper, Switch } from "@mui/material";
import { GridColDef } from "@mui/x-data-grid"; import { GridColDef } from "@mui/x-data-grid";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useQueryClient } from "react-query"; import { useQueryClient } from "react-query";
@ -92,13 +92,6 @@ export const Languages = () => {
const actionColumns = useActionColumns<ILanguage>( const actionColumns = useActionColumns<ILanguage>(
EntityType.LANGUAGE, EntityType.LANGUAGE,
[ [
{
label: ActionColumnLabel.Toggle,
action: (row) => toggleDefault(row),
requires: [PermissionAction.UPDATE],
getState: (row) => row.isDefault,
helperText: t("button.mark_as_default"),
},
{ {
label: ActionColumnLabel.Edit, label: ActionColumnLabel.Edit,
action: (row) => editDialogCtl.openDialog(row), action: (row) => editDialogCtl.openDialog(row),
@ -131,15 +124,6 @@ export const Languages = () => {
renderHeader, renderHeader,
headerAlign: "left", headerAlign: "left",
}, },
{
flex: 1,
field: "isDefault",
headerName: t("label.is_default"),
disableColumnMenu: true,
renderHeader,
headerAlign: "left",
valueGetter: (value) => (value ? t("label.yes") : t("label.no")),
},
{ {
flex: 1, flex: 1,
field: "isRTL", field: "isRTL",
@ -149,6 +133,27 @@ export const Languages = () => {
headerAlign: "left", headerAlign: "left",
valueGetter: (value) => (value ? t("label.yes") : t("label.no")), valueGetter: (value) => (value ? t("label.yes") : t("label.no")),
}, },
{
maxWidth: 120,
field: "isDefault",
headerName: t("label.is_default"),
disableColumnMenu: true,
renderHeader,
headerAlign: "left",
renderCell: (params) => (
<Switch
checked={params.value}
color="primary"
inputProps={{ "aria-label": "primary checkbox" }}
disabled={
!hasPermission(EntityType.LANGUAGE, PermissionAction.UPDATE)
}
onChange={() => {
toggleDefault(params.row);
}}
/>
),
},
{ {
minWidth: 140, minWidth: 140,
field: "createdAt", field: "createdAt",

View File

@ -8,7 +8,7 @@
import { faUsers } from "@fortawesome/free-solid-svg-icons"; import { faUsers } from "@fortawesome/free-solid-svg-icons";
import PersonAddAlt1Icon from "@mui/icons-material/PersonAddAlt1"; import PersonAddAlt1Icon from "@mui/icons-material/PersonAddAlt1";
import { Button, Grid, Paper } from "@mui/material"; import { Button, Grid, Paper, Switch } from "@mui/material";
import { GridColDef } from "@mui/x-data-grid"; import { GridColDef } from "@mui/x-data-grid";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -23,6 +23,7 @@ import { buildRenderPicture } from "@/app-components/tables/columns/renderPictur
import { DataGrid } from "@/app-components/tables/DataGrid"; import { DataGrid } from "@/app-components/tables/DataGrid";
import { useFind } from "@/hooks/crud/useFind"; import { useFind } from "@/hooks/crud/useFind";
import { useUpdate } from "@/hooks/crud/useUpdate"; import { useUpdate } from "@/hooks/crud/useUpdate";
import { useAuth } from "@/hooks/useAuth";
import { useConfig } from "@/hooks/useConfig"; import { useConfig } from "@/hooks/useConfig";
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog"; import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
import { useHasPermission } from "@/hooks/useHasPermission"; import { useHasPermission } from "@/hooks/useHasPermission";
@ -42,6 +43,7 @@ export const Users = () => {
const { ssoEnabled } = useConfig(); const { ssoEnabled } = useConfig();
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const { user } = useAuth();
const { mutateAsync: updateUser } = useUpdate(EntityType.USER, { const { mutateAsync: updateUser } = useUpdate(EntityType.USER, {
onError: (error) => { onError: (error) => {
toast.error(error.message || t("message.internal_server_error")); toast.error(error.message || t("message.internal_server_error"));
@ -136,33 +138,29 @@ export const Users = () => {
maxWidth: 120, maxWidth: 120,
field: "state", field: "state",
headerName: t("label.status"), headerName: t("label.status"),
resizable: false,
disableColumnMenu: true, disableColumnMenu: true,
renderCell: (params) => (
<Grid justifyContent="center" alignItems="center">
<Button
variant="contained"
color={params.row.state ? "success" : "error"}
sx={{
paddingX: 2,
paddingY: 1,
}}
onClick={() => {
updateUser({
id: params.row.id,
params: {
state: !params.row.state,
},
});
}}
disabled={ssoEnabled}
>
{t(params.row.state ? "label.enabled" : "label.disabled")}
</Button>
</Grid>
),
headerAlign: "center",
renderHeader, renderHeader,
headerAlign: "left",
renderCell: (params) => (
<Switch
checked={params.value}
color="primary"
inputProps={{ "aria-label": "primary checkbox" }}
disabled={
params.row.id === user?.id ||
ssoEnabled ||
!hasPermission(EntityType.USER, PermissionAction.UPDATE)
}
onChange={() => {
updateUser({
id: params.row.id,
params: {
state: !params.row.state,
},
});
}}
/>
),
}, },
{ {
minWidth: 140, minWidth: 140,