mirror of
https://github.com/etiennecollin/unifi-voucher-manager.git
synced 2025-10-23 08:12:15 +00:00
refactor: voucher cards and voucher status
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { Voucher } from "@/types/voucher";
|
||||
import { formatCode, formatDuration, formatGuestUsage } from "@/utils/format";
|
||||
import {
|
||||
formatCode,
|
||||
formatDuration,
|
||||
formatGuestUsage,
|
||||
formatStatus,
|
||||
} from "@/utils/format";
|
||||
import { memo, useCallback } from "react";
|
||||
|
||||
type Props = {
|
||||
@@ -12,6 +17,8 @@ type Props = {
|
||||
const VoucherCard = ({ voucher, selected, editMode, onClick }: Props) => {
|
||||
const statusClass = voucher.expired
|
||||
? "bg-status-danger text-status-danger"
|
||||
: voucher.activatedAt
|
||||
? "bg-status-warning text-status-warning"
|
||||
: "bg-status-success text-status-success";
|
||||
const onClickHandler = useCallback(
|
||||
() => onClick?.(voucher),
|
||||
@@ -69,7 +76,7 @@ const VoucherCard = ({ voucher, selected, editMode, onClick }: Props) => {
|
||||
<span
|
||||
className={`px-2 py-1 rounded-lg text-xs font-semibold uppercase ${statusClass}`}
|
||||
>
|
||||
{voucher.expired ? "Expired" : "Active"}
|
||||
{formatStatus(voucher.expired, voucher.activatedAt)}
|
||||
</span>
|
||||
{voucher.expiresAt && (
|
||||
<span className="text-xs">Expires: {voucher.expiresAt}</span>
|
||||
|
@@ -3,15 +3,17 @@
|
||||
import Modal from "@/components/modals/Modal";
|
||||
import Spinner from "@/components/utils/Spinner";
|
||||
import { api } from "@/utils/api";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
formatBytes,
|
||||
formatDuration,
|
||||
formatGuestUsage,
|
||||
formatSpeed,
|
||||
formatStatus,
|
||||
} from "@/utils/format";
|
||||
import VoucherCode from "@/components/utils/VoucherCode";
|
||||
import { Voucher } from "@/types/voucher";
|
||||
import { TriState } from "@/types/state";
|
||||
|
||||
type Props = {
|
||||
voucher: Voucher;
|
||||
@@ -20,8 +22,7 @@ type Props = {
|
||||
|
||||
export default function VoucherModal({ voucher, onClose }: Props) {
|
||||
const [details, setDetails] = useState<Voucher | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
const [state, setState] = useState<TriState | null>(null);
|
||||
const lastFetchedId = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -31,35 +32,39 @@ export default function VoucherModal({ voucher, onClose }: Props) {
|
||||
}
|
||||
|
||||
(async () => {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
setState("loading");
|
||||
lastFetchedId.current = voucher.id;
|
||||
|
||||
try {
|
||||
const res = await api.getVoucherDetails(voucher.id);
|
||||
setDetails(res);
|
||||
setState("ok");
|
||||
} catch {
|
||||
setError(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setState("error");
|
||||
}
|
||||
})();
|
||||
}, [voucher.id]);
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
switch (state) {
|
||||
case null:
|
||||
case "loading":
|
||||
return <Spinner />;
|
||||
case "error":
|
||||
return (
|
||||
<Modal onClose={onClose}>
|
||||
<VoucherCode voucher={voucher} contentClassName="mb-8" />
|
||||
{loading ? (
|
||||
<Spinner />
|
||||
) : error || details == null ? (
|
||||
<div className="card text-status-danger text-center">
|
||||
Failed to load detailed information
|
||||
</div>
|
||||
) : (
|
||||
);
|
||||
case "ok":
|
||||
if (details == null) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{(
|
||||
[
|
||||
["Status", details.expired ? "Expired" : "Active"],
|
||||
["Status", formatStatus(details.expired, details.activatedAt)],
|
||||
["Name", details.name || "No note"],
|
||||
["Created", details.createdAt],
|
||||
...(details.activatedAt
|
||||
@@ -94,7 +99,14 @@ export default function VoucherModal({ voucher, onClose }: Props) {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
}
|
||||
}, [state, details]);
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose}>
|
||||
<VoucherCode voucher={voucher} contentClassName="mb-8" />
|
||||
{renderContent()}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@@ -6,6 +6,15 @@ export function formatMaxGuests(maxGuests: number | null | undefined) {
|
||||
return !maxGuests ? "Unlimited" : Math.max(maxGuests, 0);
|
||||
}
|
||||
|
||||
export function formatStatus(
|
||||
expired: boolean,
|
||||
activatedAt: string | null | undefined,
|
||||
) {
|
||||
if (expired) return "Expired";
|
||||
if (activatedAt) return "Active";
|
||||
return "Available";
|
||||
}
|
||||
|
||||
export function formatDuration(m: number | null | undefined) {
|
||||
if (!m) return "Unlimited";
|
||||
const days = Math.floor(m / 1440),
|
||||
|
Reference in New Issue
Block a user