Add record table widget to dashboards (#18747)
## Demo https://github.com/user-attachments/assets/584de452-544a-41f8-ae9f-4be9e9d0cd9f ## Problem - Dashboards only supported chart widgets — tabular record data had no inline widget type - `RecordTable` was tightly coupled to the record index: HTML IDs, CSS variables, and hover portals were global strings with no per-instance scoping, so multiple tables on the same page would collide - `updateRecordTableCSSVariable`, `RECORD_TABLE_HTML_ID`, and cell portal IDs were hardcoded — placing two tables caused hover portals and CSS column widths to bleed across instances - Grid drag-select captured record UUIDs as cell IDs, producing `NaN` layout coordinates and a full-page freeze on second widget creation ## Fix - `RECORD_TABLE` is now a valid widget type across the full stack — server DTOs, DB enum migration, universal config mapping, GraphQL codegen, shared types (`RecordTableConfigurationDto`, `WidgetType`, `addRecordTableWidgetType` migration) - A record table widget can be placed on a dashboard and boots from a View ID with no record index dependency — `StandaloneRecordTableProvider` + `StandaloneRecordTableViewLoadEffect` (wraps existing `RecordTableWithWrappers` unchanged) - Selecting a data source auto-creates a dedicated View with up to 6 initial fields; switching source or deleting the widget cleans up the View — `useCreateViewForRecordTableWidget` + `useDeleteViewForRecordTableWidget` - The settings panel exposes source, field visibility/reorder, filter conditions, sort rules, and editable widget title — `SidePanelPageLayoutRecordTableSettings` + sub-pages, matching chart widget pattern - Filters, sorts, and aggregate operations update the table in real time but only persist to the View on explicit dashboard save — `useSaveRecordTableWidgetsViewDataOnDashboardSave` (diff + flush on save) - Headers are always non-interactive (no dropdown, no cursor pointer); columns are resizable only in edit mode; cells are non-editable in both modes — `isRecordTableColumnHeadersReadOnlyComponentState`, `isRecordTableColumnResizableComponentState`, `isRecordTableCellsNonEditableComponentState` (Jotai component states) - Hover portals and CSS column widths no longer bleed between multiple table widgets — `getRecordTableHtmlId(tableId)`, `getRecordTableCellId(tableId, …)`, `updateRecordTableCSSVariable(tableId, …)` scope all DOM IDs and CSS variables per instance - Clicking inside a widget's content area no longer opens the settings panel — `WidgetCardContent` stops click propagation when editable, limiting settings-open to the card header and chrome - Second widget creation no longer freezes the page — `PageLayoutGridLayout` drag-select filters by `cell-` prefix to exclude record UUIDs from grid cell detection ## Follow-up fixes **Widget save flow** - Saving a dashboard silently dropped record table widget changes (column visibility, order, filters, sorts, aggregates) because widget data save was bundled inside the layout save and only ran when layout structure changed - Widget data now persists independently via `useSavePageLayoutWidgetsData`, called in all save paths (dashboard save, record page save, layout customization save); saves are also skipped when nothing has changed **Drag-and-drop / checkbox columns in widget** - Record table widgets showed the drag handle column and checkbox selection column even though row reordering and multi-select are meaningless in a read-only widget - Two new component states (`isRecordTableDragColumnHiddenComponentState`, `isRecordTableCheckboxColumnHiddenComponentState`) hide each column independently; widget tables now display only data columns **Sticky column layout** - Sticky positioning of the first three columns used `:nth-of-type` CSS selectors — when drag or checkbox columns were hidden, the selector targeted the wrong column and the first data column didn't stick - Sticky CSS now targets semantic class names (`RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME`, etc.) so sticky behavior is correct regardless of which columns are hidden **Save/Cancel buttons during edit mode** - Save and Cancel command-menu buttons were unpinned during dashboard edit mode because the pin logic excluded all items while `isPageInEditMode` was true - Items whose availability expression contains `isPageInEditMode` are now exempted from the unpin rule; Save/Cancel stay pinned during editing **Title input auto-focus** - Selecting "Record Table" as widget type auto-focused the title input, interrupting the configuration flow - `focusTitleInput` is now `false` when navigating to record table settings **Morph relation field error** - A field with missing `morphRelations` metadata crashed the page with a "refresh" error from `mapObjectMetadataToGraphQLQuery` - Now returns an empty array and silently omits the field from the query instead of crashing **`updateRecordMutation` prop removal** - `RecordTableWithWrappers` required callers to pass an `updateRecordMutation` callback, duplicating `useUpdateOneRecord` at every usage site - The mutation is now owned inside `RecordTableContextProvider` via `RecordTableUpdateContext`; the prop is gone **Standalone → Widget module rename** - `record-table-standalone` module renamed to `record-table-widget` — `StandaloneRecordTable` → `RecordTableWidget`, `StandaloneRecordTableViewLoadEffect` → `RecordTableWidgetViewLoadEffect`, etc. **RecordTableRow cell extraction** - Row rendering logic (`RecordTableCellDragAndDrop`, `RecordTableCellCheckbox`, `RecordTableFieldsCells`, hotkey/arrow-key effects) was duplicated between `RecordTableRow` and `RecordTableRowVirtualizedFullData` - Extracted `RecordTableRowCells` (shared cell content) and `RecordTableStaticTr` (non-draggable `<tr>` wrapper); when drag column is hidden, rows render inside a static `<tr>` instead of the draggable wrapper **View load effect metadata tracking** - `RecordTableWidgetViewLoadEffect` now tracks `objectMetadataItem.updatedAt` alongside `viewId` to re-load states when metadata changes (e.g. field additions), preventing stale column data **Data source dropdown deduplication** - Extracted `filterReadableActiveObjectMetadataItems` util, shared by both chart and record table data source dropdowns — removes duplicated permission-filtering logic **RECORD_TABLE view identifier mapping (server)** - Added `RECORD_TABLE` case to `fromPageLayoutWidgetConfigurationToUniversalConfiguration` and `fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration` so widget views are properly mapped during workspace import/export **GraphQL error handler typing (server)** - `formatError` parameter changed from `any` to `unknown`; `workspaceQueryRunnerGraphqlApiExceptionHandler` broadened from `QueryFailedErrorWithCode` to `Error | QueryFailedError` — removes unsafe type casts **Save hook signature** - `useSaveRecordTableWidgetsViewDataOnDashboardSave` no longer takes `pageLayoutId` in constructor; receives it as a callback parameter, eliminating the need for `useAtomComponentStateCallbackState` **Customize Dashboard hidden during edit mode** - The "Customize Dashboard" command was still visible while already editing — its `conditionalAvailabilityExpression` now includes `not isPageInEditMode` **Fields dropdown split** - `RecordTableFieldsDropdownContent` (300+ lines) split into `RecordTableFieldsDropdownVisibleFieldsContent` and `RecordTableFieldsDropdownHiddenFieldsContent` **Checkbox placeholder cleanup** - Removed unnecessary `StyledRecordTableTdContainer` wrapper from `RecordTableCellCheckboxPlaceholder`
This commit is contained in:
+2
-13
@@ -1,6 +1,5 @@
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { type RecordUpdateHookParams } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal';
|
||||
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
|
||||
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
||||
@@ -22,23 +21,13 @@ export const RecordIndexTableContainer = ({
|
||||
RECORD_INDEX_REMOVE_SORTING_MODAL_ID,
|
||||
);
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord();
|
||||
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord({
|
||||
objectNameSingular,
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<RecordIndexTableContainerEffect />
|
||||
<RecordTableWithWrappers
|
||||
recordTableId={recordTableId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
viewBarId={viewBarInstanceId}
|
||||
updateRecordMutation={updateEntity}
|
||||
/>
|
||||
{isModalOpened && <RecordIndexRemoveSortingModal />}
|
||||
</>
|
||||
|
||||
+11
@@ -7,6 +7,7 @@ import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/uti
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
|
||||
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRecordGroups';
|
||||
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
|
||||
@@ -32,6 +33,7 @@ import { type View } from '@/views/types/View';
|
||||
import { getFilterableFields } from '@/views/utils/getFilterableFields';
|
||||
import { mapViewFieldToRecordField } from '@/views/utils/mapViewFieldToRecordField';
|
||||
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
||||
import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
import { atom, useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
@@ -132,6 +134,10 @@ export const useLoadRecordIndexStates = () => {
|
||||
allFilterableFields,
|
||||
);
|
||||
|
||||
const recordFilterGroups = mapViewFilterGroupsToRecordFilterGroups(
|
||||
view.viewFilterGroups ?? [],
|
||||
);
|
||||
|
||||
const contextStoreFilters = mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterableFieldMetadataItems,
|
||||
@@ -172,6 +178,10 @@ export const useLoadRecordIndexStates = () => {
|
||||
currentRecordFiltersComponentState.atomFamily({
|
||||
instanceId: recordIndexId,
|
||||
});
|
||||
const currentRecordFilterGroupsAtom =
|
||||
currentRecordFilterGroupsComponentState.atomFamily({
|
||||
instanceId: recordIndexId,
|
||||
});
|
||||
const currentRecordSortsAtom =
|
||||
currentRecordSortsComponentState.atomFamily({
|
||||
instanceId: recordIndexId,
|
||||
@@ -237,6 +247,7 @@ export const useLoadRecordIndexStates = () => {
|
||||
batchSet(hasInitializedFieldsAtom, true);
|
||||
|
||||
batchSet(currentRecordFiltersAtom, recordFilters);
|
||||
batchSet(currentRecordFilterGroupsAtom, recordFilterGroups);
|
||||
batchSet(hasInitializedFiltersAtom, true);
|
||||
|
||||
batchSet(currentRecordSortsAtom, view.viewSorts);
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordTableWidgetSetReadOnlyColumnHeadersEffect } from '@/object-record/record-table-widget/components/RecordTableWidgetSetReadOnlyColumnHeadersEffect';
|
||||
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
||||
|
||||
export const RecordTableWidget = () => {
|
||||
const { objectNameSingular, recordIndexId, viewBarInstanceId } =
|
||||
useRecordIndexContextOrThrow();
|
||||
|
||||
return (
|
||||
<>
|
||||
<RecordTableWidgetSetReadOnlyColumnHeadersEffect
|
||||
recordTableId={recordIndexId}
|
||||
/>
|
||||
<RecordTableWithWrappers
|
||||
recordTableId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
viewBarId={viewBarInstanceId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type RecordTableWidgetContextStoreInitEffectProps = {
|
||||
objectMetadataItemId: string;
|
||||
viewId: string;
|
||||
};
|
||||
|
||||
export const RecordTableWidgetContextStoreInitEffect = ({
|
||||
objectMetadataItemId,
|
||||
viewId,
|
||||
}: RecordTableWidgetContextStoreInitEffectProps) => {
|
||||
const setContextStoreCurrentObjectMetadataItemId = useSetAtomComponentState(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
);
|
||||
|
||||
const setContextStoreCurrentViewId = useSetAtomComponentState(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
const setContextStoreCurrentViewType = useSetAtomComponentState(
|
||||
contextStoreCurrentViewTypeComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setContextStoreCurrentObjectMetadataItemId(objectMetadataItemId);
|
||||
setContextStoreCurrentViewId(viewId);
|
||||
setContextStoreCurrentViewType(ContextStoreViewType.Table);
|
||||
}, [
|
||||
objectMetadataItemId,
|
||||
viewId,
|
||||
setContextStoreCurrentObjectMetadataItemId,
|
||||
setContextStoreCurrentViewId,
|
||||
setContextStoreCurrentViewType,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
|
||||
import { RecordTableWidgetContextStoreInitEffect } from '@/object-record/record-table-widget/components/RecordTableWidgetContextStoreInitEffect';
|
||||
import { RecordTableWidgetViewLoadEffect } from '@/object-record/record-table-widget/components/RecordTableWidgetViewLoadEffect';
|
||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { type PropsWithChildren, useCallback } from 'react';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { getAppPath } from 'twenty-shared/utils';
|
||||
|
||||
type RecordTableWidgetProviderProps = PropsWithChildren<{
|
||||
objectNameSingular: string;
|
||||
viewId: string;
|
||||
widgetId: string;
|
||||
}>;
|
||||
|
||||
export const RecordTableWidgetProvider = ({
|
||||
objectNameSingular,
|
||||
viewId,
|
||||
widgetId,
|
||||
children,
|
||||
}: RecordTableWidgetProviderProps) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||
objectMetadataItem.namePlural,
|
||||
viewId,
|
||||
);
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const objectPermissions = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const {
|
||||
fieldDefinitionByFieldMetadataItemId,
|
||||
fieldMetadataItemByFieldMetadataItemId,
|
||||
labelIdentifierFieldMetadataItem,
|
||||
recordFieldByFieldMetadataItemId,
|
||||
} = useRecordIndexFieldMetadataDerivedStates(
|
||||
objectMetadataItem,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const indexIdentifierUrl = useCallback(
|
||||
(recordId: string) => {
|
||||
return getAppPath(AppPath.RecordShowPage, {
|
||||
objectNameSingular,
|
||||
objectRecordId: recordId,
|
||||
});
|
||||
},
|
||||
[objectNameSingular],
|
||||
);
|
||||
|
||||
const handleIndexRecordsLoaded = useCallback(() => {}, []);
|
||||
|
||||
if (!objectPermissions.canReadObjectRecords) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: `record-table-widget-${widgetId}` }}
|
||||
>
|
||||
<RecordTableWidgetContextStoreInitEffect
|
||||
objectMetadataItemId={objectMetadataItem.id}
|
||||
viewId={viewId}
|
||||
/>
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
objectPermissionsByObjectMetadataId,
|
||||
recordIndexId,
|
||||
viewBarInstanceId: recordIndexId,
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
objectNameSingular,
|
||||
objectMetadataItem,
|
||||
onIndexRecordsLoaded: handleIndexRecordsLoaded,
|
||||
indexIdentifierUrl,
|
||||
recordFieldByFieldMetadataItemId,
|
||||
labelIdentifierFieldMetadataItem,
|
||||
fieldMetadataItemByFieldMetadataItemId,
|
||||
fieldDefinitionByFieldMetadataItemId,
|
||||
}}
|
||||
>
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
>
|
||||
<RecordComponentInstanceContextsWrapper
|
||||
componentInstanceId={recordIndexId}
|
||||
>
|
||||
<RecordTableWidgetViewLoadEffect
|
||||
viewId={viewId}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
{children}
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</RecordIndexContextProvider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { isRecordTableCellsNonEditableComponentState } from '@/object-record/record-table/states/isRecordTableCellsNonEditableComponentState';
|
||||
import { isRecordTableColumnHeadersReadOnlyComponentState } from '@/object-record/record-table/states/isRecordTableColumnHeadersReadOnlyComponentState';
|
||||
import { isRecordTableColumnResizableComponentState } from '@/object-record/record-table/states/isRecordTableColumnResizableComponentState';
|
||||
import { useStore } from 'jotai';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const RecordTableWidgetSetReadOnlyColumnHeadersEffect = ({
|
||||
recordTableId,
|
||||
}: {
|
||||
recordTableId: string;
|
||||
}) => {
|
||||
const store = useStore();
|
||||
const isPageLayoutInEditMode = useIsPageLayoutInEditMode();
|
||||
|
||||
useEffect(() => {
|
||||
store.set(
|
||||
isRecordTableColumnHeadersReadOnlyComponentState.atomFamily({
|
||||
instanceId: recordTableId,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
store.set(
|
||||
isRecordTableDragColumnHiddenComponentState.atomFamily({
|
||||
instanceId: recordTableId,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
store.set(
|
||||
isRecordTableCheckboxColumnHiddenComponentState.atomFamily({
|
||||
instanceId: recordTableId,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
store.set(
|
||||
isRecordTableColumnResizableComponentState.atomFamily({
|
||||
instanceId: recordTableId,
|
||||
}),
|
||||
isPageLayoutInEditMode,
|
||||
);
|
||||
|
||||
store.set(
|
||||
isRecordTableCellsNonEditableComponentState.atomFamily({
|
||||
instanceId: recordTableId,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}, [store, recordTableId, isPageLayoutInEditMode]);
|
||||
|
||||
return null;
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
|
||||
import { lastLoadedRecordTableWidgetViewIdComponentState } from '@/object-record/record-table-widget/states/lastLoadedRecordTableWidgetViewIdComponentState';
|
||||
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { viewFromViewIdFamilySelector } from '@/views/states/selectors/viewFromViewIdFamilySelector';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type RecordTableWidgetViewLoadEffectProps = {
|
||||
viewId: string;
|
||||
objectMetadataItem: EnrichedObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const RecordTableWidgetViewLoadEffect = ({
|
||||
viewId,
|
||||
objectMetadataItem,
|
||||
}: RecordTableWidgetViewLoadEffectProps) => {
|
||||
const { loadRecordIndexStates } = useLoadRecordIndexStates();
|
||||
|
||||
const [
|
||||
lastLoadedRecordTableWidgetViewId,
|
||||
setLastLoadedRecordTableWidgetViewId,
|
||||
] = useAtomComponentState(lastLoadedRecordTableWidgetViewIdComponentState);
|
||||
|
||||
const viewFromViewId = useAtomFamilySelectorValue(
|
||||
viewFromViewIdFamilySelector,
|
||||
{
|
||||
viewId,
|
||||
},
|
||||
);
|
||||
|
||||
const viewHasFields =
|
||||
isDefined(viewFromViewId) && viewFromViewId.viewFields.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(viewFromViewId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!viewHasFields) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
viewId === lastLoadedRecordTableWidgetViewId?.viewId &&
|
||||
objectMetadataItem.updatedAt ===
|
||||
lastLoadedRecordTableWidgetViewId?.objectMetadataItemUpdatedAt
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadRecordIndexStates(viewFromViewId, objectMetadataItem);
|
||||
|
||||
setLastLoadedRecordTableWidgetViewId({
|
||||
viewId,
|
||||
objectMetadataItemUpdatedAt: objectMetadataItem.updatedAt,
|
||||
});
|
||||
}, [
|
||||
viewId,
|
||||
lastLoadedRecordTableWidgetViewId,
|
||||
setLastLoadedRecordTableWidgetViewId,
|
||||
viewFromViewId,
|
||||
viewHasFields,
|
||||
objectMetadataItem,
|
||||
loadRecordIndexStates,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
export const lastLoadedRecordTableWidgetViewIdComponentState =
|
||||
createAtomComponentState<{
|
||||
viewId: string;
|
||||
objectMetadataItemUpdatedAt: string;
|
||||
} | null>({
|
||||
key: 'lastLoadedRecordTableWidgetViewIdComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: ContextStoreComponentInstanceContext,
|
||||
});
|
||||
+33
-3
@@ -6,6 +6,8 @@ import { RECORD_TABLE_COLUMN_MIN_WIDTH } from '@/object-record/record-table/cons
|
||||
import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthVariableName';
|
||||
import { RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE } from '@/object-record/record-table/constants/RecordTableLabelIdentifierColumnWidthOnMobile';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState';
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState';
|
||||
@@ -25,7 +27,7 @@ export const RecordTableColumnWidthEffect = () => {
|
||||
resizedFieldMetadataIdComponentState,
|
||||
);
|
||||
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
const { visibleRecordFields, recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const shouldCompactRecordTableFirstColumn = useAtomComponentStateValue(
|
||||
shouldCompactRecordTableFirstColumnComponentState,
|
||||
@@ -35,6 +37,14 @@ export const RecordTableColumnWidthEffect = () => {
|
||||
recordTableWidthComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(resizedFieldMetadataId)) {
|
||||
return;
|
||||
@@ -44,6 +54,8 @@ export const RecordTableColumnWidthEffect = () => {
|
||||
recordFields: visibleRecordFields,
|
||||
shouldCompactFirstColumn: shouldCompactRecordTableFirstColumn,
|
||||
tableWidth: recordTableWidth,
|
||||
isDragColumnHidden: isRecordTableDragColumnHidden,
|
||||
isCheckboxColumnHidden: isRecordTableCheckboxColumnHidden,
|
||||
});
|
||||
|
||||
const { visibleRecordFieldsWidth } = computeVisibleRecordFieldsWidthOnTable(
|
||||
@@ -53,31 +65,44 @@ export const RecordTableColumnWidthEffect = () => {
|
||||
},
|
||||
);
|
||||
|
||||
const dragColumnWidth = isRecordTableDragColumnHidden
|
||||
? 0
|
||||
: RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH;
|
||||
|
||||
const checkboxColumnWidth = isRecordTableCheckboxColumnHidden
|
||||
? 0
|
||||
: RECORD_TABLE_COLUMN_CHECKBOX_WIDTH;
|
||||
|
||||
const leftColumnsWidth = dragColumnWidth + checkboxColumnWidth;
|
||||
|
||||
const totalTableBodyWidth =
|
||||
visibleRecordFieldsWidth +
|
||||
RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH +
|
||||
RECORD_TABLE_COLUMN_CHECKBOX_WIDTH +
|
||||
leftColumnsWidth +
|
||||
RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH +
|
||||
lastColumnWidth +
|
||||
visibleRecordFields.length;
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_VIRTUALIZATION_BODY_PLACEHOLDER_WIDTH_CSS_VARIABLE_NAME,
|
||||
`${totalTableBodyWidth}px`,
|
||||
);
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME,
|
||||
`${lastColumnWidth}px`,
|
||||
);
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME,
|
||||
`${lastColumnWidth}px`,
|
||||
);
|
||||
|
||||
for (const [index, recordField] of visibleRecordFields.entries()) {
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
getRecordTableColumnFieldWidthCSSVariableName(index),
|
||||
`${recordField.size}px`,
|
||||
);
|
||||
@@ -85,6 +110,7 @@ export const RecordTableColumnWidthEffect = () => {
|
||||
|
||||
if (shouldCompactRecordTableFirstColumn) {
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
getRecordTableColumnFieldWidthCSSVariableName(0),
|
||||
`${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px`,
|
||||
);
|
||||
@@ -93,6 +119,7 @@ export const RecordTableColumnWidthEffect = () => {
|
||||
visibleRecordFields[0]?.size ?? RECORD_TABLE_COLUMN_MIN_WIDTH;
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
getRecordTableColumnFieldWidthCSSVariableName(0),
|
||||
`${firstColumnWidth}px`,
|
||||
);
|
||||
@@ -102,6 +129,9 @@ export const RecordTableColumnWidthEffect = () => {
|
||||
visibleRecordFields,
|
||||
recordTableWidth,
|
||||
shouldCompactRecordTableFirstColumn,
|
||||
isRecordTableDragColumnHidden,
|
||||
isRecordTableCheckboxColumnHidden,
|
||||
recordTableId,
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
||||
+24
-4
@@ -4,8 +4,10 @@ import {
|
||||
getRecordTableColumnWidthInlineStyles,
|
||||
RecordTableStyleWrapper,
|
||||
} from '@/object-record/record-table/components/RecordTableStyleWrapper';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { RecordTableWidthEffect } from '@/object-record/record-table/components/RecordTableWidthEffect';
|
||||
import { RECORD_TABLE_HTML_ID } from '@/object-record/record-table/constants/RecordTableHtmlId';
|
||||
import { getRecordTableHtmlId } from '@/object-record/record-table/utils/getRecordTableHtmlId';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody';
|
||||
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
|
||||
@@ -18,6 +20,7 @@ import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-sel
|
||||
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
|
||||
import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
@@ -128,9 +131,26 @@ export const RecordTableContent = ({
|
||||
[store, isSomeCellInEditMode, recordTableHoverPositionCallbackState],
|
||||
);
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const columnWidthStyles = useMemo(
|
||||
() => getRecordTableColumnWidthInlineStyles(visibleRecordFields),
|
||||
[visibleRecordFields],
|
||||
() =>
|
||||
getRecordTableColumnWidthInlineStyles({
|
||||
visibleRecordFields,
|
||||
isDragColumnHidden: isRecordTableDragColumnHidden,
|
||||
isCheckboxColumnHidden: isRecordTableCheckboxColumnHidden,
|
||||
}),
|
||||
[
|
||||
visibleRecordFields,
|
||||
isRecordTableDragColumnHidden,
|
||||
isRecordTableCheckboxColumnHidden,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -139,7 +159,7 @@ export const RecordTableContent = ({
|
||||
ref={tableBodyRef}
|
||||
isDragging={isDragging}
|
||||
style={columnWidthStyles}
|
||||
id={RECORD_TABLE_HTML_ID}
|
||||
id={getRecordTableHtmlId(recordTableId)}
|
||||
onMouseMove={handleDelegatedMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
|
||||
+20
-2
@@ -1,15 +1,18 @@
|
||||
import { type ReactNode } from 'react';
|
||||
import { type ReactNode, useCallback } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { RecordTableContextProvider as RecordTableContextInternalProvider } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
|
||||
import { type RecordUpdateHookParams } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { RECORD_TABLE_CELL_INPUT_ID_PREFIX } from '@/object-record/record-table/constants/RecordTableCellInputIdPrefix';
|
||||
import { RECORD_TABLE_COLUMN_MIN_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnMinWidth';
|
||||
import { RecordTableUpdateContext } from '@/object-record/record-table/contexts/RecordTableUpdateContext';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { ViewOpenRecordIn } from '~/generated-metadata/graphql';
|
||||
@@ -43,6 +46,19 @@ export const RecordTableContextProvider = ({
|
||||
visibleRecordFieldsComponentSelector,
|
||||
);
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord();
|
||||
|
||||
const updateRecord = useCallback(
|
||||
({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord({
|
||||
objectNameSingular,
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
},
|
||||
[objectNameSingular, updateOneRecord],
|
||||
);
|
||||
|
||||
const recordIndexOpenRecordIn = useAtomStateValue(
|
||||
recordIndexOpenRecordInState,
|
||||
);
|
||||
@@ -71,7 +87,9 @@ export const RecordTableContextProvider = ({
|
||||
triggerEvent,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<RecordTableUpdateContext.Provider value={updateRecord}>
|
||||
{children}
|
||||
</RecordTableUpdateContext.Provider>
|
||||
</RecordTableContextInternalProvider>
|
||||
</RecordFieldsScopeContextProvider>
|
||||
);
|
||||
|
||||
+25
-6
@@ -7,7 +7,9 @@ import { RecordTableWidthEffect } from '@/object-record/record-table/components/
|
||||
import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidth';
|
||||
import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidth';
|
||||
import { RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnDragAndDropWidth';
|
||||
import { RECORD_TABLE_HTML_ID } from '@/object-record/record-table/constants/RecordTableHtmlId';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { getRecordTableHtmlId } from '@/object-record/record-table/utils/getRecordTableHtmlId';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
|
||||
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||
@@ -33,7 +35,15 @@ export interface RecordTableEmptyProps {
|
||||
}
|
||||
|
||||
export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
const { visibleRecordFields, recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const recordTableWidth = useAtomComponentStateValue(
|
||||
recordTableWidthComponentState,
|
||||
@@ -66,10 +76,19 @@ export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => {
|
||||
visibleRecordFields,
|
||||
});
|
||||
|
||||
const dragColumnWidth = isRecordTableDragColumnHidden
|
||||
? 0
|
||||
: RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH;
|
||||
|
||||
const checkboxColumnWidth = isRecordTableCheckboxColumnHidden
|
||||
? 0
|
||||
: RECORD_TABLE_COLUMN_CHECKBOX_WIDTH;
|
||||
|
||||
const leftColumnsWidth = dragColumnWidth + checkboxColumnWidth;
|
||||
|
||||
const emptyTableContainerComputedWidth =
|
||||
visibleRecordFieldsWidth +
|
||||
RECORD_TABLE_COLUMN_CHECKBOX_WIDTH +
|
||||
RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH +
|
||||
leftColumnsWidth +
|
||||
RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH +
|
||||
totalColumnsBorderWidth +
|
||||
resizeOffsetToAddOnlyIfItMakesTableContainerGrow;
|
||||
@@ -80,7 +99,7 @@ export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => {
|
||||
);
|
||||
|
||||
const columnWidthStyles = useMemo(
|
||||
() => getRecordTableColumnWidthInlineStyles(visibleRecordFields),
|
||||
() => getRecordTableColumnWidthInlineStyles({ visibleRecordFields }),
|
||||
[visibleRecordFields],
|
||||
);
|
||||
|
||||
@@ -89,7 +108,7 @@ export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => {
|
||||
<RecordTableStyleWrapper
|
||||
ref={tableBodyRef}
|
||||
style={columnWidthStyles}
|
||||
id={RECORD_TABLE_HTML_ID}
|
||||
id={getRecordTableHtmlId(recordTableId)}
|
||||
>
|
||||
<RecordTableHeader />
|
||||
</RecordTableStyleWrapper>
|
||||
|
||||
+9
@@ -3,6 +3,7 @@ import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/r
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
import { isRecordTableCellsNonEditableComponentState } from '@/object-record/record-table/states/isRecordTableCellsNonEditableComponentState';
|
||||
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
|
||||
import { isRecordTableCreateDisabled } from '@/object-record/record-table/utils/isRecordTableCreateDisabled';
|
||||
import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows';
|
||||
@@ -17,6 +18,10 @@ import { IconPlus } from 'twenty-ui/display';
|
||||
export const RecordTableNoRecordGroupAddNew = () => {
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableCellsNonEditable = useAtomComponentStateValue(
|
||||
isRecordTableCellsNonEditableComponentState,
|
||||
);
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
@@ -58,6 +63,10 @@ export const RecordTableNoRecordGroupAddNew = () => {
|
||||
totalNumberOfRecordsToVirtualize,
|
||||
]);
|
||||
|
||||
if (isRecordTableCellsNonEditable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hasAnySoftDeleteFilterOnView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+5
@@ -1,6 +1,7 @@
|
||||
import { shouldCompactRecordIndexLabelIdentifierComponentState } from '@/object-record/record-index/states/shouldCompactRecordIndexLabelIdentifierComponentState';
|
||||
import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName';
|
||||
import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState';
|
||||
@@ -15,6 +16,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
export const RecordTableScrollAndZIndexEffect = () => {
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
|
||||
const isMobile = useIsMobile();
|
||||
const [
|
||||
@@ -51,6 +53,7 @@ export const RecordTableScrollAndZIndexEffect = () => {
|
||||
: 'hidden';
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME,
|
||||
newVisibilityOfShadows,
|
||||
);
|
||||
@@ -66,6 +69,7 @@ export const RecordTableScrollAndZIndexEffect = () => {
|
||||
: 'hidden';
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME,
|
||||
newVisibilityOfShadows,
|
||||
);
|
||||
@@ -88,6 +92,7 @@ export const RecordTableScrollAndZIndexEffect = () => {
|
||||
scrollWrapperHTMLElement?.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, [
|
||||
recordTableId,
|
||||
scrollWrapperHTMLElement,
|
||||
isRecordTableScrolledVertically,
|
||||
isRecordTableScrolledHorizontally,
|
||||
|
||||
+11
-5
@@ -2,6 +2,7 @@ import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
|
||||
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
|
||||
import { getRecordTableCellId } from '@/object-record/record-table/utils/getRecordTableCellId';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -19,7 +20,6 @@ export const RecordTableScrollToFocusedCellEffect = () => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
// Handle cell focus
|
||||
useEffect(() => {
|
||||
if (!isRecordTableCellFocusActive) {
|
||||
return;
|
||||
@@ -30,7 +30,11 @@ export const RecordTableScrollToFocusedCellEffect = () => {
|
||||
}
|
||||
|
||||
const focusElement = document.getElementById(
|
||||
`record-table-cell-${recordTableFocusPosition.column}-${recordTableFocusPosition.row}`,
|
||||
getRecordTableCellId(
|
||||
recordTableId,
|
||||
recordTableFocusPosition.column,
|
||||
recordTableFocusPosition.row,
|
||||
),
|
||||
);
|
||||
|
||||
if (!focusElement) {
|
||||
@@ -41,9 +45,11 @@ export const RecordTableScrollToFocusedCellEffect = () => {
|
||||
|
||||
if (isSecondColumn) {
|
||||
const checkBoxColumnCell = document.getElementById(
|
||||
`record-table-cell-0-0`,
|
||||
getRecordTableCellId(recordTableId, 0, 0),
|
||||
);
|
||||
const firstColumnCell = document.getElementById(
|
||||
getRecordTableCellId(recordTableId, 1, 0),
|
||||
);
|
||||
const firstColumnCell = document.getElementById(`record-table-cell-1-0`);
|
||||
|
||||
if (isDefined(checkBoxColumnCell) && isDefined(firstColumnCell)) {
|
||||
const checkBoxColumnWidth = checkBoxColumnCell.offsetWidth;
|
||||
@@ -63,7 +69,7 @@ export const RecordTableScrollToFocusedCellEffect = () => {
|
||||
focusElement.style.scrollMarginBottom = '';
|
||||
}
|
||||
};
|
||||
}, [recordTableFocusPosition, isRecordTableCellFocusActive]);
|
||||
}, [recordTableId, recordTableFocusPosition, isRecordTableCellFocusActive]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
+8
-2
@@ -1,6 +1,7 @@
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { getRecordTableCellId } from '@/object-record/record-table/utils/getRecordTableCellId';
|
||||
import { focusedRecordTableRowIndexComponentState } from '@/object-record/record-table/states/focusedRecordTableRowIndexComponentState';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
@@ -42,7 +43,7 @@ export const RecordTableScrollToFocusedRowEffect = () => {
|
||||
}
|
||||
|
||||
const focusElement = document.getElementById(
|
||||
`record-table-cell-0-${focusedRecordTableRowIndex}`,
|
||||
getRecordTableCellId(recordTableId, 0, focusedRecordTableRowIndex),
|
||||
);
|
||||
|
||||
if (!focusElement) {
|
||||
@@ -59,7 +60,12 @@ export const RecordTableScrollToFocusedRowEffect = () => {
|
||||
focusElement.style.scrollMarginBottom = '';
|
||||
}
|
||||
};
|
||||
}, [focusedRecordTableRowIndex, isRecordTableRowFocusActive, allRecordIds]);
|
||||
}, [
|
||||
recordTableId,
|
||||
focusedRecordTableRowIndex,
|
||||
isRecordTableRowFocusActive,
|
||||
allRecordIds,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
+49
-29
@@ -19,6 +19,11 @@ import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export { HorizontalScrollBoxShadowCSS, VerticalScrollBoxShadowCSS };
|
||||
|
||||
export const RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR =
|
||||
'--record-table-drag-drop-width';
|
||||
export const RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR =
|
||||
'--record-table-checkbox-width';
|
||||
|
||||
const MAX_COLUMNS = 100;
|
||||
|
||||
const columnFieldWidthRules = Array.from(
|
||||
@@ -31,9 +36,15 @@ const columnFieldWidthRules = Array.from(
|
||||
}`,
|
||||
).join('\n');
|
||||
|
||||
export const getRecordTableColumnWidthInlineStyles = (
|
||||
visibleRecordFields: RecordField[],
|
||||
): Record<string, string> => {
|
||||
export const getRecordTableColumnWidthInlineStyles = ({
|
||||
visibleRecordFields,
|
||||
isDragColumnHidden,
|
||||
isCheckboxColumnHidden,
|
||||
}: {
|
||||
visibleRecordFields: RecordField[];
|
||||
isDragColumnHidden?: boolean;
|
||||
isCheckboxColumnHidden?: boolean;
|
||||
}): Record<string, string> => {
|
||||
const style: Record<string, string> = {};
|
||||
|
||||
for (let i = 0; i < visibleRecordFields.length; i++) {
|
||||
@@ -41,6 +52,14 @@ export const getRecordTableColumnWidthInlineStyles = (
|
||||
`${visibleRecordFields[i].size}px`;
|
||||
}
|
||||
|
||||
style[RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR] = isDragColumnHidden
|
||||
? '0px'
|
||||
: `${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px`;
|
||||
|
||||
style[RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR] = isCheckboxColumnHidden
|
||||
? '0px'
|
||||
: `${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px`;
|
||||
|
||||
return style;
|
||||
};
|
||||
|
||||
@@ -58,64 +77,65 @@ const StyledTable = styled.div<{
|
||||
|
||||
width: 100%;
|
||||
|
||||
div.header-cell:nth-of-type(n + 5) {
|
||||
div.header-cell {
|
||||
z-index: ${TABLE_Z_INDEX.headerColumns.headerColumnsNormal};
|
||||
}
|
||||
|
||||
div.header-cell:nth-of-type(1) {
|
||||
div.header-cell.${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME} {
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
|
||||
left: 0px;
|
||||
|
||||
position: sticky;
|
||||
|
||||
z-index: ${TABLE_Z_INDEX.headerColumns.headerColumnsSticky};
|
||||
}
|
||||
|
||||
div.header-cell:nth-of-type(2) {
|
||||
div.header-cell.${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME} {
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
|
||||
left: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
|
||||
left: var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR});
|
||||
position: sticky;
|
||||
|
||||
top: 0;
|
||||
|
||||
z-index: ${TABLE_Z_INDEX.headerColumns.headerColumnsSticky};
|
||||
}
|
||||
|
||||
div.header-cell:nth-of-type(3) {
|
||||
div.header-cell.${getRecordTableColumnFieldWidthClassName(0)} {
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
left: ${`${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px`};
|
||||
left: calc(
|
||||
var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR}) +
|
||||
var(${RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR})
|
||||
);
|
||||
position: sticky;
|
||||
|
||||
right: 0;
|
||||
|
||||
z-index: ${TABLE_Z_INDEX.headerColumns.headerColumnsSticky};
|
||||
|
||||
${HorizontalScrollBoxShadowCSS}
|
||||
}
|
||||
|
||||
div.table-cell:nth-of-type(1) {
|
||||
div.table-cell.${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME} {
|
||||
left: 0px;
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
}
|
||||
|
||||
div.table-cell:nth-of-type(2) {
|
||||
left: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
|
||||
div.table-cell.${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME} {
|
||||
left: var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR});
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
}
|
||||
|
||||
div.table-cell-0-0 {
|
||||
left: ${`${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px`};
|
||||
left: calc(
|
||||
var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR}) +
|
||||
var(${RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR})
|
||||
);
|
||||
position: sticky;
|
||||
|
||||
${HorizontalScrollBoxShadowCSS}
|
||||
}
|
||||
|
||||
div.table-cell:nth-of-type(3) {
|
||||
left: ${`${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px`};
|
||||
div.table-cell.${getRecordTableColumnFieldWidthClassName(0)} {
|
||||
left: calc(
|
||||
var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR}) +
|
||||
var(${RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR})
|
||||
);
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
|
||||
@@ -123,15 +143,15 @@ const StyledTable = styled.div<{
|
||||
}
|
||||
|
||||
div.${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME} {
|
||||
max-width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
|
||||
min-width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
|
||||
width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
|
||||
max-width: var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR});
|
||||
min-width: var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR});
|
||||
width: var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR});
|
||||
}
|
||||
|
||||
div.${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME} {
|
||||
max-width: ${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px;
|
||||
min-width: ${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px;
|
||||
width: ${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px;
|
||||
max-width: var(${RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR});
|
||||
min-width: var(${RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR});
|
||||
width: var(${RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR});
|
||||
}
|
||||
|
||||
div.${RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH_CLASS_NAME} {
|
||||
|
||||
+1
-8
@@ -1,5 +1,4 @@
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
|
||||
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView';
|
||||
import { RecordTable } from '@/object-record/record-table/components/RecordTable';
|
||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
||||
@@ -11,17 +10,14 @@ import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/use
|
||||
import { PageFocusId } from '@/types/PageFocusId';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
||||
|
||||
type RecordTableWithWrappersProps = {
|
||||
objectNameSingular: string;
|
||||
recordTableId: string;
|
||||
viewBarId: string;
|
||||
updateRecordMutation: (params: any) => void;
|
||||
};
|
||||
|
||||
export const RecordTableWithWrappers = ({
|
||||
updateRecordMutation,
|
||||
objectNameSingular,
|
||||
recordTableId,
|
||||
viewBarId,
|
||||
@@ -66,10 +62,7 @@ export const RecordTableWithWrappers = ({
|
||||
<ScrollWrapper
|
||||
componentInstanceId={`record-table-scroll-${recordTableId}`}
|
||||
>
|
||||
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
||||
<RecordTable />
|
||||
<RecordIndexTableContainerEffect />
|
||||
</RecordUpdateContext.Provider>
|
||||
<RecordTable />
|
||||
</ScrollWrapper>
|
||||
</EntityDeleteContext.Provider>
|
||||
</RecordTableContextProvider>
|
||||
|
||||
+3
-3
@@ -2,6 +2,6 @@ import { createContext } from 'react';
|
||||
|
||||
import { type RecordUpdateHookParams } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
|
||||
export const RecordUpdateContext = createContext<
|
||||
(params: RecordUpdateHookParams) => void
|
||||
>({} as any);
|
||||
export const RecordTableUpdateContext = createContext<
|
||||
((params: RecordUpdateHookParams) => void) | undefined
|
||||
>(undefined);
|
||||
+12
@@ -1,4 +1,6 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState';
|
||||
import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState';
|
||||
import { computeLastRecordTableColumnWidth } from '@/object-record/record-table/utils/computeLastRecordTableColumnWidth';
|
||||
@@ -15,10 +17,20 @@ export const useRecordTableLastColumnWidthToFill = () => {
|
||||
shouldCompactRecordTableFirstColumnComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const { lastColumnWidth } = computeLastRecordTableColumnWidth({
|
||||
recordFields: visibleRecordFields,
|
||||
tableWidth: recordTableWidth,
|
||||
shouldCompactFirstColumn: shouldCompactRecordTableFirstColumn,
|
||||
isDragColumnHidden: isRecordTableDragColumnHidden,
|
||||
isCheckboxColumnHidden: isRecordTableCheckboxColumnHidden,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
+15
-2
@@ -4,6 +4,9 @@ import { RecordTableRowDraggableContextProvider } from '@/object-record/record-t
|
||||
import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody';
|
||||
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
|
||||
import { RecordTableCellDragAndDrop } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { RecordTableCellLoading } from '@/object-record/record-table/record-table-cell/components/RecordTableCellLoading';
|
||||
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
|
||||
import { RecordTablePlusButtonCellPlaceholder } from '@/object-record/record-table/record-table-cell/components/RecordTablePlusButtonCellPlaceholder';
|
||||
@@ -12,6 +15,14 @@ import { RecordTableTr } from '@/object-record/record-table/record-table-row/com
|
||||
export const RecordTableBodyLoading = () => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
return (
|
||||
<RecordTableBody>
|
||||
{Array.from({ length: 80 }).map((_, rowIndex) => (
|
||||
@@ -38,8 +49,10 @@ export const RecordTableBodyLoading = () => {
|
||||
data-testid={`row-id-${rowIndex}`}
|
||||
data-selectable-id={`row-id-${rowIndex}`}
|
||||
>
|
||||
<RecordTableCellDragAndDrop />
|
||||
<RecordTableCellCheckbox />
|
||||
{!isRecordTableDragColumnHidden && <RecordTableCellDragAndDrop />}
|
||||
{!isRecordTableCheckboxColumnHidden && (
|
||||
<RecordTableCellCheckbox />
|
||||
)}
|
||||
{visibleRecordFields.map((recordField, index) => (
|
||||
<RecordTableCellLoading
|
||||
key={recordField.fieldMetadataItemId}
|
||||
|
||||
+4
-4
@@ -65,13 +65,13 @@ const cloneColumnFieldWidthRules = Array.from(
|
||||
).join('\n');
|
||||
|
||||
const StyledRowDraggableCloneCSSBridge = styled.div`
|
||||
div.table-cell:nth-of-type(1) {
|
||||
div.table-cell.${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME} {
|
||||
left: 0px;
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
}
|
||||
|
||||
div.table-cell:nth-of-type(2) {
|
||||
div.table-cell.${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME} {
|
||||
left: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
@@ -84,7 +84,7 @@ const StyledRowDraggableCloneCSSBridge = styled.div`
|
||||
${HorizontalScrollBoxShadowCSS}
|
||||
}
|
||||
|
||||
div.table-cell:nth-of-type(3) {
|
||||
div.table-cell.${getRecordTableColumnFieldWidthClassName(0)} {
|
||||
left: ${`${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px`};
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
@@ -147,7 +147,7 @@ export const RecordTableBodyVirtualizedDraggableClone = ({
|
||||
|
||||
const columnWidthStyles = useMemo(() => {
|
||||
const styles: Record<string, string> =
|
||||
getRecordTableColumnWidthInlineStyles(visibleRecordFields);
|
||||
getRecordTableColumnWidthInlineStyles({ visibleRecordFields });
|
||||
styles[RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME] =
|
||||
`${lastColumnWidth}px`;
|
||||
styles[
|
||||
|
||||
+8
-1
@@ -5,7 +5,9 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont
|
||||
import { isFieldIdentifierDisplay } from '@/object-record/record-field/ui/meta-types/display/utils/isFieldIdentifierDisplay';
|
||||
import { RECORD_CHIP_CLICK_OUTSIDE_ID } from '@/object-record/record-table/constants/RecordChipClickOutsideId';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||
import { getRecordTableCellId } from '@/object-record/record-table/utils/getRecordTableCellId';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledBaseContainer = styled.div<{
|
||||
@@ -57,6 +59,7 @@ export const RecordTableCellBaseContainer = ({
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const { cellPosition } = useContext(RecordTableCellContext);
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const isChipDisplay = isFieldIdentifierDisplay(
|
||||
fieldDefinition,
|
||||
@@ -74,7 +77,11 @@ export const RecordTableCellBaseContainer = ({
|
||||
fontColorSecondary={theme.font.color.secondary}
|
||||
fontColorMedium={theme.border.color.medium}
|
||||
isReadOnly={isReadOnly ?? false}
|
||||
id={`record-table-cell-${cellPosition.column}-${cellPosition.row}`}
|
||||
id={getRecordTableCellId(
|
||||
recordTableId,
|
||||
cellPosition.column,
|
||||
cellPosition.row,
|
||||
)}
|
||||
data-record-table-col={cellPosition.column}
|
||||
data-record-table-row={cellPosition.row}
|
||||
data-click-outside-id={
|
||||
|
||||
+9
-15
@@ -19,24 +19,18 @@ const StyledContainer = styled.div`
|
||||
width: ${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH};
|
||||
`;
|
||||
|
||||
const StyledRecordTableTdContainer = styled.div`
|
||||
border-left: 1px solid transparent;
|
||||
`;
|
||||
|
||||
export const RecordTableCellCheckboxPlaceholder = () => {
|
||||
const { hasUserSelectedAllRows } = useRecordTableBodyContextOrThrow();
|
||||
|
||||
return (
|
||||
<StyledRecordTableTdContainer>
|
||||
<RecordTableCellStyleWrapper
|
||||
isSelected={hasUserSelectedAllRows}
|
||||
hasRightBorder={false}
|
||||
widthClassName={RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME}
|
||||
>
|
||||
<StyledContainer data-select-disable>
|
||||
<Checkbox hoverable checked={hasUserSelectedAllRows === true} />
|
||||
</StyledContainer>
|
||||
</RecordTableCellStyleWrapper>
|
||||
</StyledRecordTableTdContainer>
|
||||
<RecordTableCellStyleWrapper
|
||||
isSelected={hasUserSelectedAllRows}
|
||||
hasRightBorder={false}
|
||||
widthClassName={RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME}
|
||||
>
|
||||
<StyledContainer data-select-disable>
|
||||
<Checkbox hoverable checked={hasUserSelectedAllRows === true} />
|
||||
</StyledContainer>
|
||||
</RecordTableCellStyleWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
+21
-13
@@ -9,9 +9,11 @@ import { getJunctionConfig } from '@/object-record/record-field/ui/utils/junctio
|
||||
import { getTargetObjectMetadataIdsFromField } from '@/object-record/record-field/ui/utils/junction/getTargetObjectMetadataIdsFromField';
|
||||
import { hasJunctionConfig } from '@/object-record/record-field/ui/utils/junction/hasJunctionConfig';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableUpdateContext } from '@/object-record/record-table/contexts/RecordTableUpdateContext';
|
||||
import { isRecordTableCellsNonEditableComponentState } from '@/object-record/record-table/states/isRecordTableCellsNonEditableComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useContext, type ReactNode } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
type RecordTableCellFieldContextGenericProps = {
|
||||
@@ -25,6 +27,10 @@ export const RecordTableCellFieldContextGeneric = ({
|
||||
}: RecordTableCellFieldContextGenericProps) => {
|
||||
const { recordId, isRecordReadOnly } = useRecordTableRowContextOrThrow();
|
||||
|
||||
const isRecordTableCellsNonEditable = useAtomComponentStateValue(
|
||||
isRecordTableCellsNonEditableComponentState,
|
||||
);
|
||||
|
||||
const { objectMetadataItem, objectMetadataItems, objectPermissions } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
@@ -36,7 +42,7 @@ export const RecordTableCellFieldContextGeneric = ({
|
||||
const fieldDefinition =
|
||||
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
|
||||
|
||||
const updateRecord = useContext(RecordUpdateContext);
|
||||
const updateRecord = useContext(RecordTableUpdateContext);
|
||||
|
||||
let hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
|
||||
|
||||
@@ -90,7 +96,7 @@ export const RecordTableCellFieldContextGeneric = ({
|
||||
fieldMetadataItemId: recordField.fieldMetadataItemId,
|
||||
recordId,
|
||||
fieldDefinition: fieldDefinition,
|
||||
useUpdateRecord: () => [updateRecord, {}],
|
||||
useUpdateRecord: updateRecord ? () => [updateRecord, {}] : undefined,
|
||||
isLabelIdentifier: isLabelIdentifierField({
|
||||
fieldMetadataItem: {
|
||||
id: fieldDefinition.fieldMetadataId,
|
||||
@@ -99,16 +105,18 @@ export const RecordTableCellFieldContextGeneric = ({
|
||||
objectMetadataItem,
|
||||
}),
|
||||
displayedMaxRows: 1,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly: isRecordReadOnly ?? false,
|
||||
isSystemObject: objectMetadataItem.isSystem,
|
||||
objectPermissions,
|
||||
fieldMetadataItem: {
|
||||
id: fieldDefinition.fieldMetadataId,
|
||||
isUIReadOnly: fieldDefinition.metadata.isUIReadOnly ?? false,
|
||||
isCustom: fieldDefinition.metadata.isCustom ?? false,
|
||||
},
|
||||
}),
|
||||
isRecordFieldReadOnly:
|
||||
isRecordTableCellsNonEditable ||
|
||||
isRecordFieldReadOnly({
|
||||
isRecordReadOnly: isRecordReadOnly ?? false,
|
||||
isSystemObject: objectMetadataItem.isSystem,
|
||||
objectPermissions,
|
||||
fieldMetadataItem: {
|
||||
id: fieldDefinition.fieldMetadataId,
|
||||
isUIReadOnly: fieldDefinition.metadata.isUIReadOnly ?? false,
|
||||
isCustom: fieldDefinition.metadata.isCustom ?? false,
|
||||
},
|
||||
}),
|
||||
isForbidden: !hasObjectReadPermissions,
|
||||
}}
|
||||
>
|
||||
|
||||
+20
-13
@@ -3,10 +3,11 @@ import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordF
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { shouldCompactRecordIndexLabelIdentifierComponentState } from '@/object-record/record-index/states/shouldCompactRecordIndexLabelIdentifierComponentState';
|
||||
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableUpdateContext } from '@/object-record/record-table/contexts/RecordTableUpdateContext';
|
||||
import { isRecordTableCellsNonEditableComponentState } from '@/object-record/record-table/states/isRecordTableCellsNonEditableComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useContext, type ReactNode } from 'react';
|
||||
|
||||
@@ -24,6 +25,10 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
|
||||
const { recordId, isRecordReadOnly, rowIndex } =
|
||||
useRecordTableRowContextOrThrow();
|
||||
|
||||
const isRecordTableCellsNonEditable = useAtomComponentStateValue(
|
||||
isRecordTableCellsNonEditableComponentState,
|
||||
);
|
||||
|
||||
const { recordField } = useContext(RecordTableCellContext);
|
||||
const { objectMetadataItem, onRecordIdentifierClick, triggerEvent } =
|
||||
useRecordTableContextOrThrow();
|
||||
@@ -39,7 +44,7 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
|
||||
|
||||
const hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
|
||||
|
||||
const updateRecord = useContext(RecordUpdateContext);
|
||||
const updateRecord = useContext(RecordTableUpdateContext);
|
||||
|
||||
const fieldDefinition =
|
||||
fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
|
||||
@@ -53,20 +58,22 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
|
||||
value={{
|
||||
recordId,
|
||||
fieldDefinition,
|
||||
useUpdateRecord: () => [updateRecord, {}],
|
||||
useUpdateRecord: updateRecord ? () => [updateRecord, {}] : undefined,
|
||||
isLabelIdentifier: true,
|
||||
isLabelIdentifierCompact: shouldCompactRecordIndexLabelIdentifier,
|
||||
displayedMaxRows: 1,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly: isRecordReadOnly ?? false,
|
||||
isSystemObject: objectMetadataItem.isSystem,
|
||||
objectPermissions,
|
||||
fieldMetadataItem: {
|
||||
id: recordField.fieldMetadataItemId,
|
||||
isUIReadOnly: fieldDefinition.metadata.isUIReadOnly ?? false,
|
||||
isCustom: fieldDefinition.metadata.isCustom ?? false,
|
||||
},
|
||||
}),
|
||||
isRecordFieldReadOnly:
|
||||
isRecordTableCellsNonEditable ||
|
||||
isRecordFieldReadOnly({
|
||||
isRecordReadOnly: isRecordReadOnly ?? false,
|
||||
isSystemObject: objectMetadataItem.isSystem,
|
||||
objectPermissions,
|
||||
fieldMetadataItem: {
|
||||
id: recordField.fieldMetadataItemId,
|
||||
isUIReadOnly: fieldDefinition.metadata.isUIReadOnly ?? false,
|
||||
isCustom: fieldDefinition.metadata.isCustom ?? false,
|
||||
},
|
||||
}),
|
||||
maxWidth: recordField.size,
|
||||
onRecordChipClick: handleChipClick,
|
||||
isForbidden: !hasObjectReadPermissions,
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ export const RecordTableCellHoveredPortalContent = () => {
|
||||
|
||||
const { isRecordFieldReadOnly: isReadOnly } = useContext(FieldContext);
|
||||
|
||||
const isFieldInputOnly = useIsFieldInputOnly();
|
||||
const isFieldInputOnly = useIsFieldInputOnly() && !isReadOnly;
|
||||
|
||||
const showButton =
|
||||
!isFieldInputOnly &&
|
||||
|
||||
+11
-1
@@ -1,5 +1,7 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableCellPortalContexts } from '@/object-record/record-table/record-table-cell/components/RecordTableCellPortalContexts';
|
||||
import { type TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||
import { getRecordTableCellId } from '@/object-record/record-table/utils/getRecordTableCellId';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -10,8 +12,16 @@ export const RecordTableCellPortalWrapper = ({
|
||||
position: TableCellPosition;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const cellId = getRecordTableCellId(
|
||||
recordTableId,
|
||||
position.column,
|
||||
position.row,
|
||||
);
|
||||
|
||||
const tableCellAnchorElement = document.body.querySelector<HTMLAnchorElement>(
|
||||
`#record-table-cell-${position.column}-${position.row}`,
|
||||
`#${cellId}`,
|
||||
);
|
||||
|
||||
if (!isDefined(tableCellAnchorElement)) {
|
||||
|
||||
+10
-7
@@ -1,4 +1,4 @@
|
||||
import { RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnDragAndDropWidth';
|
||||
import { RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnDragAndDropWidthClassName';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { styled } from '@linaria/react';
|
||||
import { cx } from '@linaria/core';
|
||||
@@ -8,13 +8,9 @@ const StyledPlaceholderDragAndDropCell = styled.div`
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
border-bottom: 1px solid ${themeCssVariables.background.primary};
|
||||
height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
|
||||
left: 0;
|
||||
|
||||
min-width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
|
||||
|
||||
overflow: hidden;
|
||||
position: sticky;
|
||||
width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
|
||||
`;
|
||||
|
||||
export const RecordTableDragAndDropPlaceholderCell = ({
|
||||
@@ -22,5 +18,12 @@ export const RecordTableDragAndDropPlaceholderCell = ({
|
||||
}: {
|
||||
className?: string;
|
||||
}) => {
|
||||
return <StyledPlaceholderDragAndDropCell className={cx(className)} />;
|
||||
return (
|
||||
<StyledPlaceholderDragAndDropCell
|
||||
className={cx(
|
||||
RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME,
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+34
-29
@@ -1,3 +1,4 @@
|
||||
import { MockedProvider } from '@apollo/client/testing/react';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { Provider as JotaiProvider } from 'jotai';
|
||||
|
||||
@@ -33,38 +34,42 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
return (
|
||||
<JotaiProvider store={jotaiStore}>
|
||||
<RecordComponentInstanceContextsWrapper
|
||||
componentInstanceId={recordTableId}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId={recordTableId}>
|
||||
<RecordTableContextProvider
|
||||
recordTableId={recordTableId}
|
||||
viewBarId="viewBarId"
|
||||
objectNameSingular={CoreObjectNameSingular.Person}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
recordId: 'recordId',
|
||||
isLabelIdentifier: false,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
<MockedProvider>
|
||||
<RecordComponentInstanceContextsWrapper
|
||||
componentInstanceId={recordTableId}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId={recordTableId}>
|
||||
<RecordTableContextProvider
|
||||
recordTableId={recordTableId}
|
||||
viewBarId="viewBarId"
|
||||
objectNameSingular={CoreObjectNameSingular.Person}
|
||||
>
|
||||
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={recordTableRowDraggableContextValue}
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
recordId: 'recordId',
|
||||
isLabelIdentifier: false,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<RecordTableRowContextProvider
|
||||
value={recordTableRowContextValue}
|
||||
>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{ ...recordTableCellContextValue }}
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={recordTableRowDraggableContextValue}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{ ...recordTableCellContextValue }}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
</MockedProvider>
|
||||
</JotaiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
+35
-30
@@ -1,3 +1,4 @@
|
||||
import { MockedProvider } from '@apollo/client/testing/react';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { Provider as JotaiProvider } from 'jotai';
|
||||
|
||||
@@ -33,39 +34,43 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
return (
|
||||
<JotaiProvider store={jotaiStore}>
|
||||
<RecordComponentInstanceContextsWrapper
|
||||
componentInstanceId={recordTableId}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId={recordTableId}>
|
||||
<RecordTableContextProvider
|
||||
recordTableId={recordTableId}
|
||||
viewBarId="viewBarId"
|
||||
objectNameSingular={CoreObjectNameSingular.Person}
|
||||
onRecordIdentifierClick={() => {}}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
recordId: 'recordId',
|
||||
isLabelIdentifier: false,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
<MockedProvider>
|
||||
<RecordComponentInstanceContextsWrapper
|
||||
componentInstanceId={recordTableId}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId={recordTableId}>
|
||||
<RecordTableContextProvider
|
||||
recordTableId={recordTableId}
|
||||
viewBarId="viewBarId"
|
||||
objectNameSingular={CoreObjectNameSingular.Person}
|
||||
onRecordIdentifierClick={() => {}}
|
||||
>
|
||||
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={recordTableRowDraggableContextValue}
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
recordId: 'recordId',
|
||||
isLabelIdentifier: false,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<RecordTableRowContextProvider
|
||||
value={recordTableRowContextValue}
|
||||
>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{ ...recordTableCellContextValue }}
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={recordTableRowDraggableContextValue}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{ ...recordTableCellContextValue }}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
</MockedProvider>
|
||||
</JotaiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
+8
-3
@@ -1,8 +1,10 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import {
|
||||
RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR,
|
||||
RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR,
|
||||
} from '@/object-record/record-table/components/RecordTableStyleWrapper';
|
||||
import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidth';
|
||||
import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidth';
|
||||
import { RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnDragAndDropWidth';
|
||||
import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnLastEmptyColumnWidthClassName';
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
@@ -15,7 +17,10 @@ const StyledPlaceholderDragAndDropFooterCell = styled.div`
|
||||
bottom: 0;
|
||||
left: 0px;
|
||||
position: sticky;
|
||||
width: ${`${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px`};
|
||||
width: calc(
|
||||
var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR}) +
|
||||
var(${RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR})
|
||||
);
|
||||
|
||||
z-index: ${TABLE_Z_INDEX.footer.stickyColumn};
|
||||
`;
|
||||
|
||||
+46
-10
@@ -2,21 +2,29 @@ import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidth';
|
||||
import { RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnDragAndDropWidth';
|
||||
import {
|
||||
RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR,
|
||||
RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR,
|
||||
} from '@/object-record/record-table/components/RecordTableStyleWrapper';
|
||||
import { RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE } from '@/object-record/record-table/constants/RecordTableLabelIdentifierColumnWidthOnMobile';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
|
||||
import { RecordTableColumnAggregateFooterValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue';
|
||||
import { RecordTableColumnFooterWithDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown';
|
||||
import { hasAggregateOperationForViewFieldFamilySelector } from '@/object-record/record-table/record-table-footer/states/hasAggregateOperationForViewFieldFamilySelector';
|
||||
import { isRecordTableColumnResizableComponentState } from '@/object-record/record-table/states/isRecordTableColumnResizableComponentState';
|
||||
import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { cx } from '@linaria/core';
|
||||
import { findByProperty, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledColumnFooterCell = styled.div<{
|
||||
columnWidth: number;
|
||||
isFirstCell: boolean;
|
||||
isReadOnly: boolean;
|
||||
}>`
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
border-right: solid 1px ${themeCssVariables.background.primary};
|
||||
@@ -25,16 +33,23 @@ const StyledColumnFooterCell = styled.div<{
|
||||
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
|
||||
cursor: ${({ isReadOnly }) => (isReadOnly ? 'default' : 'pointer')};
|
||||
height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
|
||||
left: ${({ isFirstCell }) =>
|
||||
isFirstCell
|
||||
? `${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px`
|
||||
? `calc(var(${RECORD_TABLE_DRAG_DROP_WIDTH_CSS_VAR}) + var(${RECORD_TABLE_CHECKBOX_WIDTH_CSS_VAR}))`
|
||||
: 'auto'};
|
||||
|
||||
min-width: ${({ columnWidth }) => columnWidth}px;
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.secondary};
|
||||
background: ${({ isReadOnly }) =>
|
||||
isReadOnly
|
||||
? themeCssVariables.background.primary
|
||||
: themeCssVariables.background.secondary};
|
||||
}
|
||||
|
||||
min-width: ${({ columnWidth }) => columnWidth}px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
padding: 0;
|
||||
@@ -80,10 +95,21 @@ export const RecordTableAggregateFooterCell = ({
|
||||
}) => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
const { fieldMetadataId } = useContext(
|
||||
const { fieldMetadataId, viewFieldId } = useContext(
|
||||
RecordTableColumnAggregateFooterCellContext,
|
||||
);
|
||||
|
||||
const hasAggregateOperationForViewField = useAtomFamilySelectorValue(
|
||||
hasAggregateOperationForViewFieldFamilySelector,
|
||||
{ viewFieldId },
|
||||
);
|
||||
|
||||
const isRecordTableColumnResizable = useAtomComponentStateValue(
|
||||
isRecordTableColumnResizableComponentState,
|
||||
);
|
||||
|
||||
const isFooterReadOnly = !isRecordTableColumnResizable;
|
||||
|
||||
const recordField = visibleRecordFields.find(
|
||||
findByProperty('fieldMetadataItemId', fieldMetadataId),
|
||||
);
|
||||
@@ -98,16 +124,26 @@ export const RecordTableAggregateFooterCell = ({
|
||||
<StyledColumnFooterCell
|
||||
columnWidth={recordField.size + 1}
|
||||
isFirstCell={isFirstCell}
|
||||
isReadOnly={isFooterReadOnly}
|
||||
className={cx(
|
||||
'footer-cell',
|
||||
getRecordTableColumnFieldWidthClassName(columnIndex),
|
||||
)}
|
||||
>
|
||||
<StyledColumnFootContainer>
|
||||
<RecordTableColumnFooterWithDropdown
|
||||
currentRecordGroupId={currentRecordGroupId}
|
||||
isFirstCell={isFirstCell}
|
||||
/>
|
||||
{isFooterReadOnly ? (
|
||||
hasAggregateOperationForViewField ? (
|
||||
<RecordTableColumnAggregateFooterValue
|
||||
fieldMetadataId={fieldMetadataId}
|
||||
dropdownId={`${fieldMetadataId}-footer-readonly`}
|
||||
/>
|
||||
) : null
|
||||
) : (
|
||||
<RecordTableColumnFooterWithDropdown
|
||||
currentRecordGroupId={currentRecordGroupId}
|
||||
isFirstCell={isFirstCell}
|
||||
/>
|
||||
)}
|
||||
</StyledColumnFootContainer>
|
||||
</StyledColumnFooterCell>
|
||||
);
|
||||
|
||||
+26
-3
@@ -5,10 +5,15 @@ import { RecordTableHeaderAddColumnButton } from '@/object-record/record-table/r
|
||||
import { RecordTableHeaderCell } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCell';
|
||||
import { RecordTableHeaderCheckboxColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn';
|
||||
import { RecordTableHeaderDragDropColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn';
|
||||
import { RecordTableHeaderEmptyLastColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderEmptyLastColumn';
|
||||
import { RecordTableHeaderFirstCell } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderFirstCell';
|
||||
import { RecordTableHeaderFirstScrollableCell } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderFirstScrollableCell';
|
||||
import { RecordTableHeaderLastEmptyColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderLastEmptyColumn';
|
||||
import { useResizeTableHeader } from '@/object-record/record-table/record-table-header/hooks/useResizeTableHeader';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableColumnHeadersReadOnlyComponentState } from '@/object-record/record-table/states/isRecordTableColumnHeadersReadOnlyComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { styled } from '@linaria/react';
|
||||
import { filterOutByProperty } from 'twenty-shared/utils';
|
||||
|
||||
@@ -24,6 +29,18 @@ export const RecordTableHeader = () => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
const { labelIdentifierFieldMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const isRecordTableColumnHeadersReadOnly = useAtomComponentStateValue(
|
||||
isRecordTableColumnHeadersReadOnlyComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const recordFieldsWithoutLabelIdentifierAndFirstOne = visibleRecordFields
|
||||
.filter(
|
||||
filterOutByProperty(
|
||||
@@ -37,8 +54,10 @@ export const RecordTableHeader = () => {
|
||||
|
||||
return (
|
||||
<StyledHeaderContainer>
|
||||
<RecordTableHeaderDragDropColumn />
|
||||
<RecordTableHeaderCheckboxColumn />
|
||||
{!isRecordTableDragColumnHidden && <RecordTableHeaderDragDropColumn />}
|
||||
{!isRecordTableCheckboxColumnHidden && (
|
||||
<RecordTableHeaderCheckboxColumn />
|
||||
)}
|
||||
<RecordTableHeaderFirstCell />
|
||||
<RecordTableHeaderFirstScrollableCell />
|
||||
{recordFieldsWithoutLabelIdentifierAndFirstOne.map(
|
||||
@@ -50,7 +69,11 @@ export const RecordTableHeader = () => {
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<RecordTableHeaderAddColumnButton />
|
||||
{isRecordTableColumnHeadersReadOnly ? (
|
||||
<RecordTableHeaderEmptyLastColumn />
|
||||
) : (
|
||||
<RecordTableHeaderAddColumnButton />
|
||||
)}
|
||||
<RecordTableHeaderLastEmptyColumn />
|
||||
</StyledHeaderContainer>
|
||||
);
|
||||
|
||||
-1
@@ -30,7 +30,6 @@ const StyledPlusIconHeaderCell = styled.div<{
|
||||
? `1px solid ${themeCssVariables.border.color.light}`
|
||||
: 'none'};
|
||||
|
||||
border-right: ${themeCssVariables.border.color.light} !important;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
+32
-12
@@ -2,9 +2,12 @@ import { type RecordField } from '@/object-record/record-field/types/RecordField
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableColumnHead } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHead';
|
||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||
import { RecordTableHeaderCellContainer } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer';
|
||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
||||
import { isRecordTableColumnHeadersReadOnlyComponentState } from '@/object-record/record-table/states/isRecordTableColumnHeadersReadOnlyComponentState';
|
||||
import { isRecordTableColumnResizableComponentState } from '@/object-record/record-table/states/isRecordTableColumnResizableComponentState';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
@@ -28,6 +31,14 @@ export const RecordTableHeaderCell = ({
|
||||
}: RecordTableHeaderCellProps) => {
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableColumnHeadersReadOnly = useAtomComponentStateValue(
|
||||
isRecordTableColumnHeadersReadOnlyComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableColumnResizable = useAtomComponentStateValue(
|
||||
isRecordTableColumnResizableComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableRowActive = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
0,
|
||||
@@ -74,19 +85,28 @@ export const RecordTableHeaderCell = ({
|
||||
key={recordField.fieldMetadataItemId}
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
isResizing={isResizingAnyColumn}
|
||||
isReadOnly={isRecordTableColumnHeadersReadOnly}
|
||||
>
|
||||
<RecordTableHeaderResizeHandler
|
||||
recordFieldIndex={recordFieldIndex}
|
||||
position="left"
|
||||
/>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
<RecordTableHeaderResizeHandler
|
||||
recordFieldIndex={recordFieldIndex}
|
||||
position="right"
|
||||
/>
|
||||
{isRecordTableColumnResizable && (
|
||||
<RecordTableHeaderResizeHandler
|
||||
recordFieldIndex={recordFieldIndex}
|
||||
position="left"
|
||||
/>
|
||||
)}
|
||||
{isRecordTableColumnHeadersReadOnly ? (
|
||||
<RecordTableColumnHead recordField={recordField} />
|
||||
) : (
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
)}
|
||||
{isRecordTableColumnResizable && (
|
||||
<RecordTableHeaderResizeHandler
|
||||
recordFieldIndex={recordFieldIndex}
|
||||
position="right"
|
||||
/>
|
||||
)}
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+7
-5
@@ -6,6 +6,7 @@ const StyledHeaderCell = styled.div<{
|
||||
zIndex?: number;
|
||||
shouldDisplayBorderBottom: boolean;
|
||||
isResizing: boolean;
|
||||
isReadOnly: boolean;
|
||||
}>`
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
border-bottom: ${({ shouldDisplayBorderBottom }) =>
|
||||
@@ -16,7 +17,8 @@ const StyledHeaderCell = styled.div<{
|
||||
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
|
||||
cursor: ${({ isResizing }) => (isResizing ? 'col-resize' : 'pointer')};
|
||||
cursor: ${({ isResizing, isReadOnly }) =>
|
||||
isReadOnly ? 'default' : isResizing ? 'col-resize' : 'pointer'};
|
||||
height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
|
||||
max-height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
@@ -27,15 +29,15 @@ const StyledHeaderCell = styled.div<{
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background: ${({ isResizing }) =>
|
||||
isResizing
|
||||
background: ${({ isResizing, isReadOnly }) =>
|
||||
isReadOnly || isResizing
|
||||
? themeCssVariables.background.primary
|
||||
: themeCssVariables.background.secondary};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${({ isResizing }) =>
|
||||
isResizing
|
||||
background: ${({ isResizing, isReadOnly }) =>
|
||||
isReadOnly || isResizing
|
||||
? themeCssVariables.background.primary
|
||||
: themeCssVariables.background.tertiary};
|
||||
}
|
||||
|
||||
+3
-49
@@ -3,7 +3,6 @@ import { styled } from '@linaria/react';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidth';
|
||||
import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidthClassName';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
@@ -11,26 +10,16 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { useSelectAllRows } from '@/object-record/record-table/hooks/internal/useSelectAllRows';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector';
|
||||
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { cx } from '@linaria/core';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
|
||||
const StyledContainer = styled.div<{
|
||||
shouldDisplayBorderBottom: boolean;
|
||||
}>`
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
border-bottom: ${({ shouldDisplayBorderBottom }) =>
|
||||
shouldDisplayBorderBottom
|
||||
? `1px solid ${themeCssVariables.border.color.light}`
|
||||
: 'none'};
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
display: flex;
|
||||
height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
justify-content: center;
|
||||
@@ -41,7 +30,6 @@ const StyledContainer = styled.div<{
|
||||
const StyledColumnHeaderCell = styled.div`
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
max-height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
@@ -85,37 +73,6 @@ export const RecordTableHeaderCheckboxColumn = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const isRecordTableRowActive = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
0,
|
||||
);
|
||||
|
||||
const isRecordTableRowFocused = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
0,
|
||||
);
|
||||
|
||||
const isRecordTableRowFocusActive = useAtomComponentStateValue(
|
||||
isRecordTableRowFocusActiveComponentState,
|
||||
);
|
||||
|
||||
const isFirstRowActiveOrFocused =
|
||||
isRecordTableRowActive ||
|
||||
(isRecordTableRowFocused && isRecordTableRowFocusActive);
|
||||
|
||||
const isRecordTableScrolledVertically = useAtomComponentStateValue(
|
||||
isRecordTableScrolledVerticallyComponentState,
|
||||
);
|
||||
|
||||
const hasRecordGroups = useAtomComponentSelectorValue(
|
||||
hasRecordGroupsComponentSelector,
|
||||
);
|
||||
|
||||
const shouldDisplayBorderBottom =
|
||||
hasRecordGroups ||
|
||||
!isFirstRowActiveOrFocused ||
|
||||
isRecordTableScrolledVertically;
|
||||
|
||||
return (
|
||||
<StyledColumnHeaderCell
|
||||
className={cx(
|
||||
@@ -123,10 +80,7 @@ export const RecordTableHeaderCheckboxColumn = () => {
|
||||
RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME,
|
||||
)}
|
||||
>
|
||||
<StyledContainer
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
data-select-disable
|
||||
>
|
||||
<StyledContainer data-select-disable>
|
||||
<Checkbox
|
||||
hoverable
|
||||
checked={checked}
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidth';
|
||||
import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidthClassName';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { cx } from '@linaria/core';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledEmptyHeaderCell = styled.div<{
|
||||
shouldDisplayBorderBottom: boolean;
|
||||
}>`
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
border-bottom: ${({ shouldDisplayBorderBottom }) =>
|
||||
shouldDisplayBorderBottom
|
||||
? `1px solid ${themeCssVariables.border.color.light}`
|
||||
: 'none'};
|
||||
height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
max-height: ${RECORD_TABLE_ROW_HEIGHT}px;
|
||||
width: ${RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH}px;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderEmptyLastColumn = () => {
|
||||
const isRecordTableRowActive = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
0,
|
||||
);
|
||||
|
||||
const isRecordTableRowFocused = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
0,
|
||||
);
|
||||
|
||||
const isRecordTableRowFocusActive = useAtomComponentStateValue(
|
||||
isRecordTableRowFocusActiveComponentState,
|
||||
);
|
||||
|
||||
const isFirstRowActiveOrFocused =
|
||||
isRecordTableRowActive ||
|
||||
(isRecordTableRowFocused && isRecordTableRowFocusActive);
|
||||
|
||||
const isRecordTableScrolledVertically = useAtomComponentStateValue(
|
||||
isRecordTableScrolledVerticallyComponentState,
|
||||
);
|
||||
|
||||
const hasRecordGroups = useAtomComponentSelectorValue(
|
||||
hasRecordGroupsComponentSelector,
|
||||
);
|
||||
|
||||
const shouldDisplayBorderBottom =
|
||||
hasRecordGroups ||
|
||||
!isFirstRowActiveOrFocused ||
|
||||
isRecordTableScrolledVertically;
|
||||
|
||||
return (
|
||||
<StyledEmptyHeaderCell
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
className={cx(
|
||||
'header-cell',
|
||||
RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH_CLASS_NAME,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+24
-6
@@ -1,6 +1,7 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableColumnHead } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHead';
|
||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
||||
|
||||
@@ -10,6 +11,8 @@ import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/s
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { RecordTableHeaderLabelIdentifierCellPlusButton } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderLabelIdentifierCellPlusButton';
|
||||
import { getVisibleFieldWithLowestPosition } from '@/object-record/record-table/record-table-header/utils/getVisibleFieldWithLowestPosition';
|
||||
import { isRecordTableColumnHeadersReadOnlyComponentState } from '@/object-record/record-table/states/isRecordTableColumnHeadersReadOnlyComponentState';
|
||||
import { isRecordTableColumnResizableComponentState } from '@/object-record/record-table/states/isRecordTableColumnResizableComponentState';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
@@ -34,6 +37,14 @@ export const RecordTableHeaderFirstCell = () => {
|
||||
const { objectMetadataItem, visibleRecordFields } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableColumnHeadersReadOnly = useAtomComponentStateValue(
|
||||
isRecordTableColumnHeadersReadOnlyComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableColumnResizable = useAtomComponentStateValue(
|
||||
isRecordTableColumnResizableComponentState,
|
||||
);
|
||||
|
||||
const [iconIsVisible, setIconIsVisible] = useState(false);
|
||||
|
||||
const isRecordTableRowActive = useAtomComponentFamilyStateValue(
|
||||
@@ -87,18 +98,25 @@ export const RecordTableHeaderFirstCell = () => {
|
||||
onMouseLeave={() => setIconIsVisible(false)}
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
isResizing={isResizingAnyColumn}
|
||||
isReadOnly={isRecordTableColumnHeadersReadOnly}
|
||||
>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
{iconIsVisible && (
|
||||
{isRecordTableColumnHeadersReadOnly ? (
|
||||
<RecordTableColumnHead recordField={recordField} />
|
||||
) : (
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
)}
|
||||
{iconIsVisible && !isRecordTableColumnHeadersReadOnly && (
|
||||
<StyledPlusButtonWrapper>
|
||||
<RecordTableHeaderLabelIdentifierCellPlusButton />
|
||||
</StyledPlusButtonWrapper>
|
||||
)}
|
||||
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={0} position="right" />
|
||||
{isRecordTableColumnResizable && (
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={0} position="right" />
|
||||
)}
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+26
-6
@@ -2,12 +2,15 @@ import { type RecordField } from '@/object-record/record-field/types/RecordField
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableColumnHead } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHead';
|
||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
||||
|
||||
import { RecordTableHeaderCellContainer } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer';
|
||||
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { isRecordTableColumnHeadersReadOnlyComponentState } from '@/object-record/record-table/states/isRecordTableColumnHeadersReadOnlyComponentState';
|
||||
import { isRecordTableColumnResizableComponentState } from '@/object-record/record-table/states/isRecordTableColumnResizableComponentState';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
@@ -24,6 +27,14 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
const { objectMetadataItem, visibleRecordFields } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableColumnHeadersReadOnly = useAtomComponentStateValue(
|
||||
isRecordTableColumnHeadersReadOnlyComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableColumnResizable = useAtomComponentStateValue(
|
||||
isRecordTableColumnResizableComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableRowActive = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
0,
|
||||
@@ -81,13 +92,22 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
zIndex={TABLE_Z_INDEX.headerColumns.headerColumnsNormal}
|
||||
isResizing={isResizingAnyColumn}
|
||||
isReadOnly={isRecordTableColumnHeadersReadOnly}
|
||||
>
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={1} position="left" />
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={1} position="right" />
|
||||
{isRecordTableColumnResizable && (
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={1} position="left" />
|
||||
)}
|
||||
{isRecordTableColumnHeadersReadOnly ? (
|
||||
<RecordTableColumnHead recordField={recordField} />
|
||||
) : (
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
)}
|
||||
{isRecordTableColumnResizable && (
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={1} position="right" />
|
||||
)}
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+20
@@ -7,6 +7,8 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState';
|
||||
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState';
|
||||
@@ -69,6 +71,16 @@ export const useResizeTableHeader = () => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const handleResizeHandlerStart = useCallback<PointerEventListener>(
|
||||
({ x }) => {
|
||||
resetTableRowSelection();
|
||||
@@ -98,6 +110,7 @@ export const useResizeTableHeader = () => {
|
||||
);
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
getRecordTableColumnFieldWidthCSSVariableName(recordFieldIndex),
|
||||
`${newWidth}px`,
|
||||
);
|
||||
@@ -106,11 +119,14 @@ export const useResizeTableHeader = () => {
|
||||
recordFields: visibleRecordFields,
|
||||
shouldCompactFirstColumn: shouldCompactRecordTableFirstColumn,
|
||||
tableWidth: recordTableWidth,
|
||||
isDragColumnHidden: isRecordTableDragColumnHidden,
|
||||
isCheckboxColumnHidden: isRecordTableCheckboxColumnHidden,
|
||||
});
|
||||
|
||||
const newLastColumnWidth = lastColumnWidth - newResizeOffset;
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME,
|
||||
`${newLastColumnWidth}px`,
|
||||
);
|
||||
@@ -121,6 +137,7 @@ export const useResizeTableHeader = () => {
|
||||
);
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME,
|
||||
`${newGroupSectionLastColumnWidth}px`,
|
||||
);
|
||||
@@ -130,9 +147,12 @@ export const useResizeTableHeader = () => {
|
||||
[
|
||||
initialPointerPositionX,
|
||||
recordField,
|
||||
recordTableId,
|
||||
visibleRecordFields,
|
||||
shouldCompactRecordTableFirstColumn,
|
||||
recordTableWidth,
|
||||
isRecordTableDragColumnHidden,
|
||||
isRecordTableCheckboxColumnHidden,
|
||||
setResizeFieldOffset,
|
||||
],
|
||||
);
|
||||
|
||||
+13
-32
@@ -1,16 +1,7 @@
|
||||
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
|
||||
import { RecordTableCellDragAndDrop } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop';
|
||||
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
|
||||
import { RecordTablePlusButtonCellPlaceholder } from '@/object-record/record-table/record-table-cell/components/RecordTablePlusButtonCellPlaceholder';
|
||||
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
|
||||
import { RecordTableFieldsCells } from '@/object-record/record-table/record-table-row/components/RecordTableFieldsCells';
|
||||
import { RecordTableRowArrowKeysEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect';
|
||||
import { RecordTableRowHotkeyEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
|
||||
import { RecordTableRowCells } from '@/object-record/record-table/record-table-row/components/RecordTableRowCells';
|
||||
import { RecordTableStaticTr } from '@/object-record/record-table/record-table-row/components/RecordTableStaticTr';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
type RecordTableRowProps = {
|
||||
@@ -24,17 +15,17 @@ export const RecordTableRow = ({
|
||||
rowIndexForFocus,
|
||||
rowIndexForDrag,
|
||||
}: RecordTableRowProps) => {
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableRowFocused = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
rowIndexForFocus,
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableRowFocusActive = useAtomComponentStateValue(
|
||||
isRecordTableRowFocusActiveComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
if (isRecordTableDragColumnHidden) {
|
||||
return (
|
||||
<RecordTableStaticTr recordId={recordId} focusIndex={rowIndexForFocus}>
|
||||
<RecordTableRowCells rowIndexForFocus={rowIndexForFocus} />
|
||||
</RecordTableStaticTr>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordTableDraggableTr
|
||||
@@ -42,17 +33,7 @@ export const RecordTableRow = ({
|
||||
draggableIndex={rowIndexForDrag}
|
||||
focusIndex={rowIndexForFocus}
|
||||
>
|
||||
{isRecordTableRowFocusActive && isRecordTableRowFocused && (
|
||||
<>
|
||||
<RecordTableRowHotkeyEffect />
|
||||
<RecordTableRowArrowKeysEffect />
|
||||
</>
|
||||
)}
|
||||
<RecordTableCellDragAndDrop />
|
||||
<RecordTableCellCheckbox />
|
||||
<RecordTableFieldsCells />
|
||||
<RecordTablePlusButtonCellPlaceholder />
|
||||
<RecordTableLastEmptyCell />
|
||||
<RecordTableRowCells rowIndexForFocus={rowIndexForFocus} />
|
||||
</RecordTableDraggableTr>
|
||||
);
|
||||
};
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
|
||||
import { RecordTableCellDragAndDrop } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop';
|
||||
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
|
||||
import { RecordTablePlusButtonCellPlaceholder } from '@/object-record/record-table/record-table-cell/components/RecordTablePlusButtonCellPlaceholder';
|
||||
import { RecordTableFieldsCells } from '@/object-record/record-table/record-table-row/components/RecordTableFieldsCells';
|
||||
import { RecordTableRowArrowKeysEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect';
|
||||
import { RecordTableRowHotkeyEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
type RecordTableRowCellsProps = {
|
||||
rowIndexForFocus: number;
|
||||
};
|
||||
|
||||
export const RecordTableRowCells = ({
|
||||
rowIndexForFocus,
|
||||
}: RecordTableRowCellsProps) => {
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableRowFocused = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
rowIndexForFocus,
|
||||
);
|
||||
|
||||
const isRecordTableRowFocusActive = useAtomComponentStateValue(
|
||||
isRecordTableRowFocusActiveComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isRecordTableRowFocusActive && isRecordTableRowFocused && (
|
||||
<>
|
||||
<RecordTableRowHotkeyEffect />
|
||||
<RecordTableRowArrowKeysEffect />
|
||||
</>
|
||||
)}
|
||||
{!isRecordTableDragColumnHidden && <RecordTableCellDragAndDrop />}
|
||||
{!isRecordTableCheckboxColumnHidden && <RecordTableCellCheckbox />}
|
||||
<RecordTableFieldsCells />
|
||||
<RecordTablePlusButtonCellPlaceholder />
|
||||
<RecordTableLastEmptyCell />
|
||||
</>
|
||||
);
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
|
||||
import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
|
||||
|
||||
type RecordTableStaticTrProps = {
|
||||
recordId: string;
|
||||
focusIndex: number;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const RecordTableStaticTr = ({
|
||||
recordId,
|
||||
focusIndex,
|
||||
children,
|
||||
}: RecordTableStaticTrProps) => {
|
||||
return (
|
||||
<RecordTableTr
|
||||
recordId={recordId}
|
||||
focusIndex={focusIndex}
|
||||
isDragging={false}
|
||||
data-testid={`row-id-${recordId}`}
|
||||
data-selectable-id={recordId}
|
||||
>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={{
|
||||
isDragging: false,
|
||||
dragHandleProps: null,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableTr>
|
||||
);
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
export const isRecordTableCellsNonEditableComponentState =
|
||||
createAtomComponentState<boolean>({
|
||||
key: 'isRecordTableCellsNonEditableComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
export const isRecordTableCheckboxColumnHiddenComponentState =
|
||||
createAtomComponentState<boolean>({
|
||||
key: 'isRecordTableCheckboxColumnHiddenComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
export const isRecordTableColumnHeadersReadOnlyComponentState =
|
||||
createAtomComponentState<boolean>({
|
||||
key: 'isRecordTableColumnHeadersReadOnlyComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
export const isRecordTableColumnResizableComponentState =
|
||||
createAtomComponentState<boolean>({
|
||||
key: 'isRecordTableColumnResizableComponentState',
|
||||
defaultValue: true,
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
export const isRecordTableDragColumnHiddenComponentState =
|
||||
createAtomComponentState<boolean>({
|
||||
key: 'isRecordTableDragColumnHiddenComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { computeLastRecordTableColumnWidth } from '@/object-record/record-table/utils/computeLastRecordTableColumnWidth';
|
||||
|
||||
describe('computeLastRecordTableColumnWidth', () => {
|
||||
it('returns 0 when the table is narrower than the total content width', () => {
|
||||
const recordFields = [{ size: 100 }, { size: 100 }];
|
||||
|
||||
const { lastColumnWidth } = computeLastRecordTableColumnWidth({
|
||||
recordFields,
|
||||
tableWidth: 200,
|
||||
shouldCompactFirstColumn: false,
|
||||
isDragColumnHidden: false,
|
||||
isCheckboxColumnHidden: false,
|
||||
});
|
||||
|
||||
expect(lastColumnWidth).toBe(0);
|
||||
});
|
||||
|
||||
it('returns the remaining positive width when the table is wider than the total content width', () => {
|
||||
const recordFields = [{ size: 100 }, { size: 100 }];
|
||||
|
||||
const { lastColumnWidth } = computeLastRecordTableColumnWidth({
|
||||
recordFields,
|
||||
tableWidth: 300,
|
||||
shouldCompactFirstColumn: false,
|
||||
isDragColumnHidden: false,
|
||||
isCheckboxColumnHidden: false,
|
||||
});
|
||||
|
||||
expect(lastColumnWidth).toBe(25);
|
||||
});
|
||||
|
||||
it('excludes drag-and-drop and checkbox widths from the calculation when both columns are hidden', () => {
|
||||
const recordFields = [{ size: 100 }, { size: 100 }];
|
||||
|
||||
const { lastColumnWidth } = computeLastRecordTableColumnWidth({
|
||||
recordFields,
|
||||
tableWidth: 300,
|
||||
shouldCompactFirstColumn: false,
|
||||
isDragColumnHidden: true,
|
||||
isCheckboxColumnHidden: true,
|
||||
});
|
||||
|
||||
expect(lastColumnWidth).toBe(65);
|
||||
});
|
||||
|
||||
it('uses the compact first column width when shouldCompactFirstColumn is true', () => {
|
||||
const recordFields = [{ size: 100 }, { size: 150 }];
|
||||
|
||||
const { lastColumnWidth: lastColumnWidthWithCompact } =
|
||||
computeLastRecordTableColumnWidth({
|
||||
recordFields,
|
||||
tableWidth: 300,
|
||||
shouldCompactFirstColumn: true,
|
||||
isDragColumnHidden: false,
|
||||
isCheckboxColumnHidden: false,
|
||||
});
|
||||
|
||||
const { lastColumnWidth: lastColumnWidthWithoutCompact } =
|
||||
computeLastRecordTableColumnWidth({
|
||||
recordFields,
|
||||
tableWidth: 300,
|
||||
shouldCompactFirstColumn: false,
|
||||
isDragColumnHidden: false,
|
||||
isCheckboxColumnHidden: false,
|
||||
});
|
||||
|
||||
expect(lastColumnWidthWithCompact).toBe(37);
|
||||
expect(lastColumnWidthWithoutCompact).toBe(0);
|
||||
});
|
||||
});
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { getRecordTableCellId } from '@/object-record/record-table/utils/getRecordTableCellId';
|
||||
|
||||
describe('getRecordTableCellId', () => {
|
||||
it('returns the correct cell id format', () => {
|
||||
expect(getRecordTableCellId('my-table', 2, 5)).toBe(
|
||||
'record-table-cell-my-table-2-5',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the correct cell id format with different values', () => {
|
||||
expect(getRecordTableCellId('other-table', 0, 0)).toBe(
|
||||
'record-table-cell-other-table-0-0',
|
||||
);
|
||||
});
|
||||
});
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { getRecordTableHtmlId } from '@/object-record/record-table/utils/getRecordTableHtmlId';
|
||||
|
||||
describe('getRecordTableHtmlId', () => {
|
||||
it('returns the correct html id format', () => {
|
||||
expect(getRecordTableHtmlId('my-table')).toBe('record-table-my-table');
|
||||
});
|
||||
|
||||
it('returns the correct html id format with a different id', () => {
|
||||
expect(getRecordTableHtmlId('other-table')).toBe(
|
||||
'record-table-other-table',
|
||||
);
|
||||
});
|
||||
});
|
||||
+15
-2
@@ -8,10 +8,14 @@ export const computeLastRecordTableColumnWidth = ({
|
||||
recordFields,
|
||||
tableWidth,
|
||||
shouldCompactFirstColumn,
|
||||
isDragColumnHidden,
|
||||
isCheckboxColumnHidden,
|
||||
}: {
|
||||
recordFields: Pick<RecordField, 'size'>[];
|
||||
tableWidth: number;
|
||||
shouldCompactFirstColumn: boolean;
|
||||
isDragColumnHidden?: boolean;
|
||||
isCheckboxColumnHidden?: boolean;
|
||||
}) => {
|
||||
const { visibleRecordFieldsWidth } = computeVisibleRecordFieldsWidthOnTable({
|
||||
shouldCompactFirstColumn,
|
||||
@@ -20,9 +24,18 @@ export const computeLastRecordTableColumnWidth = ({
|
||||
|
||||
const widthOfBorders = recordFields.length + 1;
|
||||
|
||||
const dragColumnWidth = isDragColumnHidden
|
||||
? 0
|
||||
: RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH;
|
||||
|
||||
const checkboxColumnWidth = isCheckboxColumnHidden
|
||||
? 0
|
||||
: RECORD_TABLE_COLUMN_CHECKBOX_WIDTH;
|
||||
|
||||
const leftColumnsWidth = dragColumnWidth + checkboxColumnWidth;
|
||||
|
||||
const fixedColumnsWidth =
|
||||
RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH +
|
||||
RECORD_TABLE_COLUMN_CHECKBOX_WIDTH +
|
||||
leftColumnsWidth +
|
||||
RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH +
|
||||
widthOfBorders;
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export const getRecordTableCellId = (
|
||||
recordTableId: string,
|
||||
column: number,
|
||||
row: number,
|
||||
): string => {
|
||||
return `record-table-cell-${recordTableId}-${column}-${row}`;
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export const getRecordTableHtmlId = (recordTableId: string): string => {
|
||||
return `record-table-${recordTableId}`;
|
||||
};
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
import { RECORD_TABLE_HTML_ID } from '@/object-record/record-table/constants/RecordTableHtmlId';
|
||||
import { getRecordTableHtmlId } from '@/object-record/record-table/utils/getRecordTableHtmlId';
|
||||
|
||||
export const updateRecordTableCSSVariable = (
|
||||
recordTableId: string,
|
||||
cssVariableName: string,
|
||||
newValue: string,
|
||||
) => {
|
||||
document
|
||||
.querySelector<HTMLDivElement>(`#${RECORD_TABLE_HTML_ID}`)
|
||||
.querySelector<HTMLDivElement>(`#${getRecordTableHtmlId(recordTableId)}`)
|
||||
?.style.setProperty(cssVariableName, newValue);
|
||||
};
|
||||
|
||||
+14
-34
@@ -1,19 +1,10 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
|
||||
import { RecordTableCellDragAndDrop } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop';
|
||||
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
|
||||
import { RecordTablePlusButtonCellPlaceholder } from '@/object-record/record-table/record-table-cell/components/RecordTablePlusButtonCellPlaceholder';
|
||||
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
|
||||
import { RecordTableFieldsCells } from '@/object-record/record-table/record-table-row/components/RecordTableFieldsCells';
|
||||
import { RecordTableRowArrowKeysEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect';
|
||||
import { RecordTableRowHotkeyEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { RecordTableRowCells } from '@/object-record/record-table/record-table-row/components/RecordTableRowCells';
|
||||
import { RecordTableStaticTr } from '@/object-record/record-table/record-table-row/components/RecordTableStaticTr';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { RecordTableRowVirtualizedSkeleton } from '@/object-record/record-table/virtualization/components/RecordTableRowVirtualizedSkeleton';
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
|
||||
import { useAtomComponentFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilySelectorValue';
|
||||
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -21,20 +12,11 @@ type RecordTableRowVirtualizedFullDataProps = {
|
||||
realIndex: number;
|
||||
};
|
||||
|
||||
// TODO: Full Data will take its full meaning when we'll have different levels of data : with relations, only identifiers, etc.
|
||||
export const RecordTableRowVirtualizedFullData = ({
|
||||
realIndex,
|
||||
}: RecordTableRowVirtualizedFullDataProps) => {
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableRowFocused = useAtomComponentFamilyStateValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
realIndex,
|
||||
);
|
||||
|
||||
const isRecordTableRowFocusActive = useAtomComponentStateValue(
|
||||
isRecordTableRowFocusActiveComponentState,
|
||||
recordTableId,
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const recordId = useAtomComponentFamilySelectorValue(
|
||||
@@ -46,23 +28,21 @@ export const RecordTableRowVirtualizedFullData = ({
|
||||
return <RecordTableRowVirtualizedSkeleton />;
|
||||
}
|
||||
|
||||
if (isRecordTableDragColumnHidden) {
|
||||
return (
|
||||
<RecordTableStaticTr recordId={recordId} focusIndex={realIndex}>
|
||||
<RecordTableRowCells rowIndexForFocus={realIndex} />
|
||||
</RecordTableStaticTr>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordTableDraggableTr
|
||||
recordId={recordId}
|
||||
draggableIndex={realIndex}
|
||||
focusIndex={realIndex}
|
||||
>
|
||||
<RecordTableCellDragAndDrop />
|
||||
<RecordTableCellCheckbox />
|
||||
<RecordTableFieldsCells />
|
||||
<RecordTablePlusButtonCellPlaceholder />
|
||||
<RecordTableLastEmptyCell />
|
||||
{isRecordTableRowFocusActive && isRecordTableRowFocused && (
|
||||
<>
|
||||
<RecordTableRowHotkeyEffect />
|
||||
<RecordTableRowArrowKeysEffect />
|
||||
</>
|
||||
)}
|
||||
<RecordTableRowCells rowIndexForFocus={realIndex} />
|
||||
</RecordTableDraggableTr>
|
||||
);
|
||||
};
|
||||
|
||||
+17
-2
@@ -2,20 +2,35 @@ import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH_CLASS_NAME } from '@/object
|
||||
import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnLastEmptyColumnWidthClassName';
|
||||
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { isRecordTableCheckboxColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableCheckboxColumnHiddenComponentState';
|
||||
import { isRecordTableDragColumnHiddenComponentState } from '@/object-record/record-table/states/isRecordTableDragColumnHiddenComponentState';
|
||||
import { RecordTableCellCheckboxPlaceholder } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckboxPlaceholder';
|
||||
import { RecordTableCellLoading } from '@/object-record/record-table/record-table-cell/components/RecordTableCellLoading';
|
||||
import { RecordTableCellStyleWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellStyleWrapper';
|
||||
import { RecordTableDragAndDropPlaceholderCell } from '@/object-record/record-table/record-table-cell/components/RecordTableDragAndDropPlaceholderCell';
|
||||
import { RecordTableRowDiv } from '@/object-record/record-table/record-table-row/components/RecordTableRowDiv';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
export const RecordTableRowVirtualizedSkeleton = () => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
const { hasUserSelectedAllRows } = useRecordTableBodyContextOrThrow();
|
||||
|
||||
const isRecordTableDragColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableDragColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableCheckboxColumnHidden = useAtomComponentStateValue(
|
||||
isRecordTableCheckboxColumnHiddenComponentState,
|
||||
);
|
||||
|
||||
return (
|
||||
<RecordTableRowDiv isDragging={false}>
|
||||
<RecordTableDragAndDropPlaceholderCell />
|
||||
<RecordTableCellCheckboxPlaceholder />
|
||||
{!isRecordTableDragColumnHidden && (
|
||||
<RecordTableDragAndDropPlaceholderCell />
|
||||
)}
|
||||
{!isRecordTableCheckboxColumnHidden && (
|
||||
<RecordTableCellCheckboxPlaceholder />
|
||||
)}
|
||||
{visibleRecordFields.map((recordField, index) => (
|
||||
<RecordTableCellLoading
|
||||
key={recordField.fieldMetadataItemId}
|
||||
|
||||
+3
@@ -127,11 +127,13 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
resetVirtualizedRowTreadmill();
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME,
|
||||
'hidden',
|
||||
);
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
recordTableId,
|
||||
RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME,
|
||||
'hidden',
|
||||
);
|
||||
@@ -226,6 +228,7 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
upsertRecordsInStore,
|
||||
loadRecordsToVirtualRows,
|
||||
reapplyRowSelection,
|
||||
recordTableId,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user