Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Frontend/src/components/analytics/DataFreshnessIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';

export interface DataFreshnessIndicatorProps {
lastUpdated?: string;
isRefreshing?: boolean;
onRefresh?: () => void;
}

export const DataFreshnessIndicator: React.FC<DataFreshnessIndicatorProps> = ({
lastUpdated = 'Just now',
isRefreshing = false,
onRefresh,
}) => {
return (
<div className="flex items-center gap-3 px-3 py-1.5 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-full text-xs">
<span className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
Synced {lastUpdated}
</span>
<button
onClick={onRefresh}
disabled={isRefreshing}
className="text-indigo-600 dark:text-indigo-400 hover:underline disabled:opacity-50"
>
{isRefreshing ? 'Refreshing...' : 'Refresh'}
</button>
</div>
);
};

export default DataFreshnessIndicator;
45 changes: 45 additions & 0 deletions Frontend/src/components/analytics/SparklineKpiCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';

export interface SparklineKpiCardProps {
title: string;
value: string | number;
trend: number[]; // Array of numeric points
changePercent?: number;
}

export const SparklineKpiCard: React.FC<SparklineKpiCardProps> = ({
title,
value,
trend = [10, 20, 15, 30, 25, 40],
changePercent = 12.5,
}) => {
const min = Math.min(...trend);
const max = Math.max(...trend, 1);
const points = trend.map((val, idx) => {
const x = (idx / (trend.length - 1)) * 100;
const y = 30 - ((val - min) / (max - min)) * 25;
return `${x},${y}`;
}).join(' ');

return (
<div className="p-4 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800">
<p className="text-xs text-gray-500 mb-1">{title}</p>
<div className="flex items-baseline justify-between mb-2">
<span className="text-xl font-bold text-gray-900 dark:text-gray-100">{value}</span>
<span className={`text-xs font-semibold ${changePercent >= 0 ? 'text-emerald-500' : 'text-rose-500'}`}>
{changePercent >= 0 ? '+' : ''}{changePercent}%
</span>
</div>
<svg className="w-full h-8 overflow-visible" viewBox="0 0 100 30">
<polyline
fill="none"
stroke="#6366f1"
strokeWidth="2"
points={points}
/>
</svg>
</div>
);
};

export default SparklineKpiCard;
Loading