Skip to content

Commit

Permalink
feat(ui-tablemode): replace Handsontable and remove context menu
Browse files Browse the repository at this point in the history
  • Loading branch information
skamril committed Jan 10, 2025
1 parent f9edb32 commit 559dd53
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
*/

import { useEffect, useState } from "react";
import { MenuItem } from "@mui/material";
import { Button } from "@mui/material";
import { useOutletContext } from "react-router";
import { useUpdateEffect } from "react-use";
import { useTranslation } from "react-i18next";
import EditIcon from "@mui/icons-material/Edit";
import DeleteIcon from "@mui/icons-material/Delete";
import { v4 as uuidv4 } from "uuid";
import PropertiesView from "../../../../common/PropertiesView";
Expand Down Expand Up @@ -84,7 +85,25 @@ function TableModeList() {
// Event Handlers
////////////////////////////////////////////////////////////////

const handleDeleteTemplate = () => {
const handleEditClick = () => {
if (selectedTemplate) {
setDialog({
type: "edit",
templateId: selectedTemplate.id,
});
}
};

const handleDeleteClick = () => {
if (selectedTemplate) {
setDialog({
type: "delete",
templateId: selectedTemplate.id,
});
}
};

const handleDelete = () => {
setTemplates((templates) =>
templates.filter((tp) => tp.id !== dialog?.templateId),
);
Expand All @@ -106,34 +125,6 @@ function TableModeList() {
currentElement={selectedTemplate?.id}
currentElementKeyToTest="id"
setSelectedItem={({ id }) => setSelectedTemplateId(id)}
contextMenuContent={({ element, close }) => (
<>
<MenuItem
onClick={(event) => {
event.stopPropagation();
setDialog({
type: "edit",
templateId: element.id,
});
close();
}}
>
Edit
</MenuItem>
<MenuItem
onClick={(event) => {
event.stopPropagation();
setDialog({
type: "delete",
templateId: element.id,
});
close();
}}
>
Delete
</MenuItem>
</>
)}
/>
}
onAdd={() => setDialog({ type: "add", templateId: "" })}
Expand All @@ -148,6 +139,27 @@ function TableModeList() {
studyId={study.id}
type={selectedTemplate.type}
columns={selectedTemplate.columns}
extraActions={
<>
<Button
variant="outlined"
size="small"
startIcon={<EditIcon />}
onClick={handleEditClick}
>
{t("global.edit")}
</Button>
<Button
variant="outlined"
size="small"
color="error"
startIcon={<DeleteIcon />}
onClick={handleDeleteClick}
>
{t("global.delete")}
</Button>
</>
}
/>
)}
</ViewWrapper>
Expand All @@ -173,7 +185,7 @@ function TableModeList() {
<ConfirmationDialog
titleIcon={DeleteIcon}
alert="warning"
onConfirm={handleDeleteTemplate}
onConfirm={handleDelete}
onCancel={closeDialog}
open
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,11 @@ import {
ListItemButton,
ListItemIcon,
ListItemText,
Menu,
PopoverPosition,
SxProps,
Theme,
Tooltip,
} from "@mui/material";
import ArrowRightOutlinedIcon from "@mui/icons-material/ArrowRightOutlined";
import { useState } from "react";
import { IdType } from "../../../../../common/types";
import { mergeSxProp } from "../../../../../utils/muiUtils";

Expand All @@ -33,10 +30,6 @@ interface Props<T> {
currentElement?: string;
currentElementKeyToTest?: keyof T;
setSelectedItem: (item: T, index: number) => void;
contextMenuContent?: (props: {
element: T;
close: VoidFunction;
}) => React.ReactElement;
sx?: SxProps<Theme>;
}

Expand All @@ -45,43 +38,8 @@ function ListElement<T extends { id?: IdType; name: string; label?: string }>({
currentElement,
currentElementKeyToTest,
setSelectedItem,
contextMenuContent: ContextMenuContent,
sx,
}: Props<T>) {
const [contextMenuPosition, setContextMenuPosition] =
useState<PopoverPosition | null>(null);
const [elementForContext, setElementForContext] = useState<T>();

////////////////////////////////////////////////////////////////
// Event Handlers
////////////////////////////////////////////////////////////////

const handleContextMenu = (element: T) => (event: React.MouseEvent) => {
event.preventDefault();

if (!ContextMenuContent) {
return;
}

setElementForContext(element);

setContextMenuPosition(
contextMenuPosition === null
? {
left: event.clientX + 2,
top: event.clientY - 6,
}
: // Repeated context menu when it is already open closes it with Chrome 84 on Ubuntu
// Other native context menus might behave different.
// With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus.
null,
);
};

////////////////////////////////////////////////////////////////
// JSX
////////////////////////////////////////////////////////////////

return (
<Box
width="100%"
Expand All @@ -102,7 +60,6 @@ function ListElement<T extends { id?: IdType; name: string; label?: string }>({
justifyContent: "space-between",
py: 0,
}}
onContextMenu={handleContextMenu(element)}
>
<Tooltip title={element.name} placement="right">
<ListItemText
Expand All @@ -127,21 +84,6 @@ function ListElement<T extends { id?: IdType; name: string; label?: string }>({
>
<ArrowRightOutlinedIcon color="primary" />
</ListItemIcon>
{ContextMenuContent && elementForContext && (
<Menu
open={contextMenuPosition !== null}
onClose={() => setContextMenuPosition(null)}
anchorReference="anchorPosition"
anchorPosition={
contextMenuPosition !== null ? contextMenuPosition : undefined
}
>
<ContextMenuContent
element={elementForContext}
close={() => setContextMenuPosition(null)}
/>
</Menu>
)}
</ListItemButton>
))}
</Box>
Expand Down
1 change: 0 additions & 1 deletion webapp/src/components/common/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
} from "react-hook-form";

export interface SubmitHandlerPlus<
// TODO Make parameter required
TFieldValues extends FieldValues = FieldValues,
> {
values: TFieldValues;
Expand Down
71 changes: 46 additions & 25 deletions webapp/src/components/common/TableMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,47 +25,64 @@ import {
TableModeType,
} from "../../services/api/studies/tableMode/types";
import { SubmitHandlerPlus } from "./Form/types";
import TableForm from "./TableForm";
import UsePromiseCond from "./utils/UsePromiseCond";
import GridOffIcon from "@mui/icons-material/GridOff";
import EmptyView from "./page/EmptyView";
import { useTranslation } from "react-i18next";
import DataGridForm, { type DataGridFormProps } from "./DataGridForm";
import { startCase } from "lodash";
import type { GridColumn } from "@glideapps/glide-data-grid";

export interface TableModeProps<T extends TableModeType = TableModeType> {
studyId: StudyMetadata["id"];
type: T;
columns: TableModeColumnsForType<T>;
extraActions?: React.ReactNode;
}

function TableMode<T extends TableModeType>(props: TableModeProps<T>) {
const { studyId, type, columns } = props;
const [filteredColumns, setFilteredColumns] = useState(columns);
function TableMode<T extends TableModeType>({
studyId,
type,
columns,
extraActions,
}: TableModeProps<T>) {
const { t } = useTranslation();
const [gridColumns, setGridColumns] = useState<
DataGridFormProps<TableData>["columns"]
>([]);
const columnsDep = columns.join(",");

const res = usePromise(
() => getTableMode({ studyId, tableType: type, columns }),
[studyId, type, columns.join(",")],
[studyId, type, columnsDep],
);

// Filter columns based on the data received, because the API may return
// fewer columns than requested depending on the study version
useEffect(
() => {
const dataKeys = Object.keys(res.data || {});
useEffect(() => {
const rowNames = Object.keys(res.data || {});

if (dataKeys.length === 0) {
setFilteredColumns([]);
return;
}
if (rowNames.length === 0) {
setGridColumns([]);
return;
}

const data = res.data!;
const dataRowKeys = Object.keys(data[dataKeys[0]]);
const data = res.data!;
const columnNames = Object.keys(data[rowNames[0]]);

setFilteredColumns(columns.filter((col) => dataRowKeys.includes(col)));
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[res.data, columns.join(",")],
);
setGridColumns(
columns
.filter((col) => columnNames.includes(col))
.map((col) => {
const title = startCase(col);
return {
title,
id: col,
width: title.length * 10,
} satisfies GridColumn;
}),
);
}, [res.data, columnsDep]);

////////////////////////////////////////////////////////////////
// Event Handlers
Expand All @@ -83,15 +100,19 @@ function TableMode<T extends TableModeType>(props: TableModeProps<T>) {
<UsePromiseCond
response={res}
ifFulfilled={(data) =>
filteredColumns.length > 0 ? (
<TableForm
defaultValues={data}
gridColumns.length > 0 ? (
<DataGridForm
defaultData={data}
columns={gridColumns}
onSubmit={handleSubmit}
tableProps={{ columns: filteredColumns }}
autoSubmit={false}
extraActions={extraActions}
/>
) : (
<EmptyView icon={GridOffIcon} title={t("study.results.noData")} />
<EmptyView
icon={GridOffIcon}
title={t("study.results.noData")}
extraActions={extraActions}
/>
)
}
/>
Expand Down

0 comments on commit 559dd53

Please sign in to comment.