tema 735828cf32 Implement SIM features update functionality and enhance UI components
- 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.
2025-09-05 15:39:43 +09:00

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>
);
}