Skip to content

Commit

Permalink
feat(ui-common): integrate GlideDataGrid into MatrixGrid (#2134)
Browse files Browse the repository at this point in the history
  • Loading branch information
hdinia authored Sep 12, 2024
1 parent 822532c commit a35b0ce
Show file tree
Hide file tree
Showing 24 changed files with 2,630 additions and 18 deletions.
3 changes: 2 additions & 1 deletion webapp/.npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
engine-strict=true
engine-strict=true
save-prefix= # prevent the caret (^) symbol use when installing new dependencies
1 change: 1 addition & 0 deletions webapp/public/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"global.error.delete": "Deletion failed",
"global.area.add": "Add an area",
"global.add": "Add",
"global.timeSeries": "Time Series",
"login.error": "Failed to authenticate",
"tasks.title": "Tasks",
"api.title": "API",
Expand Down
1 change: 1 addition & 0 deletions webapp/public/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"global.error.delete": "La suppression a échoué",
"global.area.add": "Ajouter une zone",
"global.add": "Ajouter",
"global.timeSeries": "Séries temporelles",
"login.error": "Échec de l'authentification",
"tasks.title": "Tâches",
"api.title": "API",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import { useOutletContext } from "react-router";
import useAppSelector from "../../../../../../redux/hooks/useAppSelector";
import { getCurrentAreaId } from "../../../../../../redux/selectors";
import { MatrixStats, StudyMetadata } from "../../../../../../common/types";
import MatrixInput from "../../../../../common/MatrixInput";
import { Root } from "./style";
import Matrix from "../../../../../common/MatrixGrid/Matrix";

function Load() {
const { study } = useOutletContext<{ study: StudyMetadata }>();
const currentArea = useAppSelector(getCurrentAreaId);
const url = `input/load/series/load_${currentArea}`;

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

return (
<Root>
<MatrixInput study={study} url={url} computStats={MatrixStats.STATS} />
</Root>
);
return <Matrix url={url} />;
}

export default Load;
106 changes: 106 additions & 0 deletions webapp/src/components/common/MatrixGrid/Matrix.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Divider, Skeleton } from "@mui/material";
import MatrixGrid from ".";
import { useMatrix } from "./useMatrix";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import ImportDialog from "../dialogs/ImportDialog";
import { useOutletContext } from "react-router";
import { StudyMetadata } from "../../../common/types";
import { MatrixContainer, MatrixHeader, MatrixTitle } from "./style";
import MatrixActions from "./MatrixActions";
import EmptyView from "../page/SimpleContent";

interface MatrixProps {
url: string;
title?: string;
enableTimeSeriesColumns?: boolean;
enableAggregateColumns?: boolean;
}

function Matrix({
url,
title = "global.timeSeries",
enableTimeSeriesColumns = true,
enableAggregateColumns = false,
}: MatrixProps) {
const { t } = useTranslation();
const { study } = useOutletContext<{ study: StudyMetadata }>();
const [openImportDialog, setOpenImportDialog] = useState(false);

const {
data,
error,
isLoading,
isSubmitting,
columns,
dateTime,
handleCellEdit,
handleMultipleCellsEdit,
handleImport,
handleSaveUpdates,
pendingUpdatesCount,
undo,
redo,
canUndo,
canRedo,
} = useMatrix(study.id, url, enableTimeSeriesColumns, enableAggregateColumns);

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

if (isLoading) {
return <Skeleton sx={{ height: 1, transform: "none" }} />;
}

if (error) {
return <EmptyView title={error.toString()} />;
}

if (!data || data.length === 0) {
return <EmptyView title={t("matrix.message.matrixEmpty")} />;
}

return (
<MatrixContainer>
<MatrixHeader>
<MatrixTitle>{t(title)}</MatrixTitle>
<MatrixActions
onImport={() => setOpenImportDialog(true)}
onSave={handleSaveUpdates}
studyId={study.id}
path={url}
disabled={data.length === 0}
pendingUpdatesCount={pendingUpdatesCount}
isSubmitting={isSubmitting}
undo={undo}
redo={redo}
canUndo={canUndo}
canRedo={canRedo}
/>
</MatrixHeader>
<Divider sx={{ width: 1, mt: 1, mb: 2 }} />
<MatrixGrid
data={data}
columns={columns}
rows={data.length}
dateTime={dateTime}
onCellEdit={handleCellEdit}
onMultipleCellsEdit={handleMultipleCellsEdit}
readOnly={isSubmitting}
/>
{openImportDialog && (
<ImportDialog
open={openImportDialog}
title={t("matrix.importNewMatrix")}
dropzoneText={t("matrix.message.importHint")}
onCancel={() => setOpenImportDialog(false)}
onImport={handleImport}
accept={{ "text/*": [".csv", ".tsv", ".txt"] }}
/>
)}
</MatrixContainer>
);
}

export default Matrix;
101 changes: 101 additions & 0 deletions webapp/src/components/common/MatrixGrid/MatrixActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Box, Divider, IconButton, Tooltip } from "@mui/material";
import SplitButton from "../buttons/SplitButton";
import DownloadMatrixButton from "../DownloadMatrixButton";
import FileDownload from "@mui/icons-material/FileDownload";
import { useTranslation } from "react-i18next";
import { LoadingButton } from "@mui/lab";
import Save from "@mui/icons-material/Save";
import { Undo, Redo } from "@mui/icons-material";

interface MatrixActionsProps {
onImport: VoidFunction;
onSave: VoidFunction;
studyId: string;
path: string;
disabled: boolean;
pendingUpdatesCount: number;
isSubmitting: boolean;
undo: VoidFunction;
redo: VoidFunction;
canUndo: boolean;
canRedo: boolean;
}

function MatrixActions({
onImport,
onSave,
studyId,
path,
disabled,
pendingUpdatesCount,
isSubmitting,
undo,
redo,
canUndo,
canRedo,
}: MatrixActionsProps) {
const { t } = useTranslation();

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

return (
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<Tooltip title={t("global.undo")}>
<span>
<IconButton
onClick={undo}
disabled={!canUndo}
size="small"
color="primary"
>
<Undo fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Tooltip title={t("global.redo")}>
<span>
<IconButton
onClick={redo}
disabled={!canRedo}
size="small"
color="primary"
>
<Redo fontSize="small" />
</IconButton>
</span>
</Tooltip>
<LoadingButton
onClick={onSave}
loading={isSubmitting}
loadingPosition="start"
startIcon={<Save />}
variant="contained"
size="small"
disabled={pendingUpdatesCount === 0}
>
({pendingUpdatesCount})
</LoadingButton>
<Divider sx={{ mx: 2 }} orientation="vertical" flexItem />
<SplitButton
options={[t("global.import.fromFile"), t("global.import.fromDatabase")]}
onClick={onImport}
size="small"
ButtonProps={{
startIcon: <FileDownload />,
}}
disabled={isSubmitting}
>
{t("global.import")}
</SplitButton>
<DownloadMatrixButton
studyId={studyId}
path={path}
disabled={disabled || isSubmitting}
/>
</Box>
);
}

export default MatrixActions;
Loading

0 comments on commit a35b0ce

Please sign in to comment.