mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-30 16:19:57 +00:00
feat(ui): optimize table UI
This commit is contained in:
parent
5c6be439e8
commit
7db933199a
@ -66,16 +66,12 @@ const CertificateList = ({ withPagination }: CertificateListProps) => {
|
||||
return (
|
||||
<div className="">
|
||||
{leftDays > 0 ? (
|
||||
<div className="text-green-500">
|
||||
{leftDays} / {allDays} {t("certificate.props.expiry.days")}
|
||||
</div>
|
||||
<div className="text-green-500">{t("certificate.props.expiry.left_days", { left: leftDays, total: allDays })}</div>
|
||||
) : (
|
||||
<div className="text-red-500">{t("certificate.props.expiry.expired")}</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
{new Date(expireAt).toLocaleString().split(" ")[0]} {t("certificate.props.expiry.text.expire")}
|
||||
</div>
|
||||
<div>{t("certificate.props.expiry.expiration", { date: new Date(expireAt).toLocaleString().split(" ")[0] })}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -8,9 +8,11 @@
|
||||
|
||||
"certificate.props.domain": "Name",
|
||||
"certificate.props.expiry": "Expiry",
|
||||
"certificate.props.expiry.days": "Days",
|
||||
"certificate.props.expiry.left_days": "{{left}} / {{total}} days left",
|
||||
"certificate.props.expiry.expired": "Expired",
|
||||
"certificate.props.expiry.text.expire": "Expire",
|
||||
"certificate.props.expiry.expiration": "Expire on {{date}}",
|
||||
"certificate.props.expiry.filter.expire_soon": "Expire Soon",
|
||||
"certificate.props.expiry.filter.expired": "Expired",
|
||||
"certificate.props.workflow": "Workflow",
|
||||
"certificate.props.source": "Source",
|
||||
"certificate.props.certificate_chain": "Certificate Chain",
|
||||
|
@ -11,6 +11,7 @@
|
||||
"common.delete.succeeded.message": "Delete Successful",
|
||||
"common.delete.failed.message": "Delete Failed",
|
||||
"common.next": "Next",
|
||||
"common.reset": "Reset",
|
||||
"common.confirm": "Confirm",
|
||||
"common.cancel": "Cancel",
|
||||
"common.submit": "Submit",
|
||||
|
@ -21,7 +21,9 @@
|
||||
"workflow.props.description": "Description",
|
||||
"workflow.props.description.placeholder": "Please enter description",
|
||||
"workflow.props.executionMethod": "Execution Method",
|
||||
"workflow.props.enabled": "Enabled",
|
||||
"workflow.props.state": "State",
|
||||
"workflow.props.state.filter.enabled": "Enabled",
|
||||
"workflow.props.state.filter.disabled": "Disabled",
|
||||
"workflow.props.createdAt": "Created",
|
||||
"workflow.props.updatedAt": "Updated",
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"access.page.title": "授权管理",
|
||||
|
||||
"access.nodata": "暂无授权信息,请先创建。",
|
||||
"access.nodata": "暂无授权信息,请先新建",
|
||||
|
||||
"access.action.add": "新建授权",
|
||||
"access.action.edit": "编辑授权",
|
||||
|
@ -1,16 +1,18 @@
|
||||
{
|
||||
"certificate.page.title": "证书管理",
|
||||
|
||||
"certificate.nodata": "暂无证书,创建一个工作流去生成证书吧~ 😀",
|
||||
"certificate.nodata": "暂无证书,新建一个工作流去生成证书吧~ 😀",
|
||||
|
||||
"certificate.action.view": "查看证书",
|
||||
"certificate.action.download": "下载证书",
|
||||
|
||||
"certificate.props.domain": "名称",
|
||||
"certificate.props.expiry": "有效期限",
|
||||
"certificate.props.expiry.days": "天",
|
||||
"certificate.props.expiry.left_days": "{{left}} / {{total}} 天",
|
||||
"certificate.props.expiry.expired": "已到期",
|
||||
"certificate.props.expiry.text.expire": "到期",
|
||||
"certificate.props.expiry.expiration": "{{date}} 到期",
|
||||
"certificate.props.expiry.filter.expire_soon": "即将到期",
|
||||
"certificate.props.expiry.filter.expired": "已到期",
|
||||
"certificate.props.workflow": "所属工作流",
|
||||
"certificate.props.source": "来源",
|
||||
"certificate.props.certificate_chain": "证书内容",
|
||||
|
@ -11,6 +11,7 @@
|
||||
"common.delete.succeeded.message": "删除成功",
|
||||
"common.delete.failed.message": "删除失败",
|
||||
"common.next": "下一步",
|
||||
"common.reset": "重置",
|
||||
"common.confirm": "确认",
|
||||
"common.cancel": "取消",
|
||||
"common.submit": "提交",
|
||||
|
@ -1,9 +1,7 @@
|
||||
{
|
||||
"workflow.page.title": "工作流",
|
||||
|
||||
"certificate.nodata": "暂无证书,创建一个工作流去生成证书吧~ 😀",
|
||||
|
||||
"workflow.nodata": "No workflows yet. Try to create a workflow to generate certificates! 😀",
|
||||
"workflow.nodata": "暂无工作流,请先新建",
|
||||
|
||||
"workflow.detail.title": "流程",
|
||||
"workflow.detail.history": "历史",
|
||||
@ -23,7 +21,9 @@
|
||||
"workflow.props.description": "描述",
|
||||
"workflow.props.description.placeholder": "请输入描述",
|
||||
"workflow.props.executionMethod": "执行方式",
|
||||
"workflow.props.enabled": "是否启用",
|
||||
"workflow.props.state": "启用状态",
|
||||
"workflow.props.state.filter.enabled": "启用",
|
||||
"workflow.props.state.filter.disabled": "未启用",
|
||||
"workflow.props.createdAt": "创建时间",
|
||||
"workflow.props.updatedAt": "更新时间",
|
||||
|
||||
|
@ -16,11 +16,6 @@ export const convertZulu2Beijing = (zuluTime: string) => {
|
||||
return formattedBeijingTime;
|
||||
};
|
||||
|
||||
export const getDate = (zuluTime: string) => {
|
||||
const time = convertZulu2Beijing(zuluTime);
|
||||
return time.split(" ")[0];
|
||||
};
|
||||
|
||||
export const getLeftDays = (zuluTime: string) => {
|
||||
const time = convertZulu2Beijing(zuluTime);
|
||||
const date = time.split(" ")[0];
|
||||
@ -38,49 +33,3 @@ export const diffDays = (date1: string, date2: string) => {
|
||||
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
|
||||
return days;
|
||||
};
|
||||
|
||||
export function getTimeBefore(days: number): string {
|
||||
// 获取当前时间
|
||||
const currentDate = new Date();
|
||||
|
||||
// 减去指定的天数
|
||||
currentDate.setUTCDate(currentDate.getUTCDate() - days);
|
||||
|
||||
// 格式化日期为 yyyy-mm-dd
|
||||
const year = currentDate.getUTCFullYear();
|
||||
const month = String(currentDate.getUTCMonth() + 1).padStart(2, "0"); // 月份从 0 开始
|
||||
const day = String(currentDate.getUTCDate()).padStart(2, "0");
|
||||
|
||||
// 格式化时间为 hh:ii:ss
|
||||
const hours = String(currentDate.getUTCHours()).padStart(2, "0");
|
||||
const minutes = String(currentDate.getUTCMinutes()).padStart(2, "0");
|
||||
const seconds = String(currentDate.getUTCSeconds()).padStart(2, "0");
|
||||
|
||||
// 组合成 yyyy-mm-dd hh:ii:ss 格式
|
||||
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
export function getTimeAfter(days: number): string {
|
||||
// 获取当前时间
|
||||
const currentDate = new Date();
|
||||
|
||||
// 加上指定的天数
|
||||
currentDate.setUTCDate(currentDate.getUTCDate() + days);
|
||||
|
||||
// 格式化日期为 yyyy-mm-dd
|
||||
const year = currentDate.getUTCFullYear();
|
||||
const month = String(currentDate.getUTCMonth() + 1).padStart(2, "0"); // 月份从 0 开始
|
||||
const day = String(currentDate.getUTCDate()).padStart(2, "0");
|
||||
|
||||
// 格式化时间为 hh:ii:ss
|
||||
const hours = String(currentDate.getUTCHours()).padStart(2, "0");
|
||||
const minutes = String(currentDate.getUTCMinutes()).padStart(2, "0");
|
||||
const seconds = String(currentDate.getUTCSeconds()).padStart(2, "0");
|
||||
|
||||
// 组合成 yyyy-mm-dd hh:ii:ss 格式
|
||||
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
|
||||
return formattedDate;
|
||||
}
|
||||
|
@ -24,9 +24,7 @@ const ConsoleLayout = () => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
token: { colorBgContainer },
|
||||
} = theme.useToken();
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
const menuItems: Required<MenuProps>["items"] = [
|
||||
{
|
||||
@ -56,10 +54,15 @@ const ConsoleLayout = () => {
|
||||
];
|
||||
const [menuSelectedKey, setMenuSelectedKey] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
const getActiveMenuItem = () => {
|
||||
const item =
|
||||
menuItems.find((item) => item!.key === location.pathname) ??
|
||||
menuItems.find((item) => item!.key !== "/" && location.pathname.startsWith(item!.key as string));
|
||||
return item;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const item = getActiveMenuItem();
|
||||
if (item) {
|
||||
setMenuSelectedKey(item.key as string);
|
||||
} else {
|
||||
@ -68,7 +71,7 @@ const ConsoleLayout = () => {
|
||||
}, [location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (menuSelectedKey) {
|
||||
if (menuSelectedKey && menuSelectedKey !== getActiveMenuItem()?.key) {
|
||||
navigate(menuSelectedKey);
|
||||
}
|
||||
}, [menuSelectedKey]);
|
||||
@ -116,7 +119,7 @@ const ConsoleLayout = () => {
|
||||
</Layout.Sider>
|
||||
|
||||
<Layout>
|
||||
<Layout.Header style={{ padding: 0, background: colorBgContainer }}>
|
||||
<Layout.Header style={{ padding: 0, background: themeToken.colorBgContainer }}>
|
||||
<div className="flex items-center justify-between size-full px-4 overflow-hidden">
|
||||
<div className="flex items-center gap-4 size-full">{/* <Button icon={<MenuIcon />} size="large" /> */}</div>
|
||||
<div className="flex-grow flex items-center justify-end gap-4 size-full overflow-hidden">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Button, Empty, Modal, notification, Space, Table, Tooltip, Typography, type TableProps } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
@ -13,9 +13,6 @@ import { useConfigContext } from "@/providers/config";
|
||||
const AccessList = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// a flag to fix the twice-rendering issue in strict mode
|
||||
const mountRef = useRef(true);
|
||||
|
||||
const [modalApi, ModelContextHolder] = Modal.useModal();
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
@ -134,11 +131,6 @@ const AccessList = () => {
|
||||
}, [page, pageSize, configContext.config.accesses]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mountRef.current) {
|
||||
mountRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
fetchTableData();
|
||||
}, [fetchTableData]);
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Empty, notification, Space, Table, Tooltip, Typography, type TableProps } from "antd";
|
||||
import { Button, Divider, Empty, Menu, notification, Radio, Space, Table, theme, Tooltip, Typography, type MenuProps, type TableProps } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
import { Eye as EyeIcon } from "lucide-react";
|
||||
import { Eye as EyeIcon, Filter as FilterIcon } from "lucide-react";
|
||||
import moment from "moment";
|
||||
|
||||
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
|
||||
import { Certificate as CertificateType } from "@/domain/certificate";
|
||||
import { list as listCertificate, type CertificateListReq } from "@/repository/certificate";
|
||||
import { diffDays, getLeftDays } from "@/lib/time";
|
||||
|
||||
const CertificateList = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -17,8 +16,7 @@ const CertificateList = () => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// a flag to fix the twice-rendering issue in strict mode
|
||||
const mountRef = useRef(true);
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
@ -40,21 +38,65 @@ const CertificateList = () => {
|
||||
{
|
||||
key: "expiry",
|
||||
title: t("certificate.props.expiry"),
|
||||
filterDropdown: ({ setSelectedKeys, confirm, clearFilters }) => {
|
||||
const items: Required<MenuProps>["items"] = [
|
||||
["expireSoon", "certificate.props.expiry.filter.expire_soon"],
|
||||
["expired", "certificate.props.expiry.filter.expired"],
|
||||
].map(([key, label]) => {
|
||||
return {
|
||||
key,
|
||||
label: <Radio checked={filters["state"] === key}>{t(label)}</Radio>,
|
||||
onClick: () => {
|
||||
if (filters["state"] !== key) {
|
||||
setFilters((prev) => ({ ...prev, state: key }));
|
||||
setSelectedKeys([key]);
|
||||
}
|
||||
|
||||
confirm({ closeDropdown: true });
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const handleResetClick = () => {
|
||||
setFilters((prev) => ({ ...prev, state: undefined }));
|
||||
setSelectedKeys([]);
|
||||
clearFilters?.();
|
||||
confirm();
|
||||
};
|
||||
|
||||
const handleConfirmClick = () => {
|
||||
confirm();
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 0 }}>
|
||||
<Menu items={items} selectable={false} />
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Space className="justify-end w-full" style={{ padding: themeToken.paddingSM }}>
|
||||
<Button size="small" disabled={!filters.state} onClick={handleResetClick}>
|
||||
{t("common.reset")}
|
||||
</Button>
|
||||
<Button type="primary" size="small" onClick={handleConfirmClick}>
|
||||
{t("common.confirm")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
filterIcon: () => <FilterIcon size={14} />,
|
||||
render: (_, record) => {
|
||||
const leftDays = getLeftDays(record.expireAt);
|
||||
const allDays = diffDays(record.expireAt, record.created);
|
||||
const total = moment(record.expireAt).diff(moment(record.created), "d") + 1;
|
||||
const left = moment(record.expireAt).diff(moment(), "d");
|
||||
return (
|
||||
<Space className="max-w-full" direction="vertical" size={4}>
|
||||
{leftDays > 0 ? (
|
||||
<Typography.Text type="success">
|
||||
{leftDays} / {allDays} {t("certificate.props.expiry.days")}
|
||||
</Typography.Text>
|
||||
{left > 0 ? (
|
||||
<Typography.Text type="success">{t("certificate.props.expiry.left_days", { left, total })}</Typography.Text>
|
||||
) : (
|
||||
<Typography.Text type="danger">{t("certificate.props.expiry.expired")}</Typography.Text>
|
||||
)}
|
||||
|
||||
<Typography.Text type="secondary">
|
||||
{moment(record.expireAt).format("YYYY-MM-DD")} {t("certificate.props.expiry.text.expire")}
|
||||
{t("certificate.props.expiry.expiration", { date: moment(record.expireAt).format("YYYY-MM-DD") })}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
@ -122,21 +164,31 @@ const CertificateList = () => {
|
||||
const [tableData, setTableData] = useState<CertificateType[]>([]);
|
||||
const [tableTotal, setTableTotal] = useState<number>(0);
|
||||
|
||||
const [filters, setFilters] = useState<Record<string, unknown>>({});
|
||||
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [pageSize, setPageSize] = useState<number>(10);
|
||||
|
||||
const [currentRecord, setCurrentRecord] = useState<CertificateType>();
|
||||
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setFilters({ ...filters, state: searchParams.get("state") });
|
||||
setPage(parseInt(+searchParams.get("page")! + "") || 1);
|
||||
setPageSize(parseInt(+searchParams.get("perPage")! + "") || 10);
|
||||
}, []);
|
||||
|
||||
const fetchTableData = useCallback(async () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
|
||||
const state = searchParams.get("state");
|
||||
const req: CertificateListReq = { page: page, perPage: pageSize };
|
||||
if (state) {
|
||||
req.state = state as CertificateListReq["state"];
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await listCertificate(req);
|
||||
const resp = await listCertificate({
|
||||
page: page,
|
||||
perPage: pageSize,
|
||||
state: filters["state"] as CertificateListReq["state"],
|
||||
});
|
||||
|
||||
setTableData(resp.items);
|
||||
setTableTotal(resp.totalItems);
|
||||
@ -146,20 +198,12 @@ const CertificateList = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [page, pageSize]);
|
||||
}, [filters, page, pageSize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mountRef.current) {
|
||||
mountRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
fetchTableData();
|
||||
}, [fetchTableData]);
|
||||
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [currentRecord, setCurrentRecord] = useState<CertificateType>();
|
||||
|
||||
const handleViewClick = (certificate: CertificateType) => {
|
||||
setDrawerOpen(true);
|
||||
setCurrentRecord(certificate);
|
||||
@ -194,6 +238,10 @@ const CertificateList = () => {
|
||||
},
|
||||
}}
|
||||
rowKey={(record) => record.id}
|
||||
onChange={(_, filters, __, extra) => {
|
||||
console.log(filters);
|
||||
extra.action === "filter" && fetchTableData();
|
||||
}}
|
||||
/>
|
||||
|
||||
<CertificateDetailDrawer
|
||||
|
@ -1,13 +1,29 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Empty, Modal, notification, Space, Switch, Table, Tooltip, Typography, type TableProps } from "antd";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Empty,
|
||||
Menu,
|
||||
Modal,
|
||||
notification,
|
||||
Radio,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
theme,
|
||||
Tooltip,
|
||||
Typography,
|
||||
type MenuProps,
|
||||
type TableProps,
|
||||
} from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
import { Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Trash2Icon } from "lucide-react";
|
||||
import { Filter as FilterIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Trash2Icon } from "lucide-react";
|
||||
import moment from "moment";
|
||||
|
||||
import { Workflow as WorkflowType } from "@/domain/workflow";
|
||||
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow, type WorkflowListReq } from "@/repository/workflow";
|
||||
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
|
||||
|
||||
const WorkflowList = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -15,8 +31,7 @@ const WorkflowList = () => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// a flag to fix the twice-rendering issue in strict mode
|
||||
const mountRef = useRef(true);
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
const [modalApi, ModelContextHolder] = Modal.useModal();
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
@ -63,19 +78,63 @@ const WorkflowList = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "enabled",
|
||||
title: t("workflow.props.enabled"),
|
||||
key: "state",
|
||||
title: t("workflow.props.state"),
|
||||
filterDropdown: ({ setSelectedKeys, confirm, clearFilters }) => {
|
||||
const items: Required<MenuProps>["items"] = [
|
||||
["enabled", "workflow.props.state.filter.enabled"],
|
||||
["disabled", "workflow.props.state.filter.disabled"],
|
||||
].map(([key, label]) => {
|
||||
return {
|
||||
key,
|
||||
label: <Radio checked={filters["state"] === key}>{t(label)}</Radio>,
|
||||
onClick: () => {
|
||||
if (filters["state"] !== key) {
|
||||
setFilters((prev) => ({ ...prev, state: key }));
|
||||
setSelectedKeys([key]);
|
||||
}
|
||||
|
||||
confirm({ closeDropdown: true });
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const handleResetClick = () => {
|
||||
setFilters((prev) => ({ ...prev, state: undefined }));
|
||||
setSelectedKeys([]);
|
||||
clearFilters?.();
|
||||
confirm();
|
||||
};
|
||||
|
||||
const handleConfirmClick = () => {
|
||||
confirm();
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 0 }}>
|
||||
<Menu items={items} selectable={false} />
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Space className="justify-end w-full" style={{ padding: themeToken.paddingSM }}>
|
||||
<Button size="small" disabled={!filters.state} onClick={handleResetClick}>
|
||||
{t("common.reset")}
|
||||
</Button>
|
||||
<Button type="primary" size="small" onClick={handleConfirmClick}>
|
||||
{t("common.confirm")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
filterIcon: () => <FilterIcon size={14} />,
|
||||
render: (_, record) => {
|
||||
const enabled = record.enabled;
|
||||
return (
|
||||
<>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
handleEnabledChange(record);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -136,22 +195,27 @@ const WorkflowList = () => {
|
||||
const [tableData, setTableData] = useState<WorkflowType[]>([]);
|
||||
const [tableTotal, setTableTotal] = useState<number>(0);
|
||||
|
||||
const [filters, setFilters] = useState<Record<string, unknown>>({});
|
||||
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [pageSize, setPageSize] = useState<number>(10);
|
||||
|
||||
// TODO: 表头筛选
|
||||
useEffect(() => {
|
||||
setFilters({ ...filters, state: searchParams.get("state") });
|
||||
setPage(parseInt(+searchParams.get("page")! + "") || 1);
|
||||
setPageSize(parseInt(+searchParams.get("perPage")! + "") || 10);
|
||||
}, []);
|
||||
|
||||
const fetchTableData = useCallback(async () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
|
||||
const state = searchParams.get("state");
|
||||
const req: WorkflowListReq = { page: page, perPage: pageSize };
|
||||
if (state == "enabled") {
|
||||
req.enabled = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await listWorkflow(req);
|
||||
const resp = await listWorkflow({
|
||||
page: page,
|
||||
perPage: pageSize,
|
||||
enabled: (filters["state"] as string) === "enabled" ? true : (filters["state"] as string) === "disabled" ? false : undefined,
|
||||
});
|
||||
|
||||
setTableData(resp.items);
|
||||
setTableTotal(resp.totalItems);
|
||||
@ -161,14 +225,9 @@ const WorkflowList = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [searchParams, page, pageSize]);
|
||||
}, [filters, page, pageSize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mountRef.current) {
|
||||
mountRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
fetchTableData();
|
||||
}, [fetchTableData]);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { type RecordListOptions } from "pocketbase";
|
||||
import moment from "moment";
|
||||
|
||||
import { type Certificate } from "@/domain/certificate";
|
||||
import { getTimeAfter } from "@/lib/time";
|
||||
import { getPocketBase } from "./pocketbase";
|
||||
|
||||
export type CertificateListReq = {
|
||||
@ -23,7 +23,7 @@ export const list = async (req: CertificateListReq) => {
|
||||
|
||||
if (req.state === "expireSoon") {
|
||||
options.filter = pb.filter("expireAt<{:expiredAt}", {
|
||||
expiredAt: getTimeAfter(15),
|
||||
expiredAt: moment().add(15, "d").toDate(),
|
||||
});
|
||||
} else if (req.state === "expired") {
|
||||
options.filter = pb.filter("expireAt<={:expiredAt}", {
|
||||
|
Loading…
x
Reference in New Issue
Block a user