Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { deepCopyViaJson } from '@/utils/object';
import { useDataTableStore } from '@/providers/dataTable';
import { useMetadata } from '@/providers/metadata';
import { calculateDefaultColumns } from '../utils';
import { BackendRepositoryType } from '@/providers/dataTable/repository/backendRepository';

export interface IColumnsEditorModal {
readOnly: boolean;
Expand All @@ -21,14 +22,15 @@ export const ColumnsEditorModal: FC<IColumnsEditorModal> = ({ onChange, value, v
const isSmall = useMedia('(max-width: 480px)');
const dataTableStore = useDataTableStore(false); // Don't require - modal may not be in a DataTable context
const metadata = useMetadata(false); // Don't require - DataTable may not be in a DataSource
const isEntitySource = dataTableStore?.getRepository?.()?.repositoryType === BackendRepositoryType;

const [startedEmpty, setStartedEmpty] = useState(false);
const [prevValue, setPrevValue] = useState<ColumnsItemProps[]>(deepCopyViaJson(value));
const [localValue, setLocalValue] = useState<ColumnsItemProps[]>(deepCopyViaJson(value));

// Prepopulate with default columns when modal opens if items are empty and we're in a DataTable context
useEffect(() => {
if (visible && dataTableStore && metadata?.metadata && (!value || value.length === 0)) {
if (visible && dataTableStore && isEntitySource && metadata?.metadata && (!value || value.length === 0)) {
const loadDefaultColumns = async (): Promise<void> => {
try {
const defaultColumns = await calculateDefaultColumns(metadata.metadata);
Expand All @@ -45,7 +47,7 @@ export const ColumnsEditorModal: FC<IColumnsEditorModal> = ({ onChange, value, v

loadDefaultColumns();
}
}, [metadata?.metadata, visible, onChange]);
}, [metadata?.metadata, visible, onChange, isEntitySource]);

const onOk = (): void => {
onChange?.(deepCopyViaJson(localValue)); // make copy of localValue to re-render table
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ import { useComponentValidation } from '@/providers/validationErrors';
import { parseFetchError } from '../utils';
import { useMetadata } from '@/providers/metadata';
import { isPropertiesArray } from '@/interfaces/metadata';
import { BackendRepositoryType } from '@/providers/dataTable/repository/backendRepository';

const columnsMismatchError = 'CONFIGURATION ERROR: The DataTable columns do not match the data source. Please change the columns configured to suit your data source.';

// Factory component that conditionally renders TableWrapper or StandaloneTable based on data context
const TableComponentFactory: React.FC<{ model: ITableComponentProps }> = ({ model }) => {
const store = useDataTableStore(false);
const metadata = useMetadata(false);
const repositoryType = store?.getRepository?.()?.repositoryType;
const isEntitySource = repositoryType === BackendRepositoryType;
const configuredColumns = useMemo(
() => flattenConfiguredColumns(model.items as ColumnsItemProps[]),
[model.items],
Expand Down Expand Up @@ -69,11 +72,12 @@ const TableComponentFactory: React.FC<{ model: ITableComponentProps }> = ({ mode
[dataColumns, metadataPropertyNameSet],
);
const columnsMismatch = useMemo(
() => dataColumns.length > 0 &&
() => isEntitySource &&
dataColumns.length > 0 &&
metadataPropertyNameSet.size > 0 &&
invalidDataColumns.length > 0 &&
invalidDataColumns.length === dataColumns.length, // Only error when ALL columns are invalid
[dataColumns.length, metadataPropertyNameSet.size, invalidDataColumns.length],
[dataColumns.length, metadataPropertyNameSet.size, invalidDataColumns.length, isEntitySource],
);

// CRITICAL: Register validation errors - FormComponent will display them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { useFormDesignerOrUndefined } from '@/providers/formDesigner';
import { StandaloneTable } from './standaloneTable';
import { isPropertiesArray } from '@/interfaces/metadata';
import { ColumnsItemProps } from '@/providers/datatableColumnsConfigurator/models';
import { BackendRepositoryType } from '@/providers/dataTable/repository/backendRepository';

type TableWrapperProps = ITableComponentProps & { columnsMismatch?: boolean };

Expand Down Expand Up @@ -237,30 +238,38 @@ export const TableWrapper: FC<TableWrapperProps> = (props) => {
removeColumnFilter,
tableFilter,
contextValidation,
getRepository,
} = useDataTableStore();

const { totalRows } = useDataTable();
const repositoryType = getRepository?.()?.repositoryType;
const isEntitySource = repositoryType === BackendRepositoryType;

requireColumns(); // our component requires columns loading. it's safe to call on each render

const shouldRegisterColumns = !isEntitySource ||
qualifyingColumns.length > 0 ||
normalizedConfiguredColumns.length === 0 ||
hasNonDataColumns;

useDeepCompareEffect(() => {
// Register columns if:
// 1. At least one column matches metadata properties, OR
// 2. There are no configured columns (empty state), OR
// 3. There are non-data columns that don't need to match metadata
if (qualifyingColumns.length > 0 || normalizedConfiguredColumns.length === 0 || hasNonDataColumns)
if (shouldRegisterColumns)
registerConfigurableColumns(id, permissibleColumns);
// Note: registerConfigurableColumns is omitted from dependencies to avoid effect re-runs
// when the actions object is recreated. The effect only needs to re-run when the actual
// column configuration changes (qualifyingColumns, normalizedConfiguredColumns, etc.)
}, [qualifyingColumns.length, normalizedConfiguredColumns.length, hasNonDataColumns, id, permissibleColumns]);
}, [shouldRegisterColumns, id, permissibleColumns]);

// Auto-configure columns when DataTable is dropped into a DataContext
useEffect(() => {
let cancelled = false;

// Only attempt auto-config if we have empty configuredColumns and haven't tried yet
if (hasAutoConfiguredRef.current || !isDesignMode || !formDesigner) {
if (hasAutoConfiguredRef.current || !isDesignMode || !formDesigner || !isEntitySource) {
return () => {
cancelled = true;
};
Expand Down Expand Up @@ -307,7 +316,7 @@ export const TableWrapper: FC<TableWrapperProps> = (props) => {
return () => {
cancelled = true;
};
}, [isDesignMode, formDesigner, metadata?.metadata, configuredColumns, id]);
}, [isDesignMode, formDesigner, metadata?.metadata, configuredColumns, id, isEntitySource]);

const renderSidebarContent = (): JSX.Element => {
if (isFiltering) {
Expand Down
17 changes: 10 additions & 7 deletions shesha-reactjs/src/providers/dataTable/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,10 @@ export const prepareColumn = (
columns: DataTableColumnDto[],
userConfig: IDataTableUserConfig,
): ITableColumn => {
const userColumnId = isDataColumnProps(column) ? column.propertyName : column.id;
const resolvedPropertyName = isDataColumnProps(column)
? (column.propertyName || column.accessor || column.id)
: undefined;
const userColumnId = isDataColumnProps(column) ? resolvedPropertyName : column.id;
const userColumn = userConfig?.columns?.find((c) => c.id === userColumnId);

const baseProps: ITableColumn = {
Expand All @@ -196,17 +199,17 @@ export const prepareColumn = (
const colVisibility =
userColumn?.show === null || userColumn?.show === undefined ? column.isVisible : userColumn?.show;

const srvColumn = column.propertyName
? columns.find((c) => camelcaseDotNotation(c.propertyName) === camelcaseDotNotation(column.propertyName))
const srvColumn = resolvedPropertyName
? columns.find((c) => camelcaseDotNotation(c.propertyName) === camelcaseDotNotation(resolvedPropertyName))
: {};

const dataCol: ITableDataColumn = {
...baseProps,
id: column.propertyName,
accessor: camelcaseDotNotation(column?.propertyName),
propertyName: column.propertyName,
id: resolvedPropertyName || column.id,
accessor: resolvedPropertyName ? camelcaseDotNotation(resolvedPropertyName) : column.accessor,
propertyName: resolvedPropertyName,

propertiesToFetch: column.propertyName,
propertiesToFetch: resolvedPropertyName,
isEnitty: srvColumn?.dataType === 'entity',

createComponent: column.createComponent,
Expand Down