| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| export type ItemSyncStatus = |
| | 'synced' |
| | 'local-newer' |
| | 'server-newer' |
| | 'conflict' |
| | 'local-only' |
| | 'server-only' |
| | 'syncing' |
| | 'error'; |
|
|
| |
| |
| |
| export interface SyncableItem { |
| id: string; |
| name: string; |
| type: 'project' | 'skill' | 'template' | 'modelTemplate' | 'interviewTemplate'; |
| localUpdatedAt: Date | null; |
| serverUpdatedAt: Date | null; |
| lastSyncedAt: Date | null; |
| status: ItemSyncStatus; |
| } |
|
|
| |
| |
| |
| export interface CategorySyncStatus { |
| items: SyncableItem[]; |
| syncedCount: number; |
| localNewerCount: number; |
| serverNewerCount: number; |
| conflictCount: number; |
| localOnlyCount: number; |
| serverOnlyCount: number; |
| } |
|
|
| |
| |
| |
| export interface DetailedSyncStatus { |
| projects: CategorySyncStatus; |
| skills: CategorySyncStatus; |
| templates: CategorySyncStatus; |
| modelTemplates: CategorySyncStatus; |
| interviewTemplates: CategorySyncStatus; |
| loading: boolean; |
| error: string | null; |
| } |
|
|
| |
| |
| |
| export interface SkillSyncStatus { |
| id: string; |
| name: string; |
| updatedAt: string; |
| } |
|
|
| |
| |
| |
| export interface TemplateSyncStatus { |
| id: string; |
| name: string; |
| updatedAt: string; |
| } |
|
|
| |
| |
| |
| export interface ModelTemplateSyncStatus { |
| id: string; |
| name: string; |
| updatedAt: string; |
| } |
|
|
| |
| |
| |
| export interface InterviewTemplateSyncStatus { |
| id: string; |
| name: string; |
| updatedAt: string; |
| } |
|
|
| |
| |
| |
| export interface ProjectSyncStatus { |
| id: string; |
| name: string; |
| updatedAt: string; |
| } |
|
|
| |
| |
| |
| export interface EnhancedSyncStatusResponse { |
| success: boolean; |
| projects: ProjectSyncStatus[]; |
| skills: SkillSyncStatus[]; |
| templates: TemplateSyncStatus[]; |
| modelTemplates: ModelTemplateSyncStatus[]; |
| interviewTemplates: InterviewTemplateSyncStatus[]; |
| summary: { |
| projectCount: number; |
| skillCount: number; |
| templateCount: number; |
| modelTemplateCount: number; |
| interviewTemplateCount: number; |
| deploymentCount: number; |
| lastUpdated: string | null; |
| isUninitialized: boolean; |
| }; |
| } |
|
|
| |
| |
| |
| export function calculateCategoryCounts(items: SyncableItem[]): Omit<CategorySyncStatus, 'items'> { |
| return { |
| syncedCount: items.filter(i => i.status === 'synced').length, |
| localNewerCount: items.filter(i => i.status === 'local-newer').length, |
| serverNewerCount: items.filter(i => i.status === 'server-newer').length, |
| conflictCount: items.filter(i => i.status === 'conflict').length, |
| localOnlyCount: items.filter(i => i.status === 'local-only').length, |
| serverOnlyCount: items.filter(i => i.status === 'server-only').length, |
| }; |
| } |
|
|
| |
| |
| |
| |
| export function calculateItemSyncStatus( |
| localUpdatedAt: Date | null, |
| serverUpdatedAt: Date | null, |
| lastSyncedAt: Date | null |
| ): ItemSyncStatus { |
| |
| if (!localUpdatedAt) { |
| return serverUpdatedAt ? 'server-only' : 'synced'; |
| } |
|
|
| |
| if (!serverUpdatedAt) { |
| return 'local-only'; |
| } |
|
|
| |
| if (!lastSyncedAt) { |
| if (localUpdatedAt > serverUpdatedAt) { |
| return 'local-newer'; |
| } else if (serverUpdatedAt > localUpdatedAt) { |
| return 'server-newer'; |
| } |
| return 'synced'; |
| } |
|
|
| |
| const localChanged = localUpdatedAt > lastSyncedAt; |
| const serverChanged = serverUpdatedAt > lastSyncedAt; |
|
|
| if (localChanged && serverChanged) { |
| return 'conflict'; |
| } |
|
|
| if (localChanged) { |
| return 'local-newer'; |
| } |
|
|
| if (serverChanged) { |
| return 'server-newer'; |
| } |
|
|
| return 'synced'; |
| } |
|
|