feat: unify mobile account filters
This commit is contained in:
@@ -6,6 +6,7 @@ import { api } from '../../api/client';
|
||||
import type { AdminUser, CustomerUserInput, CustomerVehicleGrantInput } from '../../api/types';
|
||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
||||
import { TablePagination } from '../shared/TablePagination';
|
||||
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
|
||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||
@@ -42,6 +43,13 @@ type CustomerAccessState = {
|
||||
attention: boolean;
|
||||
};
|
||||
|
||||
const customerScopeOptions: Array<{ value: CustomerScopeFilter; label: string }> = [
|
||||
{ value: 'all', label: '全部状态' },
|
||||
{ value: 'enabled', label: '启用账号' },
|
||||
{ value: 'disabled', label: '停用账号' },
|
||||
{ value: 'attention', label: '待完善权限' }
|
||||
];
|
||||
|
||||
const shanghaiDateTimeFormatter = new Intl.DateTimeFormat('sv-SE', {
|
||||
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false
|
||||
});
|
||||
@@ -104,6 +112,8 @@ export default function UsersPage() {
|
||||
const [customerKeyword, setCustomerKeyword] = useState('');
|
||||
const deferredCustomerKeyword = useDeferredValue(customerKeyword.trim());
|
||||
const [customerStatus, setCustomerStatus] = useState<CustomerScopeFilter>('all');
|
||||
const [draftCustomerKeyword, setDraftCustomerKeyword] = useState('');
|
||||
const [draftCustomerStatus, setDraftCustomerStatus] = useState<CustomerScopeFilter>('all');
|
||||
const [filtersCollapsed, setFiltersCollapsed] = useState(true);
|
||||
const visibleCustomers = useMemo(() => {
|
||||
const keyword = deferredCustomerKeyword.toLocaleLowerCase('zh-CN');
|
||||
@@ -139,9 +149,11 @@ export default function UsersPage() {
|
||||
const editingGrantPlate = editingGrant ? vehicleLabels[editingGrant.vin] : '';
|
||||
const grantWindowInvalid = Boolean(editingGrant && (!editingGrant.validFrom || (editingGrant.validTo && editingGrant.validTo <= editingGrant.validFrom)));
|
||||
const editorVisible = creating || Boolean(selected);
|
||||
const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
|
||||
const vehicleComposerVisible = !mobileLayout || creating || draft.vehicleGrants.length === 0 || vehicleComposerOpen;
|
||||
const hasUnsavedChanges = useMemo(() => draftSignature(draft) !== draftSignature(baselineDraft), [baselineDraft, draft]);
|
||||
const draftAccessState = customerAccessState(draft.status, draft.menuKeys.length, draft.vehicleGrants.length);
|
||||
useSideSheetA11y(mobileFiltersOpen, '.v2-user-mobile-filter-sidesheet', 'v2-user-mobile-filters', '客户账号筛选', '关闭客户账号筛选');
|
||||
useSideSheetA11y(editorVisible, '.v2-user-editor-sidesheet', 'v2-user-editor-sheet', '客户账号详情', '关闭账号详情');
|
||||
useSideSheetA11y(Boolean(editingGrant), '.v2-user-grant-sidesheet', 'v2-user-grant-window', '车辆授权有效期', '关闭车辆授权有效期');
|
||||
|
||||
@@ -330,6 +342,32 @@ export default function UsersPage() {
|
||||
event.preventDefault();
|
||||
persistDraft();
|
||||
};
|
||||
const openMobileFilters = () => {
|
||||
setDraftCustomerKeyword(customerKeyword);
|
||||
setDraftCustomerStatus(customerStatus);
|
||||
setFiltersCollapsed(false);
|
||||
};
|
||||
const closeMobileFilters = () => {
|
||||
setDraftCustomerKeyword(customerKeyword);
|
||||
setDraftCustomerStatus(customerStatus);
|
||||
setFiltersCollapsed(true);
|
||||
};
|
||||
const applyMobileFilters = () => {
|
||||
setCustomerKeyword(draftCustomerKeyword);
|
||||
setCustomerStatus(draftCustomerStatus);
|
||||
setFiltersCollapsed(true);
|
||||
};
|
||||
const resetMobileFilters = () => {
|
||||
setDraftCustomerKeyword('');
|
||||
setDraftCustomerStatus('all');
|
||||
setCustomerKeyword('');
|
||||
setCustomerStatus('all');
|
||||
setFiltersCollapsed(true);
|
||||
};
|
||||
const submitMobileFilters = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
applyMobileFilters();
|
||||
};
|
||||
|
||||
return <div className="v2-user-admin">
|
||||
<div className="v2-user-discovery-shell">
|
||||
@@ -342,18 +380,58 @@ export default function UsersPage() {
|
||||
meta={<Typography.Text type="tertiary">本地账号 · 可扩展外部身份源</Typography.Text>}
|
||||
actions={<Button className="v2-workspace-mobile-tool-button is-primary" theme="solid" icon={<IconPlus />} aria-label="新建客户账号" onClick={startCreate}>新建客户账号</Button>}
|
||||
/>
|
||||
<WorkspaceFilterPanel
|
||||
{mobileLayout ? <div className="v2-user-mobile-discovery">
|
||||
<MobileFilterToggle
|
||||
title="账号范围"
|
||||
summary={`${customerScopeLabel(customerStatus)}${customerKeyword.trim() ? ` · ${customerKeyword.trim()}` : ''}`}
|
||||
expanded={mobileFiltersOpen}
|
||||
expandedLabel="关闭"
|
||||
collapsedLabel="修改"
|
||||
onToggle={mobileFiltersOpen ? closeMobileFilters : openMobileFilters}
|
||||
/>
|
||||
<SideSheet
|
||||
className="v2-user-mobile-filter-sidesheet"
|
||||
visible={mobileFiltersOpen}
|
||||
placement="bottom"
|
||||
height="min(62dvh, 480px)"
|
||||
aria-label="客户账号筛选"
|
||||
title={<div className="v2-user-mobile-filter-title"><strong>筛选客户账号</strong><span>按账号信息与权限状态缩小客户范围</span></div>}
|
||||
onCancel={closeMobileFilters}
|
||||
footer={<div className="v2-user-mobile-filter-footer"><Button theme="light" onClick={resetMobileFilters}>重置条件</Button><Button theme="solid" onClick={applyMobileFilters}>应用筛选</Button></div>}
|
||||
>
|
||||
<form className="v2-user-mobile-filter-form" onSubmit={submitMobileFilters}>
|
||||
<section aria-label="客户账号筛选条件">
|
||||
<header><strong>账号范围</strong><span>筛选仅在应用后更新客户目录,关闭不会丢失当前结果。</span></header>
|
||||
<div>
|
||||
<label>
|
||||
<span>客户账号</span>
|
||||
<Input aria-label="搜索客户账号" prefix={<IconSearch />} showClear value={draftCustomerKeyword} onChange={setDraftCustomerKeyword} placeholder="搜索名称、账号或客户标识" />
|
||||
</label>
|
||||
<label>
|
||||
<span id="v2-user-mobile-status-filter-label">访问状态</span>
|
||||
<Select
|
||||
aria-labelledby="v2-user-mobile-status-filter-label"
|
||||
value={draftCustomerStatus}
|
||||
onChange={(value) => setDraftCustomerStatus(String(value) as CustomerScopeFilter)}
|
||||
optionList={customerScopeOptions}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</SideSheet>
|
||||
</div> : <WorkspaceFilterPanel
|
||||
className="v2-user-filter-panel"
|
||||
title="账号范围"
|
||||
description="按客户名称、登录账号与权限可用状态查找"
|
||||
mobileSummary={`${customerScopeLabel(customerStatus)}${customerKeyword.trim() ? ` · ${customerKeyword.trim()}` : ''}`}
|
||||
expanded={!filtersCollapsed}
|
||||
expanded
|
||||
status={users.isPending ? '正在读取账号' : `${visibleCustomers.length} / ${customers.length} 个账号`}
|
||||
statusColor={customerKeyword.trim() || customerStatus !== 'all' ? 'blue' : 'grey'}
|
||||
collapsedLabel="修改"
|
||||
onToggle={() => setFiltersCollapsed((value) => !value)}
|
||||
>
|
||||
<div className={`v2-user-filter-form${filtersCollapsed ? ' is-mobile-collapsed' : ''}`}>
|
||||
<div className="v2-user-filter-form">
|
||||
<label className="v2-user-filter-keyword">
|
||||
<span>客户账号</span>
|
||||
<Input className="v2-user-list-search" aria-label="搜索客户账号" prefix={<IconSearch />} showClear value={customerKeyword} onChange={setCustomerKeyword} placeholder="搜索名称、账号或客户标识" />
|
||||
@@ -364,12 +442,7 @@ export default function UsersPage() {
|
||||
aria-labelledby="v2-user-status-filter-label"
|
||||
value={customerStatus}
|
||||
onChange={(value) => setCustomerStatus(String(value) as typeof customerStatus)}
|
||||
optionList={[
|
||||
{ value: 'all', label: '全部状态' },
|
||||
{ value: 'enabled', label: '启用账号' },
|
||||
{ value: 'disabled', label: '停用账号' },
|
||||
{ value: 'attention', label: '待完善权限' }
|
||||
]}
|
||||
optionList={customerScopeOptions}
|
||||
/>
|
||||
</label>
|
||||
<Button
|
||||
@@ -387,7 +460,7 @@ export default function UsersPage() {
|
||||
清空
|
||||
</Button>
|
||||
</div>
|
||||
</WorkspaceFilterPanel>
|
||||
</WorkspaceFilterPanel>}
|
||||
</div>
|
||||
<div className={`v2-user-admin-grid${creating || selected ? ' is-editor-open' : ''}`}>
|
||||
<Card className="v2-user-list" bodyStyle={{ padding: 0 }}>
|
||||
|
||||
Reference in New Issue
Block a user