feat: search by keyword on AccessList, CertificateList, WorkflowList

This commit is contained in:
Fu Diwei
2025-02-13 20:31:11 +08:00
parent 970fba90e0
commit 664bb692b6
15 changed files with 262 additions and 144 deletions

View File

@@ -1,14 +1,16 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import {
DeleteOutlined as DeleteOutlinedIcon,
EditOutlined as EditOutlinedIcon,
PlusOutlined as PlusOutlinedIcon,
ReloadOutlined as ReloadOutlinedIcon,
SnippetsOutlined as SnippetsOutlinedIcon,
} from "@ant-design/icons";
import { PageHeader } from "@ant-design/pro-components";
import { useRequest } from "ahooks";
import { Avatar, Button, Empty, Modal, Space, Table, type TableProps, Tooltip, Typography, notification } from "antd";
import { Avatar, Button, Card, Empty, Flex, Input, Modal, Space, Table, type TableProps, Tooltip, Typography, notification } from "antd";
import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
@@ -20,6 +22,8 @@ import { useAccessesStore } from "@/stores/access";
import { getErrMsg } from "@/utils/error";
const AccessList = () => {
const [searchParams] = useSearchParams();
const { t } = useTranslation();
const [modalApi, ModelContextHolder] = Modal.useModal();
@@ -116,6 +120,12 @@ const AccessList = () => {
const [tableData, setTableData] = useState<AccessModel[]>([]);
const [tableTotal, setTableTotal] = useState<number>(0);
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
return {
keyword: searchParams.get("keyword"),
};
});
const [page, setPage] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
@@ -134,14 +144,21 @@ const AccessList = () => {
() => {
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const items = accesses.slice(startIndex, endIndex);
const list = accesses.filter((e) => {
const keyword = (filters["keyword"] as string | undefined)?.trim();
if (keyword) {
return e.name.includes(keyword);
}
return true;
});
return Promise.resolve({
items,
totalItems: accesses.length,
items: list.slice(startIndex, endIndex),
totalItems: list.length,
});
},
{
refreshDeps: [accesses, page, pageSize],
refreshDeps: [accesses, filters, page, pageSize],
onSuccess: (res) => {
setTableData(res.items);
setTableTotal(res.totalItems);
@@ -149,6 +166,16 @@ const AccessList = () => {
}
);
const handleSearch = (value: string) => {
setFilters((prev) => ({ ...prev, keyword: value }));
};
const handleReloadClick = () => {
if (loading) return;
fetchAccesses();
};
const handleDeleteClick = async (data: AccessModel) => {
modalApi.confirm({
title: t("access.action.delete"),
@@ -186,30 +213,43 @@ const AccessList = () => {
]}
/>
<Table<AccessModel>
columns={tableColumns}
dataSource={tableData}
loading={!loadedAtOnce || loading}
locale={{
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("access.nodata")} />,
}}
pagination={{
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
<Card size="small">
<div className="mb-4">
<Flex gap="small">
<div className="flex-1">
<Input.Search allowClear defaultValue={filters["keyword"] as string} placeholder={t("access.search.placeholder")} onSearch={handleSearch} />
</div>
<div>
<Button icon={<ReloadOutlinedIcon spin={loading} />} onClick={handleReloadClick} />
</div>
</Flex>
</div>
<Table<AccessModel>
columns={tableColumns}
dataSource={tableData}
loading={!loadedAtOnce || loading}
locale={{
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("access.nodata")} />,
}}
pagination={{
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
</Card>
</div>
);
};

View File

@@ -1,10 +1,28 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useSearchParams } from "react-router-dom";
import { DeleteOutlined as DeleteOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
import { DeleteOutlined as DeleteOutlinedIcon, ReloadOutlined as ReloadOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
import { PageHeader } from "@ant-design/pro-components";
import { useRequest } from "ahooks";
import { Button, Divider, Empty, Menu, type MenuProps, Modal, Radio, Space, Table, type TableProps, Tooltip, Typography, notification, theme } from "antd";
import {
Button,
Card,
Divider,
Empty,
Flex,
Input,
Menu,
type MenuProps,
Modal,
Radio,
Space,
Table,
type TableProps,
Tooltip,
Typography,
notification,
theme,
} from "antd";
import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
@@ -191,6 +209,7 @@ const CertificateList = () => {
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
return {
keyword: searchParams.get("keyword"),
state: searchParams.get("state"),
};
});
@@ -205,9 +224,10 @@ const CertificateList = () => {
} = useRequest(
() => {
return listCertificate({
keyword: filters["keyword"] as string,
state: filters["state"] as ListCertificateRequest["state"],
page: page,
perPage: pageSize,
state: filters["state"] as ListCertificateRequest["state"],
});
},
{
@@ -229,6 +249,16 @@ const CertificateList = () => {
}
);
const handleSearch = (value: string) => {
setFilters((prev) => ({ ...prev, keyword: value.trim() }));
};
const handleReloadClick = () => {
if (loading) return;
refreshData();
};
const handleDeleteClick = (certificate: CertificateModel) => {
modalApi.confirm({
title: t("certificate.action.delete"),
@@ -255,30 +285,43 @@ const CertificateList = () => {
<PageHeader title={t("certificate.page.title")} />
<Table<CertificateModel>
columns={tableColumns}
dataSource={tableData}
loading={loading}
locale={{
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("certificate.nodata"))} />,
}}
pagination={{
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
<Card size="small">
<div className="mb-4">
<Flex gap="small">
<div className="flex-1">
<Input.Search allowClear defaultValue={filters["keyword"] as string} placeholder={t("certificate.search.placeholder")} onSearch={handleSearch} />
</div>
<div>
<Button icon={<ReloadOutlinedIcon spin={loading} />} onClick={handleReloadClick} />
</div>
</Flex>
</div>
<Table<CertificateModel>
columns={tableColumns}
dataSource={tableData}
loading={loading}
locale={{
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("certificate.nodata"))} />,
}}
pagination={{
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
</Card>
</div>
);
};

View File

@@ -8,6 +8,7 @@ import {
DeleteOutlined as DeleteOutlinedIcon,
EditOutlined as EditOutlinedIcon,
PlusOutlined as PlusOutlinedIcon,
ReloadOutlined as ReloadOutlinedIcon,
StopOutlined as StopOutlinedIcon,
SyncOutlined as SyncOutlinedIcon,
} from "@ant-design/icons";
@@ -16,8 +17,11 @@ import { PageHeader } from "@ant-design/pro-components";
import { useRequest } from "ahooks";
import {
Button,
Card,
Divider,
Empty,
Flex,
Input,
Menu,
type MenuProps,
Modal,
@@ -235,6 +239,7 @@ const WorkflowList = () => {
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
return {
keyword: searchParams.get("keyword"),
state: searchParams.get("state"),
};
});
@@ -249,9 +254,10 @@ const WorkflowList = () => {
} = useRequest(
() => {
return listWorkflow({
keyword: filters["keyword"] as string,
enabled: (filters["state"] as string) === "enabled" ? true : (filters["state"] as string) === "disabled" ? false : undefined,
page: page,
perPage: pageSize,
enabled: (filters["state"] as string) === "enabled" ? true : (filters["state"] as string) === "disabled" ? false : undefined,
});
},
{
@@ -273,10 +279,20 @@ const WorkflowList = () => {
}
);
const handleSearch = (value: string) => {
setFilters((prev) => ({ ...prev, keyword: value.trim() }));
};
const handleCreateClick = () => {
navigate("/workflows/new");
};
const handleReloadClick = () => {
if (loading) return;
refreshData();
};
const handleEnabledChange = async (workflow: WorkflowModel) => {
try {
if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) {
@@ -345,30 +361,43 @@ const WorkflowList = () => {
]}
/>
<Table<WorkflowModel>
columns={tableColumns}
dataSource={tableData}
loading={loading}
locale={{
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("workflow.nodata"))} />,
}}
pagination={{
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
<Card size="small">
<div className="mb-4">
<Flex gap="small">
<div className="flex-1">
<Input.Search allowClear defaultValue={filters["keyword"] as string} placeholder={t("workflow.search.placeholder")} onSearch={handleSearch} />
</div>
<div>
<Button icon={<ReloadOutlinedIcon spin={loading} />} onClick={handleReloadClick} />
</div>
</Flex>
</div>
<Table<WorkflowModel>
columns={tableColumns}
dataSource={tableData}
loading={loading}
locale={{
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={getErrMsg(loadedError ?? t("workflow.nodata"))} />,
}}
pagination={{
current: page,
pageSize: pageSize,
total: tableTotal,
showSizeChanger: true,
onChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
onShowSizeChange: (page: number, pageSize: number) => {
setPage(page);
setPageSize(pageSize);
},
}}
rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
</Card>
</div>
);
};