2025-09-04 18:34:28 +09:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
import React, { useState, useEffect, useCallback } from "react";
|
2025-09-09 15:45:03 +09:00
|
|
|
|
import {
|
2025-09-04 18:34:28 +09:00
|
|
|
|
DevicePhoneMobileIcon,
|
|
|
|
|
|
ExclamationTriangleIcon,
|
2025-09-09 15:45:03 +09:00
|
|
|
|
ArrowPathIcon,
|
2025-11-21 18:41:14 +09:00
|
|
|
|
DocumentTextIcon,
|
2025-09-09 15:45:03 +09:00
|
|
|
|
} from "@heroicons/react/24/outline";
|
2025-11-21 18:41:14 +09:00
|
|
|
|
import { type SimDetails } from "./SimDetailsCard";
|
2025-09-20 11:35:40 +09:00
|
|
|
|
import { apiClient } from "@/lib/api";
|
2025-11-21 18:41:14 +09:00
|
|
|
|
import Link from "next/link";
|
2025-09-04 18:34:28 +09:00
|
|
|
|
|
|
|
|
|
|
interface SimManagementSectionProps {
|
|
|
|
|
|
subscriptionId: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
interface SimInfo {
|
|
|
|
|
|
details: SimDetails;
|
|
|
|
|
|
usage?: {
|
|
|
|
|
|
todayUsageMb: number;
|
|
|
|
|
|
recentDaysUsage: Array<{ date: string; usageMb: number }>;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 18:34:28 +09:00
|
|
|
|
export function SimManagementSection({ subscriptionId }: SimManagementSectionProps) {
|
|
|
|
|
|
const [simInfo, setSimInfo] = useState<SimInfo | null>(null);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
2025-11-21 18:41:14 +09:00
|
|
|
|
const [activeTab, setActiveTab] = useState<"sim" | "invoices">("sim");
|
2025-09-04 18:34:28 +09:00
|
|
|
|
|
2025-09-09 16:13:17 +09:00
|
|
|
|
const fetchSimInfo = useCallback(async () => {
|
2025-11-21 18:41:14 +09:00
|
|
|
|
try {
|
2025-09-04 18:34:28 +09:00
|
|
|
|
setError(null);
|
2025-09-09 15:45:03 +09:00
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
const response = await apiClient.GET("/api/subscriptions/{id}/sim", {
|
2025-09-18 16:23:56 +09:00
|
|
|
|
params: { path: { id: subscriptionId } },
|
|
|
|
|
|
});
|
2025-09-09 15:45:03 +09:00
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
const payload = response.data as { details: SimDetails; usage: any } | undefined;
|
2025-10-21 13:21:03 +09:00
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
if (!payload) {
|
|
|
|
|
|
throw new Error("Failed to load SIM information");
|
2025-11-17 11:04:53 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-18 16:23:56 +09:00
|
|
|
|
setSimInfo(payload);
|
2025-09-09 15:59:30 +09:00
|
|
|
|
} catch (err: unknown) {
|
2025-11-21 18:41:14 +09:00
|
|
|
|
const hasStatus = (v: unknown): v is { status: number } =>
|
|
|
|
|
|
typeof v === "object" &&
|
|
|
|
|
|
v !== null &&
|
|
|
|
|
|
"status" in v &&
|
|
|
|
|
|
typeof (v as { status: unknown }).status === "number";
|
2025-09-09 15:59:30 +09:00
|
|
|
|
if (hasStatus(err) && err.status === 400) {
|
2025-09-09 15:45:03 +09:00
|
|
|
|
setError("This subscription is not a SIM service");
|
2025-11-21 18:41:14 +09:00
|
|
|
|
} else {
|
|
|
|
|
|
setError(err instanceof Error ? err.message : "Failed to load SIM information");
|
2025-09-04 18:34:28 +09:00
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
2025-11-21 18:41:14 +09:00
|
|
|
|
setLoading(false);
|
2025-09-04 18:34:28 +09:00
|
|
|
|
}
|
2025-09-09 16:13:17 +09:00
|
|
|
|
}, [subscriptionId]);
|
2025-09-04 18:34:28 +09:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-09-09 15:59:30 +09:00
|
|
|
|
void fetchSimInfo();
|
2025-09-09 16:13:17 +09:00
|
|
|
|
}, [fetchSimInfo]);
|
2025-09-04 18:34:28 +09:00
|
|
|
|
|
|
|
|
|
|
const handleRefresh = () => {
|
2025-11-21 18:41:14 +09:00
|
|
|
|
setLoading(true);
|
2025-09-09 15:59:30 +09:00
|
|
|
|
void fetchSimInfo();
|
2025-09-04 18:34:28 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleActionSuccess = () => {
|
2025-09-09 15:59:30 +09:00
|
|
|
|
void fetchSimInfo();
|
2025-09-04 18:34:28 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
return (
|
2025-11-21 18:41:14 +09:00
|
|
|
|
<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>
|
2025-09-05 15:39:43 +09:00
|
|
|
|
</div>
|
2025-09-04 18:34:28 +09:00
|
|
|
|
</div>
|
2025-11-21 18:41:14 +09:00
|
|
|
|
</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>
|
2025-09-05 15:39:43 +09:00
|
|
|
|
</div>
|
2025-11-21 18:41:14 +09:00
|
|
|
|
<div className="h-12 bg-gray-200 rounded-full"></div>
|
2025-09-04 18:34:28 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
return (
|
2025-11-21 18:41:14 +09:00
|
|
|
|
<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>
|
2025-09-04 18:34:28 +09:00
|
|
|
|
</div>
|
2025-11-21 18:41:14 +09:00
|
|
|
|
<div className="p-5 text-center py-12">
|
2025-09-05 15:39:43 +09:00
|
|
|
|
<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>
|
2025-11-21 18:41:14 +09:00
|
|
|
|
<h3 className="text-xl font-semibold text-gray-900 mb-3">Unable to Load SIM Information</h3>
|
2025-09-05 15:39:43 +09:00
|
|
|
|
<p className="text-gray-600 mb-8 max-w-md mx-auto">{error}</p>
|
2025-09-04 18:34:28 +09:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleRefresh}
|
2025-11-21 18:41:14 +09:00
|
|
|
|
className="inline-flex items-center px-6 py-3 rounded-full text-white bg-[#2F80ED] hover:bg-[#2671d9] font-semibold"
|
2025-09-04 18:34:28 +09:00
|
|
|
|
>
|
2025-09-05 15:39:43 +09:00
|
|
|
|
<ArrowPathIcon className="h-5 w-5 mr-2" />
|
2025-09-04 18:34:28 +09:00
|
|
|
|
Retry
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!simInfo) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
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;
|
2025-10-21 11:44:06 +09:00
|
|
|
|
|
2025-09-04 18:34:28 +09:00
|
|
|
|
return (
|
2025-11-21 18:41:14 +09:00
|
|
|
|
<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 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>
|
|
|
|
|
|
</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>
|
|
|
|
|
|
</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>
|
2025-09-05 18:22:55 +09:00
|
|
|
|
</div>
|
2025-09-04 18:34:28 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
{/* 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>
|
2025-09-05 18:22:55 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
{/* 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"
|
2025-09-09 15:45:03 +09:00
|
|
|
|
fill="none"
|
2025-11-21 18:41:14 +09:00
|
|
|
|
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>
|
2025-09-05 15:39:43 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-11-21 18:41:14 +09:00
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 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>
|
|
|
|
|
|
</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>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-09-05 15:39:43 +09:00
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
|
{/* 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>
|
2025-09-05 15:39:43 +09:00
|
|
|
|
</div>
|
2025-09-04 18:34:28 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|