refactor: converge account and access workspaces on semi ui
This commit is contained in:
@@ -15,6 +15,7 @@ import { WorkspaceConfirmDialog } from '../shared/WorkspaceDialog';
|
||||
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
|
||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||
import { WorkspaceSideSheet, type WorkspaceSideSheetBadgeColor } from '../shared/WorkspaceSideSheet';
|
||||
import { WorkspaceDateTimeField } from '../shared/WorkspaceDateTimeField';
|
||||
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
|
||||
import { PanelEmpty, PanelLoading } from '../shared/AsyncState';
|
||||
|
||||
@@ -149,6 +150,7 @@ export default function UsersPage() {
|
||||
const [vehicleLabels, setVehicleLabels] = useState<Record<string, string>>({});
|
||||
const [feedback, setFeedback] = useState('');
|
||||
const [editingGrantVIN, setEditingGrantVIN] = useState('');
|
||||
const [grantEndEnabled, setGrantEndEnabled] = useState(false);
|
||||
const [confirmation, setConfirmation] = useState<'clear-vehicles' | 'discard-editor' | null>(null);
|
||||
const attentionCustomerCount = useMemo(() => customers.filter((user) => customerAccessState(user.status, user.menuKeys.length, user.vehicles.length).attention).length, [customers]);
|
||||
const readyCustomers = useMemo(() => visibleCustomers.filter((user) => customerAccessState(user.status, user.menuKeys.length, user.vehicles.length).key === 'ready').length, [visibleCustomers]);
|
||||
@@ -186,7 +188,11 @@ export default function UsersPage() {
|
||||
];
|
||||
const editingGrant = useMemo(() => draft.vehicleGrants.find((grant) => grant.vin === editingGrantVIN), [draft.vehicleGrants, editingGrantVIN]);
|
||||
const editingGrantPlate = editingGrant ? vehicleLabels[editingGrant.vin] : '';
|
||||
const grantWindowInvalid = Boolean(editingGrant && (!editingGrant.validFrom || (editingGrant.validTo && editingGrant.validTo <= editingGrant.validFrom)));
|
||||
const grantWindowInvalid = Boolean(editingGrant && (
|
||||
!editingGrant.validFrom
|
||||
|| (grantEndEnabled && !editingGrant.validTo)
|
||||
|| (editingGrant.validTo && editingGrant.validTo <= editingGrant.validFrom)
|
||||
));
|
||||
const editorVisible = creating || Boolean(selected);
|
||||
const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
|
||||
const vehicleComposerVisible = !mobileLayout || creating || draft.vehicleGrants.length === 0 || vehicleComposerOpen;
|
||||
@@ -272,6 +278,7 @@ export default function UsersPage() {
|
||||
setVehicleLabels({});
|
||||
setFeedback('');
|
||||
setEditingGrantVIN('');
|
||||
setGrantEndEnabled(false);
|
||||
setConfirmation(null);
|
||||
};
|
||||
const closeEditor = () => {
|
||||
@@ -288,6 +295,7 @@ export default function UsersPage() {
|
||||
setVehicleLabels({});
|
||||
setFeedback('');
|
||||
setEditingGrantVIN('');
|
||||
setGrantEndEnabled(false);
|
||||
setConfirmation(null);
|
||||
};
|
||||
const requestCloseEditor = () => {
|
||||
@@ -311,6 +319,7 @@ export default function UsersPage() {
|
||||
setVehicleComposerOpen(user.vehicles.length === 0);
|
||||
setFeedback('');
|
||||
setEditingGrantVIN('');
|
||||
setGrantEndEnabled(false);
|
||||
};
|
||||
const toggleVIN = (vin: string) => {
|
||||
const isAssigned = assigned.has(vin);
|
||||
@@ -327,7 +336,10 @@ export default function UsersPage() {
|
||||
setFeedback(isAssigned
|
||||
? `${vehicleLabels[vin] || vin} 已从授权草稿移除,保存后生效`
|
||||
: `${vehicleLabels[vin] || vin} 已加入授权草稿,保存后生效`);
|
||||
if (editingGrantVIN === vin) setEditingGrantVIN('');
|
||||
if (editingGrantVIN === vin) {
|
||||
setEditingGrantVIN('');
|
||||
setGrantEndEnabled(false);
|
||||
}
|
||||
};
|
||||
const clearVehicleGrants = () => {
|
||||
const removedCount = draft.vehicleGrants.length;
|
||||
@@ -336,12 +348,22 @@ export default function UsersPage() {
|
||||
setAssignedPage(1);
|
||||
setVehicleComposerOpen(true);
|
||||
setEditingGrantVIN('');
|
||||
setGrantEndEnabled(false);
|
||||
setFeedback(`已从草稿移除 ${removedCount} 辆车,保存后生效`);
|
||||
setConfirmation(null);
|
||||
};
|
||||
const updateGrant = (vin: string, patch: Partial<CustomerVehicleGrantInput>) => setDraft((value) => ({
|
||||
...value, vehicleGrants: value.vehicleGrants.map((grant) => grant.vin === vin ? { ...grant, ...patch } : grant)
|
||||
}));
|
||||
const openGrantEditor = (vin: string) => {
|
||||
const grant = draft.vehicleGrants.find((item) => item.vin === vin);
|
||||
setGrantEndEnabled(Boolean(grant?.validTo));
|
||||
setEditingGrantVIN(vin);
|
||||
};
|
||||
const closeGrantEditor = () => {
|
||||
setEditingGrantVIN('');
|
||||
setGrantEndEnabled(false);
|
||||
};
|
||||
const addBulkVINs = () => {
|
||||
const next = bulkVINs.split(/[\s,,;;]+/).map((value) => value.trim().toUpperCase()).filter(Boolean);
|
||||
setDraft((value) => {
|
||||
@@ -364,7 +386,7 @@ export default function UsersPage() {
|
||||
const invalidGrant = draft.vehicleGrants.find((grant) => !grant.validFrom || (grant.validTo && grant.validTo <= grant.validFrom));
|
||||
if (invalidGrant) {
|
||||
setActiveSection('vehicles');
|
||||
setEditingGrantVIN(invalidGrant.vin);
|
||||
openGrantEditor(invalidGrant.vin);
|
||||
setFeedback('请检查车辆授权有效期:停用时间必须晚于启用时间');
|
||||
return;
|
||||
}
|
||||
@@ -684,7 +706,7 @@ export default function UsersPage() {
|
||||
/>
|
||||
</div>
|
||||
{visibleAssignedGrants.length ? <div className="v2-assigned-vins">{visibleAssignedGrants.map((grant) => { const plate = vehicleLabels[grant.vin]; return <Card key={grant.vin} className={`v2-assigned-vehicle-card${mobileLayout ? ' is-mobile-compact' : ''}`} bodyStyle={{ padding: 0 }}>
|
||||
<header><span><b>{plate || '未登记车牌'}</b><small>{grant.vin}</small></span><span className="v2-assigned-vehicle-actions"><Button theme="light" type="tertiary" size="small" aria-haspopup="dialog" aria-controls="v2-user-grant-window" aria-label={`调整 ${plate || grant.vin} 有效期`} onClick={() => setEditingGrantVIN(grant.vin)}>调整有效期</Button><Button theme="borderless" type="tertiary" size="small" aria-label={`移除 ${plate || grant.vin}`} icon={<IconDelete />} onClick={() => toggleVIN(grant.vin)} /></span></header>
|
||||
<header><span><b>{plate || '未登记车牌'}</b><small>{grant.vin}</small></span><span className="v2-assigned-vehicle-actions"><Button theme="light" type="tertiary" size="small" aria-haspopup="dialog" aria-controls="v2-user-grant-window" aria-label={`调整 ${plate || grant.vin} 有效期`} onClick={() => openGrantEditor(grant.vin)}>调整有效期</Button><Button theme="borderless" type="tertiary" size="small" aria-label={`移除 ${plate || grant.vin}`} icon={<IconDelete />} onClick={() => toggleVIN(grant.vin)} /></span></header>
|
||||
<div className="v2-assigned-vehicle-interval" aria-label={`授权有效期:启用 ${formatGrantTime(grant.validFrom)};停用 ${grant.validTo ? formatGrantTime(grant.validTo) : '持续有效'}`}><span><small>启用</small><strong>{formatGrantTime(grant.validFrom)}</strong></span><i aria-hidden="true">→</i><span><small>停用</small><strong>{grant.validTo ? formatGrantTime(grant.validTo) : '持续有效'}</strong></span></div>
|
||||
</Card>; })}</div> : <Empty className="v2-user-assigned-filter-empty" title="没有匹配的授权车辆" description="请更换车牌或 VIN 关键词。" />}
|
||||
<div className="v2-assigned-pagination">
|
||||
@@ -728,19 +750,30 @@ export default function UsersPage() {
|
||||
{ label: '启用', value: formatGrantTime(editingGrant.validFrom), detail: '授权开始' },
|
||||
{ label: '停用', value: editingGrant.validTo ? formatGrantTime(editingGrant.validTo) : '持续有效', detail: editingGrant.validTo ? '授权结束' : '未设置结束时间', tone: editingGrant.validTo ? 'warning' : 'success' }
|
||||
] : []}
|
||||
onCancel={() => setEditingGrantVIN('')}
|
||||
onCancel={closeGrantEditor}
|
||||
footerNote="完成后仍需保存账号权限才会生效"
|
||||
primaryAction={{ label: '完成', onClick: () => setEditingGrantVIN(''), disabled: grantWindowInvalid }}
|
||||
primaryAction={{ label: '完成', onClick: closeGrantEditor, disabled: grantWindowInvalid }}
|
||||
>
|
||||
{editingGrant ? <div className="v2-user-grant-form">
|
||||
<div className="v2-user-grant-field-group" role="group" aria-labelledby="v2-user-grant-window-heading">
|
||||
<header><span><strong id="v2-user-grant-window-heading">访问时间窗口</strong><small>只在授权区间内开放该车数据</small></span><Tag color="blue" type="light" size="small">上海时间</Tag></header>
|
||||
<div>
|
||||
<label><span>启用时间</span><Input aria-label={`${editingGrantPlate || editingGrant.vin} 启用时间`} type="datetime-local" required value={editingGrant.validFrom} onChange={(value) => updateGrant(editingGrant.vin, { validFrom: value })} /></label>
|
||||
<label><span>停用时间(可选)</span><Input aria-label={`${editingGrantPlate || editingGrant.vin} 停用时间`} type="datetime-local" min={editingGrant.validFrom} value={editingGrant.validTo} onChange={(value) => updateGrant(editingGrant.vin, { validTo: value })} /></label>
|
||||
<label><span>启用时间</span><WorkspaceDateTimeField ariaLabel={`${editingGrantPlate || editingGrant.vin} 启用时间`} value={editingGrant.validFrom} onChange={(value) => updateGrant(editingGrant.vin, { validFrom: value })} /></label>
|
||||
<div className="v2-user-grant-policy">
|
||||
<span><strong>持续有效</strong><small>{grantEndEnabled ? '关闭后必须指定停用时间' : '保持开启则不自动收回权限'}</small></span>
|
||||
<Switch
|
||||
aria-label={`${editingGrantPlate || editingGrant.vin} 持续有效`}
|
||||
checked={!grantEndEnabled}
|
||||
onChange={(checked) => {
|
||||
setGrantEndEnabled(!checked);
|
||||
if (checked) updateGrant(editingGrant.vin, { validTo: '' });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{grantEndEnabled ? <label><span>停用时间</span><WorkspaceDateTimeField ariaLabel={`${editingGrantPlate || editingGrant.vin} 停用时间`} value={editingGrant.validTo} onChange={(value) => updateGrant(editingGrant.vin, { validTo: value })} placeholder="选择停用日期和时间" showClear /></label> : null}
|
||||
</div>
|
||||
</div>
|
||||
{grantWindowInvalid ? <p role="alert">请设置启用时间,并确保停用时间晚于启用时间。</p> : <p>不设置停用时间时,车辆授权将持续有效。</p>}
|
||||
{grantWindowInvalid ? <p role="alert">请设置完整时间,并确保停用时间晚于启用时间。</p> : <p>{grantEndEnabled ? '到达停用时间后,客户将不再看到该车数据。' : '当前授权持续有效,不会自动收回。'}</p>}
|
||||
</div> : null}
|
||||
</WorkspaceSideSheet>
|
||||
<WorkspaceConfirmDialog
|
||||
|
||||
Reference in New Issue
Block a user