feat(management-navigation-single-company-ux): 收敛管理导航与单公司配置体验
这个提交包含在:
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -14,8 +15,9 @@ import {
|
||||
import { fetchLegalEntities } from "../api/masterData";
|
||||
import { isApiError } from "../api/types";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
import { hasPermission } from "../auth/permissions";
|
||||
import { hasAnyPermission, hasPermission } from "../auth/permissions";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { resolveSingleCompanyState } from "../features/company/singleCompany";
|
||||
import { PageScaffold } from "../components/patterns/PageScaffold";
|
||||
import { SectionPanel } from "../components/patterns/SectionPanel";
|
||||
import { Button } from "../components/ui/button";
|
||||
@@ -33,6 +35,7 @@ export function PaymentAccountsPage() {
|
||||
const { permissions } = useAuth();
|
||||
const canManage = hasPermission(permissions, "payment.account.manage");
|
||||
const canReveal = hasPermission(permissions, "payment.account.read_full");
|
||||
const canManageCompanyInformation = hasAnyPermission(permissions, ["masterdata.view", "masterdata.manage"]);
|
||||
const [editing, setEditing] = useState<PaymentAccount | null>(null);
|
||||
const [legalEntityId, setLegalEntityId] = useState("");
|
||||
const [bankInstitutionId, setBankInstitutionId] = useState("");
|
||||
@@ -51,9 +54,19 @@ export function PaymentAccountsPage() {
|
||||
enabled: canManage,
|
||||
});
|
||||
|
||||
const activeCompanies = legalEntitiesQuery.data?.items ?? [];
|
||||
const companyState = resolveSingleCompanyState(activeCompanies);
|
||||
const singleCompany = companyState.kind === "single" ? companyState.company : null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!editing && singleCompany) {
|
||||
setLegalEntityId(singleCompany.id);
|
||||
}
|
||||
}, [editing, singleCompany]);
|
||||
|
||||
const resetForm = () => {
|
||||
setEditing(null);
|
||||
setLegalEntityId("");
|
||||
setLegalEntityId(singleCompany?.id ?? "");
|
||||
setBankInstitutionId("");
|
||||
setAccountName("");
|
||||
setAccountNumber("");
|
||||
@@ -93,17 +106,56 @@ export function PaymentAccountsPage() {
|
||||
const accounts = accountsQuery.data?.items ?? [];
|
||||
return (
|
||||
<PageScaffold>
|
||||
<PageHeader title="企业银行账户" subtitle="维护法人主体的付款来源账户;完整账号仅在填写原因后临时显示。" />
|
||||
<PageHeader title="企业银行账户" subtitle="维护当前公司的付款来源账户;完整账号仅在填写原因后临时显示。" />
|
||||
<div className="mx-auto max-w-6xl space-y-5">
|
||||
{canManage ? (
|
||||
{canManage && legalEntitiesQuery.isLoading ? (
|
||||
<SectionPanel title="企业银行账户">
|
||||
<div className="text-sm text-zinc-500">正在读取公司信息...</div>
|
||||
</SectionPanel>
|
||||
) : null}
|
||||
|
||||
{canManage && legalEntitiesQuery.isError ? (
|
||||
<SectionPanel title="企业银行账户">
|
||||
<div className="text-sm text-red-600">公司信息读取失败,请刷新后重试。</div>
|
||||
</SectionPanel>
|
||||
) : null}
|
||||
|
||||
{canManage && !legalEntitiesQuery.isLoading && !legalEntitiesQuery.isError && companyState.kind === "empty" ? (
|
||||
<SectionPanel title="先完善公司信息">
|
||||
<div className="space-y-3 text-sm text-zinc-600">
|
||||
<p>企业银行账户必须关联当前公司。请先完善公司信息,再回来录入付款账户。</p>
|
||||
{canManageCompanyInformation ? (
|
||||
<Button asChild type="button" variant="secondary">
|
||||
<Link to="/system/master-data">完善公司信息</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<p>请联系管理员先完善公司信息。</p>
|
||||
)}
|
||||
</div>
|
||||
</SectionPanel>
|
||||
) : null}
|
||||
|
||||
{canManage && companyState.kind === "multiple" ? (
|
||||
<SectionPanel title="公司信息需要处理">
|
||||
<div className="space-y-3 text-sm text-red-600">
|
||||
<p>检测到多条启用的公司信息,系统不会猜测付款账户应关联哪一条。请先处理公司信息。</p>
|
||||
{canManageCompanyInformation ? (
|
||||
<Button asChild type="button" variant="secondary">
|
||||
<Link to="/system/master-data">查看公司信息</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</SectionPanel>
|
||||
) : null}
|
||||
|
||||
{canManage && singleCompany ? (
|
||||
<SectionPanel title={editing ? "替换企业银行账户" : "新增企业银行账户"}>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="payment-account-legal-entity">法人主体</Label>
|
||||
<select id="payment-account-legal-entity" className="select-std w-full" value={legalEntityId} disabled={Boolean(editing)} onChange={(event) => setLegalEntityId(event.target.value)}>
|
||||
<option value="">请选择</option>
|
||||
{(legalEntitiesQuery.data?.items ?? []).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||
</select>
|
||||
<Label>当前公司</Label>
|
||||
<div className="input-std flex min-h-10 items-center bg-zinc-50 text-sm text-zinc-700" data-testid="payment-account-company">
|
||||
{editing?.legalEntity.name ?? singleCompany.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="payment-account-bank">开户银行</Label>
|
||||
@@ -158,7 +210,7 @@ export function PaymentAccountsPage() {
|
||||
</Dialog>
|
||||
<Dialog open={Boolean(disableTarget)} onOpenChange={(open) => { if (!open) setDisableTarget(null); }}>
|
||||
<DialogContent className="max-w-md p-5">
|
||||
<DialogHeader><DialogTitle>停用企业银行账户</DialogTitle><DialogDescription>停用后不能为新付款批次生成制单;历史批次仍保留冻结账户摘要。确认停用 {disableTarget?.legalEntity.name ?? "该法人主体"} 的当前账户吗?</DialogDescription></DialogHeader>
|
||||
<DialogHeader><DialogTitle>停用企业银行账户</DialogTitle><DialogDescription>停用后不能为新付款批次生成制单;历史批次仍保留冻结账户摘要。确认停用 {disableTarget?.legalEntity.name ?? "当前公司"} 的当前账户吗?</DialogDescription></DialogHeader>
|
||||
<DialogFooter><Button type="button" variant="secondary" onClick={() => setDisableTarget(null)}>返回</Button><Button type="button" variant="destructive" disabled={disableMutation.isPending} onClick={() => { if (disableTarget) disableMutation.mutate(disableTarget.id); }}>{disableMutation.isPending ? "停用中..." : "确认停用"}</Button></DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
在新工单中引用
屏蔽一个用户