diff --git a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/pom.xml b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/pom.xml index 5513af9f..81b448df 100644 --- a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/pom.xml +++ b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/pom.xml @@ -179,7 +179,7 @@ pnpm - generate-sources + test run build @@ -190,6 +190,31 @@ ${generation-target} + + + hu.blackbelt.judo.generator + judo-diff-checker-maven-plugin + ${revision} + + + + checkDiffs + + generate-sources + + + + ${project.basedir}/target/frontend-react/ + ${project.basedir}/src/test/resources/snapshots/frontend-react/ + + src/pages/God/God/Galaxies/AccessTablePage/index.tsx + src/pages/God/God/Galaxies/AccessViewPage/index.tsx + src/containers/View/Galaxy/Table/components/ViewGalaxyTableTableComponent/index.tsx + src/components/table/LazyTable.tsx + src/components/table/EagerTable.tsx + + + diff --git a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/components/table/EagerTable.tsx.snapshot b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/components/table/EagerTable.tsx.snapshot new file mode 100644 index 00000000..d5b15010 --- /dev/null +++ b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/components/table/EagerTable.tsx.snapshot @@ -0,0 +1,543 @@ +////////////////////////////////////////////////////////////////////////////// +// G E N E R A T E D S O U R C E +// -------------------------------- +// Factory expression: +// Path expression: 'src/components/table/EagerTable.tsx' +// Template name: actor/src/components/table/EagerTable.tsx +// Template file: actor/src/components/table/EagerTable.tsx.hbs + +import { Box, Button, Typography } from '@mui/material'; +import { + GridColDef, + GridEventListener, + GridFilterModel, + GridLogicOperator, + GridRowClassNameParams, + GridRowModel, + GridRowSelectionModel, + GridSortModel, + GridToolbarContainer, + GridValidRowModel, + useGridApiRef, +} from '@mui/x-data-grid-pro'; +import { gridColumnDefinitionsSelector } from '@mui/x-data-grid-pro'; +import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; +import { ElementType, MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Filter, FilterOption } from '~/components-api'; +import { useConfirmDialog } from '~/components/dialog'; +import { ContextMenu, ContextMenuApi, StripedDataGrid, columnsActionCalculator } from '~/components/table'; +import { RowHighlightLegend } from '~/components/table'; +import { basePageSizeOptions, baseTableConfig } from '~/config'; +import { useDataStore } from '~/hooks'; +import { useL10N } from '~/l10n/l10n-context'; +import type { QueryCustomizer } from '~/services/data-api/common/QueryCustomizer'; +import { transformRowStylings } from '~/theme/table-row-highlighting'; +import type { RowStylerConfigured, TableRowHighlightingHook } from '~/theme/table-row-highlighting'; +import { + ToolBarActionProps, + applyInMemoryFilters, + calculateActionName, + fileHandling, + getUpdatedRowsSelected, + mapAllFiltersToQueryCustomizerProperties, + mapFilterModelToFilters, + mapFilterToFilterModel, + processQueryCustomizer, +} from '~/utilities'; +import type { DialogResultReason, FiltersSerializer, PersistedColumnInfo, TableRowAction } from '~/utilities'; +import { MdiIcon } from '../MdiIcon'; + +interface EagerTableProps { + sidekickComponentFilter: string; + uniqueId: string; + dataProp: T[]; + defaultSortParamsForTable: GridSortModel; + tablePageLimit: number; + tableColumns: GridColDef[]; + tableRowActions: TableRowAction[]; + tableFilterOptions: FilterOption[]; + isOwnerLoading?: boolean; + validationError?: string; + actions: any; + allowSelectMultiple?: boolean; + dataElementId: string; + crudOperationsDisplayed: number; + transferOperationsDisplayed: number; + onRowClick?: GridEventListener<'rowClick'>; + dataElement?: any; + isSelectorTable?: boolean; + editMode?: boolean; + containerHasTable?: boolean; + toolBarActions: ToolBarActionProps[]; + additionalToolbarButtons?: (...args: any[]) => Record; + tableHasSelectorColumn?: boolean; + maskAction: () => string; + ownerData?: any; + checkboxSelection?: boolean; + isFormUpdateable?: () => boolean; + enabledByName?: string; + relationName: string; + filtersSerializer: FiltersSerializer; +} + +export function EagerTable>( + props: EagerTableProps, +) { + const { + sidekickComponentFilter, + uniqueId, + dataProp, + defaultSortParamsForTable, + tablePageLimit, + tableColumns, + tableRowActions, + tableFilterOptions, + isOwnerLoading, + validationError, + actions, + allowSelectMultiple, + dataElementId, + crudOperationsDisplayed, + transferOperationsDisplayed, + onRowClick, + dataElement, + editMode, + containerHasTable, + toolBarActions, + additionalToolbarButtons, + tableHasSelectorColumn, + maskAction, + ownerData, + isFormUpdateable, + enabledByName, + relationName, + checkboxSelection, + filtersSerializer, + } = props; + + const apiRef = useGridApiRef(); + const filterModelKey = `${uniqueId}-filterModel`; + const filtersKey = `${uniqueId}-filters`; + const rowsPerPageKey = `${uniqueId}-rowsPerPage`; + const sortModelKey = `${uniqueId}-sortModel`; + const columnStateKey = `${uniqueId}-columnState`; + + const { openConfirmDialog } = useConfirmDialog(); + const { locale: l10nLocale } = useL10N(); + const { downloadFile, extractFileNameFromToken } = fileHandling(); + const { getItemParsed, getItemParsedWithDefault, setItemStringified } = useDataStore('sessionStorage'); + const { t } = useTranslation(); + + const [isInternalLoading, setIsInternalLoading] = useState(false); + const [data, setData] = useState[]>(dataProp); + const [selectionModel, setSelectionModel] = useState([]); + const [sortModel, setSortModel] = useState( + getItemParsedWithDefault(sortModelKey, defaultSortParamsForTable), + ); + const [filterModel, setFilterModel] = useState( + getItemParsedWithDefault(filterModelKey, { items: [] }), + ); + const [filters, setFilters] = useState( + filtersSerializer.deserialize(getItemParsedWithDefault(filtersKey, [])), + ); + const [columnState, setColumnState] = useState(getItemParsedWithDefault(columnStateKey, [])); + const [rowsPerPage, setRowsPerPage] = useState(getItemParsedWithDefault(rowsPerPageKey, tablePageLimit)); + const [paginationModel, setPaginationModel] = useState({ + pageSize: rowsPerPage, + page: 0, + }); + + const [queryCustomizer, setQueryCustomizer] = useState>({ + _mask: maskAction(), + _seek: { + limit: rowsPerPage + 1, + }, + _orderBy: sortModel.length + ? sortModel.map((s) => ({ + attribute: s.field, + descending: s.sort === 'desc', + })) + : [], + ...mapAllFiltersToQueryCustomizerProperties(filters), + }); + + const isLoading = useMemo(() => isInternalLoading || !!isOwnerLoading, [isInternalLoading, isOwnerLoading]); + + const selectedRows = useRef([]); + const contextMenuRef = useRef(null); + + const handleContextMenu = (event: MouseEvent) => { + contextMenuRef.current?.handleContextMenu(event); + }; + + const columns = useMemo[]>(() => tableColumns, [l10nLocale]); + + const rowActions: TableRowAction[] = useMemo(() => tableRowActions, [actions, isLoading]); + + const getSelectedRows: () => T[] = useCallback(() => { + if (tableHasSelectorColumn) { + return selectedRows.current; + } else { + return []; + } + }, [selectedRows.current]); + + const effectiveTableColumns = useMemo(() => { + const cols = [ + ...columns, + ...columnsActionCalculator(dataElementId, rowActions, t, isLoading, getSelectedRows, ownerData, { + crudOperationsDisplayed: crudOperationsDisplayed, + transferOperationsDisplayed: transferOperationsDisplayed, + }), + ]; + if (columnState.length) { + const adjusted = columnState.sort((a, b) => a.index - b.index).map((c) => cols.find((t) => t.field === c.field)!); + + for (const c of adjusted) { + const colWithWidth = columnState.find((x) => x.field === c.field && x.width !== undefined); + if (colWithWidth) { + // mutate in place + c.width = colWithWidth.width; + } + } + return adjusted; + } + return cols; + }, [columns, rowActions, getSelectedRows, ownerData, isLoading, columnState]); + + const onColumnsChanged = () => { + // Handles column resizing and re-ordering + // skip selector column + const newOrder: PersistedColumnInfo[] = gridColumnDefinitionsSelector(apiRef) + .filter((c) => c.field !== '__check__') + .map((current, idx) => ({ field: current.field, index: idx, width: current.computedWidth })); + setItemStringified(columnStateKey, newOrder); + setColumnState(newOrder); + }; + + const getRowIdentifier: (row: T) => string = (row) => row.__identifier!; + + const clearSelections = () => { + handleOnSelection([]); + }; + + const pageSizeOptions = useMemo(() => { + const opts: Set = new Set([rowsPerPage, ...basePageSizeOptions]); + return Array.from(opts.values()).sort((a, b) => a - b); + }, [rowsPerPage]); + + const filterOptions = useMemo(() => tableFilterOptions, [l10nLocale]); + + function handleFilterModelChange(newModel: GridFilterModel) { + setFilterModel(newModel); + setItemStringified(filterModelKey, newModel); + + if (newModel.items.every((i: any) => i.value !== undefined)) { + const newFilters: Filter[] = mapFilterModelToFilters(newModel, filterOptions); + + if (Array.isArray(newFilters)) { + handleFiltersChange(newFilters); + } + } + } + + const handleFiltersChange = (newFilters: Filter[]) => { + setPaginationModel((prevState) => ({ + ...prevState, + page: 0, + })); + setFilters(newFilters); + setItemStringified(filtersKey, filtersSerializer.serialize(newFilters)); + + setQueryCustomizer((prevQueryCustomizer: S | QueryCustomizer) => { + // remove previous filter values, so that we can always start with a clean slate + for (const name of columns.map((c) => c.field)) { + delete (prevQueryCustomizer as any)[name]; + } + return { + ...prevQueryCustomizer, + _seek: { + limit: rowsPerPage + 1, + }, + ...mapAllFiltersToQueryCustomizerProperties(newFilters), + }; + }); + }; + + function handleSortModelChange(newModel: GridSortModel) { + setPaginationModel((prevState) => ({ + ...prevState, + page: 0, + })); + setSortModel(newModel); + setItemStringified(sortModelKey, newModel); + + const _orderBy = newModel + .filter((m: any) => m.sort) + .map((m: any) => ({ + attribute: m.field, + descending: m.sort === 'desc', + })); + + setQueryCustomizer((prevQueryCustomizer) => { + const strippedQueryCustomizer: S | QueryCustomizer = { + ...prevQueryCustomizer, + }; + if (!!strippedQueryCustomizer._seek) { + delete strippedQueryCustomizer._seek.lastItem; + } + // we need to reset _seek so that previous configuration is erased + return { + ...strippedQueryCustomizer, + _orderBy, + _seek: { + limit: rowsPerPage + 1, + }, + }; + }); + } + + let handleOnSelection = (newSelectionModel: GridRowSelectionModel) => { + selectedRows.current = getUpdatedRowsSelected(selectedRows, data, newSelectionModel); + setSelectionModel(selectedRows.current.map(getRowIdentifier)); + }; + + useEffect(() => { + const newData = applyInMemoryFilters(filters, dataElement ?? []); + setData(newData); + handleOnSelection(selectionModel); + }, [dataElement, filters]); + + let additionalToolbarActions: Record; + + if (additionalToolbarButtons) { + if (containerHasTable) { + additionalToolbarActions = additionalToolbarButtons(data, isLoading, getSelectedRows(), clearSelections); + } else { + additionalToolbarActions = additionalToolbarButtons( + data, + isLoading, + getSelectedRows(), + clearSelections, + ownerData, + editMode, + isFormUpdateable, + ); + } + } else { + additionalToolbarActions = {}; + } + + const AdditionalToolbarActions = () => { + return ( + <> + {Object.keys(additionalToolbarActions).map((key) => { + const AdditionalButton = additionalToolbarActions[key]; + return ; + })} + + ); + }; + + useEffect(() => { + if (filterModel.items.every((i: any) => i.value !== undefined)) { + setFilters(mapFilterModelToFilters(filterModel, filterOptions)); + } + }, [dataElement, filterModel]); + + async function filterAction(id: string) { + const filterResults = await actions[calculateActionName(relationName, 'filterAction')]!( + id, + filterOptions, + filterModel, + filters, + ); + if (filterResults.model) { + handleFilterModelChange({ + ...filterResults.model!, + }); + } + } + + return ( + <> + + (props.validationError ? `2px solid ${theme.palette.error.main}` : undefined), + }} + slotProps={{ + cell: { + onContextMenu: handleContextMenu, + }, + filterPanel: { + logicOperators: [GridLogicOperator.And], + }, + }} + getRowId={getRowIdentifier} + loading={isLoading} + rows={data} + getRowClassName={(params: GridRowClassNameParams) => { + return params.indexRelativeToCurrentPage % 2 === 0 ? 'even' : 'odd'; + }} + columns={effectiveTableColumns} + onColumnOrderChange={onColumnsChanged} + onColumnWidthChange={onColumnsChanged} + disableRowSelectionOnClick + hideFooterSelectedRowCount={!allowSelectMultiple} + checkboxSelection={checkboxSelection !== false} + rowSelectionModel={selectionModel} + onRowSelectionModelChange={handleOnSelection} + keepNonExistentRowsSelected + onRowClick={onRowClick} + sortModel={sortModel} + onSortModelChange={handleSortModelChange} + filterModel={filterModel} + onFilterModelChange={handleFilterModelChange} + paginationModel={paginationModel} + onPaginationModelChange={setPaginationModel} + slots={{ + toolbar: () => ( + + {toolBarActions.map((toolBarAction: ToolBarActionProps) => + actions[toolBarAction.name] && + toolBarAction.enabled(data, selectionModel, ownerData, isFormUpdateable) ? ( + + ) : null, + )} + {} +
{/* Placeholder */}
+
+ ), + }} + /> + {validationError ? ( + theme.palette.error.main, + display: 'flex', + alignItems: 'center', + pl: 1, + pr: 1, + }} + > + + {validationError} + + ) : null} + { + handleFilterModelChange({ + ...filterModel, + items: [...filterModel.items, mapFilterToFilterModel(filter)], + }); + }} + onExcludeByCell={(filter: Filter) => { + handleFilterModelChange({ + ...filterModel, + items: [...filterModel.items, mapFilterToFilterModel(filter)], + }); + }} + /> + + ); +} diff --git a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/components/table/LazyTable.tsx.snapshot b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/components/table/LazyTable.tsx.snapshot new file mode 100644 index 00000000..8f8fe222 --- /dev/null +++ b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/components/table/LazyTable.tsx.snapshot @@ -0,0 +1,738 @@ +////////////////////////////////////////////////////////////////////////////// +// G E N E R A T E D S O U R C E +// -------------------------------- +// Factory expression: +// Path expression: 'src/components/table/LazyTable.tsx' +// Template name: actor/src/components/table/LazyTable.tsx +// Template file: actor/src/components/table/LazyTable.tsx.hbs + +import { Box, Button, Typography } from '@mui/material'; +import { + GridColDef, + GridEventListener, + GridFilterModel, + GridLogicOperator, + GridRowClassNameParams, + GridRowId, + GridRowModel, + GridRowParams, + GridRowSelectionModel, + GridSortModel, + GridToolbarContainer, + GridValidRowModel, + useGridApiRef, +} from '@mui/x-data-grid-pro'; +import { gridColumnDefinitionsSelector } from '@mui/x-data-grid-pro'; +import { ComponentProxy, useTrackService } from '@pandino/react-hooks'; +import { + Dispatch, + ElementType, + MouseEvent, + SetStateAction, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { useTranslation } from 'react-i18next'; +import { CustomTablePagination } from '~/components'; +import { Filter, FilterOption } from '~/components-api'; +import { useConfirmDialog } from '~/components/dialog'; +import { + ContextMenu, + ContextMenuApi, + RowHighlightLegend, + StripedDataGrid, + columnsActionCalculator, +} from '~/components/table'; +import { basePageSizeOptions, baseTableConfig, filterDebounceMs } from '~/config'; +import { useDataStore } from '~/hooks'; +import { useL10N } from '~/l10n/l10n-context'; +import type { JudoStored } from '~/services/data-api/common/JudoStored'; +import type { QueryCustomizer } from '~/services/data-api/common/QueryCustomizer'; +import { X_JUDO_COUNT } from '~/services/data-api/rest/headers'; +import { transformRowStylings } from '~/theme/table-row-highlighting'; +import type { RowStylerConfigured, TableRowHighlightingHook } from '~/theme/table-row-highlighting'; +import { + ToolBarActionProps, + calculateActionName, + fileHandling, + getUpdatedRowsSelected, + isRowSelectable, + mapAllFiltersToQueryCustomizerProperties, + mapFilterModelToFilters, + mapFilterToFilterModel, + processQueryCustomizer, + useErrorHandler, +} from '~/utilities'; +import type { DialogResultReason, FiltersSerializer, PersistedColumnInfo, TableRowAction } from '~/utilities'; +import { MdiIcon } from '../MdiIcon'; + +interface LazyTableProps { + sidekickComponentFilter: string; + uniqueId: string; + dataProp: T[]; + defaultSortParamsForTable: GridSortModel; + tablePageLimit: number; + tableColumns: GridColDef[]; + tableRowActions: TableRowAction[]; + tableFilterOptions: FilterOption[]; + isOwnerLoading?: boolean; + validationError?: string; + actions: any; + allowSelectMultiple?: boolean; + dataElementId: string; + crudOperationsDisplayed: number; + transferOperationsDisplayed: number; + containerIsSelector?: boolean; + containerIsRelationSelector?: boolean; + onRowClick?: GridEventListener<'rowClick'>; + setSelectionDiff?: Dispatch>; + alreadySelected?: JudoStored[]; + containerHasTable?: boolean; + hasRefreshAction?: boolean; + maskAction: () => string; + fetch: any; + refreshCounter: number; + ownerData?: any; + editMode?: boolean; + showTotalCount?: boolean; + toolBarActions: ToolBarActionProps[]; + additionalToolbarButtons?: (...args: any[]) => Record; + tableHasSelectorColumn?: boolean; + selectionDiff?: T[]; + isFormUpdateable?: () => boolean; + enabledByName?: string; + checkboxSelection?: boolean; + relationName: string; + filtersSerializer: FiltersSerializer; +} + +export function LazyTable>( + props: LazyTableProps, +) { + const { + sidekickComponentFilter, + uniqueId, + dataProp, + defaultSortParamsForTable, + tablePageLimit, + tableColumns, + tableRowActions, + tableFilterOptions, + isOwnerLoading, + validationError, + actions, + allowSelectMultiple, + dataElementId, + crudOperationsDisplayed, + transferOperationsDisplayed, + containerIsSelector, + containerIsRelationSelector, + onRowClick, + setSelectionDiff, + alreadySelected, + containerHasTable, + hasRefreshAction, + maskAction, + fetch, + refreshCounter, + ownerData, + editMode, + showTotalCount, + toolBarActions, + additionalToolbarButtons, + tableHasSelectorColumn, + selectionDiff, + isFormUpdateable, + enabledByName, + relationName, + checkboxSelection, + filtersSerializer, + } = props; + + const apiRef = useGridApiRef(); + const filterModelKey = `${uniqueId}-filterModel`; + const filtersKey = `${uniqueId}-filters`; + const rowsPerPageKey = `${uniqueId}-rowsPerPage`; + const sortModelKey = `${uniqueId}-sortModel`; + const columnStateKey = `${uniqueId}-columnState`; + + const { locale: l10nLocale } = useL10N(); + const { downloadFile, extractFileNameFromToken } = fileHandling(); + const { getItemParsed, getItemParsedWithDefault, setItemStringified } = useDataStore('sessionStorage'); + const { t } = useTranslation(); + const handleError = useErrorHandler(); + + const { openConfirmDialog } = useConfirmDialog(); + const [isInternalLoading, setIsInternalLoading] = useState(false); + const [totalCount, setTotalCount] = useState(-1); + const [data, setData] = useState[]>(dataProp); + const [selectionModel, setSelectionModel] = useState([]); + const [sortModel, setSortModel] = useState( + getItemParsedWithDefault(sortModelKey, defaultSortParamsForTable), + ); + const [filterModel, setFilterModel] = useState( + getItemParsedWithDefault(filterModelKey, { items: [] }), + ); + const [filters, setFilters] = useState( + filtersSerializer.deserialize(getItemParsedWithDefault(filtersKey, [])), + ); + const [columnState, setColumnState] = useState(getItemParsedWithDefault(columnStateKey, [])); + const [rowsPerPage, setRowsPerPage] = useState(getItemParsedWithDefault(rowsPerPageKey, tablePageLimit)); + const [paginationModel, setPaginationModel] = useState({ + pageSize: rowsPerPage, + page: 0, + }); + + const [queryCustomizer, setQueryCustomizer] = useState>({ + _mask: maskAction(), + _seek: { + limit: rowsPerPage + 1, + }, + _orderBy: sortModel.length + ? sortModel.map((s) => ({ + attribute: s.field, + descending: s.sort === 'desc', + })) + : [], + ...mapAllFiltersToQueryCustomizerProperties(filters), + }); + + const [page, setPage] = useState(0); + const [rowCount, setRowCount] = useState(0); + const [lastItem, setLastItem] = useState(); + const [firstItem, setFirstItem] = useState(); + const [isNextButtonEnabled, setIsNextButtonEnabled] = useState(true); + + const isLoading = useMemo(() => isInternalLoading || !!isOwnerLoading, [isInternalLoading, isOwnerLoading]); + + const selectedRows = useRef([]); + const contextMenuRef = useRef(null); + + const handleContextMenu = (event: MouseEvent) => { + contextMenuRef.current?.handleContextMenu(event); + }; + + const columns = useMemo[]>(() => tableColumns, [l10nLocale]); + + const rowActions: TableRowAction[] = useMemo(() => tableRowActions, [actions, isLoading]); + + const getSelectedRows: () => T[] = useCallback(() => { + if (tableHasSelectorColumn) { + if (containerIsSelector) { + return selectionDiff ?? []; + } else { + return selectedRows.current; + } + } else { + return []; + } + }, [selectionDiff, selectedRows.current]); + + const effectiveTableColumns = useMemo(() => { + const cols = [ + ...columns, + ...columnsActionCalculator(dataElementId, rowActions, t, isLoading, getSelectedRows, ownerData, { + crudOperationsDisplayed: crudOperationsDisplayed, + transferOperationsDisplayed: transferOperationsDisplayed, + }), + ]; + if (columnState.length) { + const adjusted = columnState.sort((a, b) => a.index - b.index).map((c) => cols.find((t) => t.field === c.field)!); + for (const c of adjusted) { + const colWithWidth = columnState.find((x) => x.field === c.field && x.width !== undefined); + if (colWithWidth) { + // mutate in place + c.width = colWithWidth.width; + } + } + return adjusted; + } + return cols; + }, [columns, rowActions, getSelectedRows, ownerData, isLoading, columnState]); + + const onColumnsChanged = () => { + // Handles column resizing and re-ordering + // skip selector column + const newOrder: PersistedColumnInfo[] = gridColumnDefinitionsSelector(apiRef) + .filter((c) => c.field !== '__check__') + .map((current, idx) => ({ field: current.field, index: idx, width: current.computedWidth })); + setItemStringified(columnStateKey, newOrder); + setColumnState(newOrder); + }; + + const getRowIdentifier: (row: T) => string = (row) => row.__identifier!; + + const clearSelections = () => { + handleOnSelection([]); + }; + + const pageSizeOptions = useMemo(() => { + const opts: Set = new Set([rowsPerPage, ...basePageSizeOptions]); + return Array.from(opts.values()).sort((a, b) => a - b); + }, [rowsPerPage]); + + // really need this in case of eager table + const setPageSize = useCallback((newValue: number) => { + setRowsPerPage(newValue); + setItemStringified(rowsPerPageKey, newValue); + setPage(0); + + setQueryCustomizer((prevQueryCustomizer: S | QueryCustomizer) => { + // we need to reset _seek so that previous configuration is erased + return { + ...prevQueryCustomizer, + _seek: { + limit: newValue + 1, + }, + }; + }); + }, []); + + const filterOptions = useMemo(() => tableFilterOptions, [l10nLocale]); + + function handleFilterModelChange(newModel: GridFilterModel) { + setFilterModel(newModel); + setItemStringified(filterModelKey, newModel); + + if (newModel.items.every((i: any) => i.value !== undefined)) { + const newFilters: Filter[] = mapFilterModelToFilters(newModel, filterOptions); + + if (Array.isArray(newFilters)) { + handleFiltersChange(newFilters); + } + } + } + + const handleFiltersChange = (newFilters: Filter[]) => { + setPage(0); + setFilters(newFilters); + setItemStringified(filtersKey, filtersSerializer.serialize(newFilters)); + + setQueryCustomizer((prevQueryCustomizer: S | QueryCustomizer) => { + // remove previous filter values, so that we can always start with a clean slate + for (const name of columns.map((c) => c.field)) { + delete (prevQueryCustomizer as any)[name]; + } + return { + ...prevQueryCustomizer, + _seek: { + limit: rowsPerPage + 1, + }, + ...mapAllFiltersToQueryCustomizerProperties(newFilters), + }; + }); + }; + + function handleSortModelChange(newModel: GridSortModel) { + setPage(0); + setSortModel(newModel); + setItemStringified(sortModelKey, newModel); + + const _orderBy = newModel + .filter((m: any) => m.sort) + .map((m: any) => ({ + attribute: m.field, + descending: m.sort === 'desc', + })); + + setQueryCustomizer((prevQueryCustomizer) => { + const strippedQueryCustomizer: S | QueryCustomizer = { + ...prevQueryCustomizer, + }; + if (!!strippedQueryCustomizer._seek) { + delete strippedQueryCustomizer._seek.lastItem; + } + // we need to reset _seek so that previous configuration is erased + return { + ...strippedQueryCustomizer, + _orderBy, + _seek: { + limit: rowsPerPage + 1, + }, + }; + }); + } + + async function handlePageChange(isNext: boolean) { + setQueryCustomizer((prevQueryCustomizer) => { + return { + ...prevQueryCustomizer, + _seek: { + limit: isNext ? rowsPerPage + 1 : rowsPerPage, + reverse: !isNext, + lastItem: isNext ? lastItem : firstItem, + }, + }; + }); + + setIsNextButtonEnabled(!isNext); + } + + let handleIsRowSelectable: any; + let handleOnSelection: any; + if (containerIsSelector) { + handleIsRowSelectable = useCallback((params: GridRowParams & { __selected?: boolean }>) => { + if (!containerIsRelationSelector) { + // For operation inputs, we allow all elements to be selected. + return true; + } else { + if (containerIsRelationSelector) { + return isRowSelectable(params.row, !allowSelectMultiple, alreadySelected); + } else { + return isRowSelectable(params.row, !allowSelectMultiple); + } + } + }, []); + + handleOnSelection = (newSelectionModel: GridRowSelectionModel) => { + if (!Array.isArray(selectionModel)) return; + if (allowSelectMultiple) { + // added new items + if (newSelectionModel.length > selectionModel.length) { + const diff = newSelectionModel.length - selectionModel.length; + const newItemsId = [...newSelectionModel].slice(diff * -1); + const newItems: TStored[] = data.filter( + (value) => newItemsId.indexOf(value.__identifier as GridRowId) !== -1, + ) as TStored[]; + if (setSelectionDiff !== undefined) { + setSelectionDiff((prevSelectedItems: TStored[]) => { + if (!Array.isArray(prevSelectedItems)) return []; + return [...prevSelectedItems, ...newItems]; + }); + } + } + // removed items + if (newSelectionModel.length < selectionModel.length) { + const removedItemsId = selectionModel.filter((value) => newSelectionModel.indexOf(value) === -1); + if (setSelectionDiff !== undefined) { + setSelectionDiff((prevSelectedItems: TStored[]) => { + if (!Array.isArray(prevSelectedItems)) return []; + return [ + ...prevSelectedItems.filter((value) => removedItemsId.indexOf(value.__identifier as GridRowId) === -1), + ]; + }); + } + } + setSelectionModel(newSelectionModel); + } else { + if (newSelectionModel.length === 0) { + setSelectionModel([]); + if (setSelectionDiff !== undefined) { + setSelectionDiff([]); + return; + } + } + + const lastId = newSelectionModel[newSelectionModel.length - 1]; + + setSelectionModel([lastId]); + if (setSelectionDiff !== undefined) { + setSelectionDiff([(data as TStored[]).find((value) => value.__identifier === lastId)!]); + } + } + }; + } else { + handleOnSelection = (newSelectionModel: GridRowSelectionModel) => { + selectedRows.current = getUpdatedRowsSelected(selectedRows, data, newSelectionModel); + setSelectionModel(selectedRows.current.map(getRowIdentifier)); + }; + } + + async function fetching() { + setIsInternalLoading(true); + + try { + const processedQueryCustomizer = hasRefreshAction + ? { + ...processQueryCustomizer(queryCustomizer), + _mask: maskAction ? maskAction() : queryCustomizer._mask, + } + : { + ...processQueryCustomizer(queryCustomizer), + }; + const { data: res, headers } = await fetch!(processedQueryCustomizer); + + if (showTotalCount) { + setTotalCount(headers[X_JUDO_COUNT] ? Number(headers[X_JUDO_COUNT]) : -1); + } + + if (res.length > rowsPerPage) { + setIsNextButtonEnabled(true); + res.pop(); + } else if (queryCustomizer._seek?.limit === rowsPerPage + 1) { + setIsNextButtonEnabled(false); + } + + setData(res); + setFirstItem(res[0]); + setLastItem(res[res.length - 1]); + setRowCount(res.length || 0); + } catch (error) { + handleError(error); + } finally { + setIsInternalLoading(false); + } + } + + async function fetchData() { + if (!containerHasTable) { + if (ownerData && ownerData.__signedIdentifier) { + await fetching(); + } + } else { + await fetching(); + } + } + + useEffect(() => { + fetchData(); + handleOnSelection(selectionModel); + }, [queryCustomizer, refreshCounter]); + + let additionalToolbarActions: Record; + + if (additionalToolbarButtons) { + if (containerHasTable) { + additionalToolbarActions = additionalToolbarButtons(data, isLoading, getSelectedRows(), clearSelections); + } else { + additionalToolbarActions = additionalToolbarButtons( + data, + isLoading, + getSelectedRows(), + clearSelections, + ownerData, + editMode, + isFormUpdateable, + ); + } + } else { + additionalToolbarActions = {}; + } + + const AdditionalToolbarActions = () => { + return ( + <> + {Object.keys(additionalToolbarActions).map((key) => { + const AdditionalButton = additionalToolbarActions[key]; + return ; + })} + + ); + }; + + async function filterAction(id: string) { + const filterResults = await actions[calculateActionName(relationName, 'filterAction')]!( + id, + filterOptions, + filterModel, + filters, + ); + if (filterResults.model) { + handleFilterModelChange({ + ...filterResults.model!, + }); + } + } + + return ( + <> + + (props.validationError ? `2px solid ${theme.palette.error.main}` : undefined), + }} + slotProps={{ + cell: { + onContextMenu: handleContextMenu, + }, + filterPanel: { + logicOperators: [GridLogicOperator.And], + }, + }} + getRowId={getRowIdentifier} + loading={isLoading} + rows={data} + getRowClassName={(params: GridRowClassNameParams) => { + return params.indexRelativeToCurrentPage % 2 === 0 ? 'even' : 'odd'; + }} + columns={effectiveTableColumns} + onColumnOrderChange={onColumnsChanged} + onColumnWidthChange={onColumnsChanged} + disableRowSelectionOnClick={!containerIsSelector} + isRowSelectable={handleIsRowSelectable} + hideFooterSelectedRowCount={!allowSelectMultiple} + checkboxSelection={checkboxSelection !== false} + rowSelectionModel={selectionModel} + onRowSelectionModelChange={handleOnSelection} + keepNonExistentRowsSelected + onRowClick={onRowClick} + sortModel={sortModel} + onSortModelChange={handleSortModelChange} + filterModel={filterModel} + onFilterModelChange={handleFilterModelChange} + paginationModel={paginationModel} + onPaginationModelChange={setPaginationModel} + paginationMode="server" + sortingMode="server" + filterMode="server" + filterDebounceMs={filterDebounceMs} + rowCount={rowsPerPage} + slots={{ + toolbar: () => ( + + {toolBarActions.map((toolBarAction: ToolBarActionProps) => + actions[toolBarAction.name] && + toolBarAction.enabled(data, selectionModel, ownerData, isFormUpdateable) ? ( + + ) : null, + )} + {} +
{/* Placeholder */}
+
+ ), + pagination: () => ( + + ), + }} + /> + {validationError ? ( + theme.palette.error.main, + display: 'flex', + alignItems: 'center', + pl: 1, + pr: 1, + }} + > + + {validationError} + + ) : null} + { + handleFilterModelChange({ + ...filterModel, + items: [...filterModel.items, mapFilterToFilterModel(filter)], + }); + }} + onExcludeByCell={(filter: Filter) => { + handleFilterModelChange({ + ...filterModel, + items: [...filterModel.items, mapFilterToFilterModel(filter)], + }); + }} + /> + + ); +} diff --git a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Table/components/ViewGalaxyTableTableComponent/index.tsx.snapshot b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Table/components/ViewGalaxyTableTableComponent/index.tsx.snapshot new file mode 100644 index 00000000..caeecfd2 --- /dev/null +++ b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/containers/View/Galaxy/Table/components/ViewGalaxyTableTableComponent/index.tsx.snapshot @@ -0,0 +1,602 @@ +////////////////////////////////////////////////////////////////////////////// +// G E N E R A T E D S O U R C E +// -------------------------------- +// Factory expression: #getTablesForPageContainers(#application) +// Path expression: 'src/containers/'+#containerPath(#self.pageContainer)+'/components/'+#tableComponentName(#self)+'/index.tsx' +// Template name: actor/src/containers/components/table/index.tsx +// Template file: actor/src/containers/components/table/index.tsx.hbs + +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import { GridLogicOperator, useGridApiRef } from '@mui/x-data-grid-pro'; +import { gridColumnDefinitionsSelector } from '@mui/x-data-grid-pro'; +import type { + GridColDef, + GridFilterModel, + GridRenderCellParams, + GridRowClassNameParams, + GridRowId, + GridRowModel, + GridRowParams, + GridRowSelectionModel, + GridSortItem, + GridSortModel, + GridValidRowModel, +} from '@mui/x-data-grid-pro'; +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { ComponentProxy, useTrackComponent } from '@pandino/react-hooks'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import type { Dispatch, ElementType, FC, MouseEvent, SetStateAction } from 'react'; +import { useTranslation } from 'react-i18next'; +import { CustomTablePagination, MdiIcon } from '~/components'; +import type { Filter, FilterOption } from '~/components-api'; +import { FilterType } from '~/components-api'; +import { useConfirmDialog } from '~/components/dialog'; +import { + ContextMenu, + EagerTable, + LazyTable, + StripedDataGrid, + booleanColumnOperators, + columnsActionCalculator, + dateTimeColumnOperators, + numericColumnOperators, + stringColumnOperators, +} from '~/components/table'; +import type { ContextMenuApi } from '~/components/table/ContextMenu'; +import { baseColumnConfig, basePageSizeOptions, baseTableConfig, filterDebounceMs } from '~/config'; +import { CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY } from '~/custom'; +import { useL10N } from '~/l10n/l10n-context'; +import type { JudoIdentifiable } from '~/services/data-api/common/JudoIdentifiable'; +import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy'; +import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer'; +import { ViewGalaxyStoredSerializer } from '~/services/data-api/rest/ViewGalaxySerializer'; +import type { JudoRestResponse } from '~/services/data-api/rest/requestResponse'; +import { + TABLE_COLUMN_CUSTOMIZER_HOOK_INTERFACE_KEY, + deserializeFilters, + getUpdatedRowsSelected, + mapAllFiltersToQueryCustomizerProperties, + mapFilterModelToFilters, + mapFilterToFilterModel, + processQueryCustomizer, + randomUtils, + serializeFilters, + useErrorHandler, +} from '~/utilities'; +import type { + ColumnCustomizerHook, + DialogResult, + FiltersSerializer, + PersistedColumnInfo, + SidekickComponentProps, + TableRowAction, + ToolBarActionProps, +} from '~/utilities'; +import type { ViewGalaxyTableTableComponentActionDefinitions, ViewGalaxyTableTableComponentProps } from './types'; + +export const VIEW_GALAXY_TABLE_TABLE_COMPONENT_SIDEKICK_COMPONENT_INTERFACE_KEY = + 'ViewGalaxyTableTableComponentSidekickComponent'; + +export const filtersSerializer: FiltersSerializer = { + serialize: (filters: Filter[]) => + serializeFilters(filters, ViewGalaxyStoredSerializer.getInstance()), + deserialize: (filters: Filter[]) => + deserializeFilters(filters, ViewGalaxyStoredSerializer.getInstance()), +}; + +// XMIID: God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable +// Name: Table +export function ViewGalaxyTableTableComponent(props: ViewGalaxyTableTableComponentProps) { + const { uniqueId, actions, dataPath, refreshCounter, isOwnerLoading, isDraft, validationError } = props; + const sidekickComponentFilter = `(&(${OBJECTCLASS}=${CUSTOM_VISUAL_ELEMENT_INTERFACE_KEY})(component=${VIEW_GALAXY_TABLE_TABLE_COMPONENT_SIDEKICK_COMPONENT_INTERFACE_KEY}))`; + + const { openConfirmDialog } = useConfirmDialog(); + const { t } = useTranslation(); + const handleError = useErrorHandler(); + const { locale: l10nLocale } = useL10N(); + + const nameColumn: GridColDef = { + ...baseColumnConfig, + field: 'name', + headerName: t('View.Galaxy.Table.name', { defaultValue: 'Name' }) as string, + headerClassName: 'data-grid-column-header', + + width: 230, + type: 'string', + filterable: true && true, + filterOperators: stringColumnOperators, + }; + const realColumn: GridColDef = { + ...baseColumnConfig, + field: 'real', + headerName: t('View.Galaxy.Table.real', { defaultValue: 'Real' }) as string, + headerClassName: 'data-grid-column-header', + + width: 100, + type: 'boolean', + filterable: true && true, + align: 'center', + renderCell: (params: GridRenderCellParams) => { + if (params.row.real === null || params.row.real === undefined) { + return ; + } else if (params.row.real) { + return ; + } + return ; + }, + filterOperators: booleanColumnOperators, + }; + const constellationColumn: GridColDef = { + ...baseColumnConfig, + field: 'constellation', + headerName: t('View.Galaxy.Table.constellation', { defaultValue: 'Constellation' }) as string, + headerClassName: 'data-grid-column-header', + + width: 230, + type: 'string', + filterable: true && true, + filterOperators: stringColumnOperators, + }; + const magnitudeColumn: GridColDef = { + ...baseColumnConfig, + field: 'magnitude', + headerName: t('View.Galaxy.Table.magnitude', { defaultValue: 'Magnitude' }) as string, + headerClassName: 'data-grid-column-header', + + width: 100, + type: 'number', + filterable: true && true, + valueFormatter: (value?: number) => { + if (value === null || value === undefined) { + return ''; + } + return new Intl.NumberFormat(l10nLocale).format(value); + }, + filterOperators: numericColumnOperators, + }; + const nakedEyeColumn: GridColDef = { + ...baseColumnConfig, + field: 'nakedEye', + headerName: t('View.Galaxy.Table.nakedEye', { defaultValue: 'Naked Eye' }) as string, + headerClassName: 'data-grid-column-header', + + width: 100, + type: 'boolean', + filterable: true && true, + align: 'center', + renderCell: (params: GridRenderCellParams) => { + if (params.row.nakedEye === null || params.row.nakedEye === undefined) { + return ; + } else if (params.row.nakedEye) { + return ; + } + return ; + }, + filterOperators: booleanColumnOperators, + }; + const darkMatterColumn: GridColDef = { + ...baseColumnConfig, + field: 'darkMatter', + headerName: t('View.Galaxy.Table.darkMatter', { defaultValue: 'Dark Matter' }) as string, + headerClassName: 'data-grid-column-header', + + width: 100, + type: 'number', + filterable: true && true, + valueFormatter: (value?: number) => { + if (value === null || value === undefined) { + return ''; + } + return new Intl.NumberFormat(l10nLocale).format(value); + }, + filterOperators: numericColumnOperators, + }; + const intergalacticDustColumn: GridColDef = { + ...baseColumnConfig, + field: 'intergalacticDust', + headerName: t('View.Galaxy.Table.intergalacticDust', { defaultValue: 'Intergalactic Dust' }) as string, + headerClassName: 'data-grid-column-header', + + width: 100, + type: 'number', + filterable: true && true, + valueFormatter: (value?: number) => { + if (value === null || value === undefined) { + return ''; + } + return new Intl.NumberFormat(l10nLocale).format(value); + }, + filterOperators: numericColumnOperators, + }; + const interstellarMediumColumn: GridColDef = { + ...baseColumnConfig, + field: 'interstellarMedium', + headerName: t('View.Galaxy.Table.interstellarMedium', { defaultValue: 'Interstellar Medium' }) as string, + headerClassName: 'data-grid-column-header', + + width: 100, + type: 'number', + filterable: true && true, + valueFormatter: (value?: number) => { + if (value === null || value === undefined) { + return ''; + } + return new Intl.NumberFormat(l10nLocale).format(value); + }, + filterOperators: numericColumnOperators, + }; + const discoveredColumn: GridColDef = { + ...baseColumnConfig, + field: 'discovered', + headerName: t('View.Galaxy.Table.discovered', { defaultValue: 'Discovered' }) as string, + headerClassName: 'data-grid-column-header', + + width: 170, + type: 'dateTime', + filterable: true && true, + valueFormatter: (value?: Date) => { + return ( + value && + new Intl.DateTimeFormat(l10nLocale, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + }).format(value) + ); + }, + filterOperators: dateTimeColumnOperators, + }; + + const columns = useMemo[]>( + () => [ + nameColumn, + realColumn, + constellationColumn, + magnitudeColumn, + nakedEyeColumn, + darkMatterColumn, + intergalacticDustColumn, + interstellarMediumColumn, + discoveredColumn, + ], + [l10nLocale], + ); + + const toolBarActions: ToolBarActionProps[] = [ + { + name: 'filterAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTableFilterButton', + startIcon: 'filter', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.filter', defaultValue: 'Set Filters' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => true, + isBulk: false, + }, + { + name: 'refreshAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTableRefreshButton', + startIcon: 'refresh', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.refresh', defaultValue: 'Refresh' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => true, + isBulk: false, + }, + { + name: 'exportAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableExportButton', + startIcon: 'file-export-outline', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.export', defaultValue: 'Export' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => true, + isBulk: false, + }, + { + name: 'openCreateFormAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableCreateButton', + startIcon: 'note-add', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.open-create-form', defaultValue: 'Create' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => true, + isBulk: false, + }, + { + name: 'openAddSelectorAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableAddSelectorButton', + startIcon: 'attachment-plus', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.open-add-selector', defaultValue: 'Add' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => true, + isBulk: false, + }, + { + name: 'openSetSelectorAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableSetSelectorButton', + startIcon: 'attachment-plus', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.open-set-selector', defaultValue: 'Set' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => true, + isBulk: false, + }, + { + name: 'clearAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableClearButton', + startIcon: 'link_off', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.clear', defaultValue: 'Clear' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => data.length > 0, + isBulk: false, + }, + { + name: 'bulkRemoveAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableBulkRemoveButton', + startIcon: 'link_off', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.bulk-remove', defaultValue: 'Remove' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => selectionModel.length > 0, + isBulk: true, + }, + { + name: 'bulkDeleteAction', + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableBulkDeleteButton', + startIcon: 'delete_forever', + variant: 'text', + hiddenBy: false, + label: { translationKey: 'judo.action.bulk-delete', defaultValue: 'Delete' }, + enabled: ( + data: ViewGalaxyStored[], + selectionModel: GridRowSelectionModel, + ownerData?: any, + isFormUpdateable?: () => boolean, + ): boolean => selectionModel.length > 0, + isBulk: true, + }, + ]; + + const rowActions: TableRowAction[] = [ + { + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableRowRemoveButton', + label: t('judo.action.remove', { defaultValue: 'Remove' }) as string, + icon: , + isCRUD: true, + disabled: ( + row: ViewGalaxyStored, + isLoading: boolean, + getSelectedRows: () => ViewGalaxyStored[], + ownerdata?: any, + ): boolean => (getSelectedRows && getSelectedRows().length > 0) || isLoading, + action: actions.removeAction + ? async (rowData) => { + await actions.removeAction!(rowData); + } + : undefined, + }, + { + id: 'God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableRowDeleteButton', + label: t('View.Galaxy.Table.Delete', { defaultValue: 'Delete' }) as string, + icon: , + isCRUD: true, + disabled: ( + row: ViewGalaxyStored, + isLoading: boolean, + getSelectedRows: () => ViewGalaxyStored[], + ownerdata?: any, + ): boolean => + (getSelectedRows && getSelectedRows().length > 0) || + (typeof row.__deleteable === 'boolean' && !row.__deleteable) || + isLoading, + action: actions.rowDeleteAction + ? async (rowData) => { + await actions.rowDeleteAction!(rowData); + } + : undefined, + }, + { + id: 'God/(esm/_uqp7kDV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallOperationButton/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTable)', + label: t('View.Galaxy.Table.createDarkMatter', { defaultValue: 'createDarkMatter' }) as string, + icon: , + disabled: ( + row: ViewGalaxyStored, + isLoading: boolean, + getSelectedRows: () => ViewGalaxyStored[], + ownerdata?: any, + ): boolean => (getSelectedRows && getSelectedRows().length > 0) || isLoading, + action: actions.createDarkMatterAction + ? async (rowData) => { + await actions.createDarkMatterAction!(rowData); + } + : undefined, + }, + { + id: 'God/(esm/_uqqioTV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallOperationButton/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTable)', + label: t('View.Galaxy.Table.createIntergalacticDust', { defaultValue: 'createIntergalacticDust' }) as string, + icon: , + disabled: ( + row: ViewGalaxyStored, + isLoading: boolean, + getSelectedRows: () => ViewGalaxyStored[], + ownerdata?: any, + ): boolean => (getSelectedRows && getSelectedRows().length > 0) || isLoading, + action: actions.createIntergalacticDustAction + ? async (rowData) => { + await actions.createIntergalacticDustAction!(rowData); + } + : undefined, + }, + { + id: 'God/(esm/_uqqiozV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallOperationButton/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTable)', + label: t('View.Galaxy.Table.createInterstellarMedium', { defaultValue: 'createInterstellarMedium' }) as string, + icon: , + disabled: ( + row: ViewGalaxyStored, + isLoading: boolean, + getSelectedRows: () => ViewGalaxyStored[], + ownerdata?: any, + ): boolean => (getSelectedRows && getSelectedRows().length > 0) || isLoading, + action: actions.createInterstellarMediumAction + ? async (rowData) => { + await actions.createInterstellarMediumAction!(rowData); + } + : undefined, + }, + ]; + + const filterOptions: FilterOption[] = [ + { + id: 'God/(esm/_8AiKcE7tEeycO-gUAWxcVg)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'name', + label: t('View.Galaxy.Table.name', { defaultValue: 'Name' }) as string, + filterType: FilterType.string, + }, + + { + id: 'God/(esm/_EIBPIFjXEeyV2_3ibolaNQ)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'real', + label: t('View.Galaxy.Table.real', { defaultValue: 'Real' }) as string, + filterType: FilterType.trinaryLogic, + }, + + { + id: 'God/(esm/_Vne4AFjJEeyV2_3ibolaNQ)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'constellation', + label: t('View.Galaxy.Table.constellation', { defaultValue: 'Constellation' }) as string, + filterType: FilterType.string, + }, + + { + id: 'God/(esm/_Vnh7UFjJEeyV2_3ibolaNQ)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'magnitude', + label: t('View.Galaxy.Table.magnitude', { defaultValue: 'Magnitude' }) as string, + filterType: FilterType.numeric, + }, + + { + id: 'God/(esm/_Vnh7UVjJEeyV2_3ibolaNQ)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'nakedEye', + label: t('View.Galaxy.Table.nakedEye', { defaultValue: 'Naked Eye' }) as string, + filterType: FilterType.trinaryLogic, + }, + + { + id: 'God/(esm/_cE_G8FjJEeyV2_3ibolaNQ)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'darkMatter', + label: t('View.Galaxy.Table.darkMatter', { defaultValue: 'Dark Matter' }) as string, + filterType: FilterType.numeric, + }, + + { + id: 'God/(esm/_cFAVEFjJEeyV2_3ibolaNQ)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'intergalacticDust', + label: t('View.Galaxy.Table.intergalacticDust', { defaultValue: 'Intergalactic Dust' }) as string, + filterType: FilterType.numeric, + }, + + { + id: 'God/(esm/_cFA8IFjJEeyV2_3ibolaNQ)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'interstellarMedium', + label: t('View.Galaxy.Table.interstellarMedium', { defaultValue: 'Interstellar Medium' }) as string, + filterType: FilterType.numeric, + }, + + { + id: 'God/(esm/_ihOqMBMcEe2_DOUDKkB20Q)/TableColumnFilter/(discriminator/God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable)', + attributeName: 'discovered', + label: t('View.Galaxy.Table.discovered', { defaultValue: 'Discovered' }) as string, + filterType: FilterType.dateTime, + }, + ]; + + return ( +
+ + uniqueId={`God/(esm/_YT0hQE7rEeycO-gUAWxcVg)/TransferObjectTableTable-${uniqueId}`} + dataProp={[]} + defaultSortParamsForTable={[ + { field: 'name', sort: 'asc' }, + { field: 'magnitude', sort: 'desc' }, + ]} + tablePageLimit={25} + tableColumns={columns} + tableRowActions={rowActions} + tableFilterOptions={filterOptions} + isOwnerLoading={isOwnerLoading} + validationError={validationError} + actions={actions} + dataElementId={'God/(esm/_YTkpoE7rEeycO-gUAWxcVg)/ClassType'} + crudOperationsDisplayed={1} + transferOperationsDisplayed={0} + onRowClick={ + actions.openPageAction + ? async (params: GridRowParams) => await actions.openPageAction!(params.row, false) + : undefined + } + sidekickComponentFilter={sidekickComponentFilter} + containerHasTable={true} + maskAction={actions.getMask!} + fetch={actions.refreshAction} + refreshCounter={refreshCounter} + toolBarActions={toolBarActions} + additionalToolbarButtons={actions?.AdditionalToolbarButtons} + tableHasSelectorColumn={true} + enabledByName="" + relationName="" + filtersSerializer={filtersSerializer} + /> +
+ ); +} diff --git a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessTablePage/index.tsx.snapshot b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessTablePage/index.tsx.snapshot new file mode 100644 index 00000000..3fc2e9ae --- /dev/null +++ b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessTablePage/index.tsx.snapshot @@ -0,0 +1,299 @@ +////////////////////////////////////////////////////////////////////////////// +// G E N E R A T E D S O U R C E +// -------------------------------- +// Factory expression: #getPagesForRouting(#application) +// Path expression: 'src/pages/'+#pagePath(#self)+'/index.tsx' +// Template name: actor/src/pages/index.tsx +// Template file: actor/src/pages/index.tsx.hbs + +import type { GridFilterModel } from '@mui/x-data-grid-pro'; +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { useTrackService } from '@pandino/react-hooks'; +import { Suspense, lazy, useCallback, useMemo, useRef, useState } from 'react'; +import type { Dispatch, FC, ReactNode, SetStateAction } from 'react'; +import { useTranslation } from 'react-i18next'; +import { v4 as uuidv4 } from 'uuid'; +import { useJudoNavigation } from '~/components'; +import type { Filter, FilterOption } from '~/components-api'; +import { useConfirmDialog, useFilterDialog } from '~/components/dialog'; +import type { ViewGalaxyTablePageActions, ViewGalaxyTablePageProps } from '~/containers/View/Galaxy/Table/types'; +import { useGodGodGalaxiesAccessFormPage } from '~/dialogs/God/God/Galaxies/AccessFormPage/hooks'; +import { useViewGalaxyTableCreateDarkMatterInputForm } from '~/dialogs/View/Galaxy/Table/CreateDarkMatter/Input/Form/hooks'; +import { useViewGalaxyTableCreateIntergalacticDustInputForm } from '~/dialogs/View/Galaxy/Table/CreateIntergalacticDust/Input/Form/hooks'; +import { useViewGalaxyTableCreateInterstellarMediumInputForm } from '~/dialogs/View/Galaxy/Table/CreateInterstellarMedium/Input/Form/hooks'; +import { useCRUDDialog, useSnacks, useViewData } from '~/hooks'; +import { routeToGodGodGalaxiesAccessViewPage } from '~/routes'; +import type { JudoIdentifiable } from '~/services/data-api/common/JudoIdentifiable'; +import { draftIdentifierPrefix } from '~/services/data-api/common/utils'; +import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy'; +import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer'; +import type { JudoRestResponse } from '~/services/data-api/rest/requestResponse'; +import { GodServiceForGalaxiesImpl } from '~/services/data-axios/GodServiceForGalaxiesImpl'; +import { judoAxiosProvider } from '~/services/data-axios/JudoAxiosProvider'; +import { PageContainerTransition } from '~/theme/animations'; +import { getValue, processQueryCustomizer, setValue, simpleCloneDeep, useErrorHandler } from '~/utilities'; +import type { DialogResult } from '~/utilities'; +import { type ViewGalaxyTableViewModel, ViewGalaxyTableViewModelContext } from './context'; +import type { ViewGalaxyTableActionsHook } from './customization'; +import { GOD_GOD_GALAXIES_ACCESS_TABLE_PAGE_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; +import type { ViewGalaxyTablePageActionsExtended } from './types'; + +const ViewGalaxyTablePageContainer = lazy(() => import('~/containers/View/Galaxy/Table/ViewGalaxyTablePageContainer')); + +// XMIID: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTablePageDefinition +// Name: God::God::galaxies::AccessTablePage +export default function GodGodGalaxiesAccessTablePage() { + const dataPath = ''; + const isDraft = false; + const owner = useRef(null); + + // Services + const godServiceForGalaxiesImpl = useMemo(() => new GodServiceForGalaxiesImpl(judoAxiosProvider), []); + + // Hooks section + const { t } = useTranslation(); + const { showSuccessSnack, showErrorSnack } = useSnacks(); + const { navigate, back: navigateBack } = useJudoNavigation(); + const { openFilterDialog } = useFilterDialog(); + const { openConfirmDialog } = useConfirmDialog(); + const { setLatestViewData, setRouterPageData } = useViewData(); + const handleError = useErrorHandler(); + const openCRUDDialog = useCRUDDialog(); + + // State section + const [isLoading, setIsLoading] = useState(false); + const [editMode, setEditMode] = useState(false); + const [refreshCounter, setRefreshCounter] = useState(0); + const [data, setData] = useState([]); + + // Masks + const getMask: () => string = () => { + return '{constellation,darkMatter,discovered,intergalacticDust,interstellarMedium,magnitude,nakedEye,name,real}'; + }; + + // Private actions + const submit = async () => {}; + const refresh = async (forced = false) => { + setRefreshCounter((prev) => prev + 1); + }; + const produceDataAdjustedOwner = useCallback(() => { + const copy = simpleCloneDeep(owner.current); + setValue(copy, dataPath, simpleCloneDeep(data)); + return copy; + }, [data, owner]); + + // Validation + + // Pandino Action overrides + const { service: customActionsHook } = useTrackService( + `(${OBJECTCLASS}=${GOD_GOD_GALAXIES_ACCESS_TABLE_PAGE_ACTIONS_HOOK_INTERFACE_KEY})`, + ); + const customActions: ViewGalaxyTablePageActionsExtended | undefined = customActionsHook?.(data, editMode); + + // Dialog hooks + const openGodGodGalaxiesAccessFormPage = useGodGodGalaxiesAccessFormPage(); + const openViewGalaxyTableCreateDarkMatterInputForm = useViewGalaxyTableCreateDarkMatterInputForm(); + const openViewGalaxyTableCreateIntergalacticDustInputForm = useViewGalaxyTableCreateIntergalacticDustInputForm(); + const openViewGalaxyTableCreateInterstellarMediumInputForm = useViewGalaxyTableCreateInterstellarMediumInputForm(); + + // Action section + const getPageTitle = (): string => { + return t('View.Galaxy.Table', { defaultValue: 'Galaxies' }); + }; + // BackAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableBackAction + const backAction = async () => { + navigateBack(); + }; + // BulkDeleteAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableBulkDeleteAction + const bulkDeleteAction = async (selectedRows: ViewGalaxyStored[]): Promise>> => { + return new Promise((resolve) => { + openCRUDDialog({ + dialogTitle: t('judo.action.bulk-delete', { defaultValue: 'Delete' }), + itemTitleFn: (item) => (actions?.getRowRepresentation ? actions.getRowRepresentation(item) : item.name!), + selectedItems: selectedRows, + action: async (item, successHandler: () => void, errorHandler: (error: any) => void) => { + try { + await godServiceForGalaxiesImpl.delete(item); + successHandler(); + } catch (error) { + errorHandler(error); + } + }, + autoCloseOnSuccess: true, + onClose: async (needsRefresh) => { + if (needsRefresh) { + setRefreshCounter((prev) => prev + 1); + resolve({ + result: 'delete', + data: [], + }); + } else { + resolve({ + result: 'close', + data: [], + }); + } + }, + }); + }); + }; + // OpenCreateFormAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableCreateAction + const openCreateFormAction = async () => { + const { + result, + data: returnedData, + openCreated, + } = await openGodGodGalaxiesAccessFormPage({ + ownerData: produceDataAdjustedOwner(), + isDraft: false, + dataPath: `${dataPath ? dataPath + '.' : ''}`, + }); + if (result === 'submit') { + setRefreshCounter((prev) => prev + 1); + } + if (openCreated && returnedData) { + await openPageAction(returnedData!); + } + }; + // RowDeleteAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableRowDeleteAction + const rowDeleteAction = async (target: ViewGalaxyStored) => { + try { + const confirmed = await openConfirmDialog( + 'row-delete-action', + t('judo.modal.confirm.confirm-delete', { + defaultValue: 'Are you sure you would like to delete the selected element?', + }), + t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }), + ); + if (confirmed) { + await godServiceForGalaxiesImpl.delete(target); + showSuccessSnack(t('judo.action.delete.success', { defaultValue: 'Delete successful' })); + setRefreshCounter((prev) => prev + 1); + } + } catch (error) { + handleError(error, undefined, target); + } + }; + // FilterAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableTableFilterAction + const filterAction = async ( + id: string, + filterOptions: FilterOption[], + model?: GridFilterModel, + filters?: Filter[], + ): Promise<{ model?: GridFilterModel; filters?: Filter[] }> => { + const newFilters = await openFilterDialog(id, filterOptions, filters); + return { + filters: newFilters, + }; + }; + // RefreshAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableTableRefreshAction + const refreshAction = async ( + queryCustomizer: ViewGalaxyQueryCustomizer, + ): Promise> => { + try { + setIsLoading(true); + setEditMode(false); + return godServiceForGalaxiesImpl.list(undefined, queryCustomizer); + } catch (error) { + handleError(error); + setLatestViewData(null); + setRouterPageData(null); + return Promise.reject(error); + } finally { + setIsLoading(false); + } + }; + // OpenPageAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTableRowViewAction + const openPageAction = async (target: ViewGalaxyStored, isDraftParam?: boolean) => { + // if the `target` is missing we are likely navigating to a relation table page, in which case we need the owner's id + navigate(routeToGodGodGalaxiesAccessViewPage((target as ViewGalaxyStored)!.__signedIdentifier)); + }; + // OpenOperationInputFormAction: God/(esm/_uqp7kDV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTablePageDefinition) + const createDarkMatterAction = async ( + target: ViewGalaxyStored, + isDraft?: boolean, + ownerValidation?: (target: any) => Promise, + ) => { + const { result, data: returnedData } = await openViewGalaxyTableCreateDarkMatterInputForm({ + ownerData: target, + }); + if (result === 'submit') { + await refresh(); + } + }; + // OpenOperationInputFormAction: God/(esm/_uqqioTV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTablePageDefinition) + const createIntergalacticDustAction = async ( + target: ViewGalaxyStored, + isDraft?: boolean, + ownerValidation?: (target: any) => Promise, + ) => { + const { result, data: returnedData } = await openViewGalaxyTableCreateIntergalacticDustInputForm({ + ownerData: target, + }); + if (result === 'submit') { + await refresh(); + } + }; + // OpenOperationInputFormAction: God/(esm/_uqqiozV3Ee-rMcMaep1uNQ)/OperationFormTableRowCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessTablePageDefinition) + const createInterstellarMediumAction = async ( + target: ViewGalaxyStored, + isDraft?: boolean, + ownerValidation?: (target: any) => Promise, + ) => { + const { result, data: returnedData } = await openViewGalaxyTableCreateInterstellarMediumInputForm({ + ownerData: target, + }); + if (result === 'submit') { + await refresh(); + } + }; + + const actions: ViewGalaxyTablePageActions = { + getPageTitle, + backAction, + bulkDeleteAction, + openCreateFormAction, + rowDeleteAction, + filterAction, + refreshAction, + openPageAction, + createDarkMatterAction, + createIntergalacticDustAction, + createInterstellarMediumAction, + getMask, + ...(customActions ?? {}), + }; + + // ViewModel setup + const viewModel: ViewGalaxyTableViewModel = { + actions, + isLoading, + setIsLoading, + refreshCounter, + editMode, + setEditMode, + refresh, + }; + + // Effect section + + return ( + + +
+ + + + + + ); +} diff --git a/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessViewPage/index.tsx.snapshot b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessViewPage/index.tsx.snapshot new file mode 100644 index 00000000..07e4dcef --- /dev/null +++ b/judo-ui-react-itest/ActionGroupTestPro/action_group_test_pro__god/src/test/resources/snapshots/frontend-react/src/pages/God/God/Galaxies/AccessViewPage/index.tsx.snapshot @@ -0,0 +1,603 @@ +////////////////////////////////////////////////////////////////////////////// +// G E N E R A T E D S O U R C E +// -------------------------------- +// Factory expression: #getPagesForRouting(#application) +// Path expression: 'src/pages/'+#pagePath(#self)+'/index.tsx' +// Template name: actor/src/pages/index.tsx +// Template file: actor/src/pages/index.tsx.hbs + +import type { GridFilterModel } from '@mui/x-data-grid-pro'; +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { useTrackService } from '@pandino/react-hooks'; +import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import type { Dispatch, FC, ReactNode, SetStateAction } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { v4 as uuidv4 } from 'uuid'; +import { useJudoNavigation } from '~/components'; +import type { Filter, FilterOption } from '~/components-api'; +import { useConfirmDialog, useFilterDialog } from '~/components/dialog'; +import type { ViewGalaxyViewPageActions, ViewGalaxyViewPageProps } from '~/containers/View/Galaxy/View/types'; +import { useViewGalaxyAstronomerRelationFormPage } from '~/dialogs/View/Galaxy/Astronomer/RelationFormPage/hooks'; +import { useViewGalaxyStarsRelationFormPage } from '~/dialogs/View/Galaxy/Stars/RelationFormPage/hooks'; +import { useViewGalaxyStarsRelationViewPage } from '~/dialogs/View/Galaxy/Stars/RelationViewPage/hooks'; +import { useViewGalaxyViewCreateDarkMatterInputForm } from '~/dialogs/View/Galaxy/View/CreateDarkMatter/Input/Form/hooks'; +import { useViewGalaxyViewCreateIntergalacticDustInputForm } from '~/dialogs/View/Galaxy/View/CreateIntergalacticDust/Input/Form/hooks'; +import { useViewGalaxyViewCreateInterstellarMediumInputForm } from '~/dialogs/View/Galaxy/View/CreateInterstellarMedium/Input/Form/hooks'; +import { useViewGalaxyViewGroupDiscovererAstronomerLinkSetSelectorPage } from '~/dialogs/View/Galaxy/View/Group/Discoverer/Astronomer/LinkSetSelectorPage/hooks'; +import { useCRUDDialog, useSnacks, useViewData } from '~/hooks'; +import { routeToViewGalaxyAstronomerRelationViewPage } from '~/routes'; +import { routeToViewGalaxyMatterRelationTablePage } from '~/routes'; +import type { JudoIdentifiable } from '~/services/data-api/common/JudoIdentifiable'; +import { draftIdentifierPrefix } from '~/services/data-api/common/utils'; +import type { ViewAstronomer, ViewAstronomerStored } from '~/services/data-api/model/ViewAstronomer'; +import type { ViewGalaxy, ViewGalaxyStored } from '~/services/data-api/model/ViewGalaxy'; +import type { ViewMatter, ViewMatterStored } from '~/services/data-api/model/ViewMatter'; +import type { ViewStar, ViewStarStored } from '~/services/data-api/model/ViewStar'; +import type { ViewAstronomerQueryCustomizer } from '~/services/data-api/rest/ViewAstronomerQueryCustomizer'; +import type { ViewGalaxyQueryCustomizer } from '~/services/data-api/rest/ViewGalaxyQueryCustomizer'; +import type { ViewMatterQueryCustomizer } from '~/services/data-api/rest/ViewMatterQueryCustomizer'; +import type { ViewStarQueryCustomizer } from '~/services/data-api/rest/ViewStarQueryCustomizer'; +import type { JudoRestResponse } from '~/services/data-api/rest/requestResponse'; +import { GodServiceForGalaxiesImpl } from '~/services/data-axios/GodServiceForGalaxiesImpl'; +import { judoAxiosProvider } from '~/services/data-axios/JudoAxiosProvider'; +import { PageContainerTransition } from '~/theme/animations'; +import { + fileHandling, + getValue, + processQueryCustomizer, + setValue, + simpleCloneDeep, + useErrorHandler, +} from '~/utilities'; +import type { DialogResult } from '~/utilities'; +import { type ViewGalaxyViewViewModel, ViewGalaxyViewViewModelContext } from './context'; +import type { ViewGalaxyViewActionsHook } from './customization'; +import { GOD_GOD_GALAXIES_ACCESS_VIEW_PAGE_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; +import type { ViewGalaxyViewPageActionsExtended } from './types'; + +const ViewGalaxyViewPageContainer = lazy(() => import('~/containers/View/Galaxy/View/ViewGalaxyViewPageContainer')); + +// XMIID: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition +// Name: God::God::galaxies::AccessViewPage +export default function GodGodGalaxiesAccessViewPage() { + const dataPath = ''; + const isDraft = false; + const owner = useRef(null); + + // Router params section + const { signedIdentifier } = useParams(); + + // Services + const godServiceForGalaxiesImpl = useMemo(() => new GodServiceForGalaxiesImpl(judoAxiosProvider), []); + + // Hooks section + const { t } = useTranslation(); + const { showSuccessSnack, showErrorSnack } = useSnacks(); + const { navigate, back: navigateBack } = useJudoNavigation(); + const { openFilterDialog } = useFilterDialog(); + const { openConfirmDialog } = useConfirmDialog(); + const { setLatestViewData, setRouterPageData } = useViewData(); + const handleError = useErrorHandler(); + const openCRUDDialog = useCRUDDialog(); + const { exportFile } = fileHandling(); + + // State section + const [isLoading, setIsLoading] = useState(false); + const [editMode, setEditMode] = useState(false); + const [refreshCounter, setRefreshCounter] = useState(0); + const [data, setData] = useState({ __signedIdentifier: signedIdentifier } as ViewGalaxyStored); + const [validation, setValidation] = useState>(new Map()); + + // Ref section + + // Callback section + const storeDiff: (attributeName: keyof ViewGalaxy, value: any) => void = useCallback( + (attributeName: keyof ViewGalaxy, value: any) => { + setData((prevData) => ({ + ...prevData, + [attributeName]: value, + })); + if (!editMode) { + setEditMode(true); + } + }, + [data, editMode], + ); + const isFormUpdateable = useCallback(() => { + return true && typeof data?.__updateable === 'boolean' && data?.__updateable; + }, [data]); + const isFormDeleteable = useCallback(() => { + return true && typeof data?.__deleteable === 'boolean' && data?.__deleteable; + }, [data]); + + const getPageQueryCustomizer: () => ViewGalaxyQueryCustomizer = () => ({ + _mask: actions.getMask + ? actions.getMask!() + : '{constellation,darkMatter,discovered,intergalacticDust,interstellarMedium,magnitude,nakedEye,name,originOfName,real,astronomer{born,name,derivedMessage{message},messages{message},singleMessage{message}},stars{name,lastObservation{date,name},observations{date,name},planets{habitable,inhabited,name,peaceful,creatures{id,name}}}}', + }); + + // Masks + const getMask = () => + '{constellation,darkMatter,discovered,intergalacticDust,interstellarMedium,magnitude,nakedEye,name,originOfName,real,astronomer{born,name,derivedMessage{message},messages{message},singleMessage{message}},stars{name,lastObservation{date,name},observations{date,name},planets{habitable,inhabited,name,peaceful,creatures{id,name}}}}'; + const getAstronomerMask = () => '{born,name,derivedMessage{message},messages{message},singleMessage{message}}'; + const getStarsMask = () => + '{name,lastObservation{date,name},observations{date,name},planets{habitable,inhabited,name,peaceful,creatures{id,name,punished,rewarded}}}'; + + // Private actions + const submit = async () => { + await updateAction(); + }; + const refresh = async (forced = false) => { + if (!editMode || forced) { + if (actions.refreshAction) { + await actions.refreshAction!(processQueryCustomizer(getPageQueryCustomizer())); + } + } + }; + const produceDataAdjustedOwner = useCallback(() => { + const copy = simpleCloneDeep(owner.current); + setValue(copy, dataPath, simpleCloneDeep(data)); + return copy; + }, [data, owner]); + + // Validation + const validate: (target: any) => Promise = useCallback( + async (target) => { + await godServiceForGalaxiesImpl.validateUpdate(target); + }, + [data, godServiceForGalaxiesImpl], + ); + + // Pandino Action overrides + const { service: customActionsHook } = useTrackService( + `(${OBJECTCLASS}=${GOD_GOD_GALAXIES_ACCESS_VIEW_PAGE_ACTIONS_HOOK_INTERFACE_KEY})`, + ); + const customActions: ViewGalaxyViewPageActionsExtended | undefined = customActionsHook?.( + data, + editMode, + storeDiff, + refresh, + submit, + ); + + // Dialog hooks + const openViewGalaxyViewCreateDarkMatterInputForm = useViewGalaxyViewCreateDarkMatterInputForm(); + const openViewGalaxyViewCreateIntergalacticDustInputForm = useViewGalaxyViewCreateIntergalacticDustInputForm(); + const openViewGalaxyViewCreateInterstellarMediumInputForm = useViewGalaxyViewCreateInterstellarMediumInputForm(); + const openViewGalaxyViewGroupDiscovererAstronomerLinkSetSelectorPage = + useViewGalaxyViewGroupDiscovererAstronomerLinkSetSelectorPage(); + const openViewGalaxyAstronomerRelationFormPage = useViewGalaxyAstronomerRelationFormPage(); + const openViewGalaxyStarsRelationFormPage = useViewGalaxyStarsRelationFormPage(); + const openViewGalaxyStarsRelationViewPage = useViewGalaxyStarsRelationViewPage(); + + // Action section + const getPageTitle = (data: ViewGalaxy): string => { + return data.name as string; + }; + // BackAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewBackAction + const backAction = async () => { + navigateBack(); + }; + // CancelAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewCancelAction + const cancelAction = async () => { + setValidation(new Map()); + // no need to set editMode to false, given refresh should do it implicitly + await refresh(true); + }; + // DeleteAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewDeleteAction + const deleteAction = async () => { + try { + const confirmed = await openConfirmDialog( + 'delete-action', + t('judo.modal.confirm.confirm-delete', { + defaultValue: 'Are you sure you would like to delete the selected element?', + }), + t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }), + ); + if (confirmed) { + await godServiceForGalaxiesImpl.delete(data); + showSuccessSnack(t('judo.action.delete.success', { defaultValue: 'Delete successful' })); + navigateBack(); + } + } catch (error) { + handleError(error, undefined, data); + } + }; + // RefreshAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewRefreshAction + const refreshAction = async ( + queryCustomizer: ViewGalaxyQueryCustomizer, + ): Promise> => { + try { + setIsLoading(true); + setEditMode(false); + const response = await godServiceForGalaxiesImpl.refresh( + { __signedIdentifier: signedIdentifier } as any, + getPageQueryCustomizer(), + ); + const { data: result } = response; + if (!isDraft) { + owner.current = result; + } + setData(result); + setLatestViewData(result); + setRouterPageData(result); + if (customActions?.postRefreshAction) { + await customActions?.postRefreshAction(result, storeDiff, setValidation); + } + return response; + } catch (error) { + handleError(error); + setLatestViewData(null); + setRouterPageData(null); + return Promise.reject(error); + } finally { + setIsLoading(false); + setRefreshCounter((prevCounter) => prevCounter + 1); + } + }; + // UpdateAction: God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewUpdateAction + const updateAction = async () => { + setIsLoading(true); + try { + const { data: res } = await godServiceForGalaxiesImpl.update(data); + if (res) { + showSuccessSnack(t('judo.action.save.success', { defaultValue: 'Changes saved' })); + setValidation(new Map()); + setEditMode(false); + await actions.refreshAction!(getPageQueryCustomizer()); + } + } catch (error) { + handleError(error, { setValidation }, data); + } finally { + setIsLoading(false); + } + }; + // AutocompleteRangeAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkAutocompleteRangeAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const astronomerAutocompleteRangeAction = async ( + queryCustomizer: ViewAstronomerQueryCustomizer, + ): Promise => { + try { + const { data: result } = await godServiceForGalaxiesImpl.getRangeForAstronomer(data, queryCustomizer); + return result; + } catch (error: any) { + handleError(error); + return Promise.resolve([]); + } + }; + // AutocompleteSetAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkAutocompleteSetAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const astronomerAutocompleteSetAction = async (selected: ViewAstronomerStored) => { + try { + storeDiff('astronomer', selected); + } catch (error) { + handleError(error); + return Promise.reject(error); + } + }; + // OpenCreateFormAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkCreateAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const astronomerOpenCreateFormAction = async () => { + const { + result, + data: returnedData, + openCreated, + } = await openViewGalaxyAstronomerRelationFormPage({ + ownerData: produceDataAdjustedOwner(), + ownerValidation: validate, + isDraft: true, + dataPath: `${dataPath ? dataPath + '.' : ''}astronomer`, + }); + if (result === 'submit' && !editMode) { + await actions.refreshAction!(processQueryCustomizer(getPageQueryCustomizer())); + } else if (result === 'submit-draft' && returnedData) { + const decoratedData = { + ...returnedData, + __identifier: `${draftIdentifierPrefix}${uuidv4()}`, + }; + const newData = decoratedData; + storeDiff('astronomer', newData); + return; + } + if (openCreated && returnedData) { + await astronomerOpenPageAction(returnedData!); + } + }; + // RowDeleteAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkDeleteAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const astronomerRowDeleteAction = async (target: ViewAstronomerStored) => { + try { + const confirmed = await openConfirmDialog( + 'row-delete-action', + t('judo.modal.confirm.confirm-delete', { + defaultValue: 'Are you sure you would like to delete the selected element?', + }), + t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }), + ); + if (confirmed) { + storeDiff('astronomer', null); + } + } catch (error) { + handleError(error, undefined, target); + } + }; + // OpenSelectorAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkSetSelectorOpenPageAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const astronomerOpenSetSelectorAction = async (): Promise => { + const { result, data: returnedData } = await openViewGalaxyViewGroupDiscovererAstronomerLinkSetSelectorPage({ + ownerData: data, + alreadySelected: data.astronomer ? [data.astronomer] : [], + }); + if (result === 'submit') { + if (Array.isArray(returnedData) && returnedData.length) { + try { + storeDiff('astronomer', returnedData[0]); + return returnedData[0]; + } catch (error) { + console.error(error); + return undefined; + } + } + } + return undefined; + }; + // UnsetAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkUnsetAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const astronomerUnsetAction = async (target: ViewAstronomer | ViewAstronomerStored) => { + storeDiff('astronomer', null); + }; + // OpenPageAction: God/(esm/_conRARMcEe2_DOUDKkB20Q)/TabularReferenceFieldLinkViewAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const astronomerOpenPageAction = async (target: ViewAstronomerStored, isDraftParam?: boolean) => { + // if the `target` is missing we are likely navigating to a relation table page, in which case we need the owner's id + navigate( + routeToViewGalaxyAstronomerRelationViewPage(((target as ViewAstronomerStored) || data).__signedIdentifier), + ); + }; + // BulkDeleteAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableBulkDeleteAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const starsBulkDeleteAction = async ( + selectedRows: ViewStarStored[], + ): Promise>> => { + return new Promise((resolve) => { + const selectedIds = selectedRows.map((r) => r.__identifier); + const newList = (data?.stars ?? []).filter((c: any) => !selectedIds.includes(c.__identifier)); + storeDiff('stars', newList); + resolve({ + result: 'delete', + data: [], + }); + }); + }; + // OpenCreateFormAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableCreateAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const starsOpenCreateFormAction = async () => { + const itemIndex = (data.stars || []).length; // length gives next without -1-ing it + const { + result, + data: returnedData, + openCreated, + } = await openViewGalaxyStarsRelationFormPage({ + ownerData: produceDataAdjustedOwner(), + ownerValidation: validate, + isDraft: true, + dataPath: `${dataPath ? dataPath + '.' : ''}stars[${itemIndex}]`, + }); + if (result === 'submit' && !editMode) { + await actions.refreshAction!(processQueryCustomizer(getPageQueryCustomizer())); + } else if (result === 'submit-draft' && returnedData) { + const decoratedData = { + ...returnedData, + __identifier: `${draftIdentifierPrefix}${uuidv4()}`, + }; + const newData = [...(data.stars || []), decoratedData]; + storeDiff('stars', newData); + return; + } + if (openCreated && returnedData) { + await starsOpenPageAction(returnedData!); + } + }; + // ExportAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableExportAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const starsExportAction = async (queryCustomizer: ViewStarQueryCustomizer) => { + try { + setIsLoading(true); + const response = await godServiceForGalaxiesImpl.exportStars( + { __signedIdentifier: signedIdentifier } as any, + queryCustomizer, + ); + + exportFile(response); + } catch (error) { + console.error(error); + } finally { + setIsLoading(false); + } + }; + // FilterAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableFilterAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const starsFilterAction = async ( + id: string, + filterOptions: FilterOption[], + model?: GridFilterModel, + filters?: Filter[], + ): Promise<{ model?: GridFilterModel; filters?: Filter[] }> => { + const newFilters = await openFilterDialog(id, filterOptions, filters); + return { + filters: newFilters, + }; + }; + // RowDeleteAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableRowDeleteAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const starsRowDeleteAction = async (target: ViewStarStored) => { + try { + const confirmed = await openConfirmDialog( + 'row-delete-action', + t('judo.modal.confirm.confirm-delete', { + defaultValue: 'Are you sure you would like to delete the selected element?', + }), + t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }), + ); + if (confirmed) { + const newList = (data?.stars ?? []).filter((c: any) => c.__identifier !== target.__identifier); + storeDiff('stars', newList); + } + } catch (error) { + handleError(error, undefined, target); + } + }; + // OpenPageAction: God/(esm/_8AxbAE7tEeycO-gUAWxcVg)/TabularReferenceTableRowViewAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const starsOpenPageAction = async (target: ViewStarStored, isDraftParam?: boolean) => { + const itemIndex = (data.stars || []).findIndex((r) => r.__identifier === target.__identifier)!; + if (isDraftParam) { + const { result, data: returnedData } = await openViewGalaxyStarsRelationViewPage({ + ownerData: produceDataAdjustedOwner(), + isDraft: true, + ownerValidation: validate, + dataPath: `${dataPath ? dataPath + '.' : ''}stars[${itemIndex!}]`, + }); + // we might need to differentiate result handling between operation inputs and crud relation creates + if (result === 'submit-draft' && returnedData) { + const existingIndex = (data.stars || []).findIndex( + (r: { __identifier?: string }) => r.__identifier === returnedData.__identifier, + ); + if (existingIndex > -1) { + // we need to create a copy to keep order, and because mui datagrid freezes elements, and crashes on reference updates + const updatedList = [...(data.stars || [])]; + updatedList[existingIndex] = { + ...returnedData, + }; + storeDiff('stars', updatedList); + } + return; + } + if (result === 'delete' && returnedData) { + const existingIndex = (data.stars || []).findIndex( + (r: { __identifier?: string }) => r.__identifier === returnedData.__identifier, + ); + if (existingIndex > -1) { + // we need to create a copy to keep order, and because mui datagrid freezes elements, and crashes on reference updates + const updatedList = [...(data.stars || [])]; + updatedList.splice(existingIndex, 1); + storeDiff('stars', updatedList); + } + return; + } + if (result === 'close') { + return; + } + } else { + await openViewGalaxyStarsRelationViewPage({ + ownerData: target!, + }); + await refresh(); + } + }; + // OpenOperationInputFormAction: God/(esm/_5NwrQFyrEeylCdga_wJIBQ)/OperationFormCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const createDarkMatterAction = async (isDraft?: boolean, ownerValidation?: (target: any) => Promise) => { + const { result, data: returnedData } = await openViewGalaxyViewCreateDarkMatterInputForm({ + ownerData: data, + }); + if (result === 'submit' && !editMode) { + await refresh(); + } + }; + // OpenOperationInputFormAction: God/(esm/_5Nx5YFyrEeylCdga_wJIBQ)/OperationFormCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const createIntergalacticDustAction = async (isDraft?: boolean, ownerValidation?: (target: any) => Promise) => { + const { result, data: returnedData } = await openViewGalaxyViewCreateIntergalacticDustInputForm({ + ownerData: data, + }); + if (result === 'submit' && !editMode) { + await refresh(); + } + }; + // OpenOperationInputFormAction: God/(esm/_5Nx5YVyrEeylCdga_wJIBQ)/OperationFormCallAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const createInterstellarMediumAction = async ( + isDraft?: boolean, + ownerValidation?: (target: any) => Promise, + ) => { + const { result, data: returnedData } = await openViewGalaxyViewCreateInterstellarMediumInputForm({ + ownerData: data, + }); + if (result === 'submit' && !editMode) { + await refresh(); + } + }; + // OpenPageAction: God/(esm/_8A3hoE7tEeycO-gUAWxcVg)/TabularReferenceFieldButtonOpenPageAction/(discriminator/God/(esm/_4pyPkM_cEe6fibzd7gNETg)/AccessViewPageDefinition) + const matterOpenPageAction = async (target: ViewMatterStored, isDraftParam?: boolean) => { + // if the `target` is missing we are likely navigating to a relation table page, in which case we need the owner's id + navigate(routeToViewGalaxyMatterRelationTablePage(((target as ViewMatterStored) || data).__signedIdentifier)); + }; + + const actions: ViewGalaxyViewPageActions = { + getPageTitle, + backAction, + cancelAction, + deleteAction, + refreshAction, + updateAction, + astronomerAutocompleteRangeAction, + astronomerAutocompleteSetAction, + astronomerOpenCreateFormAction, + astronomerRowDeleteAction, + astronomerOpenSetSelectorAction, + astronomerUnsetAction, + astronomerOpenPageAction, + starsBulkDeleteAction, + starsOpenCreateFormAction, + starsExportAction, + starsFilterAction, + starsRowDeleteAction, + starsOpenPageAction, + createDarkMatterAction, + createIntergalacticDustAction, + createInterstellarMediumAction, + matterOpenPageAction, + getMask, + getAstronomerMask, + getStarsMask, + ...(customActions ?? {}), + }; + + // ViewModel setup + const viewModel: ViewGalaxyViewViewModel = { + actions, + isLoading, + setIsLoading, + refreshCounter, + editMode, + setEditMode, + refresh, + data, + validation, + setValidation, + storeDiff, + submit, + isFormUpdateable, + isFormDeleteable, + }; + + // Effect section + useEffect(() => { + (async () => { + await actions.refreshAction!(getPageQueryCustomizer()); + })(); + }, []); + + return ( + + +
+ + + + + + ); +} diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java index 7cd16cf1..ae46fd6d 100644 --- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java +++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java @@ -335,9 +335,6 @@ public static Boolean displayDropdownForButtonGroup(ButtonGroup actionGroup) { } public static String tableButtonVisibilityConditions(Button button, Table table, PageContainer container) { - if ((button.getActionDefinition().getIsFilterAction() || button.getActionDefinition().getIsFilterRelationAction()) && isUseInlineColumnFilters()) { - return "false"; - } if (button.getActionDefinition().getIsOpenCreateFormAction() && !table.isIsEager() && container.isView()) { return "!editMode && (isFormUpdateable ? isFormUpdateable() : false)"; } diff --git a/judo-ui-react/src/main/resources/actor/src/components/table/EagerTable.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/components/table/EagerTable.tsx.hbs index cff9e3cb..f2551468 100644 --- a/judo-ui-react/src/main/resources/actor/src/components/table/EagerTable.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/components/table/EagerTable.tsx.hbs @@ -478,7 +478,11 @@ export function EagerTable