Skip to content

Commit b073877

Browse files
committed
fix: task list load more on scroll bottom. gh-181
1 parent 9a9d02b commit b073877

File tree

1 file changed

+166
-135
lines changed

1 file changed

+166
-135
lines changed

src/routes/ins/$insID/_layout/tasks.tsx

Lines changed: 166 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,19 @@ import { getDuration } from '@/utils/text';
77
import { Modal, Select, Table, TagInput } from '@douyinfe/semi-ui';
88
import { ColumnProps } from '@douyinfe/semi-ui/lib/es/table';
99
import { Button } from '@nextui-org/react';
10-
import { useQuery } from '@tanstack/react-query';
10+
import { useInfiniteQuery } from '@tanstack/react-query';
1111
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
1212
import _ from 'lodash';
1313
import { TasksQuery, Task, TaskTypes, TaskStatus } from 'meilisearch';
14-
import { useEffect, useReducer } from 'react';
14+
import { useEffect, useMemo, useReducer } from 'react';
1515
import { useTranslation } from 'react-i18next';
1616
import ReactJson from 'react-json-view';
1717
import { z } from 'zod';
1818

1919
const searchSchema = z
2020
.object({
2121
indexUids: z.string().array().optional(),
22-
limit: z.number().positive().optional(),
23-
from: z.number().nonnegative().optional(),
22+
limit: z.number().positive().optional().default(20),
2423
statuses: z.string().array().optional(),
2524
types: z.string().array().optional(),
2625
})
@@ -53,15 +52,27 @@ const Page = () => {
5352
});
5453
}, [navigate, state]);
5554

