- Added new SimFeaturesUpdateRequest interface to handle optional SIM feature updates. - Implemented updateSimFeatures method in SimManagementService to process feature updates including voicemail, call waiting, international roaming, and network type. - Expanded SubscriptionsController with a new endpoint for updating SIM features. - Introduced SimFeatureToggles component for managing service options in the UI. - Enhanced DataUsageChart and SimDetailsCard components to support embedded rendering and improved styling. - Updated layout and design for better user experience in the SIM management section.
243 lines
9.5 KiB
TypeScript
243 lines
9.5 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 >= 1024) {
|
|
return `${(usageMb / 1024).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 */}
|
|
{usage.isBlacklisted && (
|
|
<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">Service Restricted</h4>
|
|
<p className="text-sm text-red-700 mt-1">
|
|
This SIM is currently blacklisted. Please contact support for assistance.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{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've 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've used {usagePercentage.toFixed(1)}% of your data quota. Consider monitoring your usage.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|