2025-09-04 18:34:28 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import {
|
|
|
|
|
DevicePhoneMobileIcon,
|
|
|
|
|
WifiIcon,
|
|
|
|
|
SignalIcon,
|
|
|
|
|
ClockIcon,
|
|
|
|
|
CheckCircleIcon,
|
|
|
|
|
ExclamationTriangleIcon,
|
|
|
|
|
XCircleIcon
|
|
|
|
|
} from '@heroicons/react/24/outline';
|
|
|
|
|
|
|
|
|
|
export interface SimDetails {
|
|
|
|
|
account: string;
|
|
|
|
|
msisdn: string;
|
2025-09-05 12:30:57 +09:00
|
|
|
iccid?: string;
|
|
|
|
|
imsi?: string;
|
2025-09-04 18:34:28 +09:00
|
|
|
eid?: string;
|
|
|
|
|
planCode: string;
|
|
|
|
|
status: 'active' | 'suspended' | 'cancelled' | 'pending';
|
|
|
|
|
simType: 'physical' | 'esim';
|
|
|
|
|
size: 'standard' | 'nano' | 'micro' | 'esim';
|
|
|
|
|
hasVoice: boolean;
|
|
|
|
|
hasSms: boolean;
|
|
|
|
|
remainingQuotaKb: number;
|
|
|
|
|
remainingQuotaMb: number;
|
2025-09-05 12:30:57 +09:00
|
|
|
startDate?: string;
|
2025-09-04 18:34:28 +09:00
|
|
|
ipv4?: string;
|
|
|
|
|
ipv6?: string;
|
2025-09-05 12:30:57 +09:00
|
|
|
voiceMailEnabled?: boolean;
|
|
|
|
|
callWaitingEnabled?: boolean;
|
|
|
|
|
internationalRoamingEnabled?: boolean;
|
|
|
|
|
networkType?: string;
|
2025-09-04 18:34:28 +09:00
|
|
|
pendingOperations?: Array<{
|
|
|
|
|
operation: string;
|
|
|
|
|
scheduledDate: string;
|
|
|
|
|
}>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface SimDetailsCardProps {
|
|
|
|
|
simDetails: SimDetails;
|
|
|
|
|
isLoading?: boolean;
|
|
|
|
|
error?: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function SimDetailsCard({ simDetails, isLoading, error }: SimDetailsCardProps) {
|
|
|
|
|
const getStatusIcon = (status: string) => {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'active':
|
|
|
|
|
return <CheckCircleIcon className="h-6 w-6 text-green-500" />;
|
|
|
|
|
case 'suspended':
|
|
|
|
|
return <ExclamationTriangleIcon className="h-6 w-6 text-yellow-500" />;
|
|
|
|
|
case 'cancelled':
|
|
|
|
|
return <XCircleIcon className="h-6 w-6 text-red-500" />;
|
|
|
|
|
case 'pending':
|
|
|
|
|
return <ClockIcon className="h-6 w-6 text-blue-500" />;
|
|
|
|
|
default:
|
|
|
|
|
return <DevicePhoneMobileIcon className="h-6 w-6 text-gray-500" />;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getStatusColor = (status: string) => {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'active':
|
|
|
|
|
return 'bg-green-100 text-green-800';
|
|
|
|
|
case 'suspended':
|
|
|
|
|
return 'bg-yellow-100 text-yellow-800';
|
|
|
|
|
case 'cancelled':
|
|
|
|
|
return 'bg-red-100 text-red-800';
|
|
|
|
|
case 'pending':
|
|
|
|
|
return 'bg-blue-100 text-blue-800';
|
|
|
|
|
default:
|
|
|
|
|
return 'bg-gray-100 text-gray-800';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatDate = (dateString: string) => {
|
|
|
|
|
try {
|
|
|
|
|
const date = new Date(dateString);
|
|
|
|
|
return date.toLocaleDateString('en-US', {
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
month: 'short',
|
|
|
|
|
day: 'numeric',
|
|
|
|
|
});
|
|
|
|
|
} catch {
|
|
|
|
|
return dateString;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatQuota = (quotaMb: number) => {
|
|
|
|
|
if (quotaMb >= 1024) {
|
|
|
|
|
return `${(quotaMb / 1024).toFixed(1)} GB`;
|
|
|
|
|
}
|
|
|
|
|
return `${quotaMb.toFixed(0)} MB`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="bg-white shadow rounded-lg p-6">
|
|
|
|
|
<div className="animate-pulse">
|
|
|
|
|
<div className="flex items-center space-x-4">
|
|
|
|
|
<div className="rounded-full bg-gray-200 h-12 w-12"></div>
|
|
|
|
|
<div className="flex-1 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 className="mt-6 space-y-3">
|
|
|
|
|
<div className="h-4 bg-gray-200 rounded"></div>
|
|
|
|
|
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
|
|
|
|
|
<div className="h-4 bg-gray-200 rounded w-4/6"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="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 SIM Details</h3>
|
|
|
|
|
<p className="text-red-600">{error}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 12:30:57 +09:00
|
|
|
// Specialized, minimal eSIM details view
|
|
|
|
|
if (simDetails.simType === 'esim') {
|
|
|
|
|
return (
|
|
|
|
|
<div className="bg-white shadow rounded-lg">
|
|
|
|
|
<div className="px-6 py-4 border-b border-gray-200">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<WifiIcon className="h-8 w-8 text-blue-600 mr-3" />
|
|
|
|
|
<div>
|
|
|
|
|
<h3 className="text-lg font-medium text-gray-900">eSIM Details</h3>
|
|
|
|
|
<p className="text-sm text-gray-500">Current Plan: {simDetails.planCode}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span className={`inline-flex px-3 py-1 text-sm font-semibold rounded-full ${getStatusColor(simDetails.status)}`}>
|
|
|
|
|
{simDetails.status.charAt(0).toUpperCase() + simDetails.status.slice(1)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="px-6 py-4">
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
|
<div>
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-3">SIM Information</h4>
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">Phone Number</label>
|
|
|
|
|
<p className="text-sm font-medium text-gray-900">{simDetails.msisdn}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">Data Remaining</label>
|
|
|
|
|
<p className="text-lg font-semibold text-green-600">{formatQuota(simDetails.remainingQuotaMb)}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-3">Service Features</h4>
|
|
|
|
|
<div className="space-y-2 text-sm">
|
|
|
|
|
<div className="flex justify-between"><span>Voice Mail (¥300/month)</span><span className="font-medium">{simDetails.voiceMailEnabled ? 'Enabled' : 'Disabled'}</span></div>
|
|
|
|
|
<div className="flex justify-between"><span>Call Waiting (¥300/month)</span><span className="font-medium">{simDetails.callWaitingEnabled ? 'Enabled' : 'Disabled'}</span></div>
|
|
|
|
|
<div className="flex justify-between"><span>International Roaming</span><span className="font-medium">{simDetails.internationalRoamingEnabled ? 'Enabled' : 'Disabled'}</span></div>
|
|
|
|
|
<div className="flex justify-between"><span>4G/5G</span><span className="font-medium">{simDetails.networkType || '5G'}</span></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Plan quick action */}
|
|
|
|
|
<div className="mt-6 pt-4 border-t border-gray-200 flex items-center justify-between">
|
|
|
|
|
<div className="text-sm text-gray-600">Current Plan: <span className="font-medium text-gray-900">{simDetails.planCode}</span></div>
|
|
|
|
|
<a href="#sim-actions" className="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
|
|
|
|
|
Change Plan
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 18:34:28 +09:00
|
|
|
return (
|
|
|
|
|
<div className="bg-white shadow rounded-lg">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="px-6 py-4 border-b border-gray-200">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<div className="text-2xl mr-3">
|
|
|
|
|
{simDetails.simType === 'esim' ? <WifiIcon className="h-8 w-8 text-blue-600" /> : <DevicePhoneMobileIcon className="h-8 w-8 text-blue-600" />}
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h3 className="text-lg font-medium text-gray-900">
|
|
|
|
|
{simDetails.simType === 'esim' ? 'eSIM Details' : 'Physical SIM Details'}
|
|
|
|
|
</h3>
|
|
|
|
|
<p className="text-sm text-gray-500">
|
|
|
|
|
{simDetails.planCode} • {simDetails.simType === 'physical' ? `${simDetails.size} SIM` : 'eSIM'}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center space-x-3">
|
|
|
|
|
{getStatusIcon(simDetails.status)}
|
|
|
|
|
<span className={`inline-flex px-3 py-1 text-sm font-semibold rounded-full ${getStatusColor(simDetails.status)}`}>
|
|
|
|
|
{simDetails.status.charAt(0).toUpperCase() + simDetails.status.slice(1)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Content */}
|
|
|
|
|
<div className="px-6 py-4">
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
|
{/* SIM Information */}
|
|
|
|
|
<div>
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-3">
|
|
|
|
|
SIM Information
|
|
|
|
|
</h4>
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">Phone Number</label>
|
|
|
|
|
<p className="text-sm font-medium text-gray-900">{simDetails.msisdn}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{simDetails.simType === 'physical' && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">ICCID</label>
|
|
|
|
|
<p className="text-sm font-mono text-gray-900 break-all">{simDetails.iccid}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{simDetails.eid && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">EID (eSIM)</label>
|
|
|
|
|
<p className="text-sm font-mono text-gray-900 break-all">{simDetails.eid}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-09-05 12:30:57 +09:00
|
|
|
{simDetails.imsi && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">IMSI</label>
|
|
|
|
|
<p className="text-sm font-mono text-gray-900">{simDetails.imsi}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-09-04 18:34:28 +09:00
|
|
|
|
2025-09-05 12:30:57 +09:00
|
|
|
{simDetails.startDate && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">Service Start Date</label>
|
|
|
|
|
<p className="text-sm text-gray-900">{formatDate(simDetails.startDate)}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-09-04 18:34:28 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Service Features */}
|
|
|
|
|
<div>
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-3">
|
|
|
|
|
Service Features
|
|
|
|
|
</h4>
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">Data Remaining</label>
|
|
|
|
|
<p className="text-lg font-semibold text-green-600">{formatQuota(simDetails.remainingQuotaMb)}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center space-x-4">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<SignalIcon className={`h-4 w-4 mr-1 ${simDetails.hasVoice ? 'text-green-500' : 'text-gray-400'}`} />
|
|
|
|
|
<span className={`text-sm ${simDetails.hasVoice ? 'text-green-600' : 'text-gray-500'}`}>
|
|
|
|
|
Voice {simDetails.hasVoice ? 'Enabled' : 'Disabled'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<DevicePhoneMobileIcon className={`h-4 w-4 mr-1 ${simDetails.hasSms ? 'text-green-500' : 'text-gray-400'}`} />
|
|
|
|
|
<span className={`text-sm ${simDetails.hasSms ? 'text-green-600' : 'text-gray-500'}`}>
|
|
|
|
|
SMS {simDetails.hasSms ? 'Enabled' : 'Disabled'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{(simDetails.ipv4 || simDetails.ipv6) && (
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-xs text-gray-500">IP Address</label>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
{simDetails.ipv4 && (
|
|
|
|
|
<p className="text-sm font-mono text-gray-900">IPv4: {simDetails.ipv4}</p>
|
|
|
|
|
)}
|
|
|
|
|
{simDetails.ipv6 && (
|
|
|
|
|
<p className="text-sm font-mono text-gray-900">IPv6: {simDetails.ipv6}</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Pending Operations */}
|
|
|
|
|
{simDetails.pendingOperations && simDetails.pendingOperations.length > 0 && (
|
|
|
|
|
<div className="mt-6 pt-6 border-t border-gray-200">
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-3">
|
|
|
|
|
Pending Operations
|
|
|
|
|
</h4>
|
|
|
|
|
<div className="bg-blue-50 rounded-lg p-4">
|
|
|
|
|
{simDetails.pendingOperations.map((operation, index) => (
|
|
|
|
|
<div key={index} className="flex items-center text-sm">
|
|
|
|
|
<ClockIcon className="h-4 w-4 text-blue-500 mr-2" />
|
|
|
|
|
<span className="text-blue-800">
|
|
|
|
|
{operation.operation} scheduled for {formatDate(operation.scheduledDate)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|