258 lines
9.3 KiB
TypeScript
258 lines
9.3 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import React from "react";
|
||
|
|
import { ChartBarIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||
|
|
|
||
|
|
export interface SimUsage {
|
||
|
|
account: string;
|
||
|
|
todayUsageKb: number;
|
||
|
|
todayUsageMb: number;
|
||
|
|
recentDaysUsage: Array<{
|
||
|
|
date: string;
|
||
|
|
usageKb: number;
|
||
|
|
usageMb: number;
|
||
|
|
}>;
|
||
|
|
isBlacklisted: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface DataUsageChartProps {
|
||
|
|
usage: SimUsage;
|
||
|
|
remainingQuotaMb: number;
|
||
|
|
isLoading?: boolean;
|
||
|
|
error?: string | null;
|
||
|
|
embedded?: boolean; // when true, render content without card container
|
||
|
|
}
|
||
|
|
|
||
|
|
export function DataUsageChart({
|
||
|
|
usage,
|
||
|
|
remainingQuotaMb,
|
||
|
|
isLoading,
|
||
|
|
error,
|
||
|
|
embedded = false,
|
||
|
|
}: DataUsageChartProps) {
|
||
|
|
const formatUsage = (usageMb: number) => {
|
||
|
|
if (usageMb >= 1000) {
|
||
|
|
return `${(usageMb / 1000).toFixed(1)} GB`;
|
||
|
|
}
|
||
|
|
return `${usageMb.toFixed(0)} MB`;
|
||
|
|
};
|
||
|
|
|
||
|
|
const getUsageColor = (percentage: number) => {
|
||
|
|
if (percentage >= 90) return "bg-red-500";
|
||
|
|
if (percentage >= 75) return "bg-yellow-500";
|
||
|
|
if (percentage >= 50) return "bg-orange-500";
|
||
|
|
return "bg-green-500";
|
||
|
|
};
|
||
|
|
|
||
|
|
const getUsageTextColor = (percentage: number) => {
|
||
|
|
if (percentage >= 90) return "text-red-600";
|
||
|
|
if (percentage >= 75) return "text-yellow-600";
|
||
|
|
if (percentage >= 50) return "text-orange-600";
|
||
|
|
return "text-green-600";
|
||
|
|
};
|
||
|
|
|
||
|
|
if (isLoading) {
|
||
|
|
return (
|
||
|
|
<div className={`${embedded ? "" : "bg-white shadow rounded-lg "}p-6`}>
|
||
|
|
<div className="animate-pulse">
|
||
|
|
<div className="h-6 bg-gray-200 rounded w-1/3 mb-4"></div>
|
||
|
|
<div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
|
||
|
|
<div className="h-8 bg-gray-200 rounded mb-4"></div>
|
||
|
|
<div className="space-y-2">
|
||
|
|
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
||
|
|
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (error) {
|
||
|
|
return (
|
||
|
|
<div className={`${embedded ? "" : "bg-white shadow rounded-lg "}p-6`}>
|
||
|
|
<div className="text-center">
|
||
|
|
<ExclamationTriangleIcon className="h-12 w-12 text-red-400 mx-auto mb-4" />
|
||
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Error Loading Usage Data</h3>
|
||
|
|
<p className="text-red-600">{error}</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate total usage from recent days (assume it includes today)
|
||
|
|
const totalRecentUsage =
|
||
|
|
usage.recentDaysUsage.reduce((sum, day) => sum + day.usageMb, 0) + usage.todayUsageMb;
|
||
|
|
const totalQuota = remainingQuotaMb + totalRecentUsage;
|
||
|
|
const usagePercentage = totalQuota > 0 ? (totalRecentUsage / totalQuota) * 100 : 0;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={`${embedded ? "" : "bg-white shadow-lg rounded-xl border border-gray-100 hover:shadow-xl transition-shadow duration-300"}`}
|
||
|
|
>
|
||
|
|
{/* Header */}
|
||
|
|
<div className={`${embedded ? "" : "px-6 lg:px-8 py-5 border-b border-gray-200"}`}>
|
||
|
|
<div className="flex items-center">
|
||
|
|
<div className="bg-blue-50 rounded-xl p-2 mr-4">
|
||
|
|
<ChartBarIcon className="h-6 w-6 text-blue-600" />
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 className="text-xl font-semibold text-gray-900">Data Usage</h3>
|
||
|
|
<p className="text-sm text-gray-600">Current month usage and remaining quota</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Content */}
|
||
|
|
<div className={`${embedded ? "" : "px-6 lg:px-8 py-6"}`}>
|
||
|
|
{/* Current Usage Overview */}
|
||
|
|
<div className="mb-6">
|
||
|
|
<div className="flex justify-between items-center mb-2">
|
||
|
|
<span className="text-sm font-medium text-gray-700">Used this month</span>
|
||
|
|
<span className={`text-sm font-semibold ${getUsageTextColor(usagePercentage)}`}>
|
||
|
|
{formatUsage(totalRecentUsage)} of {formatUsage(totalQuota)}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Progress Bar */}
|
||
|
|
<div className="w-full bg-gray-200 rounded-full h-3">
|
||
|
|
<div
|
||
|
|
className={`h-3 rounded-full transition-all duration-300 ${getUsageColor(usagePercentage)}`}
|
||
|
|
style={{ width: `${Math.min(usagePercentage, 100)}%` }}
|
||
|
|
></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between text-xs text-gray-500 mt-1">
|
||
|
|
<span>0%</span>
|
||
|
|
<span className={getUsageTextColor(usagePercentage)}>
|
||
|
|
{usagePercentage.toFixed(1)}% used
|
||
|
|
</span>
|
||
|
|
<span>100%</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Today's Usage */}
|
||
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
|
||
|
|
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl p-6 border border-blue-200">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div className="text-3xl font-bold text-blue-600">
|
||
|
|
{formatUsage(usage.todayUsageMb)}
|
||
|
|
</div>
|
||
|
|
<div className="text-sm font-medium text-blue-700 mt-1">Used today</div>
|
||
|
|
</div>
|
||
|
|
<div className="bg-blue-200 rounded-full p-3">
|
||
|
|
<svg
|
||
|
|
className="h-6 w-6 text-blue-600"
|
||
|
|
fill="none"
|
||
|
|
stroke="currentColor"
|
||
|
|
viewBox="0 0 24 24"
|
||
|
|
>
|
||
|
|
<path
|
||
|
|
strokeLinecap="round"
|
||
|
|
strokeLinejoin="round"
|
||
|
|
strokeWidth={2}
|
||
|
|
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
|
||
|
|
/>
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-xl p-6 border border-green-200">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div className="text-3xl font-bold text-green-600">
|
||
|
|
{formatUsage(remainingQuotaMb)}
|
||
|
|
</div>
|
||
|
|
<div className="text-sm font-medium text-green-700 mt-1">Remaining</div>
|
||
|
|
</div>
|
||
|
|
<div className="bg-green-200 rounded-full p-3">
|
||
|
|
<svg
|
||
|
|
className="h-6 w-6 text-green-600"
|
||
|
|
fill="none"
|
||
|
|
stroke="currentColor"
|
||
|
|
viewBox="0 0 24 24"
|
||
|
|
>
|
||
|
|
<path
|
||
|
|
strokeLinecap="round"
|
||
|
|
strokeLinejoin="round"
|
||
|
|
strokeWidth={2}
|
||
|
|
d="M20 12H4m16 0l-4 4m4-4l-4-4"
|
||
|
|
/>
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Recent Days Usage */}
|
||
|
|
{usage.recentDaysUsage.length > 0 && (
|
||
|
|
<div>
|
||
|
|
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-3">
|
||
|
|
Recent Usage History
|
||
|
|
</h4>
|
||
|
|
<div className="space-y-2">
|
||
|
|
{usage.recentDaysUsage.slice(0, 5).map((day, index) => {
|
||
|
|
const dayPercentage = totalQuota > 0 ? (day.usageMb / totalQuota) * 100 : 0;
|
||
|
|
return (
|
||
|
|
<div key={index} className="flex items-center justify-between py-2">
|
||
|
|
<span className="text-sm text-gray-600">
|
||
|
|
{new Date(day.date).toLocaleDateString("en-US", {
|
||
|
|
month: "short",
|
||
|
|
day: "numeric",
|
||
|
|
})}
|
||
|
|
</span>
|
||
|
|
<div className="flex items-center space-x-3">
|
||
|
|
<div className="w-24 bg-gray-200 rounded-full h-2">
|
||
|
|
<div
|
||
|
|
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
||
|
|
style={{ width: `${Math.min(dayPercentage, 100)}%` }}
|
||
|
|
></div>
|
||
|
|
</div>
|
||
|
|
<span className="text-sm font-medium text-gray-900 w-16 text-right">
|
||
|
|
{formatUsage(day.usageMb)}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Warnings */}
|
||
|
|
|
||
|
|
{usagePercentage >= 90 && (
|
||
|
|
<div className="mt-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
||
|
|
<div className="flex items-center">
|
||
|
|
<ExclamationTriangleIcon className="h-5 w-5 text-red-500 mr-2" />
|
||
|
|
<div>
|
||
|
|
<h4 className="text-sm font-medium text-red-800">High Usage Warning</h4>
|
||
|
|
<p className="text-sm text-red-700 mt-1">
|
||
|
|
You have used {usagePercentage.toFixed(1)}% of your data quota. Consider topping
|
||
|
|
up to avoid service interruption.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{usagePercentage >= 75 && usagePercentage < 90 && (
|
||
|
|
<div className="mt-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||
|
|
<div className="flex items-center">
|
||
|
|
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-500 mr-2" />
|
||
|
|
<div>
|
||
|
|
<h4 className="text-sm font-medium text-yellow-800">Usage Notice</h4>
|
||
|
|
<p className="text-sm text-yellow-700 mt-1">
|
||
|
|
You have used {usagePercentage.toFixed(1)}% of your data quota. Consider
|
||
|
|
monitoring your usage.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|