Skip to content

Commit

Permalink
feat(fe): add recycle bin & delete features
Browse files Browse the repository at this point in the history
  • Loading branch information
lehuygiang28 committed Jun 13, 2024
1 parent 2a6c1c4 commit 134ae2e
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 11 deletions.
6 changes: 6 additions & 0 deletions apps/fe/src/app/(auth)/recycle-bin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { ThemedLayout } from '~/components/themed-layout';

export default function Layout({ children }: React.PropsWithChildren) {
return <ThemedLayout>{children}</ThemedLayout>;
}
192 changes: 192 additions & 0 deletions apps/fe/src/app/(auth)/recycle-bin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
'use client';

import { HttpError, useDelete, useUpdate } from '@refinedev/core';
import { List, useTable } from '@refinedev/antd';
import { Space, Table, Typography, Button, Popconfirm } from 'antd';
import { FileProtectOutlined, RollbackOutlined, DeleteOutlined } from '@ant-design/icons';
import { toString as cronReadable } from 'cronstrue';
import { getSchedule, stringToArray } from 'cron-converter';

import { type TaskDto } from '~be/app/tasks/dtos';
import { HttpMethodTag } from '~/components/tag/http-method-tag';
import { HttpMethodEnum } from '~be/app/tasks/tasks.enum';
import Link from 'next/link';

const { Text } = Typography;

interface TableSearch {
name?: string;
}

export default function TaskList() {
const {
tableProps: { pagination, ...tableProps },
} = useTable<TaskDto, HttpError, TableSearch>({
resource: 'tasks',
syncWithLocation: true,
pagination: {
mode: 'server',
},
filters: {
mode: 'server',
initial: [
{
field: 'isDeleted',
value: true,
operator: 'eq',
},
],
},
});
const { mutate: mutateDelete } = useDelete<TaskDto>({});
const { mutate: mutateRestore } = useUpdate<TaskDto>({});

return (
<List>
<Table<TaskDto>
{...tableProps}
rowKey="_id"
pagination={{
...pagination,
position: ['bottomRight'],
size: 'small',
showSizeChanger: true,
showTotal: (total) => `Total ${total} tasks`,
defaultPageSize: 5,
pageSizeOptions: [5, 10, 20, 50, 100],
showTitle: false,
}}
>
<Table.Column
dataIndex="name"
title={'Name'}
onFilter={(value, record) => record.name.indexOf(value as string) === 0}
sorter={(a: TaskDto, b: TaskDto) => a.name.localeCompare(b.name)}
sortDirections={['descend', 'ascend']}
/>
<Table.Column
dataIndex="method"
title={'Method'}
render={(method) => <HttpMethodTag method={method} />}
filters={Object.values(HttpMethodEnum).map((value: string) => ({
value: value,
text: <HttpMethodTag method={value} />,
}))}
onFilter={(value, record) => record.method.indexOf(String(value)) === 0}
sorter={(a: TaskDto, b: TaskDto) => a.method.localeCompare(b.method)}
sortDirections={['descend', 'ascend']}
/>
<Table.Column
dataIndex="cron"
title={'Cron'}
render={(_, record: TaskDto) => (
<>
<Space direction="vertical">
<Text>
<pre style={{ display: 'inline' }}>{record.cron}</pre>
</Text>
<Text type="secondary">
{cronReadable(record.cron, {
throwExceptionOnParseError: false,
locale: 'en',
use24HourTimeFormat: true,
})}
</Text>
</Space>
</>
)}
filters={[
{ text: 'More than once a day', value: 'more-once-day' },
{ text: 'Less than once a day', value: 'less-once-day' },
{ text: 'Every hour', value: 'hour' },
{ text: 'Every day', value: 'day' },
{ text: 'Every week', value: 'week' },
]}
onFilter={(value, record) => {
const schedule = getSchedule(stringToArray(record.cron));
const nextRun1 = schedule.next();
const nextRun2 = schedule.next();
const diffInHours = Math.abs(nextRun1 - nextRun2) / 36e5;

switch (value) {
case 'more-once-day':
return diffInHours < 24;
case 'less-once-day':
return diffInHours >= 24;
case 'hour':
return diffInHours <= 1;
case 'day':
return diffInHours <= 24 && diffInHours > 1;
case 'week':
return diffInHours <= 168 && diffInHours > 24;
default:
return true;
}
}}
sorter={(a: TaskDto, b: TaskDto) => {
const scheduleA = getSchedule(stringToArray(a.cron));
const scheduleB = getSchedule(stringToArray(b.cron));
const nextRunA = scheduleA.next();
const nextRunB = scheduleB.next();
return nextRunA - nextRunB;
}}
/>
<Table.Column
title={'Actions'}
dataIndex="actions"
render={(_, record: TaskDto) => (
<Space>
<Link href={`/tasks/logs/${record._id}`}>
<Button size="small" type="default">
<FileProtectOutlined />
</Button>
</Link>
<Popconfirm
key={'restore'}
okText="Yes"
cancelText="No"
icon={<RollbackOutlined style={{ color: 'green' }} />}
title="Are you sure you want to restore this task?"
onConfirm={() => {
mutateRestore({
id: record._id.toString(),
resource: 'tasks',
invalidates: ['list', 'detail'],
values: {
deletedAt: null,
},
});
}}
>
<Button size="small" type="default" icon={<RollbackOutlined />} />
</Popconfirm>
<Popconfirm
key={'delete'}
okText="Yes"
cancelText="No"
title="Are you sure you want to delete this task forever?"
description="This action cannot be undone. Your task data will be permanently deleted."
onConfirm={() => {
mutateDelete({
id: record._id.toString(),
resource: 'tasks',
invalidates: ['list', 'detail'],
meta: {
params: ['hard'],
},
});
}}
>
<Button
size="small"
type="default"
icon={<DeleteOutlined style={{ color: 'red' }} />}
/>
</Popconfirm>
</Space>
)}
/>
</Table>
</List>
);
}
10 changes: 5 additions & 5 deletions apps/fe/src/app/(auth)/tasks/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,16 @@ export default function TaskList() {
size="small"
recordItemId={record._id.toString()}
/>
{/* <DeleteButton
hideText
size="small"
recordItemId={record._id.toString()}
/> */}
<Link href={`/tasks/logs/${record._id}`}>
<Button size="small" type="default">
<FileProtectOutlined />
</Button>
</Link>
<DeleteButton
hideText
size="small"
recordItemId={record._id.toString()}
/>
</Space>
)}
/>
Expand Down
9 changes: 8 additions & 1 deletion apps/fe/src/app/_refine_context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ const App = (props: React.PropsWithChildren<AppProps>) => {
edit: '/tasks/edit/:id',
show: '/tasks/show/:id',
meta: {
canDelete: false,
canDelete: true,
},
},
{
name: 'recycle-bin',
list: '/recycle-bin',
meta: {
canDelete: true,
},
},
]}
Expand Down
10 changes: 6 additions & 4 deletions apps/fe/src/libs/utils/data-provider.util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CrudSort, Pagination } from '@refinedev/core';
import { CrudFilters, CrudSort, Pagination } from '@refinedev/core';

