Add comprehensive admin features and multi-system integration
- Add admin dashboard with sync controls and data browser - Implement RMM, Auvik, and Addigy organization mappings - Add chunked ticket sync with progress tracking - Implement entity sync service with rate limiting - Add analytics engine and performance optimizer - Create data browser for all PSA entities - Add navigation components and UI improvements - Implement background processing and sync services - Add comprehensive documentation and migration scripts - Update configuration items with multi-system support - Enhance contact management and purchase history - Add issue type assignment and LLM analyzer - Improve error handling and logging utilities
This commit is contained in:
parent
e8462ef301
commit
6eee14f8af
171 changed files with 32671 additions and 621 deletions
174
components/admin/ChunkedSyncProgress.tsx
Normal file
174
components/admin/ChunkedSyncProgress.tsx
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/**
|
||||
* Chunked Sync Progress Component
|
||||
* Displays animated progress bar for chunked ticket sync operations
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { CheckCircle2, XCircle, Loader2, Calendar } from 'lucide-react';
|
||||
|
||||
interface ChunkInfo {
|
||||
index: number;
|
||||
total: number;
|
||||
description: string;
|
||||
recordsProcessed: number;
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
||||
}
|
||||
|
||||
interface ChunkedSyncProgressProps {
|
||||
isActive: boolean;
|
||||
currentChunk?: ChunkInfo;
|
||||
completedChunks?: number;
|
||||
totalChunks?: number;
|
||||
totalRecords?: number;
|
||||
failedChunks?: string[];
|
||||
}
|
||||
|
||||
export default function ChunkedSyncProgress({
|
||||
isActive,
|
||||
currentChunk,
|
||||
completedChunks = 0,
|
||||
totalChunks = 0,
|
||||
totalRecords = 0,
|
||||
failedChunks = [],
|
||||
}: ChunkedSyncProgressProps) {
|
||||
const [animatedProgress, setAnimatedProgress] = useState(0);
|
||||
|
||||
// Animate progress bar
|
||||
useEffect(() => {
|
||||
if (totalChunks === 0) return;
|
||||
|
||||
const targetProgress = (completedChunks / totalChunks) * 100;
|
||||
|
||||
// Smooth animation
|
||||
const step = (targetProgress - animatedProgress) / 10;
|
||||
const interval = setInterval(() => {
|
||||
setAnimatedProgress(prev => {
|
||||
const next = prev + step;
|
||||
if (Math.abs(next - targetProgress) < 0.5) {
|
||||
clearInterval(interval);
|
||||
return targetProgress;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, 50);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [completedChunks, totalChunks]);
|
||||
|
||||
if (!isActive && totalChunks === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const progressPercentage = totalChunks > 0 ? (completedChunks / totalChunks) * 100 : 0;
|
||||
const hasFailures = failedChunks.length > 0;
|
||||
|
||||
return (
|
||||
<Card className="border-blue-200 bg-blue-50/50 dark:border-blue-800 dark:bg-blue-950/20">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-5 h-5 text-blue-600" />
|
||||
<CardTitle className="text-lg">Chunked Ticket Sync Progress</CardTitle>
|
||||
</div>
|
||||
{isActive ? (
|
||||
<Badge variant="default" className="bg-blue-600">
|
||||
<Loader2 className="w-3 h-3 mr-1 animate-spin" />
|
||||
Syncing
|
||||
</Badge>
|
||||
) : hasFailures ? (
|
||||
<Badge variant="destructive">
|
||||
<XCircle className="w-3 h-3 mr-1" />
|
||||
Completed with Errors
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="default" className="bg-green-600">
|
||||
<CheckCircle2 className="w-3 h-3 mr-1" />
|
||||
Completed
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<CardDescription>
|
||||
Processing tickets in monthly chunks to prevent timeouts
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Progress Bar */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="font-medium">
|
||||
{currentChunk?.description || 'Preparing...'}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{completedChunks} / {totalChunks} chunks
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={animatedProgress}
|
||||
className="h-3 transition-all duration-300"
|
||||
aria-label={`Sync progress: ${Math.round(progressPercentage)}% complete`}
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-muted-foreground">
|
||||
<span>{Math.round(progressPercentage)}% complete</span>
|
||||
<span>{totalRecords.toLocaleString()} records processed</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chunk Details */}
|
||||
{currentChunk && isActive && (
|
||||
<div className="p-3 bg-white dark:bg-card rounded-lg border border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Loader2 className="w-4 h-4 text-blue-600 dark:text-blue-400 animate-spin" />
|
||||
<span className="font-medium text-sm">
|
||||
Processing: {currentChunk.description}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Chunk {currentChunk.index} of {currentChunk.total} • {currentChunk.recordsProcessed.toLocaleString()} records so far
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Failed Chunks */}
|
||||
{failedChunks.length > 0 && (
|
||||
<div className="p-3 bg-red-50 dark:bg-red-950/20 rounded-lg border border-red-200 dark:border-red-800">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<XCircle className="w-4 h-4 text-red-600 dark:text-red-400" />
|
||||
<span className="font-medium text-sm text-red-900 dark:text-red-100">
|
||||
{failedChunks.length} chunk{failedChunks.length > 1 ? 's' : ''} failed
|
||||
</span>
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
{failedChunks.slice(0, 3).map((chunk, idx) => (
|
||||
<li key={idx} className="text-xs text-red-800 dark:text-red-200">
|
||||
• {chunk}
|
||||
</li>
|
||||
))}
|
||||
{failedChunks.length > 3 && (
|
||||
<li className="text-xs text-red-600 dark:text-red-400 italic">
|
||||
... and {failedChunks.length - 3} more
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Completion Summary */}
|
||||
{!isActive && totalChunks > 0 && (
|
||||
<div className="p-3 bg-green-50 dark:bg-green-950/20 rounded-lg border border-green-200 dark:border-green-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
<span className="font-medium text-sm text-green-900 dark:text-green-100">
|
||||
Sync completed: {totalRecords.toLocaleString()} records processed across {completedChunks} chunks
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue