| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537 |
- import {
- DataUsage,
- OrderDetail,
- OrderHistory,
- } from "../../services/product/type";
- import { productApi } from "../../apis/productApi";
- import { startLoading, stopLoading } from "../../features/loading/loadingSlice";
- import { useAppDispatch } from "../../hooks/useRedux";
- import { useMutation } from "@tanstack/react-query";
- import React, { useState, useEffect } from "react";
- import { useLocation, useNavigate } from "react-router-dom";
- import { openQRModal } from "../../features/popup/popupSlice";
- import {
- convertOrderStatusToColor,
- convertOrderStatusToText,
- formatCurrency,
- formatNumber,
- } from "../../logic/loigicUtils";
- import { useTranslation } from "react-i18next";
- import { format } from "path";
- const OrderDetailView = () => {
- const location = useLocation();
- const dispatch = useAppDispatch();
- const navigate = useNavigate();
- const { t } = useTranslation();
- const [activeTab, setActiveTab] = useState<"detail" | "manage">("detail");
- const [orderDetails, setOrderDetails] = useState<OrderDetail[] | []>([]);
- const [dataUsage, setDataUsage] = useState<DataUsage[] | []>([]);
- const state = location.state as {
- id: number;
- orderHistory: OrderHistory;
- };
- useEffect(() => {
- getOrderDetailMutation.mutate();
- }, []);
- const getOrderDetailMutation = useMutation({
- mutationFn: async () => {
- dispatch(startLoading({}));
- const res = await productApi.getOrderDetail({
- orderId: state.id,
- });
- return res;
- },
- onSuccess: (data) => {
- dispatch(stopLoading());
- console.log("Get order detail response data:", data);
- if (data && data.errorCode === "0") {
- console.log("Get order detail successful");
- setOrderDetails(data.data ?? []);
- } else {
- console.error("Get order detail failed, no token received");
- }
- },
- onError: (error: any) => {
- dispatch(stopLoading());
- console.error("Get order detail error:", error.response.data);
- },
- });
- const handleTabChange = async (tab: "detail" | "manage") => {
- if (tab === "manage") {
- // Load manage data if needed
- dispatch(startLoading({}));
- try {
- const results = await Promise.all(
- orderDetails.map(async (element) => {
- const res = await productApi.checkDataUsage({
- iccid: element?.iccid,
- });
- if (res && res.errorCode === "0") {
- return res.data;
- }
- return null;
- }),
- );
- const validResults = results.filter(Boolean);
- setDataUsage(validResults);
- console.log("All data usage results:", validResults);
- } catch (error) {
- console.error("Check data usage error:", error);
- } finally {
- dispatch(stopLoading());
- }
- }
- setActiveTab(tab);
- };
- // Circular Progress Component
- const CircularProgress = ({ dataUsage }: { dataUsage: DataUsage }) => {
- const size = 180;
- const strokeWidth = 14;
- const radius = (size - strokeWidth) / 2;
- const circumference = 2 * Math.PI * radius;
- // 240 degrees calculation
- const arcAngle = 240;
- const arcLength = (arcAngle / 360) * circumference;
- const percentage = Math.min(
- Math.max(dataUsage.usageData / dataUsage.totalData, 0),
- 1,
- );
- const strokeDashoffset = arcLength - percentage * arcLength;
- // Rotate the SVG to center the 240-degree gap at the bottom
- // The gap is 120 degrees. We want it balanced.
- // Default SVG circle starts at 3 o'clock.
- // To have the 240 arc centered at top, we rotate.
- const rotation = 150; // (360 - 240) / 2 + 90
- return (
- <div className="flex flex-col items-center">
- <div className="flex flex-col items-center justify-center">
- <span className="text-sm md:text-sm font-black text-[#000] mb-2">
- {dataUsage.expiredTime
- ? `${t("expiredAt")}: ${dataUsage.expiredTime}`
- : ""}
- </span>
- </div>
- <div className="relative" style={{ width: size, height: size }}>
- <svg
- width={size}
- height={size}
- style={{ transform: `rotate(${rotation}deg)` }}
- className="overflow-visible"
- >
- {/* Background Path (240 degrees) */}
- <circle
- cx={size / 2}
- cy={size / 2}
- r={radius}
- fill="none"
- stroke="#f0f0f0"
- strokeWidth={strokeWidth}
- strokeLinecap="round"
- strokeDasharray={`${arcLength} ${circumference}`}
- />
- {/* Progress Path (240 degrees) */}
- <circle
- cx={size / 2}
- cy={size / 2}
- r={radius}
- fill="none"
- stroke="#EE0434"
- strokeWidth={strokeWidth}
- strokeLinecap="round"
- strokeDasharray={`${arcLength} ${circumference}`}
- strokeDashoffset={strokeDashoffset}
- className="transition-all duration-1000 ease-out"
- />
- </svg>
- {/* Central Text */}
- <div className="absolute inset-0 flex flex-col items-center justify-center">
- <span className="text-3xl md:text-4xl font-black text-[#EE0434]">
- {formatNumber(dataUsage.usageData)}
- <span className="text-[16px]">{dataUsage.dataUnit}</span>
- </span>
- </div>
- {/* Min Label (Approximate position for 240deg start) */}
- <div
- className="absolute text-xl font-bold font-black text-[#000000]"
- style={{ bottom: "5%", left: "12%" }}
- >
- {formatNumber(0)}
- <span className="text-[16px]">{dataUsage.dataUnit}</span>
- </div>
- {/* Max Label (Approximate position for 240deg end) */}
- <div
- className="absolute text-xl font-bold font-black text-[#000000]"
- style={{ bottom: "5%", right: "8%" }}
- >
- {formatNumber(dataUsage.totalData)}
- <span className="text-[16px]">{dataUsage.dataUnit}</span>
- </div>
- </div>
- <p className="mt-1 text-slate-400 text-sm font-bold uppercase tracking-widest">
- {dataUsage.status === 0
- ? t("notActive")
- : dataUsage.status === 1
- ? t("active")
- : dataUsage.status === 2
- ? t("finished")
- : dataUsage.status === 3
- ? t("expired")
- : t("unknown")}
- </p>
- {/* <p className="mt-1 text-slate-400 text-sm font-bold uppercase tracking-widest">
- {100 - Math.round((used / total) * 100)}% {t("remaining")}
- </p> */}
- </div>
- );
- };
- return (
- <div className="bg-[#fcfdfe] min-h-screen pb-20">
- {/* Breadcrumb */}
- <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
- <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
- <button
- // onClick={() => onViewChange(ViewMode.HOME)}
- className="hover:text-[#EE0434] text-[16px]"
- >
- {t("home")}
- </button>
- <svg
- className="w-3 h-3"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- d="M9 5l7 7-7 7"
- strokeWidth={2.5}
- strokeLinecap="round"
- strokeLinejoin="round"
- />
- </svg>
- <button
- // onClick={() => onViewChange(ViewMode.ORDER_HISTORY)}
- className="hover:text-[#EE0434] text-[16px] font-bold"
- >
- {t("myEsim")}
- </button>
- </nav>
- </div>
- <div className="max-w-5xl mx-auto px-4 py-8">
- {/* Back Button */}
- <button
- onClick={() => navigate(-1)}
- className="mb-6 flex items-center space-x-2 px-4 py-2 bg-white border border-slate-200 rounded-lg text-slate-600 font-bold hover:bg-slate-50 transition-colors shadow-sm"
- >
- <svg
- className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M10 19l-7-7 7-7m8 14l-7-7 7-7"
- />
- </svg>
- <span>{t("back")}</span>
- </button>
- {/* Main Info Card */}
- <div className="bg-white rounded-2xl border border-[#00b0f0]/30 shadow-sm overflow-hidden mb-8">
- <div className="p-6">
- {/* Header */}
- <div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
- <div className="flex items-center gap-4">
- <div className="w-10 h-10 bg-[#00b0f0] rounded-xl flex items-center justify-center text-white shadow-sm">
- <svg
- className="w-6 h-6"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
- />
- </svg>
- </div>
- <span className="text-xl md:text-2xl font-bold text-slate-800">
- {state.orderHistory?.orderCode}
- </span>
- </div>
- <span
- className={`px-3 py-1 rounded-md text-sm font-bold shadow-sm ${convertOrderStatusToColor(state.orderHistory?.status)}`}
- >
- {convertOrderStatusToText(state.orderHistory?.status)}
- </span>
- </div>
- {/* Tabs */}
- <div className="flex space-x-1 mb-8">
- <button
- onClick={() => handleTabChange("detail")}
- className={`px-6 py-2 rounded-lg font-bold text-sm transition-colors text-[16px] ${
- activeTab === "detail"
- ? "bg-[#EE0434] text-white"
- : "text-[#EE0434] hover:bg-[#EE0434]/10"
- }`}
- >
- {t("detailDetail")}
- </button>
- <button
- onClick={() => handleTabChange("manage")}
- className={`px-6 py-2 rounded-lg font-bold text-sm transition-colors text-[16px] ${
- activeTab === "manage"
- ? "bg-[#EE0434] text-white"
- : "text-[#EE0434] hover:bg-[#EE0434]/10"
- }`}
- >
- {t("manageManage")}
- </button>
- </div>
- {/* Divider with Total */}
- <div className="relative flex items-center justify-center border-t border-slate-100 py-8">
- <span className="bg-white px-4 text-lg md:text-xl font-bold text-slate-800 absolute">
- {t("totalTotal")}:{" "}
- {formatCurrency(
- state.orderHistory?.paymentMoney,
- state.orderHistory?.curency,
- )}{" "}
- {/* <span className="text-slate-500 font-normal">
- ({state.orderHistory?.curency})
- </span> */}
- </span>
- </div>
- {/* Customer Info Grid */}
- <div className="grid grid-cols-1 md:grid-cols-4 gap-6 md:gap-4 mt-4">
- <div className="flex flex-col">
- <div className="flex items-center gap-2 text-slate-400 mb-1">
- <svg
- className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
- />
- </svg>
- <span className="text-xs font-bold text-[16px]">
- {t("fullName")}
- </span>
- </div>
- <span className="text-slate-800 font-semibold text-sm text-[16px]">
- {state?.orderHistory.customerInfo.surName}{" "}
- {state?.orderHistory.customerInfo.lastName}
- </span>
- </div>
- <div className="flex flex-col">
- <div className="flex items-center gap-2 text-slate-400 mb-1">
- <svg
- className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
- />
- </svg>
- <span className="text-xs font-bold text-[16px]">
- {t("phoneNumber")}
- </span>
- </div>
- <span className="text-slate-800 font-semibold text-sm text-[16px]">
- {state?.orderHistory.customerInfo.phoneNumber}
- </span>
- </div>
- <div className="flex flex-col">
- <div className="flex items-center gap-2 text-slate-400 mb-1">
- <svg
- className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
- />
- </svg>
- <span className="text-xs font-bold text-[16px]">Email</span>
- </div>
- <span className="text-slate-800 font-semibold text-sm text-[16px] break-all">
- {state?.orderHistory.customerInfo.email}
- </span>
- </div>
- <div className="flex flex-col">
- <div className="flex items-center gap-2 text-slate-400 mb-1">
- <svg
- className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 00-3 3z"
- />
- </svg>
- <span className="text-xs font-bold text-[16px]">
- {t("paymentMethod")}
- </span>
- </div>
- <span className="text-slate-800 font-semibold text-sm text-[16px]">
- QR Code
- </span>
- </div>
- </div>
- </div>
- </div>
- {/* Product Section */}
- <div className="space-y-4">
- {orderDetails.length > 0 && (
- <div className="flex items-center space-x-3">
- <img
- src={`${orderDetails[0].areaIconUrl}`}
- className="w-8 h-8 rounded-full object-cover border border-slate-200"
- />
- <h3 className="text-lg font-bold text-slate-900">
- SIM {orderDetails[0].areaName}
- </h3>
- </div>
- )}
- <div
- className={`rounded-2xl border border-slate-100 shadow-sm p-6 relative ${
- activeTab === "manage" ? "bg-slate-50/50" : "bg-white"
- }`}
- >
- {activeTab === "detail" ? (
- // DETAIL TAB CONTENT
- <div className="space-y-8">
- {orderDetails.map((pkg, idx) => (
- <div
- key={idx}
- className={`flex flex-col md:flex-row justify-between items-start md:items-center ${
- idx !== orderDetails.length - 1
- ? "border-b border-slate-100 pb-8"
- : ""
- }`}
- >
- <div className="space-y-2">
- <div className="flex items-center gap-2">
- <h4 className="text-xl font-bold text-slate-800">
- {pkg.packageName}
- </h4>
- {/* <span className="px-2 py-0.5 rounded bg-slate-100 text-slate-600 text-xs font-bold">
- {pkg.id}
- </span> */}
- </div>
- {/* <span className="inline-block bg-[#ff0050] text-white text-[10px] font-bold px-2 py-0.5 rounded">
- TikTok
- </span> */}
- <p className="text-slate-500 text-sm font-bold">
- {t("validityPeriod")}: {pkg.dayDuration} {t("days")}
- </p>
- </div>
- <div className="mt-4 md:mt-0 flex flex-col items-end">
- <button
- className="flex items-center text-[#EE0434] font-bold text-sm mb-2 hover:underline"
- onClick={() => {
- dispatch(openQRModal(pkg.qrcodeUrl));
- }}
- >
- <svg
- className="w-4 h-4 mr-1"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
- />
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
- />
- </svg>
- QR Code
- </button>
- <div className="text-right">
- <span className="text-xl font-bold text-slate-800">
- {formatCurrency(pkg.paymentMoney, pkg.curency)}
- </span>
- {/* <span className="text-slate-500 font-medium ml-1 font-bold">
- {" "}
- đ
- </span> */}
- </div>
- </div>
- </div>
- ))}
- </div>
- ) : (
- // MANAGE TAB CONTENT - Grid of Circular Progress
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
- {dataUsage.map((pkg, idx) => (
- <div
- key={idx}
- className="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm flex flex-col items-center hover:shadow-md transition-shadow"
- >
- <h4 className="text-lg font-bold text-slate-800 mb-2">
- {orderDetails[idx].packageName}
- </h4>
- <CircularProgress dataUsage={pkg} />
- </div>
- ))}
- </div>
- )}
- </div>
- </div>
- </div>
- </div>
- );
- };
- export default OrderDetailView;
|