Skip to content

Commit

Permalink
feat: record presort (#1040)
Browse files Browse the repository at this point in the history
* feat: record presort

* fix: rendering issue with update cell values when grouping in grid view

* fix: dragging row is not taking effect

* fix: the page crash caused when grouping conditions change

* refactor: simplify the code
  • Loading branch information
Sky-FE authored Oct 30, 2024
1 parent 0a89c89 commit ff85b31
Show file tree
Hide file tree
Showing 29 changed files with 525 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import type { ICreateRecordsVo, IRecord, IRecordsVo } from '@teable/openapi';
import type { ICreateRecordsVo, IRecord, IRecordStatusVo, IRecordsVo } from '@teable/openapi';
import {
createRecordsRoSchema,
getRecordQuerySchema,
Expand Down Expand Up @@ -177,4 +177,14 @@ export class RecordOpenApiController {
) {
return this.recordService.getDocIdsByQuery(tableId, query);
}

@Permissions('record|read')
@Get(':recordId/status')
async getRecordStatus(
@Param('tableId') tableId: string,
@Param('recordId') recordId: string,
@Query(new ZodValidationPipe(getRecordsRoSchema), TqlPipe) query: IGetRecordsRo
): Promise<IRecordStatusVo> {
return await this.recordService.getRecordStatus(tableId, recordId, query);
}
}
35 changes: 35 additions & 0 deletions apps/nestjs-backend/src/features/record/record.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import type {
IGroupHeaderPoint,
IGroupPoint,
IGroupPointsVo,
IRecordStatusVo,
IRecordsVo,
} from '@teable/openapi';
import { GroupPointType, UploadType } from '@teable/openapi';
Expand Down Expand Up @@ -1678,4 +1679,38 @@ export class RecordService {

return { groupPoints, filter: mergeFilter(filter, filterWithCollapsed) };
}

async getRecordStatus(
tableId: string,
recordId: string,
query: IGetRecordsRo
): Promise<IRecordStatusVo> {
const dbTableName = await this.getDbTableName(tableId);
const queryBuilder = this.knex(dbTableName).select('__id').where('__id', recordId).limit(1);

const result = await this.prismaService
.txClient()
.$queryRawUnsafe<{ __id: string }[]>(queryBuilder.toQuery());

const isDeleted = result.length === 0;

if (isDeleted) {
return { isDeleted, isVisible: false };
}

const queryResult = await this.getDocIdsByQuery(tableId, {
viewId: query.viewId,
skip: query.skip,
take: query.take,
filter: query.filter,
orderBy: query.orderBy,
search: query.search,
groupBy: query.groupBy,
filterLinkCellCandidate: query.filterLinkCellCandidate,
filterLinkCellSelected: query.filterLinkCellSelected,
selectedRecordIds: query.selectedRecordIds,
});
const isVisible = queryResult.ids.includes(recordId);
return { isDeleted, isVisible };
}
}
3 changes: 1 addition & 2 deletions apps/nextjs-app/src/features/app/blocks/graph/CellGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { useMutation } from '@tanstack/react-query';
import { ColorUtils } from '@teable/core';
import { DraggableHandle, X } from '@teable/icons';
import { IdReturnType, getGraph, getIdsFromRanges } from '@teable/openapi';
import { useBaseId, useTableId, useViewId } from '@teable/sdk';
import { useBaseId, useGridViewStore, useTableId, useViewId } from '@teable/sdk';
import { Button } from '@teable/ui-lib/shadcn';
import { useEffect, useRef, useState } from 'react';
import { Rnd } from 'react-rnd';
import { useMount } from 'react-use';
import { useGridViewStore } from '../view/grid/store/gridView';
import { useCellGraphStore } from './useCellGraphStore';
import { useGraph } from './useGraph';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
generateLocalId,
useGridTooltipStore,
RegionType,
useGridViewStore,
} from '@teable/sdk/components';
import {
useIsHydrated,
Expand All @@ -52,7 +53,6 @@ import type { IExpandRecordContainerRef } from '@/features/app/components/Expand
import { useHiddenFields } from '@/features/app/hooks/useHiddenFields';
import { GIRD_ROW_HEIGHT_DEFINITIONS } from '../../../../view/grid/const';
import { useSelectionOperation } from '../../../../view/grid/hooks';
import { useGridViewStore } from '../../../../view/grid/store/gridView';

interface IGridViewProps {
groupPointsServerData?: IGroupPointsVo;
Expand Down
126 changes: 105 additions & 21 deletions apps/nextjs-app/src/features/app/blocks/view/grid/GridViewBaseInner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import type {
ICellItem,
ICell,
IInnerCell,
Record,
GridView,
IGroupPoint,
IUseTablePermissionAction,
Expand Down Expand Up @@ -48,6 +47,10 @@ import {
SelectableType,
useGridRowOrder,
ExpandRecorder,
useGridViewStore,
useGridSelection,
Record,
DragRegionType,
} from '@teable/sdk';
import { GRID_DEFAULT } from '@teable/sdk/components/grid/configs';
import { useScrollFrameRate } from '@teable/sdk/components/grid/hooks';
Expand Down Expand Up @@ -76,13 +79,12 @@ import type { IExpandRecordContainerRef } from '@/features/app/components/Expand
import { tableConfig } from '@/features/i18n/table.config';
import { FieldOperator } from '../../../components/field-setting';
import { useFieldSettingStore } from '../field/useFieldSettingStore';
import { PrefillingRowContainer } from './components';
import { PrefillingRowContainer, PresortRowContainer } from './components';
import type { IConfirmNewRecordsRef } from './components/ConfirmNewRecords';
import { ConfirmNewRecords } from './components/ConfirmNewRecords';
import { GIRD_ROW_HEIGHT_DEFINITIONS } from './const';
import { DomBox } from './DomBox';
import { useCollaborate, useSelectionOperation } from './hooks';
import { useGridViewStore } from './store/gridView';

interface IGridViewBaseInnerProps {
groupPointsServerData?: IGroupPointsVo;
Expand All @@ -97,10 +99,6 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
const { groupPointsServerData, onRowExpand } = props;
const { t } = useTranslation(tableConfig.i18nNamespaces);
const router = useRouter();
const gridRef = useRef<IGridRef>(null);
const prefillingGridRef = useRef<IGridRef>(null);
const containerRef = useRef<HTMLDivElement>(null);
const expandRecordRef = useRef<IExpandRecordContainerRef>(null);
const tableId = useTableId() as string;
const activeViewId = useViewId();
const view = useView(activeViewId) as GridView | undefined;
Expand Down Expand Up @@ -128,10 +126,17 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
const realRowCount = rowCount ?? ssrRecords?.length ?? 0;
const fieldEditable = useFieldCellEditable();
const { undo, redo } = useUndoRedo();

const [expandRecord, setExpandRecord] = useState<{ tableId: string; recordId: string }>();
const confirmNewRecordsRef = useRef<IConfirmNewRecordsRef>(null);
const [newRecords, setNewRecords] = useState<ICreateRecordsRo['records']>();

const gridRef = useRef<IGridRef>(null);
const presortGridRef = useRef<IGridRef>(null);
const prefillingGridRef = useRef<IGridRef>(null);
const containerRef = useRef<HTMLDivElement>(null);
const expandRecordRef = useRef<IExpandRecordContainerRef>(null);
const confirmNewRecordsRef = useRef<IConfirmNewRecordsRef>(null);

const groupCollection = useGridGroupCollection();

const { viewQuery, collapsedGroupIds, onCollapsedGroupChanged } = useGridCollapsedGroup(
Expand All @@ -143,14 +148,23 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (

const commentCountMap = useCommentCountMap(recordsQuery);

const onRowOrdered = useGridRowOrder(recordMap);
const { onRowOrdered, setDraggingRecordIds } = useGridRowOrder(recordMap);

const { copy, paste, clear, deleteRecords } = useSelectionOperation({
collapsedGroupIds: viewQuery?.collapsedGroupIds
? Array.from(viewQuery?.collapsedGroupIds)
: undefined,
});

const {
presortRecord,
onSelectionChanged,
presortRecordData,
onPresortCellEdited,
getPresortCellContent,
setPresortRecordData,
} = useGridSelection({ recordMap, columns, viewQuery, gridRef });

const {
localRecord,
prefillingRowIndex,
Expand Down Expand Up @@ -480,10 +494,13 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
copy(selection);
};

const onCopyForPrefilling = async (selection: CombinedSelection) => {
const onCopyForSingleRow = async (
selection: CombinedSelection,
fieldValueMap?: { [fieldId: string]: unknown }
) => {
const { type } = selection;

if (type !== SelectionRegionType.Cells || prefillingFieldValueMap == null) return;
if (type !== SelectionRegionType.Cells || fieldValueMap == null) return;

const getCopyData = async () => {
const [start, end] = selection.serialize();
Expand All @@ -495,9 +512,7 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
})
.filter(Boolean) as IFieldVo[];
const content = [
selectedFields.map((field) =>
field.cellValue2String(prefillingFieldValueMap[field.id] as never)
),
selectedFields.map((field) => field.cellValue2String(fieldValueMap[field.id] as never)),
];
return { content: stringifyClipboardText(content), header: filteredPropsFields };
};
Expand Down Expand Up @@ -526,12 +541,20 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
});
};

const onSelectionChanged = useCallback(
(selection: CombinedSelection) => {
setSelection(selection);
},
[setSelection]
);
const onPasteForPresort = (selection: CombinedSelection, e: React.ClipboardEvent) => {
if (!presortRecord) return;
if (!permission['record|update']) {
return toast({ title: 'Unable to paste' });
}
paste(e, selection, { 0: presortRecord }, (records) => {
Record.updateRecord(tableId, presortRecord.id, {
fieldKeyType: FieldKeyType.Id,
record: {
fields: { ...presortRecord.fields, ...records[0].fields },
},
});
});
};

const collaborators = useCollaborate(selection, getCellContent);

Expand Down Expand Up @@ -650,6 +673,16 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
return DraggableType.All;
}, [isAutoSort]);

const onDragStart = useCallback(
(type: DragRegionType, dragIndexs: number[]) => {
if (type === DragRegionType.Rows) {
const recordIds = dragIndexs.map((index) => recordMap[index]?.id).filter(Boolean);
setDraggingRecordIds(recordIds);
}
},
[recordMap, setDraggingRecordIds]
);

const getAuthorizedFunction = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<T extends (...args: any[]) => any>(
Expand Down Expand Up @@ -686,6 +719,24 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
};
}, [rowHeight, prefillingRowIndex]);