56-
const query = useQuery({
55+
// @ts-expect-error
56+
const query = useInfiniteQuery({
5757
queryKey: ['tasks', host, state],
58-
queryFn: async () => {
58+
queryFn: async ({ pageParam }: { pageParam: { limit: number; from?: number } }) => {
5959
showRequestLoader();
6060
console.debug('getTasks', client.config, state);
6161
return await client.getTasks({
6262
..._.omitBy(state, _.isEmpty),
63+
from: pageParam.from,
64+
limit: pageParam.limit,
6365
});
6466
},
67+
initialPageParam: {
68+
limit: state.limit,
69+
},
70+
getNextPageParam: (lastPage) => {
71+
return {
72+
limit: lastPage.limit,
73+
from: lastPage.next,
74+
};
75+
},
6576
});
6677

6778
useEffect(() => {
@@ -73,142 +84,162 @@ const Page = () => {
7384
}
7485
}, [query.error, query.isError, query.isFetching]);
7586

76-
const columns: ColumnProps<Task>[] = [
77-
{
78-
title: 'UID',
79-
dataIndex: 'uid',
80-
width: 100,
81-
},
82-
{
83-
title: t('indexes'),
84-
dataIndex: 'indexUid',
85-
render: (val) => (val ? <Link to={`/ins/${currentInstance.id}/index/${val}`}>{val}</Link> : '-'),
86-
},
87-
{
88-
title: t('common:type'),
89-
dataIndex: 'type',
90-
render: (_) => t(`type.${_}`),
91-
},
92-
{
93-
title: t('common:status'),
94-
dataIndex: 'status',
95-
width: 120,
96-
render: (_) => t(`status.${_}`),
97-
},
98-
{
99-
title: t('duration'),
100-
dataIndex: 'duration',
101-
width: 200,
102-
render: (_, item) => {
103-
if (!item.duration) {
104-
return '-';
105-
}
87+
const list = useMemo(() => {
88+
return query.data?.pages.map((page) => page.results).reduce((acc, cur) => acc.concat(cur), []) || [];
89+
}, [query.data?.pages]);
10690

107-
return `${getDuration(item.duration, 'millisecond')}ms`;
91+
const columns: ColumnProps<Task>[] = useMemo(
92+
() => [
93+
{
94+
title: 'UID',
95+
dataIndex: 'uid',
96+
width: 100,
10897
},
109-
},
110-
{
111-
title: t('enqueued_at'),
112-
dataIndex: 'enqueuedAt',
113-
width: 220,
114-
render: (_, item) => {
115-
return <TimeAgo date={item.enqueuedAt} />;
98+
{
99+
title: t('indexes'),
100+
dataIndex: 'indexUid',
101+
render: (val) => (val ? <Link to={`/ins/${currentInstance.id}/index/${val}`}>{val}</Link> : '-'),
116102
},
117-
},
118-
{
119-
title: t('started_at'),
120-
dataIndex: 'startedAt',
121-
width: 220,
122-
render: (_, item) => {
123-
return <TimeAgo date={item.startedAt} />;
103+
{
104+
title: t('common:type'),
105+
dataIndex: 'type',
106+
render: (_) => t(`type.${_}`),
124107
},
125-
},
126-
{
127-
title: t('finished_at'),
128-
dataIndex: 'finishedAt',
129-
width: 220,
130-
render: (_, item) => {
131-
return <TimeAgo date={item.finishedAt} />;
108+
{
109+
title: t('common:status'),
110+
dataIndex: 'status',
111+
width: 120,
112+
render: (_) => t(`status.${_}`),
132113
},
133-
},
134-
{
135-
title: t('actions'),
136-
fixed: 'right',
137-
width: 150,
138-
render: (_, record) => (
139-
<div className="flex justify-center items-center gap-2">
140-
<Button
141-
size="sm"
142-
onClick={() => {
143-
Modal.info({
144-
title: t('common:detail'),
145-
centered: true,
146-
footer: null,
147-
content: (
148-
<div className="flex justify-center items-center p-4 pb-6">
149-
<ReactJson
150-
name={false}
151-
displayDataTypes={false}
152-
displayObjectSize={false}
153-
enableClipboard={false}
154-
src={record}
155-
collapsed={3}
156-
collapseStringsAfterLength={50}
157-
/>
158-
</div>
159-
),
160-
});
161-
}}
162-
variant="flat"
163-
>
164-
{t('common:detail')}
165-
</Button>
166-
</div>
167-
),
168-
},
169-
];
114+
{
115+
title: t('duration'),
116+
dataIndex: 'duration',
117+
width: 200,
118+
render: (_, item) => {
119+
if (!item.duration) {
120+
return '-';
121+
}
170122

171-
return (
172-
<div className="flex-1 overflow-scroll max-h-fit">
173-
<main className="p-4 flex flex-col gap-4">
174-
<div className={`flex justify-end items-center gap-4`}>
175-
<TagInput
176-
className="flex-1"
177-
placeholder={t('filter.index.placeholder')}
178-
value={state.indexUids}
179-
onChange={(value) => {
180-
updateState({ indexUids: value });
181-
}}
182-
/>
183-
<Select
184-
placeholder={t('filter.type.placeholder')}
185-
optionList={_.entries(t('type', { returnObjects: true }) as Record<string, string>).map(([k, v]) => ({
186-
value: k,
187-
label: v,
188-
}))}
189-
multiple
190-
value={state.types}
191-
onChange={(value) => {
192-
updateState({ types: (value as TaskTypes[]) || undefined });
193-
}}
194-
/>
195-
<Select
196-
placeholder={t('filter.status.placeholder')}
197-
optionList={_.entries(t('status', { returnObjects: true }) as Record<string, string>).map(([k, v]) => ({
198-
value: k,
199-
label: v,
200-
}))}
201-
multiple
202-
value={state.statuses}
203-
onChange={(value) => {
204-
updateState({ statuses: (value as TaskStatus[]) || undefined });
205-
}}
206-
/>
207-
</div>
123+
return `${getDuration(item.duration, 'millisecond')}ms`;
124+
},
125+
},
126+
{
127+
title: t('enqueued_at'),
128+
dataIndex: 'enqueuedAt',
129+
width: 220,
130+
render: (_, item) => {
131+
return <TimeAgo date={item.enqueuedAt} />;
132+
},
133+
},
134+
{
135+
title: t('started_at'),
136+
dataIndex: 'startedAt',
137+
width: 220,
138+
render: (_, item) => {
139+
return <TimeAgo date={item.startedAt} />;
140+
},
141+
},
142+
{
143+
title: t('finished_at'),
144+
dataIndex: 'finishedAt',
145+
width: 220,
146+
render: (_, item) => {
147+
return <TimeAgo date={item.finishedAt} />;
148+
},
149+
},
150+
{
151+
title: t('actions'),
152+
fixed: 'right',
153+
width: 150,
154+
render: (_, record) => (
155+
<div className="flex justify-center items-center gap-2">
156+
<Button
157+
size="sm"
158+
onClick={() => {
159+
Modal.info({
160+
title: t('common:detail'),
161+
centered: true,
162+
footer: null,
163+
content: (
164+
<div className="flex justify-center items-center p-4 pb-6">
165+
<ReactJson
166+
name={false}
167+
displayDataTypes={false}
168+
displayObjectSize={false}
169+
enableClipboard={false}
170+
src={record}
171+
collapsed={3}
172+
collapseStringsAfterLength={50}
173+
/>
174+
</div>
175+
),
176+
});
177+
}}
178+
variant="flat"
179+
>
180+
{t('common:detail')}
181+
</Button>
182+
</div>
183+
),
184+
},
185+
],
186+
[currentInstance.id, t]
187+
);
208188

209-
<Table columns={columns} dataSource={query.data?.results} pagination={false} empty={t('empty')} />
210-
</main>
211-
</div>
189+
return useMemo(
190+
() => (
191+
<div className="flex-1 max-h-fit overflow-hidden">
192+
<main className="flex flex-col gap-4 h-full">
193+
<div className={`p-4 flex justify-end items-center gap-4 sticky top-0 z-10 bg-white`}>
194+
<TagInput
195+
className="flex-1"
196+
placeholder={t('filter.index.placeholder')}
197+
value={state.indexUids}
198+
onChange={(value) => {
199+
updateState({ indexUids: value });
200+
}}
201+
/>
202+
<Select
203+
placeholder={t('filter.type.placeholder')}
204+
optionList={_.entries(t('type', { returnObjects: true }) as Record<string, string>).map(([k, v]) => ({
205+
value: k,
206+
label: v,
207+
}))}
208+
multiple
209+
value={state.types}
210+
onChange={(value) => {
211+
updateState({ types: (value as TaskTypes[]) || undefined });
212+
}}
213+
/>
214+
<Select
215+
placeholder={t('filter.status.placeholder')}
216+
optionList={_.entries(t('status', { returnObjects: true }) as Record<string, string>).map(([k, v]) => ({
217+
value: k,
218+
label: v,
219+
}))}
220+
multiple
221+
value={state.statuses}
222+
onChange={(value) => {
223+
updateState({ statuses: (value as TaskStatus[]) || undefined });
224+
}}
225+
/>
226+
</div>
227+
228+
<div
229+
className="p-2 overflow-scroll"
230+
onScroll={(e) => {
231+
const { scrollTop, clientHeight, scrollHeight } = e.target;
232+
if (Math.abs(scrollHeight - (scrollTop + clientHeight)) <= 1) {
233+
query.fetchNextPage();
234+
}
235+
}}
236+
>
237+
<Table columns={columns} dataSource={list} pagination={false} empty={t('empty')} />
238+
</div>
239+
</main>
240+
</div>
241+
),
242+
[t, state.indexUids, state.types, state.statuses, columns, list, query]
212243
);
213244
};
214245

0 commit comments

Comments
 (0)