feat(client): show digital sync failure details
This commit is contained in:
@@ -61,6 +61,19 @@ interface ManagementSyncStatus {
|
||||
failed: number;
|
||||
lastSyncAt: string | null;
|
||||
lastSyncError: string | null;
|
||||
recentFailures?: ManagementSyncFailure[];
|
||||
}
|
||||
|
||||
interface ManagementSyncFailure {
|
||||
id: string;
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
operation: string;
|
||||
attempts: number;
|
||||
lastAttemptAt: string | null;
|
||||
error: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// 技术侧 PlanTemplate 是任务模板来源;用户侧只展示为“任务”,运行时 Task/PlanStep 展示为“步骤”。
|
||||
@@ -448,6 +461,31 @@ function syncStatusSummary(status: ManagementSyncStatus | null): string {
|
||||
: '等待本地 outbox 数据。';
|
||||
}
|
||||
|
||||
function entityTypeLabel(entityType: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
plan: 'Plan',
|
||||
task: 'Task',
|
||||
run: 'Run',
|
||||
event: 'Event',
|
||||
artifact: 'Artifact',
|
||||
approval: 'Approval',
|
||||
};
|
||||
return labels[entityType] || entityType || 'Entity';
|
||||
}
|
||||
|
||||
function syncFailureSummary(failure: ManagementSyncFailure): string {
|
||||
const error = summarizeSyncError(failure.error);
|
||||
return [
|
||||
`${entityTypeLabel(failure.entityType)} / ${failure.operation}`,
|
||||
`重试 ${failure.attempts} 次`,
|
||||
error,
|
||||
].filter(Boolean).join(' · ');
|
||||
}
|
||||
|
||||
function syncFailureTime(failure: ManagementSyncFailure): string {
|
||||
return formatBeijingDateTime(failure.lastAttemptAt || failure.updatedAt || failure.createdAt) || '未尝试';
|
||||
}
|
||||
|
||||
async function readManagementSyncStatus(): Promise<ManagementSyncStatus | null> {
|
||||
const bridge = window.QimingClawBridge?.digital;
|
||||
if (!bridge?.getSyncStatus) return null;
|
||||
@@ -824,6 +862,7 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
const managementSyncLabel = syncStatusLabel(managementSyncStatus);
|
||||
const managementSyncSummary = syncStatusSummary(managementSyncStatus);
|
||||
const managementSyncError = summarizeSyncError(managementSyncStatus?.lastSyncError);
|
||||
const managementSyncFailures = managementSyncStatus?.recentFailures ?? [];
|
||||
const resolvedVisualState = resolveEmployeeVisualState({
|
||||
confirmed: Boolean(workday?.confirmed),
|
||||
currentEvent,
|
||||
@@ -1332,6 +1371,19 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
<span>{managementSyncSummary}</span>
|
||||
</div>
|
||||
|
||||
{managementSyncFailures.length > 0 && (
|
||||
<div className="de-management-sync-failures" aria-label="同步失败明细">
|
||||
{managementSyncFailures.map((failure) => (
|
||||
<div key={failure.id} className="de-management-sync-failure" title={failure.error || failure.id}>
|
||||
<span>{syncFailureTime(failure)}</span>
|
||||
<strong>{entityTypeLabel(failure.entityType)}</strong>
|
||||
<em>{failure.id}</em>
|
||||
<small>{syncFailureSummary(failure)}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(actionNotice || actionError || planDataError || workdayProjection?.demo_reason) && (
|
||||
<div className={`de-schedule-result ${actionError ? 'de-schedule-result--error' : 'de-schedule-result--success'}`} style={{ marginBottom: 10 }}>
|
||||
{actionError || actionNotice || planDataError || workdayProjection?.demo_reason}
|
||||
|
||||
Reference in New Issue
Block a user