refactor(ui): refactor accesses state using zustand store

This commit is contained in:
Fu Diwei
2024-12-11 19:55:50 +08:00
parent b744363736
commit bb3009a124
48 changed files with 359 additions and 404 deletions

View File

@@ -18,7 +18,6 @@ import {
import Version from "@/components/certimate/Version";
import { useTheme } from "@/hooks";
import { getPocketBase } from "@/repository/pocketbase";
import { ConfigProvider } from "@/providers/config";
const ConsoleLayout = () => {
const navigate = useNavigate();
@@ -52,65 +51,61 @@ const ConsoleLayout = () => {
}
return (
<>
<ConfigProvider>
<Layout className="w-full min-h-screen">
<Layout.Sider className="max-md:hidden" theme="light" width={256}>
<div className="flex flex-col items-center justify-between w-full h-full overflow-hidden">
<div className="w-full">
<SiderMenu />
</div>
<div className="w-full py-2 text-center">
<Version />
</div>
<Layout className="w-full min-h-screen">
<Layout.Sider className="max-md:hidden" theme="light" width={256}>
<div className="flex flex-col items-center justify-between w-full h-full overflow-hidden">
<div className="w-full">
<SiderMenu />
</div>
<div className="w-full py-2 text-center">
<Version />
</div>
</div>
</Layout.Sider>
<Layout>
<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 className="md:hidden" icon={<MenuIcon />} size="large" onClick={handleSiderOpen} />
<Drawer
closable={false}
destroyOnClose
open={siderOpen}
placement="left"
styles={{
content: { paddingTop: themeToken.paddingSM, paddingBottom: themeToken.paddingSM },
body: { padding: 0 },
}}
onClose={handleSiderClose}
>
<SiderMenu onSelect={() => handleSiderClose()} />
</Drawer>
</div>
</Layout.Sider>
<div className="flex-grow flex items-center justify-end gap-4 size-full overflow-hidden">
<Tooltip title={t("common.menu.theme")} mouseEnterDelay={2}>
<ThemeToggleButton size="large" />
</Tooltip>
<Tooltip title={t("common.menu.locale")} mouseEnterDelay={2}>
<LocaleToggleButton size="large" />
</Tooltip>
<Tooltip title={t("common.menu.settings")} mouseEnterDelay={2}>
<Button icon={<SettingsIcon size={18} />} size="large" onClick={handleSettingsClick} />
</Tooltip>
<Tooltip title={t("common.menu.logout")} mouseEnterDelay={2}>
<Button danger icon={<LogOutIcon size={18} />} size="large" onClick={handleLogoutClick} />
</Tooltip>
</div>
</div>
</Layout.Header>
<Layout>
<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 className="md:hidden" icon={<MenuIcon />} size="large" onClick={handleSiderOpen} />
<Drawer
closable={false}
destroyOnClose
open={siderOpen}
placement="left"
styles={{
content: { paddingTop: themeToken.paddingSM, paddingBottom: themeToken.paddingSM },
body: { padding: 0 },
}}
onClose={handleSiderClose}
>
<SiderMenu onSelect={() => handleSiderClose()} />
</Drawer>
</div>
<div className="flex-grow flex items-center justify-end gap-4 size-full overflow-hidden">
<Tooltip title={t("common.menu.theme")} mouseEnterDelay={2}>
<ThemeToggleButton size="large" />
</Tooltip>
<Tooltip title={t("common.menu.locale")} mouseEnterDelay={2}>
<LocaleToggleButton size="large" />
</Tooltip>
<Tooltip title={t("common.menu.settings")} mouseEnterDelay={2}>
<Button icon={<SettingsIcon size={18} />} size="large" onClick={handleSettingsClick} />
</Tooltip>
<Tooltip title={t("common.menu.logout")} mouseEnterDelay={2}>
<Button danger icon={<LogOutIcon size={18} />} size="large" onClick={handleLogoutClick} />
</Tooltip>
</div>
</div>
</Layout.Header>
<Layout.Content>
<div className="p-4">
<Outlet />
</div>
</Layout.Content>
</Layout>
</Layout>
</ConfigProvider>
</>
<Layout.Content>
<div className="p-4">
<Outlet />
</div>
</Layout.Content>
</Layout>
</Layout>
);
};

View File

@@ -7,9 +7,8 @@ import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
import AccessEditDialog from "@/components/certimate/AccessEditDialog";
import { accessProvidersMap, type Access as AccessType } from "@/domain/access";
import { remove as removeAccess } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { accessProvidersMap, type AccessModel } from "@/domain/access";
import { useAccessStore } from "@/stores/access";
const AccessList = () => {
const { t } = useTranslation();
@@ -17,9 +16,11 @@ const AccessList = () => {
const [modalApi, ModelContextHolder] = Modal.useModal();
const [notificationApi, NotificationContextHolder] = notification.useNotification();
const { accesses, fetchAccesses, deleteAccess } = useAccessStore();
const [loading, setLoading] = useState<boolean>(false);
const tableColumns: TableProps<AccessType>["columns"] = [
const tableColumns: TableProps<AccessModel>["columns"] = [
{
key: "$index",
align: "center",
@@ -105,13 +106,15 @@ const AccessList = () => {
),
},
];
const [tableData, setTableData] = useState<AccessType[]>([]);
const [tableData, setTableData] = useState<AccessModel[]>([]);
const [tableTotal, setTableTotal] = useState<number>(0);
const [page, setPage] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const configContext = useConfigContext();
useEffect(() => {
fetchAccesses();
}, []);
const fetchTableData = useCallback(async () => {
if (loading) return;
@@ -120,10 +123,10 @@ const AccessList = () => {
try {
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const items = configContext.config?.accesses?.slice(startIndex, endIndex) ?? [];
const items = accesses.slice(startIndex, endIndex);
setTableData(items);
setTableTotal(configContext.config?.accesses?.length ?? 0);
setTableTotal(accesses.length);
} catch (err) {
if (err instanceof ClientResponseError && err.isAbort) {
return;
@@ -134,21 +137,20 @@ const AccessList = () => {
} finally {
setLoading(false);
}
}, [page, pageSize, configContext.config.accesses]);
}, [page, pageSize, accesses]);
useEffect(() => {
fetchTableData();
}, [fetchTableData]);
const handleDeleteClick = async (data: AccessType) => {
const handleDeleteClick = async (data: AccessModel) => {
modalApi.confirm({
title: t("access.action.delete"),
content: t("access.action.delete.confirm"),
onOk: async () => {
// TODO: 有关联数据的不允许被删除
try {
const res = await removeAccess(data);
configContext.deleteAccess(res.id);
await deleteAccess(data);
} catch (err) {
console.error(err);
notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)}</> });
@@ -177,7 +179,7 @@ const AccessList = () => {
]}
/>
<Table<AccessType>
<Table<AccessModel>
columns={tableColumns}
dataSource={tableData}
loading={loading}

View File

@@ -8,7 +8,7 @@ import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
import { Certificate as CertificateType } from "@/domain/certificate";
import { CertificateModel } from "@/domain/certificate";
import { list as listCertificate, type CertificateListReq } from "@/repository/certificate";
const CertificateList = () => {
@@ -23,7 +23,7 @@ const CertificateList = () => {
const [loading, setLoading] = useState<boolean>(false);
const tableColumns: TableProps<CertificateType>["columns"] = [
const tableColumns: TableProps<CertificateModel>["columns"] = [
{
key: "$index",
align: "center",
@@ -165,7 +165,7 @@ const CertificateList = () => {
),
},
];
const [tableData, setTableData] = useState<CertificateType[]>([]);
const [tableData, setTableData] = useState<CertificateModel[]>([]);
const [tableTotal, setTableTotal] = useState<number>(0);
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
@@ -177,7 +177,7 @@ const CertificateList = () => {
const [page, setPage] = useState<number>(() => parseInt(+searchParams.get("page")! + "") || 1);
const [pageSize, setPageSize] = useState<number>(() => parseInt(+searchParams.get("perPage")! + "") || 10);
const [currentRecord, setCurrentRecord] = useState<CertificateType>();
const [currentRecord, setCurrentRecord] = useState<CertificateModel>();
const [drawerOpen, setDrawerOpen] = useState(false);
@@ -210,7 +210,7 @@ const CertificateList = () => {
fetchTableData();
}, [fetchTableData]);
const handleViewClick = (certificate: CertificateType) => {
const handleViewClick = (certificate: CertificateModel) => {
setDrawerOpen(true);
setCurrentRecord(certificate);
};
@@ -221,7 +221,7 @@ const CertificateList = () => {
<PageHeader title={t("certificate.page.title")} />
<Table<CertificateType>
<Table<CertificateModel>
columns={tableColumns}
dataSource={tableData}
loading={loading}

View File

@@ -12,7 +12,7 @@ import {
} from "lucide-react";
import { ClientResponseError } from "pocketbase";
import { type Statistic as StatisticType } from "@/domain/domain";
import { type Statistics } from "@/domain/statistics";
import { get as getStatistics } from "@/api/statistics";
const Dashboard = () => {
@@ -26,7 +26,7 @@ const Dashboard = () => {
const [loading, setLoading] = useState<boolean>(false);
const statisticGridSpans = {
const statisticsGridSpans = {
xs: { flex: "50%" },
md: { flex: "50%" },
lg: { flex: "33.3333%" },
@@ -34,15 +34,15 @@ const Dashboard = () => {
xxl: { flex: "20%" },
};
const [statistic, setStatistic] = useState<StatisticType>();
const [statistics, setStatistics] = useState<Statistics>();
const fetchStatistic = useCallback(async () => {
const fetchStatistics = useCallback(async () => {
if (loading) return;
setLoading(true);
try {
const data = await getStatistics();
setStatistic(data);
setStatistics(data);
} catch (err) {
if (err instanceof ClientResponseError && err.isAbort) {
return;
@@ -56,7 +56,7 @@ const Dashboard = () => {
}, []);
useEffect(() => {
fetchStatistic();
fetchStatistics();
}, []);
return (
@@ -66,47 +66,47 @@ const Dashboard = () => {
<PageHeader title={t("dashboard.page.title")} />
<Row className="justify-stretch" gutter={[16, 16]}>
<Col {...statisticGridSpans}>
<Col {...statisticsGridSpans}>
<StatisticCard
icon={<SquareSigmaIcon size={48} strokeWidth={1} color={themeToken.colorInfo} />}
label={t("dashboard.statistics.all_certificates")}
value={statistic?.certificateTotal ?? "-"}
value={statistics?.certificateTotal ?? "-"}
suffix={t("dashboard.statistics.unit")}
onClick={() => navigate("/certificates")}
/>
</Col>
<Col {...statisticGridSpans}>
<Col {...statisticsGridSpans}>
<StatisticCard
icon={<CalendarClockIcon size={48} strokeWidth={1} color={themeToken.colorWarning} />}
label={t("dashboard.statistics.expire_soon_certificates")}
value={statistic?.certificateExpireSoon ?? "-"}
value={statistics?.certificateExpireSoon ?? "-"}
suffix={t("dashboard.statistics.unit")}
onClick={() => navigate("/certificates?state=expireSoon")}
/>
</Col>
<Col {...statisticGridSpans}>
<Col {...statisticsGridSpans}>
<StatisticCard
icon={<CalendarX2Icon size={48} strokeWidth={1} color={themeToken.colorError} />}
label={t("dashboard.statistics.expired_certificates")}
value={statistic?.certificateExpired ?? "-"}
value={statistics?.certificateExpired ?? "-"}
suffix={t("dashboard.statistics.unit")}
onClick={() => navigate("/certificates?state=expired")}
/>
</Col>
<Col {...statisticGridSpans}>
<Col {...statisticsGridSpans}>
<StatisticCard
icon={<WorkflowIcon size={48} strokeWidth={1} color={themeToken.colorInfo} />}
label={t("dashboard.statistics.all_workflows")}
value={statistic?.workflowTotal ?? "-"}
value={statistics?.workflowTotal ?? "-"}
suffix={t("dashboard.statistics.unit")}
onClick={() => navigate("/workflows")}
/>
</Col>
<Col {...statisticGridSpans}>
<Col {...statisticsGridSpans}>
<StatisticCard
icon={<FolderCheckIcon size={48} strokeWidth={1} color={themeToken.colorSuccess} />}
label={t("dashboard.statistics.enabled_workflows")}
value={statistic?.workflowEnabled ?? "-"}
value={statistics?.workflowEnabled ?? "-"}
suffix={t("dashboard.statistics.unit")}
onClick={() => navigate("/workflows?state=enabled")}
/>

View File

@@ -12,14 +12,14 @@ import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useToast } from "@/components/ui/use-toast";
import { getErrMsg } from "@/utils/error";
import { SSLProvider as SSLProviderType, SSLProviderSetting, Settings } from "@/domain/settings";
import { SSLProvider as SSLProviderType, SSLProviderSetting, SettingsModel } from "@/domain/settings";
import { get, save } from "@/repository/settings";
import { produce } from "immer";
type SSLProviderContext = {
setting: Settings<SSLProviderSetting>;
onSubmit: (data: Settings<SSLProviderSetting>) => void;
setConfig: (config: Settings<SSLProviderSetting>) => void;
setting: SettingsModel<SSLProviderSetting>;
onSubmit: (data: SettingsModel<SSLProviderSetting>) => void;
setConfig: (config: SettingsModel<SSLProviderSetting>) => void;
};
const Context = createContext({} as SSLProviderContext);
@@ -31,12 +31,12 @@ export const useSSLProviderContext = () => {
const SSLProvider = () => {
const { t } = useTranslation();
const [config, setConfig] = useState<Settings<SSLProviderSetting>>({
const [config, setConfig] = useState<SettingsModel<SSLProviderSetting>>({
content: {
provider: "letsencrypt",
config: {},
},
} as Settings<SSLProviderSetting>);
} as SettingsModel<SSLProviderSetting>);
const { toast } = useToast();
@@ -73,7 +73,7 @@ const SSLProvider = () => {
return "";
};
const onSubmit = async (data: Settings<SSLProviderSetting>) => {
const onSubmit = async (data: SettingsModel<SSLProviderSetting>) => {
try {
console.log(data);
const resp = await save({ ...data });

View File

@@ -23,7 +23,7 @@ import { Filter as FilterIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as
import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
import { Workflow as WorkflowType } from "@/domain/workflow";
import { WorkflowModel } from "@/domain/workflow";
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
const WorkflowList = () => {
@@ -39,7 +39,7 @@ const WorkflowList = () => {
const [loading, setLoading] = useState<boolean>(false);
const tableColumns: TableProps<WorkflowType>["columns"] = [
const tableColumns: TableProps<WorkflowModel>["columns"] = [
{
key: "$index",
align: "center",
@@ -196,7 +196,7 @@ const WorkflowList = () => {
),
},
];
const [tableData, setTableData] = useState<WorkflowType[]>([]);
const [tableData, setTableData] = useState<WorkflowModel[]>([]);
const [tableTotal, setTableTotal] = useState<number>(0);
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
@@ -237,7 +237,7 @@ const WorkflowList = () => {
fetchTableData();
}, [fetchTableData]);
const handleEnabledChange = async (workflow: WorkflowType) => {
const handleEnabledChange = async (workflow: WorkflowModel) => {
try {
const resp = await saveWorkflow({
id: workflow.id,
@@ -259,7 +259,7 @@ const WorkflowList = () => {
}
};
const handleDeleteClick = (workflow: WorkflowType) => {
const handleDeleteClick = (workflow: WorkflowModel) => {
modalApi.confirm({
title: t("workflow.action.delete"),
content: t("workflow.action.delete.confirm"),
@@ -302,7 +302,7 @@ const WorkflowList = () => {
]}
/>
<Table<WorkflowType>
<Table<WorkflowModel>
columns={tableColumns}
dataSource={tableData}
loading={loading}