import { Network, Tv } from "lucide-react"; import { ActiveStatusBadge, StatusBadge } from "@/components/admin/status-badge"; import { PlanFormValue, type StreamingServiceOption, } from "./plan-form"; import { PlanActions } from "./plan-actions"; type NumericLike = number | { toString(): string } | null | undefined; interface PlanListItem { id: string; name: string; type: "PROXY" | "STREAMING"; description: string | null; durationDays: number; sortOrder: number; isActive: boolean; price: NumericLike; nodeId: string | null; inboundId: string | null; streamingServiceId: string | null; pricingMode: "TRAFFIC_SLIDER" | "FIXED_PACKAGE"; fixedTrafficGb: number | null; fixedPrice: NumericLike; totalLimit: number | null; perUserLimit: number | null; totalTrafficGb: number | null; allowRenewal: boolean; allowTrafficTopup: boolean; renewalPrice: NumericLike; renewalPricingMode: string; renewalDurationDays: number | null; renewalMinDays: number | null; renewalMaxDays: number | null; renewalTrafficGb: number | null; topupPricingMode: string; topupPricePerGb: NumericLike; topupFixedPrice: NumericLike; minTopupGb: number | null; maxTopupGb: number | null; pricePerGb: NumericLike; minTrafficGb: number | null; maxTrafficGb: number | null; node: { name: string } | null; inbound: { protocol: string; port: number; tag: string } | null; streamingService: { name: string; usedSlots: number; maxSlots: number } | null; inboundOptions: Array<{ inboundId: string; inbound: { protocol: string; port: number; tag: string }; }>; _count: { subscriptions: number }; } interface PlanCardProps { plan: PlanListItem; activeCount: number; services: StreamingServiceOption[]; batchFormId: string; } function toNumber(value: NumericLike): number | null { return value == null ? null : Number(value); } function remainingStockSummary(plan: PlanListItem, activeCount: number) { if (plan.totalLimit == null) return { value: "∞", hint: "剩余库存", empty: false }; const remaining = Math.max(0, plan.totalLimit - activeCount); return { value: remaining.toString(), hint: remaining === 0 ? "已售罄" : "剩余库存", empty: remaining === 0, }; } function buildPlanFormValue(plan: PlanListItem): PlanFormValue { return { id: plan.id, name: plan.name, type: plan.type, description: plan.description, durationDays: plan.durationDays, sortOrder: plan.sortOrder, price: toNumber(plan.price), nodeId: plan.nodeId, inboundId: plan.inboundId, inboundOptionIds: plan.inboundOptions.map((option) => option.inboundId), streamingServiceId: plan.streamingServiceId, pricingMode: plan.pricingMode, fixedTrafficGb: plan.fixedTrafficGb, fixedPrice: toNumber(plan.fixedPrice), totalLimit: plan.totalLimit, perUserLimit: plan.perUserLimit, totalTrafficGb: plan.totalTrafficGb, allowRenewal: plan.allowRenewal, allowTrafficTopup: plan.allowTrafficTopup, renewalPrice: toNumber(plan.renewalPrice), renewalPricingMode: plan.renewalPricingMode === "PER_DAY" ? "PER_DAY" : "FIXED_DURATION", renewalDurationDays: plan.renewalDurationDays, renewalMinDays: plan.renewalMinDays, renewalMaxDays: plan.renewalMaxDays, renewalTrafficGb: plan.renewalTrafficGb, topupPricingMode: plan.topupPricingMode === "FIXED_AMOUNT" ? "FIXED_AMOUNT" : "PER_GB", topupPricePerGb: toNumber(plan.topupPricePerGb), topupFixedPrice: toNumber(plan.topupFixedPrice), minTopupGb: plan.minTopupGb, maxTopupGb: plan.maxTopupGb, pricePerGb: toNumber(plan.pricePerGb), minTrafficGb: plan.minTrafficGb, maxTrafficGb: plan.maxTrafficGb, }; } export function PlanCard({ plan, activeCount, services, batchFormId }: PlanCardProps) { const planFormValue = buildPlanFormValue(plan); const stock = remainingStockSummary(plan, activeCount); const Icon = plan.type === "PROXY" ? Network : Tv; return (

{plan.name}

{plan.type === "PROXY" ? "代理" : "流媒体"}
{stock.value} {stock.hint}
); }