67 lines
1.9 KiB
TypeScript
67 lines
1.9 KiB
TypeScript
'use client';
|
|
|
|
import { RefreshCw, AlertCircle } from 'lucide-react';
|
|
import type { WidgetConfig } from '@/lib/pipeline-types';
|
|
|
|
interface WidgetWrapperProps {
|
|
config: WidgetConfig;
|
|
loading: boolean;
|
|
error?: string;
|
|
onRefresh?: () => void;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* Common wrapper for dashboard widgets.
|
|
* Handles loading, error states, and refresh functionality.
|
|
*/
|
|
export function WidgetWrapper({
|
|
config,
|
|
loading,
|
|
error,
|
|
onRefresh,
|
|
children,
|
|
}: WidgetWrapperProps) {
|
|
return (
|
|
<div className="bg-white rounded-lg border-2 border-gray-200 shadow-sm h-full flex flex-col">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-200">
|
|
<h3 className="font-semibold text-gray-900 text-sm">
|
|
{config.title}
|
|
</h3>
|
|
{onRefresh && (
|
|
<button
|
|
onClick={onRefresh}
|
|
disabled={loading}
|
|
className="p-1 text-gray-400 hover:text-gray-600 disabled:opacity-50"
|
|
title="Refresh"
|
|
>
|
|
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 p-4 overflow-auto">
|
|
{error ? (
|
|
<div className="flex items-center justify-center h-full">
|
|
<div className="text-center">
|
|
<AlertCircle className="w-8 h-8 text-red-500 mx-auto mb-2" />
|
|
<p className="text-sm text-red-600">{error}</p>
|
|
</div>
|
|
</div>
|
|
) : loading ? (
|
|
<div className="flex items-center justify-center h-full">
|
|
<div className="animate-pulse flex flex-col items-center">
|
|
<div className="h-4 w-24 bg-gray-200 rounded mb-2" />
|
|
<div className="h-3 w-16 bg-gray-200 rounded" />
|
|
</div>
|
|
</div>
|
|
) : (
|
|
children
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|