diff --git a/webapp/src/components/App/Singlestudy/explore/TableModeList/index.tsx b/webapp/src/components/App/Singlestudy/explore/TableModeList/index.tsx index 1f381e17bc..c281d9c774 100644 --- a/webapp/src/components/App/Singlestudy/explore/TableModeList/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/TableModeList/index.tsx @@ -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"; @@ -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), ); @@ -106,34 +125,6 @@ function TableModeList() { currentElement={selectedTemplate?.id} currentElementKeyToTest="id" setSelectedItem={({ id }) => setSelectedTemplateId(id)} - contextMenuContent={({ element, close }) => ( - <> - { - event.stopPropagation(); - setDialog({ - type: "edit", - templateId: element.id, - }); - close(); - }} - > - Edit - - { - event.stopPropagation(); - setDialog({ - type: "delete", - templateId: element.id, - }); - close(); - }} - > - Delete - - - )} /> } onAdd={() => setDialog({ type: "add", templateId: "" })} @@ -148,6 +139,27 @@ function TableModeList() { studyId={study.id} type={selectedTemplate.type} columns={selectedTemplate.columns} + extraActions={ + <> + + + + } /> )} @@ -173,7 +185,7 @@ function TableModeList() { diff --git a/webapp/src/components/App/Singlestudy/explore/common/ListElement.tsx b/webapp/src/components/App/Singlestudy/explore/common/ListElement.tsx index 69b9254d61..5e799e1928 100644 --- a/webapp/src/components/App/Singlestudy/explore/common/ListElement.tsx +++ b/webapp/src/components/App/Singlestudy/explore/common/ListElement.tsx @@ -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"; @@ -33,10 +30,6 @@ interface Props { currentElement?: string; currentElementKeyToTest?: keyof T; setSelectedItem: (item: T, index: number) => void; - contextMenuContent?: (props: { - element: T; - close: VoidFunction; - }) => React.ReactElement; sx?: SxProps; } @@ -45,43 +38,8 @@ function ListElement({ currentElement, currentElementKeyToTest, setSelectedItem, - contextMenuContent: ContextMenuContent, sx, }: Props) { - const [contextMenuPosition, setContextMenuPosition] = - useState(null); - const [elementForContext, setElementForContext] = useState(); - - //////////////////////////////////////////////////////////////// - // 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 ( ({ justifyContent: "space-between", py: 0, }} - onContextMenu={handleContextMenu(element)} > ({ > - {ContextMenuContent && elementForContext && ( - setContextMenuPosition(null)} - anchorReference="anchorPosition" - anchorPosition={ - contextMenuPosition !== null ? contextMenuPosition : undefined - } - > - setContextMenuPosition(null)} - /> - - )} ))} diff --git a/webapp/src/components/common/Form/types.ts b/webapp/src/components/common/Form/types.ts index d7f27f94fb..a09272e8f1 100644 --- a/webapp/src/components/common/Form/types.ts +++ b/webapp/src/components/common/Form/types.ts @@ -26,7 +26,6 @@ import { } from "react-hook-form"; export interface SubmitHandlerPlus< - // TODO Make parameter required TFieldValues extends FieldValues = FieldValues, > { values: TFieldValues; diff --git a/webapp/src/components/common/TableMode.tsx b/webapp/src/components/common/TableMode.tsx index 1809a93377..c50f5ee000 100644 --- a/webapp/src/components/common/TableMode.tsx +++ b/webapp/src/components/common/TableMode.tsx @@ -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 { studyId: StudyMetadata["id"]; type: T; columns: TableModeColumnsForType; + extraActions?: React.ReactNode; } -function TableMode(props: TableModeProps) { - const { studyId, type, columns } = props; - const [filteredColumns, setFilteredColumns] = useState(columns); +function TableMode({ + studyId, + type, + columns, + extraActions, +}: TableModeProps) { const { t } = useTranslation(); + const [gridColumns, setGridColumns] = useState< + DataGridFormProps["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 @@ -83,15 +100,19 @@ function TableMode(props: TableModeProps) { - filteredColumns.length > 0 ? ( - 0 ? ( + ) : ( - + ) } />