const presortRowStyle = useMemo(() => {
const defaultTop = rowHeight / 2;
const height = rowHeight + 5;
const rowIndex = presortRecordData?.rowIndex;

if (gridRef.current == null || rowIndex == null) {
return { top: defaultTop, height };
}

return {
top: Math.max(
gridRef.current.getRowOffset(rowIndex) + defaultTop,
GIRD_ROW_HEIGHT_DEFINITIONS[RowHeightLevel.Short]
),
height,
};
}, [rowHeight, presortRecordData]);

useEffect(() => {
if (!inPrefilling) return;
const scrollState = gridRef.current?.getScrollState();
Expand Down Expand Up @@ -731,6 +782,7 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
collaborators={collaborators}
getCellContent={getCellContent}
onDelete={getAuthorizedFunction(onDelete, 'record|update')}
onDragStart={onDragStart}
onRowOrdered={onRowOrdered}
onRowExpand={onRowExpandInner}
onRowAppend={
Expand Down Expand Up @@ -792,11 +844,43 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
getCellContent={getPrefillingCellContent}
onScrollChanged={onPrefillingGridScrollChanged}
onCellEdited={onPrefillingCellEdited}
onCopy={onCopyForPrefilling}
onCopy={(selection) => onCopyForSingleRow(selection, prefillingFieldValueMap)}
onPaste={onPasteForPrefilling}
/>
</PrefillingRowContainer>
)}
{presortRecord && (
<PresortRowContainer
style={presortRowStyle}
onClickOutside={async () => setPresortRecordData(undefined)}
>
<Grid
ref={presortGridRef}
theme={theme}
scrollBufferX={
permission['field|create'] ? scrollBuffer + columnAppendBtnWidth : scrollBuffer
}
scrollBufferY={0}
scrollBarVisible={false}
rowCount={1}
rowHeight={rowHeight}
rowIndexVisible={false}
rowControls={rowControls}
draggable={DraggableType.None}
selectable={SelectableType.Cell}
columns={columns}
columnHeaderVisible={false}
commentCountMap={commentCountMap}
freezeColumnCount={frozenColumnCount}
customIcons={customIcons}
getCellContent={getPresortCellContent}
onScrollChanged={onPrefillingGridScrollChanged}
onCellEdited={onPresortCellEdited}
onCopy={(selection) => onCopyForSingleRow(selection, presortRecord.fields)}
onPaste={onPasteForPresort}
/>
</PresortRowContainer>
)}
<RowCounter rowCount={realRowCount} className="absolute bottom-3 left-0" />
<DomBox id={componentId} />
{!onRowExpand && <ExpandRecordContainer ref={expandRecordRef} recordServerData={ssrRecord} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ import {
} from '@teable/icons';
import { deleteFields } from '@teable/openapi';
import type { GridView, IUseFieldPermissionAction } from '@teable/sdk';
import { useFields, useIsTouchDevice, useTableId, useTablePermission, useView } from '@teable/sdk';
import {
useFields,
useGridViewStore,
useIsTouchDevice,
useTableId,
useTablePermission,
useView,
} from '@teable/sdk';
import { TablePermissionContext } from '@teable/sdk/context/table-permission';
import { insertSingle } from '@teable/sdk/utils';

Expand All @@ -37,7 +44,6 @@ import { FieldOperator } from '@/features/app/components/field-setting/type';
import { tableConfig } from '@/features/i18n/table.config';
import { useFieldSettingStore } from '../../field/useFieldSettingStore';
import { useToolBarStore } from '../../tool-bar/components/useToolBarStore';
import { useGridViewStore } from '../store/gridView';
import type { IMenuItemProps } from './RecordMenu';

enum MenuItemType {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ArrowUpDown } from '@teable/icons';
import { useTranslation } from 'next-i18next';
import { useRef } from 'react';
import { useClickAway } from 'react-use';
import { tableConfig } from '@/features/i18n/table.config';

interface IRowStatusContainerProps {
children: React.ReactNode;
style?: React.CSSProperties;
onClickOutside?: () => void;
}

export const PresortRowContainer = (props: IRowStatusContainerProps) => {
const { style, children, onClickOutside } = props;
const prefillingGridContainerRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation(tableConfig.i18nNamespaces);

useClickAway(prefillingGridContainerRef, () => {
onClickOutside?.();
});

return (
<div
ref={prefillingGridContainerRef}
className="absolute left-0 w-full border-y-2 border-violet-500 dark:border-violet-700"
style={style}
>
<div className="absolute left-0 top-[-32px] flex h-8 items-center rounded-ss-lg bg-violet-500 px-2 py-1 text-background dark:border-violet-700">
<ArrowUpDown className="mr-1" />
<span className="text-[13px]">{t('table:grid.presortRowTitle')}</span>
</div>
{children}
</div>
);
};
Loading

0 comments on commit ff85b31

Please sign in to comment.