export function handlePagination(searchParams: URLSearchParams, pagination?: Pagination) {
if (pagination) {
Expand All @@ -10,10 +10,12 @@ export function handlePagination(searchParams: URLSearchParams, pagination?: Pag
return searchParams;
}

export function handleFilter(searchParams: URLSearchParams, filters?: object) {
export function handleFilter(searchParams: URLSearchParams, filters?: CrudFilters) {
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
searchParams.set(key, String(value));
filters.forEach((filter) => {
if (filter['field']) {
searchParams.set(filter['field'], String(filter.value));
}
});
}

Expand Down
15 changes: 14 additions & 1 deletion apps/fe/src/providers/data-provider/tasktr.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
'use client';

import { GetListResponse, GetListParams, BaseRecord } from '@refinedev/core';
import {
GetListResponse,
GetListParams,
BaseRecord,
DeleteOneParams,
} from '@refinedev/core';
import dataProviderSimpleRest from '@refinedev/simple-rest';
import { AxiosInstance } from 'axios';
import { handleFilter, handlePagination, handleSort } from '~/libs/utils/data-provider.util';
Expand Down Expand Up @@ -34,6 +39,14 @@ export const tasktrDataProvider = (axios: AxiosInstance) => ({
total: total,
};
},
deleteOne: async ({ resource, id, meta, variables }: DeleteOneParams) => {
if (meta?.params && Array.isArray(meta?.params)) {
const url = `${API_URL}/${resource}/${meta?.params.join('/')}/${id}`;
return axios.delete(url);
}

return dataProviderSimpleRest(API_URL, axios).deleteOne({ resource, id, meta, variables });
},
});

export default tasktrDataProvider;

0 comments on commit 134ae2e

Please sign in to comment.