From 051e2df7e3e3c3711f1abb15c45038d48f6b82a7 Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Wed, 11 Dec 2024 17:20:54 -0500 Subject: [PATCH 1/2] add dataSet actions in project page --- .../dataset-table/DataSetActions.tsx | 88 ++++++++++ .../components/dataset-table/DataSetTable.tsx | 88 ++-------- .../dataset-table/DataSetTableConfig.tsx | 164 ++++++++++-------- .../components/projects/ProjectTable.tsx | 40 ++++- src/webapp/hooks/useDataSets.ts | 11 ++ 5 files changed, 239 insertions(+), 152 deletions(-) create mode 100644 src/webapp/components/dataset-table/DataSetActions.tsx diff --git a/src/webapp/components/dataset-table/DataSetActions.tsx b/src/webapp/components/dataset-table/DataSetActions.tsx new file mode 100644 index 00000000..b6b78191 --- /dev/null +++ b/src/webapp/components/dataset-table/DataSetActions.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import i18n from "$/utils/i18n"; +import { useSnackbar } from "@eyeseetea/d2-ui-components"; +import _ from "$/domain/entities/generic/Collection"; +import { Id } from "$/domain/entities/Ref"; +import { ConfirmationModal } from "$/webapp/components/confirmation-modal/ConfirmationModal"; +import { EditSharing } from "$/webapp/components/edit-sharing/EditSharing"; +import { EditOrgUnits } from "$/webapp/components/edit-orgunits/EditOrgUnits"; +import { DataSetLogs } from "$/webapp/components/dataset-logs/DataSetLogs"; +import { Maybe } from "$/utils/ts-utils"; +import { useDeleteDataSets } from "$/webapp/hooks/useDataSets"; +import { component } from "$/utils/react"; + +export type DataSetActionsProps = { + tableAction: Maybe; + onChangeAction: (isCancelAction: boolean) => void; +}; + +function getSelectedIds(tableAction: Maybe): Id[] { + return tableAction?.ids || []; +} + +function getSelectedAction(tableAction: Maybe) { + return tableAction?.action; +} + +const DataSetActions_ = React.memo((props: DataSetActionsProps) => { + const { onChangeAction, tableAction } = props; + const snackbar = useSnackbar(); + const action = getSelectedAction(tableAction); + const selectedIds = getSelectedIds(tableAction); + + const clearTableAction = React.useCallback( + (isCancelAction?: boolean) => { + onChangeAction(isCancelAction ?? false); + }, + [onChangeAction] + ); + + const { deleteDataSets } = useDeleteDataSets({ + ids: selectedIds, + onSuccess: () => { + clearTableAction(); + snackbar.success(i18n.t("DataSets removed")); + }, + onError: message => { + snackbar.error(message); + clearTableAction(); + }, + }); + + const renderActions = () => { + switch (action) { + case "remove": + return ( + + ); + case "sharing": + return ( + clearTableAction(true)} dataSetIds={selectedIds} /> + ); + case "orgUnits": + return ( + clearTableAction(true)} + dataSetIds={selectedIds} + /> + ); + case "logs": + return ; + default: + return null; + } + }; + + return renderActions(); +}); + +export type TableAction = { + ids: Id[]; + action: "remove" | "sharing" | "orgUnits" | "logs" | "details"; +}; +export type TableConfigProps = { onAction: (action: TableAction) => void; refreshTable: number }; +export const DataSetActions = component(DataSetActions_); diff --git a/src/webapp/components/dataset-table/DataSetTable.tsx b/src/webapp/components/dataset-table/DataSetTable.tsx index 30a6fa17..4f950daa 100644 --- a/src/webapp/components/dataset-table/DataSetTable.tsx +++ b/src/webapp/components/dataset-table/DataSetTable.tsx @@ -1,94 +1,30 @@ import React from "react"; import { DataSetAttrs } from "$/domain/entities/DataSet"; -import i18n from "$/utils/i18n"; -import { ObjectsTable, useSnackbar } from "@eyeseetea/d2-ui-components"; +import { ObjectsTable } from "@eyeseetea/d2-ui-components"; import _ from "$/domain/entities/generic/Collection"; -import { Id } from "$/domain/entities/Ref"; -import { ConfirmationModal } from "$/webapp/components/confirmation-modal/ConfirmationModal"; -import { EditSharing } from "$/webapp/components/edit-sharing/EditSharing"; -import { EditOrgUnits } from "$/webapp/components/edit-orgunits/EditOrgUnits"; -import { DataSetLogs } from "$/webapp/components/dataset-logs/DataSetLogs"; + import { HomeTabs } from "$/webapp/components/home-tabs/HomeTabs"; -import { Maybe } from "$/utils/ts-utils"; -import { useDeleteDataSets } from "$/webapp/hooks/useDataSets"; +import { useDataSetsRoutes } from "$/webapp/hooks/useDataSets"; import { useTableConfig } from "$/webapp/components/dataset-table/DataSetTableConfig"; -import { useNavigateTo } from "$/webapp/routes"; import { DataSetDetails } from "$/webapp/components/dataset-table/DataSetDetails"; +import { DataSetActions, TableAction } from "$/webapp/components/dataset-table/DataSetActions"; +import { component } from "$/utils/react"; export type DataSetColumns = DataSetAttrs & { permissionDescription: string }; -export type TableAction = { - ids: Id[]; - action: "remove" | "sharing" | "orgUnits" | "logs" | "details"; -}; - -function getSelectedIds(tableAction: Maybe): Id[] { - return tableAction?.ids || []; -} -function getSelectedAction(tableAction: Maybe) { - return tableAction?.action; -} - -export const DataSetTable: React.FC = React.memo(() => { +const DataSetTable_: React.FC = React.memo(() => { const [refreshTable, setRefreshTable] = React.useState(0); const [tableAction, setTableAction] = React.useState(); - - const navigateTo = useNavigateTo(); - const snackbar = useSnackbar(); - const action = getSelectedAction(tableAction); - const selectedIds = getSelectedIds(tableAction); + const { goToCreateDataSet } = useDataSetsRoutes(); const tableConfig = useTableConfig({ onAction: setTableAction, refreshTable }); - const clearTableAction = React.useCallback((refreshTable?: boolean) => { + const refreshDataSets = React.useCallback((isCancelAction: boolean) => { setTableAction(undefined); - if (refreshTable) { + if (!isCancelAction) { setRefreshTable(prevValue => prevValue + 1); } }, []); - const { deleteDataSets } = useDeleteDataSets({ - ids: selectedIds, - onSuccess: () => { - clearTableAction(); - setRefreshTable(prevValue => prevValue + 1); - snackbar.success(i18n.t("DataSets removed")); - }, - onError: message => { - snackbar.error(message); - clearTableAction(); - }, - }); - - const closeModal = React.useCallback(() => { - setTableAction(undefined); - }, []); - - const goToCreateDataSet = React.useCallback(() => { - navigateTo("createDataSets"); - }, [navigateTo]); - - const renderActions = () => { - switch (action) { - case "remove": - return ; - case "sharing": - return ( - clearTableAction(true)} dataSetIds={selectedIds} /> - ); - case "orgUnits": - return ( - clearTableAction(true)} - dataSetIds={selectedIds} - /> - ); - case "logs": - return ; - default: - return null; - } - }; - return ( <> @@ -99,15 +35,15 @@ export const DataSetTable: React.FC = React.memo(() => { setTableAction(undefined)} /> } /> - {renderActions()} + ); }); export type TableConfigProps = { onAction: (action: TableAction) => void; refreshTable: number }; -DataSetTable.displayName = "DataSetTable"; +export const DataSetTable = component(DataSetTable_); diff --git a/src/webapp/components/dataset-table/DataSetTableConfig.tsx b/src/webapp/components/dataset-table/DataSetTableConfig.tsx index e7b075db..16c5b4a4 100644 --- a/src/webapp/components/dataset-table/DataSetTableConfig.tsx +++ b/src/webapp/components/dataset-table/DataSetTableConfig.tsx @@ -1,7 +1,11 @@ import React from "react"; import { DataSet, DataSetAttrs, DataSetList } from "$/domain/entities/DataSet"; import { useAppContext } from "$/webapp/contexts/app-context"; -import { useObjectsTable } from "@eyeseetea/d2-ui-components"; +import { + TableAction as DataTableAction, + ReferenceObject, + useObjectsTable, +} from "@eyeseetea/d2-ui-components"; import SharingIcon from "@material-ui/icons/Share"; import EditIcon from "@material-ui/icons/Edit"; import DomainIcon from "@material-ui/icons/Domain"; @@ -13,9 +17,9 @@ import ListIcon from "@material-ui/icons/List"; import _ from "$/domain/entities/generic/Collection"; import i18n from "$/utils/i18n"; -import { TableAction } from "$/webapp/components/dataset-table/DataSetTable"; import { useNavigateTo } from "$/webapp/routes"; import { parseSortField } from "$/utils/parse-sort-field"; +import { TableAction } from "$/webapp/components/dataset-table/DataSetActions"; export type DataSetColumns = DataSetAttrs & { permissionDescription: string }; @@ -52,48 +56,6 @@ export function useTableConfig(props: TableConfigProps) { }, ], actions: [ - { - name: "edit", - text: i18n.t("Edit"), - icon: , - multiple: false, - primary: true, - onClick(selectedIds) { - const dataSetId = _(selectedIds).first(); - if (!dataSetId) return; - navigateTo("editDataSets", { id: dataSetId }); - }, - }, - { - name: "sharing", - text: i18n.t("Sharing Settings"), - icon: , - multiple: true, - onClick(selectedIds) { - onAction({ ids: selectedIds, action: "sharing" }); - }, - }, - { - name: "assign_orgunits", - text: i18n.t("Assign to Organisation Units"), - icon: , - multiple: true, - onClick: selectedIds => { - onAction({ ids: selectedIds, action: "orgUnits" }); - }, - }, - { - name: "set_period_dates", - text: i18n.t("Set output/outcome period dates"), - icon: , - multiple: true, - }, - { - name: "set_end_dates", - text: i18n.t("Change output/outcome end date for year"), - icon: , - multiple: true, - }, { name: "show_details", text: i18n.t("Details"), @@ -103,30 +65,7 @@ export function useTableConfig(props: TableConfigProps) { onAction({ ids: selectedIds, action: "details" }); }, }, - { - name: "clone", - text: i18n.t("Clone"), - icon: , - multiple: false, - }, - { - name: "delete", - text: i18n.t("Delete"), - icon: , - multiple: true, - onClick(selectedIds) { - onAction({ ids: selectedIds, action: "remove" }); - }, - }, - { - name: "logs", - text: i18n.t("Logs"), - icon: , - multiple: true, - onClick(selectedIds) { - onAction({ ids: selectedIds, action: "logs" }); - }, - }, + ...getCommonActions({ onAction, navigateTo, isActive: () => true }), ], initialSorting: { field: "name", order: "asc" }, paginationOptions: { pageSizeInitialValue: 50, pageSizeOptions: [50, 100, 200] }, @@ -168,7 +107,92 @@ export function useTableConfig(props: TableConfigProps) { return tableConfig; } -export type TableConfigProps = { +export type TableConfigProps = { onAction: (action: TableAction) => void; refreshTable: number }; + +export type CommonActionsProps = { + isActive: (data: T[]) => boolean; onAction: (action: TableAction) => void; - refreshTable: number; + navigateTo: ReturnType; }; + +export function getCommonActions( + props: CommonActionsProps +): DataTableAction[] { + const { onAction, navigateTo } = props; + return [ + { + name: "edit", + text: i18n.t("Edit"), + icon: , + multiple: false, + primary: true, + isActive: props.isActive, + onClick(selectedIds) { + const dataSetId = _(selectedIds).first(); + if (!dataSetId) return; + navigateTo("editDataSets", { id: dataSetId }); + }, + }, + { + name: "sharing", + text: i18n.t("Sharing Settings"), + icon: , + multiple: true, + isActive: props.isActive, + onClick(selectedIds) { + onAction({ ids: selectedIds, action: "sharing" }); + }, + }, + { + name: "assign_orgunits", + text: i18n.t("Assign to Organisation Units"), + icon: , + multiple: true, + isActive: props.isActive, + onClick: selectedIds => { + onAction({ ids: selectedIds, action: "orgUnits" }); + }, + }, + { + name: "set_period_dates", + text: i18n.t("Set output/outcome period dates"), + icon: , + multiple: true, + isActive: props.isActive, + }, + { + name: "set_end_dates", + text: i18n.t("Change output/outcome end date for year"), + icon: , + multiple: true, + isActive: props.isActive, + }, + { + name: "clone", + text: i18n.t("Clone"), + icon: , + multiple: false, + isActive: props.isActive, + }, + { + name: "delete", + text: i18n.t("Delete"), + icon: , + multiple: true, + isActive: props.isActive, + onClick(selectedIds) { + onAction({ ids: selectedIds, action: "remove" }); + }, + }, + { + name: "logs", + text: i18n.t("Logs"), + icon: , + multiple: true, + isActive: props.isActive, + onClick(selectedIds) { + onAction({ ids: selectedIds, action: "logs" }); + }, + }, + ]; +} diff --git a/src/webapp/components/projects/ProjectTable.tsx b/src/webapp/components/projects/ProjectTable.tsx index a333eca3..7783db71 100644 --- a/src/webapp/components/projects/ProjectTable.tsx +++ b/src/webapp/components/projects/ProjectTable.tsx @@ -13,6 +13,10 @@ import i18n from "$/utils/i18n"; import { TooltipTruncate } from "$/webapp/components/tooltip-truncate/TooltipTruncate"; import { useAppContext } from "$/webapp/contexts/app-context"; import { parseSortField } from "$/utils/parse-sort-field"; +import { useNavigateTo } from "$/webapp/routes"; +import { useDataSetsRoutes } from "$/webapp/hooks/useDataSets"; +import { DataSetActions, TableAction } from "$/webapp/components/dataset-table/DataSetActions"; +import { getCommonActions } from "$/webapp/components/dataset-table/DataSetTableConfig"; type ProjectColumns = ProjectAttrs & { orgUnits: string; coreCompetencies: string }; @@ -24,6 +28,11 @@ export const ProjectTable = React.memo(() => { const { compositionRoot } = useAppContext(); const loading = useLoading(); const snackbar = useSnackbar(); + const navigateTo = useNavigateTo(); + const { goToCreateDataSet } = useDataSetsRoutes(); + const [refreshTable, setRefreshTable] = React.useState(0); + const [tableAction, setTableAction] = React.useState(); + const [isLoading, setLoading] = React.useState(false); const tableConfig = useObjectsTable( React.useMemo(() => { @@ -35,7 +44,13 @@ export const ProjectTable = React.memo(() => { text: i18n.t("Details"), icon: , primary: true, + isActive: projects => projects.every(project => !objIsDataSet(project)), }, + ...getCommonActions({ + onAction: setTableAction, + navigateTo, + isActive: projects => projects.every(project => objIsDataSet(project)), + }), ], details: [ { @@ -91,10 +106,11 @@ export const ProjectTable = React.memo(() => { searchBoxLabel: i18n.t("Search"), childrenKeys: ["dataSets"], }; - }, []), + }, [goToCreateDataSet, navigateTo]), React.useCallback( (search, pagination, sorting) => { - loading.show(true, i18n.t("Loading projects...")); + console.debug(refreshTable); + setLoading(true); return new Promise((resolve, reject) => { return compositionRoot.projects.get @@ -105,7 +121,7 @@ export const ProjectTable = React.memo(() => { }) .run( response => { - loading.hide(); + setLoading(false); return resolve({ objects: response.data.map(project => { return { @@ -123,16 +139,28 @@ export const ProjectTable = React.memo(() => { }); }, error => { - loading.hide(); + setLoading(false); snackbar.error(error.message); return reject(new Error(error.message)); } ); }); }, - [compositionRoot.projects.get, loading, snackbar] + [compositionRoot.projects.get, loading, snackbar, refreshTable] ) ); - return ; + const reloadProjects = React.useCallback((isCancelAction: boolean) => { + setTableAction(undefined); + if (!isCancelAction) { + setRefreshTable(prevValue => prevValue + 1); + } + }, []); + + return ( + <> + ; + + + ); }); diff --git a/src/webapp/hooks/useDataSets.ts b/src/webapp/hooks/useDataSets.ts index c656bfa2..2b2a38ee 100644 --- a/src/webapp/hooks/useDataSets.ts +++ b/src/webapp/hooks/useDataSets.ts @@ -5,6 +5,7 @@ import { DataSet } from "$/domain/entities/DataSet"; import { Id } from "$/domain/entities/Ref"; import i18n from "$/utils/i18n"; import { useAppContext } from "$/webapp/contexts/app-context"; +import { useNavigateTo } from "$/webapp/routes"; export function useGetDataSetsByIds(ids: Id[]) { const { compositionRoot } = useAppContext(); @@ -89,4 +90,14 @@ export function useDeleteDataSets(props: DeleteDataSetsProps) { return { deleteDataSets }; } +export function useDataSetsRoutes() { + const navigateTo = useNavigateTo(); + + const goToCreateDataSet = React.useCallback(() => { + navigateTo("createDataSets"); + }, [navigateTo]); + + return { goToCreateDataSet }; +} + type DeleteDataSetsProps = { ids: Id[]; onError: (message: string) => void; onSuccess: () => void }; From ab35c5158da524206e6f679939162b7fff20094d Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Wed, 11 Dec 2024 17:23:09 -0500 Subject: [PATCH 2/2] fixed eslint warning --- src/webapp/components/projects/ProjectTable.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/webapp/components/projects/ProjectTable.tsx b/src/webapp/components/projects/ProjectTable.tsx index 7783db71..2fe153b9 100644 --- a/src/webapp/components/projects/ProjectTable.tsx +++ b/src/webapp/components/projects/ProjectTable.tsx @@ -1,10 +1,5 @@ import React from "react"; -import { - ObjectsTable, - useLoading, - useObjectsTable, - useSnackbar, -} from "@eyeseetea/d2-ui-components"; +import { ObjectsTable, useObjectsTable, useSnackbar } from "@eyeseetea/d2-ui-components"; import DetailsIcon from "@material-ui/icons/Details"; import { DataSet } from "$/domain/entities/DataSet"; @@ -14,7 +9,6 @@ import { TooltipTruncate } from "$/webapp/components/tooltip-truncate/TooltipTru import { useAppContext } from "$/webapp/contexts/app-context"; import { parseSortField } from "$/utils/parse-sort-field"; import { useNavigateTo } from "$/webapp/routes"; -import { useDataSetsRoutes } from "$/webapp/hooks/useDataSets"; import { DataSetActions, TableAction } from "$/webapp/components/dataset-table/DataSetActions"; import { getCommonActions } from "$/webapp/components/dataset-table/DataSetTableConfig"; @@ -26,10 +20,8 @@ function objIsDataSet(project: ProjectColumns): boolean { export const ProjectTable = React.memo(() => { const { compositionRoot } = useAppContext(); - const loading = useLoading(); const snackbar = useSnackbar(); const navigateTo = useNavigateTo(); - const { goToCreateDataSet } = useDataSetsRoutes(); const [refreshTable, setRefreshTable] = React.useState(0); const [tableAction, setTableAction] = React.useState(); const [isLoading, setLoading] = React.useState(false); @@ -106,7 +98,7 @@ export const ProjectTable = React.memo(() => { searchBoxLabel: i18n.t("Search"), childrenKeys: ["dataSets"], }; - }, [goToCreateDataSet, navigateTo]), + }, [navigateTo]), React.useCallback( (search, pagination, sorting) => { console.debug(refreshTable); @@ -146,7 +138,7 @@ export const ProjectTable = React.memo(() => { ); }); }, - [compositionRoot.projects.get, loading, snackbar, refreshTable] + [compositionRoot.projects.get, snackbar, refreshTable] ) );