Refactor SimManagementSection for improved structure and user experience

- Consolidated SIM details and actions into a more organized layout, enhancing readability and usability.
- Removed unnecessary usage data handling, focusing on essential SIM details.
- Updated loading and error states for better user feedback during data fetching.
- Introduced new components for SIM actions and feature toggles, streamlining the management interface.
This commit is contained in:
tema 2025-11-21 18:47:45 +09:00
parent 9c796f59da
commit 9f9a1897ee

View File

@ -5,11 +5,11 @@ import {
DevicePhoneMobileIcon,
ExclamationTriangleIcon,
ArrowPathIcon,
DocumentTextIcon,
} from "@heroicons/react/24/outline";
import { type SimDetails } from "./SimDetailsCard";
import { SimDetailsCard, type SimDetails } from "./SimDetailsCard";
import { SimActions } from "./SimActions";
import { apiClient } from "@/lib/api";
import Link from "next/link";
import { SimFeatureToggles } from "./SimFeatureToggles";
interface SimManagementSectionProps {
subscriptionId: number;
@ -17,17 +17,12 @@ interface SimManagementSectionProps {
interface SimInfo {
details: SimDetails;
usage?: {
todayUsageMb: number;
recentDaysUsage: Array<{ date: string; usageMb: number }>;
};
}
export function SimManagementSection({ subscriptionId }: SimManagementSectionProps) {
const [simInfo, setSimInfo] = useState<SimInfo | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<"sim" | "invoices">("sim");
const fetchSimInfo = useCallback(async () => {
try {
@ -43,7 +38,8 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro
throw new Error("Failed to load SIM information");
}
setSimInfo(payload);
// Only use the details part, ignore usage data
setSimInfo({ details: payload.details });
} catch (err: unknown) {
const hasStatus = (v: unknown): v is { status: number } =>
typeof v === "object" &&
@ -51,6 +47,7 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro
"status" in v &&
typeof (v as { status: unknown }).status === "number";
if (hasStatus(err) && err.status === 400) {
// Not a SIM subscription - this component shouldn't be shown
setError("This subscription is not a SIM service");
} else {
setError(err instanceof Error ? err.message : "Failed to load SIM information");
@ -70,36 +67,31 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro
};
const handleActionSuccess = () => {
// Refresh SIM info after any successful action
void fetchSimInfo();
};
if (loading) {
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<div className="bg-[#2F80ED] rounded-b-3xl px-5 py-4">
<div className="flex items-center justify-between">
<h1 className="text-white text-xl font-bold">Service Management</h1>
<div className="flex gap-2">
<div className="px-4 py-2 rounded-full border-2 border-white bg-white text-gray-900 text-sm font-medium">
SIM Management
</div>
<div className="px-4 py-2 rounded-full border-2 border-white text-white text-sm font-medium">
Invoices
</div>
<div className="space-y-8">
<div className="bg-white shadow-lg rounded-xl border border-gray-100 p-8">
<div className="flex items-center mb-6">
<div className="bg-blue-50 rounded-xl p-2 mr-4">
<DevicePhoneMobileIcon className="h-6 w-6 text-blue-600" />
</div>
<div>
<h2 className="text-2xl font-bold text-gray-900">SIM Management</h2>
<p className="text-gray-600 mt-1">Loading your SIM service details...</p>
</div>
</div>
</div>
{/* Loading Animation */}
<div className="p-5 space-y-4">
<div className="animate-pulse space-y-4">
<div className="h-24 bg-gray-200 rounded-2xl"></div>
<div className="grid grid-cols-2 gap-4">
<div className="h-40 bg-gray-200 rounded-2xl"></div>
<div className="h-40 bg-gray-200 rounded-2xl"></div>
<div className="animate-pulse space-y-6">
<div className="h-6 bg-gradient-to-r from-gray-200 to-gray-300 rounded-lg w-3/4"></div>
<div className="h-5 bg-gradient-to-r from-gray-200 to-gray-300 rounded-lg w-1/2"></div>
<div className="h-48 bg-gradient-to-r from-gray-200 to-gray-300 rounded-xl"></div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="h-32 bg-gradient-to-r from-gray-200 to-gray-300 rounded-xl"></div>
<div className="h-32 bg-gradient-to-r from-gray-200 to-gray-300 rounded-xl"></div>
</div>
<div className="h-12 bg-gray-200 rounded-full"></div>
</div>
</div>
</div>
@ -108,19 +100,27 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro
if (error) {
return (
<div className="min-h-screen bg-gray-50">
<div className="bg-[#2F80ED] rounded-b-3xl px-5 py-4">
<h1 className="text-white text-xl font-bold">Service Management</h1>
<div className="bg-white shadow-lg rounded-xl border border-red-100 p-8">
<div className="flex items-center mb-6">
<div className="bg-blue-50 rounded-xl p-2 mr-4">
<DevicePhoneMobileIcon className="h-6 w-6 text-blue-600" />
</div>
<div>
<h2 className="text-2xl font-bold text-gray-900">SIM Management</h2>
<p className="text-gray-600 mt-1">Unable to load SIM information</p>
</div>
</div>
<div className="p-5 text-center py-12">
<div className="text-center py-12">
<div className="bg-red-50 rounded-full p-4 w-20 h-20 mx-auto mb-6">
<ExclamationTriangleIcon className="h-12 w-12 text-red-500 mx-auto" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-3">Unable to Load SIM Information</h3>
<h3 className="text-xl font-semibold text-gray-900 mb-3">
Unable to Load SIM Information
</h3>
<p className="text-gray-600 mb-8 max-w-md mx-auto">{error}</p>
<button
onClick={handleRefresh}
className="inline-flex items-center px-6 py-3 rounded-full text-white bg-[#2F80ED] hover:bg-[#2671d9] font-semibold"
className="inline-flex items-center px-6 py-3 border border-transparent text-sm font-semibold rounded-xl text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 hover:shadow-lg hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
>
<ArrowPathIcon className="h-5 w-5 mr-2" />
Retry
@ -134,190 +134,101 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro
return null;
}
const remainingGB = simInfo.details.remainingQuotaMb / 1000;
const usedMB = simInfo.usage?.todayUsageMb || 0;
const totalGB = 50; // Mock - should come from plan
const usagePercent = ((totalGB * 1000 - simInfo.details.remainingQuotaMb) / (totalGB * 1000)) * 100;
return (
<div id="sim-management" className="min-h-screen bg-gray-50">
{/* 1. Top Header Bar */}
<div className="bg-[#2F80ED] rounded-b-3xl px-5 py-4 shadow-lg">
<div id="sim-management" className="space-y-6">
{/* Header Section */}
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
<div className="flex items-center justify-between">
<h1 className="text-white text-[22px] font-bold">Service Management</h1>
<div className="flex gap-2">
<button
onClick={() => setActiveTab("sim")}
className={`px-4 py-2 rounded-full border-2 border-white text-sm font-medium transition-all ${
activeTab === "sim"
? "bg-white text-gray-900"
: "bg-transparent text-white"
}`}
>
SIM Management
</button>
<Link
href={`/subscriptions/${subscriptionId}`}
className="px-4 py-2 rounded-full border-2 border-white text-white text-sm font-medium"
>
Invoices
</Link>
<div>
<h1 className="text-2xl font-bold text-gray-900">
{simInfo.details.simType === "esim" ? "eSIM" : "Physical SIM"} Service
</h1>
<p className="text-gray-600 mt-1">Subscription ID {subscriptionId}</p>
</div>
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
{simInfo.details.status.charAt(0).toUpperCase() + simInfo.details.status.slice(1)}
</span>
</div>
</div>
{/* Content */}
<div className="p-5 space-y-4">
{/* 2. Billing Information Section */}
<div className="bg-white rounded-2xl shadow-sm p-5">
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<p className="text-sm text-gray-500 mb-1">Monthly Cost</p>
<p className="text-base font-semibold text-[#1A1A1A]">¥3,100</p>
{/* Two Column Layout */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Column - Main Actions */}
<div className="lg:col-span-2 space-y-6">
{/* Subscription Details Card */}
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
<div className="mb-4">
<h2 className="text-lg font-semibold text-gray-900">Subscription Details</h2>
</div>
<div>
<p className="text-sm text-gray-500 mb-1">Next Billing</p>
<p className="text-base font-semibold text-[#1A1A1A]">Jul 1 2025</p>
</div>
<div>
<p className="text-sm text-gray-500 mb-1">Registered</p>
<p className="text-base font-semibold text-[#1A1A1A]">Aug 2 2023</p>
</div>
</div>
</div>
{/* 3. Invoice & Data Usage Row */}
<div className="grid grid-cols-2 gap-4">
{/* Left Column - Invoice Card */}
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-2xl p-5 flex flex-col justify-between">
<div>
<p className="text-sm text-gray-600 mb-2">Latest Invoice</p>
<p className="text-3xl font-bold text-[#2F80ED] mb-4">3400 ¥</p>
</div>
<button className="w-full bg-[#2F80ED] text-white font-semibold py-3 rounded-full hover:bg-[#2671d9] transition-colors">
PAY
</button>
</div>
{/* Right Column - Data Usage Circle */}
<div className="bg-white rounded-2xl p-5 flex flex-col items-center justify-center">
<p className="text-xs text-gray-500 mb-2">Remaining data</p>
<div className="relative w-32 h-32">
<svg className="w-full h-full transform -rotate-90">
<circle
cx="64"
cy="64"
r="56"
fill="none"
stroke="#E5E7EB"
strokeWidth="8"
/>
<circle
cx="64"
cy="64"
r="56"
fill="none"
stroke="#4B8CF7"
strokeWidth="8"
strokeLinecap="round"
strokeDasharray={`${2 * Math.PI * 56}`}
strokeDashoffset={`${2 * Math.PI * 56 * (1 - usagePercent / 100)}`}
className="transition-all duration-500"
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<p className="text-2xl font-bold text-gray-900">{remainingGB.toFixed(1)} GB</p>
<p className="text-sm text-[#D72828] font-medium">{usedMB.toFixed(2)} GB</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="text-center">
<p className="text-sm text-gray-500 uppercase tracking-wide">Monthly Cost</p>
<p className="text-lg font-semibold text-gray-900">¥3,100</p>
</div>
<div className="text-center">
<p className="text-sm text-gray-500 uppercase tracking-wide">Next Billing</p>
<p className="text-lg font-semibold text-gray-900">Jul 1, 2024</p>
</div>
<div className="text-center">
<p className="text-sm text-gray-500 uppercase tracking-wide">Registration</p>
<p className="text-lg font-semibold text-gray-900">Aug 2, 2023</p>
</div>
</div>
</div>
</div>
{/* 4. Top Up Button */}
<button className="w-full bg-[#2F80ED] text-white font-semibold py-3.5 rounded-full hover:bg-[#2671d9] transition-colors shadow-md">
Top Up Data
</button>
{/* SIM Management Actions Card */}
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
<div className="mb-4">
<h2 className="text-lg font-semibold text-gray-900">SIM Management Actions</h2>
</div>
<SimActions
subscriptionId={subscriptionId}
simType={simInfo.details.simType}
status={simInfo.details.status}
currentPlanCode={simInfo.details.planCode}
onTopUpSuccess={handleActionSuccess}
onPlanChangeSuccess={handleActionSuccess}
onCancelSuccess={handleActionSuccess}
onReissueSuccess={handleActionSuccess}
embedded={true}
/>
</div>
{/* 5. SIM Management Actions Section */}
<div className="bg-[#D7D7D7] rounded-2xl p-4">
<h2 className="text-base font-semibold text-gray-900 mb-3">SIM Management Actions</h2>
<div className="grid grid-cols-2 gap-3">
<button className="bg-[#E5E5E5] rounded-xl py-6 text-center font-medium text-gray-900 hover:bg-gray-300 transition-colors">
Top Up Data
</button>
<button className="bg-[#E5E5E5] rounded-xl py-6 text-center font-medium text-gray-900 hover:bg-gray-300 transition-colors">
Change Plan
</button>
<button className="bg-[#E5E5E5] rounded-xl py-6 text-center font-medium text-gray-900 hover:bg-gray-300 transition-colors">
Reissue SIM
</button>
<button className="bg-[#E5E5E5] rounded-xl py-6 text-center font-medium text-gray-900 hover:bg-gray-300 transition-colors">
Cancel SIM
</button>
{/* Voice Status Card */}
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
<div className="mb-4">
<h2 className="text-lg font-semibold text-gray-900">Voice Status</h2>
</div>
<SimFeatureToggles
subscriptionId={subscriptionId}
voiceMailEnabled={simInfo.details.voiceMailEnabled}
callWaitingEnabled={simInfo.details.callWaitingEnabled}
internationalRoamingEnabled={simInfo.details.internationalRoamingEnabled}
networkType={simInfo.details.networkType}
onChanged={handleActionSuccess}
embedded
/>
</div>
</div>
{/* 6. Voice Status Section */}
<div className="bg-[#D7D7D7] rounded-2xl p-4">
<h2 className="text-base font-semibold text-gray-900 mb-3">Voice Status</h2>
<div className="grid grid-cols-2 gap-3">
<button
className={`rounded-xl py-6 text-center font-medium transition-colors ${
simInfo.details.voiceMailEnabled
? "bg-blue-100 text-blue-700 border-2 border-blue-400"
: "bg-[#E5E5E5] text-gray-900 hover:bg-gray-300"
}`}
>
Voice Mail
</button>
<button className="bg-[#E5E5E5] rounded-xl py-6 text-center font-medium text-gray-900 hover:bg-gray-300 transition-colors">
Network Type
<span className="block text-sm text-gray-600 mt-1">{simInfo.details.networkType || "4G"}</span>
</button>
<button
className={`rounded-xl py-6 text-center font-medium transition-colors ${
simInfo.details.callWaitingEnabled
? "bg-blue-100 text-blue-700 border-2 border-blue-400"
: "bg-[#E5E5E5] text-gray-900 hover:bg-gray-300"
}`}
>
Call Waiting
</button>
<button
className={`rounded-xl py-6 text-center font-medium transition-colors ${
simInfo.details.internationalRoamingEnabled
? "bg-blue-100 text-blue-700 border-2 border-blue-400"
: "bg-[#E5E5E5] text-gray-900 hover:bg-gray-300"
}`}
>
International Roaming
</button>
{/* Right Column - SIM Details & Usage */}
<div className="space-y-6">
{/* SIM Details Card */}
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
<div className="mb-4">
<h2 className="text-lg font-semibold text-gray-900">SIM Details</h2>
</div>
<SimDetailsCard
simDetails={simInfo.details}
isLoading={false}
error={null}
embedded={true}
showFeaturesSummary={false}
/>
</div>
</div>
{/* 7. Important Notes Section */}
<div className="bg-white rounded-2xl shadow-sm p-5">
<h3 className="text-base font-semibold text-gray-900 mb-3">Important Notes</h3>
<ul className="space-y-2 text-[13px] text-[#7A7A7A] leading-relaxed">
<li className="flex items-start">
<span className="mr-2"></span>
<span>Changes to SIM settings typically take effect instantaneously (approx. 30min)</span>
</li>
<li className="flex items-start">
<span className="mr-2"></span>
<span>May require smartphone device restart after changes are applied</span>
</li>
<li className="flex items-start">
<span className="mr-2"></span>
<span>Voice/Network/Plan change requests must be requested at least 30 minutes apart</span>
</li>
<li className="flex items-start">
<span className="mr-2"></span>
<span>Changes to Voice Mail / Call Waiting must be requested before the 25th of the month</span>
</li>
</ul>
</div>
</div>
</div>
);
}