Skip to content

Commit 8e945d8

Browse files
authored
fix/button-field (#1748)
* fix: update button component styles and enhance button click status handling - Adjusted button height in CellButton and Editor components for improved UI consistency. - Updated localization strings for button-related fields to enhance clarity. - Refactored useButtonClickStatus hook to provide a loading state for button cells. - Enhanced button cell renderer to support loading state visibility. * refactor: clean up button click status handling and update imports - Removed unused useButtonClickStatus hook from TableInfo component. - Integrated useButtonClickStatus into GridViewBaseInner for improved button click handling. - Updated useCreateCellValue2GridDisplay to conditionally check button click loading state. - Enhanced cleanup logic in useButtonClickStatus to prevent unnecessary subscriptions. * refactor: improve button click loading state handling * refactor: update button click event status hander * feat: enhance button click handling and share view support * fix: update button click response structure to include additional fields * fix: improve button click status handling by adding loading state checks * feat: integrate button click status handling across components * refactor: button click handling and loading state management
1 parent e208088 commit 8e945d8

25 files changed

Lines changed: 277 additions & 122 deletions

File tree

apps/nestjs-backend/src/event-emitter/events/table/button.event.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import type { IButtonClickVo } from '@teable/openapi';
1+
import type { IRecord } from '@teable/core';
22
import { match } from 'ts-pattern';
33
import { CoreEvent, type IEventContext } from '../core-event';
44
import { Events } from '../event.enum';
55

6-
type IButtonClickEventPayload = IButtonClickVo;
6+
type IButtonClickEventPayload = {
7+
tableId: string;
8+
fieldId: string;
9+
record: IRecord;
10+
};
711

812
export class ButtonClickEvent extends CoreEvent<IButtonClickEventPayload> {
913
public readonly name = Events.TABLE_BUTTON_CLICK;

apps/nestjs-backend/src/event-emitter/interceptor/event.Interceptor.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { match, P } from 'ts-pattern';
99
import { EMIT_EVENT_NAME } from '../decorators/emit-controller-event.decorator';
1010
import { EventEmitterService } from '../event-emitter.service';
1111
import type { IEventContext } from '../events';
12-
import { Events, BaseEventFactory, SpaceEventFactory, ButtonEventFactory } from '../events';
12+
import { Events, BaseEventFactory, SpaceEventFactory } from '../events';
1313

1414
@Injectable()
1515
export class EventMiddleware implements NestInterceptor {
@@ -69,9 +69,6 @@ export class EventMiddleware implements NestInterceptor {
6969
.with(P.union(Events.SPACE_CREATE, Events.SPACE_DELETE, Events.SPACE_UPDATE), () =>
7070
SpaceEventFactory.create(eventName, { space: resolveData, ...reqParams }, eventContext)
7171
)
72-
.with(P.union(Events.TABLE_BUTTON_CLICK), () =>
73-
ButtonEventFactory.create(eventName, { ...resolveData }, eventContext)
74-
)
7572
.otherwise(() => null);
7673
}
7774
}

apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Patch,
1010
Post,
1111
Query,
12+
Req,
1213
UploadedFile,
1314
UseInterceptors,
1415
} from '@nestjs/common';
@@ -219,14 +220,16 @@ export class RecordOpenApiController {
219220
return { taskId: '' };
220221
}
221222

222-
@Permissions('record|update')
223+
@Permissions('record|read')
223224
@Post(':recordId/:fieldId/button-click')
224225
async buttonClick(
226+
@Req() req: Express.Request,
225227
@Param('tableId') tableId: string,
226228
@Param('recordId') recordId: string,
227229
@Param('fieldId') fieldId: string
228230
): Promise<IButtonClickVo> {
229-
return await this.recordOpenApiService.buttonClick(tableId, recordId, fieldId);
231+
const result = await this.recordOpenApiService.buttonClick(tableId, recordId, fieldId);
232+
return { ...result, runId: '' };
230233
}
231234

232235
@Permissions('record|update')

apps/nextjs-app/src/features/app/blocks/share/view/component/grid/GridViewBase.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { IGetRecordsRo, IGroupPointsVo, IRangesRo } from '@teable/openapi';
55
import { saveQueryParams, shareViewCopy } from '@teable/openapi';
66
import type {
77
CombinedSelection,
8+
IButtonCell,
89
ICell,
910
ICellItem,
1011
IGridRef,
@@ -34,6 +35,7 @@ import {
3435
LARGE_QUERY_THRESHOLD,
3536
} from '@teable/sdk/components';
3637
import {
38+
useButtonClickStatus,
3739
useFields,
3840
useIsHydrated,
3941
useIsTouchDevice,
@@ -48,11 +50,13 @@ import { Skeleton, useToast } from '@teable/ui-lib/shadcn';
4850
import { uniqueId } from 'lodash';
4951
import { useRouter } from 'next/router';
5052
import { useCallback, useEffect, useMemo, useRef } from 'react';
53+
import { useTranslation } from 'react-i18next';
5154
import { useClickAway } from 'react-use';
5255
import { DomBox } from '@/features/app/blocks/view/grid/DomBox';
5356
import { useGridSearchStore } from '@/features/app/blocks/view/grid/useGridSearchStore';
5457
import { ExpandRecordContainer } from '@/features/app/components/expand-record-container';
5558
import type { IExpandRecordContainerRef } from '@/features/app/components/expand-record-container/types';
59+
import { tableConfig } from '@/features/i18n/table.config';
5660
import {
5761
GIRD_FIELD_NAME_HEIGHT_DEFINITIONS,
5862
GIRD_ROW_HEIGHT_DEFINITIONS,
@@ -65,8 +69,9 @@ interface IGridViewProps {
6569

6670
export const GridViewBase = (props: IGridViewProps) => {
6771
const { groupPointsServerData } = props;
72+
const { t } = useTranslation(tableConfig.i18nNamespaces);
6873
const view = useView();
69-
const tableId = useTableId();
74+
const tableId = useTableId() as string;
7075
const router = useRouter();
7176
const isHydrated = useIsHydrated();
7277
const gridRef = useRef<IGridRef>(null);
@@ -88,6 +93,7 @@ export const GridViewBase = (props: IGridViewProps) => {
8893
const customIcons = useGridIcons();
8994
const { openTooltip, closeTooltip } = useGridTooltipStore();
9095
const { setGridRef, searchCursor } = useGridSearchStore();
96+
const buttonClickStatusHook = useButtonClickStatus(tableId);
9197

9298
const prepare = isHydrated && view && columns.length;
9399
const { filter, sort } = view ?? {};
@@ -213,11 +219,11 @@ export const GridViewBase = (props: IGridViewProps) => {
213219
if (record !== undefined) {
214220
const fieldId = columns[colIndex]?.id;
215221
if (!fieldId) return { type: CellType.Loading };
216-
return cellValue2GridDisplay(record, colIndex);
222+
return cellValue2GridDisplay(record, colIndex, false, undefined, buttonClickStatusHook);
217223
}
218224
return { type: CellType.Loading };
219225
},
220-
[recordMap, columns, cellValue2GridDisplay]
226+
[recordMap, columns, cellValue2GridDisplay, buttonClickStatusHook]
221227
);
222228

223229
const onCopy = useCallback(
@@ -241,6 +247,29 @@ export const GridViewBase = (props: IGridViewProps) => {
241247
[columns, openStatisticMenu]
242248
);
243249

250+
const onCellValueHovered = (bounds: IRectangle, cellItem: ICellItem) => {
251+
const cellInfo = getCellContent(cellItem);
252+
if (!cellInfo?.id) {
253+
return;
254+
}
255+
256+
if (cellInfo.type === CellType.Button) {
257+
const { data } = cellInfo as IButtonCell;
258+
const { fieldOptions, cellValue } = data;
259+
const { label } = fieldOptions;
260+
const count = cellValue?.count ?? 0;
261+
const maxCount = fieldOptions?.maxCount ?? 0;
262+
openTooltip({
263+
id: componentId,
264+
text: t('sdk:common.clickedCount', {
265+
label,
266+
text: maxCount > 0 ? `${count}/${maxCount}` : `${count}`,
267+
}),
268+
position: bounds,
269+
});
270+
}
271+
};
272+
244273
const componentId = useMemo(() => uniqueId('shared-grid-view-'), []);
245274

246275
const onItemHovered = (type: RegionType, bounds: IRectangle, cellItem: ICellItem) => {
@@ -256,6 +285,10 @@ export const GridViewBase = (props: IGridViewProps) => {
256285
position: bounds,
257286
});
258287
}
288+
289+
if (type === RegionType.CellValue) {
290+
onCellValueHovered(bounds, cellItem);
291+
}
259292
};
260293

261294
const onGroupHeaderContextMenu = (groupId: string, position: IPosition) => {
@@ -319,7 +352,11 @@ export const GridViewBase = (props: IGridViewProps) => {
319352
</div>
320353
)}
321354
<DomBox id={componentId} />
322-
<ExpandRecordContainer ref={expandRecordRef} recordServerData={ssrRecord} />
355+
<ExpandRecordContainer
356+
ref={expandRecordRef}
357+
recordServerData={ssrRecord}
358+
buttonClickStatusHook={buttonClickStatusHook}
359+
/>
323360
</div>
324361
);
325362
};

apps/nextjs-app/src/features/app/blocks/table/table-header/TableInfo.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
useTablePermission,
66
useLanDayjs,
77
useIsHydrated,
8-
useButtonClickStatus,
98
} from '@teable/sdk/hooks';
109
import { Spin } from '@teable/ui-lib/base';
1110
import { cn, Input } from '@teable/ui-lib/shadcn';
@@ -26,7 +25,6 @@ export const TableInfo: React.FC<{ className?: string }> = ({ className }) => {
2625
const isHydrated = useIsHydrated();
2726

2827
const { loading: isImporting } = useImportStatus(table?.id as string);
29-
useButtonClickStatus(table?.id as string);
3028

3129
const icon = table?.icon ? (
3230
<Emoji size={'1.25rem'} emoji={table.icon} />

apps/nextjs-app/src/features/app/blocks/view/grid/GridViewBaseInner.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import {
7676
useView,
7777
useViewId,
7878
useRecordOperations,
79+
useButtonClickStatus,
7980
} from '@teable/sdk/hooks';
8081
import { ConfirmDialog, useToast } from '@teable/ui-lib';
8182
import { toast as sonnerToast } from '@teable/ui-lib/shadcn/ui/sonner';
@@ -129,6 +130,7 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
129130
const usage = useBaseUsage();
130131
const allFields = useFields({ withHidden: true });
131132
const taskStatusCollection = useContext(TaskStatusCollectionContext);
133+
const buttonClickStatusHook = useButtonClickStatus(tableId);
132134
const { columns: originalColumns, cellValue2GridDisplay } = useGridColumns();
133135
const { columns, onColumnResize } = useGridColumnResize(originalColumns);
134136
const { columnStatistics } = useGridColumnStatistics(columns);
@@ -348,13 +350,17 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
348350
if (record !== undefined) {
349351
const fieldId = columns[colIndex]?.id;
350352
if (!fieldId) return { type: CellType.Loading };
351-
return cellValue2GridDisplay(record, colIndex, false, (tableId, recordId) =>
352-
setExpandRecord({ tableId, recordId })
353+
return cellValue2GridDisplay(
354+
record,
355+
colIndex,
356+
false,
357+
(tableId, recordId) => setExpandRecord({ tableId, recordId }),
358+
buttonClickStatusHook
353359
);
354360
}
355361
return { type: CellType.Loading };
356362
},
357-
[recordMap, columns, cellValue2GridDisplay]
363+
[recordMap, columns, cellValue2GridDisplay, buttonClickStatusHook]
358364
);
359365

360366
const onCellEdited = useCallback(
@@ -1160,14 +1166,21 @@ export const GridViewBaseInner: React.FC<IGridViewBaseInnerProps> = (
11601166
)}
11611167
<RowCounter rowCount={realRowCount} className="absolute bottom-3 left-0" />
11621168
<DomBox id={componentId} />
1163-
{!onRowExpand && <ExpandRecordContainer ref={expandRecordRef} recordServerData={ssrRecord} />}
1169+
{!onRowExpand && (
1170+
<ExpandRecordContainer
1171+
ref={expandRecordRef}
1172+
recordServerData={ssrRecord}
1173+
buttonClickStatusHook={buttonClickStatusHook}
1174+
/>
1175+
)}
11641176
{expandRecord != null && (
11651177
<ExpandRecorder
11661178
tableId={expandRecord.tableId}
11671179
viewId={activeViewId}
11681180
recordId={expandRecord.recordId}
11691181
recordIds={[expandRecord.recordId]}
11701182
onClose={() => setExpandRecord(undefined)}
1183+
buttonClickStatusHook={buttonClickStatusHook}
11711184
/>
11721185
)}
11731186
<ConfirmNewRecords

apps/nextjs-app/src/features/app/components/expand-record-container/ExpandRecordContainer.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { IRecord } from '@teable/core';
2+
import type { IButtonClickStatusHook } from '@teable/sdk/hooks';
23
import { useTableId, useViewId } from '@teable/sdk/hooks';
34
import { useRouter } from 'next/router';
45
import { forwardRef, useCallback } from 'react';
@@ -7,9 +8,9 @@ import type { IExpandRecordContainerRef } from './types';
78

89
export const ExpandRecordContainer = forwardRef<
910
IExpandRecordContainerRef,
10-
{ recordServerData?: IRecord }
11+
{ recordServerData?: IRecord; buttonClickStatusHook?: IButtonClickStatusHook }
1112
>((props, forwardRef) => {
12-
const { recordServerData } = props;
13+
const { recordServerData, buttonClickStatusHook } = props;
1314
const router = useRouter();
1415
const tableId = useTableId();
1516
const viewId = useViewId();
@@ -65,6 +66,7 @@ export const ExpandRecordContainer = forwardRef<
6566
recordServerData={recordServerData}
6667
onClose={onClose}
6768
onUpdateRecordIdCallback={onUpdateRecordIdCallback}
69+
buttonClickStatusHook={buttonClickStatusHook}
6870
/>
6971
);
7072
});

apps/nextjs-app/src/features/app/components/expand-record-container/ExpandRecordContainerBase.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { IRecord } from '@teable/core';
2+
import type { IButtonClickStatusHook } from '@teable/sdk';
23
import { ExpandRecorder, ExpandRecordModel } from '@teable/sdk';
34
import { useRouter } from 'next/router';
45
import { forwardRef, useImperativeHandle, useState } from 'react';
@@ -12,9 +13,17 @@ export const ExpandRecordContainerBase = forwardRef<
1213
recordServerData?: IRecord;
1314
onClose?: () => void;
1415
onUpdateRecordIdCallback?: (recordId: string) => void;
16+
buttonClickStatusHook?: IButtonClickStatusHook;
1517
}
1618
>((props, forwardRef) => {
17-
const { tableId, viewId, recordServerData, onClose, onUpdateRecordIdCallback } = props;
19+
const {
20+
tableId,
21+
viewId,
22+
recordServerData,
23+
onClose,
24+
onUpdateRecordIdCallback,
25+
buttonClickStatusHook,
26+
} = props;
1827
const router = useRouter();
1928
const recordId = router.query.recordId as string;
2029
const commentId = router.query.commentId as string;
@@ -35,6 +44,7 @@ export const ExpandRecordContainerBase = forwardRef<
3544
model={ExpandRecordModel.Modal}
3645
onClose={onClose}
3746
onUpdateRecordIdCallback={onUpdateRecordIdCallback}
47+
buttonClickStatusHook={buttonClickStatusHook}
3848
/>
3949
);
4050
});

packages/common-i18n/src/locales/en/sdk.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@
255255
"singleSelect": "Single select",
256256
"number": "Number",
257257
"multipleSelect": "Multiple select",
258-
"link": "Link to another record",
258+
"link": "Link to record",
259259
"formula": "Formula",
260260
"date": "Date",
261261
"createdTime": "Created time",

packages/common-i18n/src/locales/en/table.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@
137137
"title": "Button",
138138
"label": "Button label",
139139
"color": "Button color",
140-
"limitCount": "Limit click count",
141-
"resetCount": "Allow resetting click count",
142-
"maxCount": "Max click count",
140+
"limitCount": "Limit clicks",
141+
"resetCount": "Allow reset",
142+
"maxCount": "Max clicks",
143143
"automation": "Automation",
144144
"customAutomation": "Custom automation"
145145
},

0 commit comments

Comments
 (0)