Skip to content

Commit 1e6e4e1

Browse files
authored
perf: calendar interaction (#1112)
* perf: add loading animation when loading data in calendar view * feat: add time prefix to event
1 parent 7f8a169 commit 1e6e4e1

5 files changed

Lines changed: 1592 additions & 1467 deletions

File tree

apps/nextjs-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
"buffer": "6.0.3",
133133
"class-variance-authority": "0.7.0",
134134
"date-fns": "2.30.0",
135+
"date-fns-tz": "2.0.1",
135136
"dayjs": "1.11.10",
136137
"echarts": "5.5.0",
137138
"emoji-mart": "5.5.2",

apps/nextjs-app/src/features/app/blocks/view/calendar/components/Calendar.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { EventResizeDoneArg } from '@fullcalendar/interaction';
1010
import interactionPlugin from '@fullcalendar/interaction';
1111
import FullCalendar from '@fullcalendar/react';
1212
import { FieldKeyType } from '@teable/core';
13-
import { ChevronLeft, ChevronRight, Calendar as CalendarIcon } from '@teable/icons';
13+
import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Loader2 } from '@teable/icons';
1414
import { updateRecord } from '@teable/openapi';
1515
import { AppContext, CalendarDailyCollectionContext } from '@teable/sdk/context';
1616
import { useTableId } from '@teable/sdk/hooks';
@@ -25,6 +25,7 @@ import {
2525
PopoverContent,
2626
PopoverTrigger,
2727
Calendar as DatePicker,
28+
cn,
2829
} from '@teable/ui-lib/shadcn';
2930
import { addDays, format } from 'date-fns';
3031
import { enUS, zhCN, ja, ru, fr } from 'date-fns/locale';
@@ -34,7 +35,7 @@ import { tableConfig } from '@/features/i18n/table.config';
3435
import { EventListContainer } from '../components/EventListContainer';
3536
import { EventMenu } from '../components/EventMenu';
3637
import { useCalendar, useEventMenuStore } from '../hooks';
37-
import { getColorByConfig } from '../util';
38+
import { getColorByConfig, getEventTitle } from '../util';
3839

3940
const ADD_EVENT_BUTTON_CLASS_NAME = 'calendar-add-event-button';
4041
const MORE_LINK_TEXT_CLASS_NAME = 'calendar-custom-more-link-text';
@@ -203,6 +204,7 @@ export const Calendar = (props: ICalendarProps) => {
203204
});
204205
};
205206

207+
const isLoading = !calendarDailyCollection;
206208
const { countMap, records = [] } = calendarDailyCollection ?? {};
207209

208210
const events = useMemo(() => {
@@ -222,8 +224,12 @@ export const Calendar = (props: ICalendarProps) => {
222224

223225
return {
224226
id: r.id,
225-
title: titleField.cellValue2String(title) || t('untitled'),
226-
start: startDateField.cellValue2String(start),
227+
title: getEventTitle(
228+
titleField.cellValue2String(title) || t('sdk:common.unnamedRecord'),
229+
start as string,
230+
startDateField
231+
),
232+
start,
227233
end: end ? addDays(new Date(end as string), 1).toISOString() : undefined,
228234
textColor,
229235
backgroundColor,
@@ -372,8 +378,14 @@ export const Calendar = (props: ICalendarProps) => {
372378
<div className="relative flex size-full flex-col overflow-hidden p-4 pt-2" ref={containerRef}>
373379
<div className="mb-2 flex items-center justify-between">
374380
<div className="flex items-center gap-2">
375-
<h2 className="text-xl font-semibold">
381+
<h2 className="flex items-center text-xl font-semibold">
376382
{title || calendarRef.current?.getApi().view.title}
383+
<Loader2
384+
className={cn(
385+
'ml-1 size-5 animate-spin transition-opacity duration-1000',
386+
isLoading ? 'opacity-100' : 'opacity-0'
387+
)}
388+
/>
377389
</h2>
378390
</div>
379391
<div className="flex items-center gap-2">

apps/nextjs-app/src/features/app/blocks/view/calendar/components/EventList.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { RecordItem, RecordList } from '@teable/sdk/components';
33
import { useRowCount } from '@teable/sdk/hooks';
44
import { useInfiniteRecords } from '@teable/sdk/hooks/use-infinite-records';
55
import { Skeleton } from '@teable/ui-lib/shadcn';
6+
import { useTranslation } from 'next-i18next';
7+
import { tableConfig } from '@/features/i18n/table.config';
68
import { useCalendar } from '../hooks';
9+
import { getEventTitle } from '../util';
710

811
interface IEventListProps {
912
query?: IQueryBaseRo;
@@ -12,7 +15,8 @@ interface IEventListProps {
1215
export const EventList = (props: IEventListProps) => {
1316
const { query } = props;
1417
const rowCount = useRowCount();
15-
const { setExpandRecordId } = useCalendar();
18+
const { titleField, startDateField, endDateField, setExpandRecordId } = useCalendar();
19+
const { t } = useTranslation(tableConfig.i18nNamespaces);
1620

1721
const { onVisibleRegionChanged, recordMap } = useInfiniteRecords(query);
1822

@@ -23,10 +27,20 @@ export const EventList = (props: IEventListProps) => {
2327
itemClassName="p-0 rounded-md aria-selected:bg-transparent aria-selected:text-inherit"
2428
itemRender={(index) => {
2529
const record = recordMap[index];
26-
if (!record) {
30+
31+
if (!record || !titleField || !startDateField || !endDateField) {
2732
return <Skeleton className="h-[30px] w-full" />;
2833
}
29-
return <RecordItem title={record.name} className="bg-background py-1" />;
34+
35+
const title = record.fields[titleField.id];
36+
const start = record.fields[startDateField.id];
37+
const displayTitle = getEventTitle(
38+
titleField.cellValue2String(title) || t('sdk:common.unnamedRecord'),
39+
start as string,
40+
startDateField
41+
);
42+
43+
return <RecordItem title={displayTitle} className="bg-background py-1" />;
3044
}}
3145
rowCount={rowCount ?? 0}
3246
onSelect={(index) => {

apps/nextjs-app/src/features/app/blocks/view/calendar/util.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { IColorConfig } from '@teable/core';
2-
import { ColorConfigType } from '@teable/core';
2+
import { ColorConfigType, TimeFormatting } from '@teable/core';
33
import { getColorPairs } from '@teable/sdk/components';
4-
import type { Record, SingleSelectField } from '@teable/sdk/model';
4+
import type { DateField, Record, SingleSelectField } from '@teable/sdk/model';
5+
import { formatInTimeZone } from 'date-fns-tz';
56
import { DEFAULT_COLOR } from './components/CalendarConfig';
67

78
export const getColorByConfig = (
@@ -22,3 +23,15 @@ export const getColorByConfig = (
2223
}
2324
return getColorPairs(color ?? DEFAULT_COLOR);
2425
};
26+
27+
export const getEventTitle = (title: string, startDate: string | null, dateField: DateField) => {
28+
const { time, timeZone } = dateField.options.formatting;
29+
const includeTime = time !== TimeFormatting.None;
30+
const timeStr = time === TimeFormatting.Hour24 ? time : 'hh:mm a';
31+
const prefixStr =
32+
includeTime && startDate
33+
? `${formatInTimeZone(new Date(startDate as string), timeZone, timeStr)} `
34+
: '';
35+
36+
return `${prefixStr}${title}`;
37+
};

0 commit comments

Comments
 (0)