@burdenoff/microfe-billing 2026.604.1 → 2026.604.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"SubscriptionDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionDetailPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscription Detail Page\n * Displays detailed information about a single subscription\n */\n\nimport { type FC, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { useSubscription, useSubscriptionMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useGetDefaultBillingAccountDashboardQuery } from '../../../../generated/global-operations';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n formatPercentage,\n} from '../../../shared/utils/format';\nimport {\n getSubscriptionStatusColor,\n getStatusBadgeClasses,\n getQuotaProgressColor,\n} from '../../../shared/utils/status';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n SubscriptionStatus,\n QuotaAssignment,\n AddonSubscription,\n SubscriptionHistory,\n Transaction,\n} from '../../../shared/types';\nimport { PricingModel } from '../../../shared/types';\nimport { ManageSeatsModal } from '../components/ManageSeatsModal';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport const SubscriptionDetailPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { subscriptionId } = useParams<{ subscriptionId: string }>();\n const navigateTo = useBillingNavigate();\n const { emit } = useBillingEventEmitter();\n const { orgId } = useBilling();\n const permissions = useBillingPermissions();\n const { selectedAccountId } = useBillingAccountSelection({\n orgId,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n const { data: defaultBillingAccountData } = useGetDefaultBillingAccountDashboardQuery({\n fetchPolicy: 'cache-first',\n });\n const billingAccountId =\n selectedAccountId ?? defaultBillingAccountData?.getDefaultBillingAccount?.id ?? undefined;\n const { subscription, isLoading, error, refetch } = useSubscription(\n subscriptionId,\n billingAccountId\n );\n const { cancelSubscription, isCanceling, updateSubscriptionSeats, isUpdatingSeats } =\n useSubscriptionMutations();\n\n // Modal state\n const [showCancelModal, setShowCancelModal] = useState(false);\n const [showManageSeatsModal, setShowManageSeatsModal] = useState(false);\n const [cancelReason, setCancelReason] = useState('');\n const [cancelError, setCancelError] = useState<string | null>(null);\n\n // Handle cancel subscription\n const handleCancelSubscription = async () => {\n if (!subscription) return;\n\n setCancelError(null);\n try {\n await cancelSubscription({\n billingAccountId: billingAccountId ?? subscription.billingAccountId,\n subscriptionId: subscription.id,\n reason: cancelReason || 'User requested cancellation',\n });\n emit('billing.subscription.cancelled', {\n route: '/billing/subscriptions',\n entityId: subscription.id,\n source: 'subscription-detail',\n reason: cancelReason || 'User requested cancellation',\n });\n setShowCancelModal(false);\n setCancelReason('');\n await refetch();\n } catch (err) {\n setCancelError(err instanceof Error ? err.message : 'Failed to cancel subscription');\n }\n };\n\n // Permission check\n if (!permissions.canViewSubscriptions) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-muted flex items-center justify-center\">\n <svg\n className=\"size-6 text-muted-foreground\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">Access Denied</h2>\n <p className=\"text-sm text-muted-foreground max-w-sm\">\n You don&apos;t have permission to view this subscription.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <div className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error || !subscription) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.subscriptions.notFound', 'Subscription not found')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {error?.message || 'The subscription you are looking for does not exist.'}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Back to Subscriptions\n </button>\n </div>\n </div>\n );\n }\n\n const plan = subscription.plan;\n const quotas = subscription.quotas || [];\n const addons = subscription.addonSubscriptions || [];\n const transactions = subscription.transactions || [];\n const history = subscription.history || [];\n const statusColor = getSubscriptionStatusColor(subscription.status);\n const isActive = subscription.status === ('active' as SubscriptionStatus);\n\n // Calculate days remaining\n const endDate = new Date(subscription.endDate);\n const startDate = new Date(subscription.startDate);\n const now = new Date();\n const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n const progressPercentage = Math.round(((totalDays - daysRemaining) / totalDays) * 100);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"p-2 -ml-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors\"\n >\n <svg className=\"size-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n <div>\n <div className=\"flex items-center gap-3\">\n <h1 className=\"text-2xl font-bold text-foreground\">{plan?.name || 'Subscription'}</h1>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatSubscriptionStatus(subscription.status)}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {plan?.pricingModel === PricingModel.PER_SEAT && plan.pricePerSeat\n ? `${formatCurrency(plan.pricePerSeat, plan.currency)} / seat / ${formatPlanDuration(plan.duration)} · ${subscription.seatCount ?? plan.minSeats ?? 1} seats = ${formatCurrency(plan.pricePerSeat * (subscription.seatCount ?? plan.minSeats ?? 1), plan.currency)} / ${formatPlanDuration(plan.duration)}`\n : plan\n ? `${formatCurrency(plan.price, plan.currency)} / ${formatPlanDuration(plan.duration)}`\n : 'Unknown plan'}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {isActive &&\n permissions.canUpdateSubscription &&\n plan?.pricingModel === PricingModel.PER_SEAT && (\n <button\n type=\"button\"\n onClick={() => setShowManageSeatsModal(true)}\n className=\"px-4 py-2 text-sm font-medium border border-border rounded-md hover:bg-accent text-foreground transition-colors\"\n >\n Manage Seats\n </button>\n )}\n {isActive && permissions.canUpdateSubscription && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}/upgrade`,\n billingAccountId ?? subscription.billingAccountId\n )\n )\n }\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Change Plan\n </button>\n )}\n {isActive && permissions.canCancelSubscription && !subscription.canceledAt && (\n <button\n type=\"button\"\n onClick={() => setShowCancelModal(true)}\n disabled={isCanceling}\n className=\"px-4 py-2 text-sm font-medium border border-border text-destructive rounded-md hover:bg-destructive/10 transition-colors disabled:opacity-50\"\n >\n {isCanceling ? 'Canceling...' : 'Cancel Subscription'}\n </button>\n )}\n {subscription.canceledAt && (\n <span className=\"px-4 py-2 text-sm font-medium text-status-warning-text bg-status-warning-bg-subtle rounded-md\">\n Cancellation Scheduled\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Period Progress */}\n {isActive && (\n <div className=\"border border-border rounded-lg bg-card p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-foreground\">Current Period</h2>\n <span className=\"text-sm text-muted-foreground\">\n {daysRemaining} days remaining\n </span>\n </div>\n <div className=\"space-y-2\">\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full bg-primary transition-all duration-300\"\n style={{ width: `${progressPercentage}%` }}\n />\n </div>\n <div className=\"flex items-center justify-between text-sm text-muted-foreground\">\n <span>{formatDate(subscription.startDate, 'short')}</span>\n <span>{formatDate(subscription.endDate, 'short')}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* Quotas */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n Quota Usage ({quotas.length})\n </h2>\n </div>\n <div className=\"p-6\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No quotas assigned to this subscription.\n </p>\n ) : (\n <div className=\"space-y-4\">\n {quotas.map((quota) => (\n <QuotaUsageItem key={quota.id} quota={quota} />\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Addons */}\n {addons.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Addons ({addons.length})</h2>\n </div>\n <div className=\"p-6\">\n <div className=\"space-y-3\">\n {addons.map((addonSub) => (\n <AddonItem key={addonSub.id} addonSubscription={addonSub} />\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Recent Transactions */}\n {transactions.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Recent Transactions</h2>\n </div>\n <div className=\"divide-y divide-border\">\n {transactions.slice(0, 5).map((transaction) => (\n <TransactionItem key={transaction.id} transaction={transaction} />\n ))}\n </div>\n {transactions.length > 5 && (\n <div className=\"p-4 text-center border-t border-border\">\n <button type=\"button\" className=\"text-sm text-primary hover:underline\">\n View all transactions\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Subscription Details */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Details</h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div>\n <p className=\"text-sm text-muted-foreground\">Subscription ID</p>\n <p className=\"font-mono text-sm text-foreground mt-0.5 break-all\">\n {subscription.id}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Plan</p>\n <p className=\"text-sm text-foreground mt-0.5\">{plan?.name || 'Unknown'}</p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Billing Account</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {subscription.billingAccount?.name || 'Unknown'}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Start Date</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {formatDate(subscription.startDate)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">End Date</p>\n <p className=\"text-sm text-foreground mt-0.5\">{formatDate(subscription.endDate)}</p>\n </div>\n {subscription.nextBillingDate && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Next Billing</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {formatDate(subscription.nextBillingDate)}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-sm text-muted-foreground\">Auto Renewal</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {subscription.autoRenewal ? 'Enabled' : 'Disabled'}\n </p>\n </div>\n {subscription.paymentGateway && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Payment Gateway</p>\n <p className=\"text-sm text-foreground mt-0.5 capitalize\">\n {subscription.paymentGateway}\n </p>\n </div>\n )}\n </div>\n </div>\n\n {/* History */}\n {history.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">History</h2>\n </div>\n <div className=\"p-6\">\n <div className=\"space-y-3\">\n {history.slice(0, 5).map((item) => (\n <HistoryItem key={item.id} item={item} />\n ))}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Manage Seats Modal */}\n {showManageSeatsModal && subscription && (\n <ManageSeatsModal\n subscription={subscription}\n onClose={() => setShowManageSeatsModal(false)}\n onConfirm={async (seatCount) => {\n await updateSubscriptionSeats({ subscriptionId: subscription.id, seatCount });\n await refetch();\n }}\n isLoading={isUpdatingSeats}\n />\n )}\n\n {/* Cancel Subscription Modal */}\n {showCancelModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div\n className=\"absolute inset-0 bg-overlay-scrim\"\n onClick={() => setShowCancelModal(false)}\n />\n <div\n className=\"relative bg-card border border-border rounded-lg shadow-lg w-full max-w-md mx-4 p-6\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"cancel-subscription-title\"\n >\n <h3\n id=\"cancel-subscription-title\"\n className=\"text-lg font-semibold text-foreground mb-2\"\n >\n Cancel Subscription\n </h3>\n <p className=\"text-sm text-muted-foreground mb-4\">\n Are you sure you want to cancel this subscription? Your subscription will remain\n active until{' '}\n <span className=\"font-medium text-foreground\">\n {formatDate(subscription.endDate)}\n </span>\n , but will not renew automatically.\n </p>\n\n <div className=\"mb-4\">\n <label className=\"block text-sm font-medium text-foreground mb-1\">\n Reason for cancellation (optional)\n </label>\n <textarea\n value={cancelReason}\n onChange={(e) => setCancelReason(e.target.value)}\n placeholder=\"Please share why you're canceling...\"\n className=\"w-full px-3 py-2 text-sm border border-border rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent resize-none\"\n rows={3}\n />\n </div>\n\n {cancelError && (\n <div className=\"mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md\">\n <p className=\"text-sm text-destructive\">{cancelError}</p>\n </div>\n )}\n\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => {\n setShowCancelModal(false);\n setCancelReason('');\n setCancelError(null);\n }}\n className=\"px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n Keep Subscription\n </button>\n <button\n type=\"button\"\n onClick={handleCancelSubscription}\n disabled={isCanceling}\n className=\"px-4 py-2 text-sm font-medium bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors disabled:opacity-50\"\n >\n {isCanceling ? 'Canceling...' : 'Confirm Cancellation'}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Sub-components\n// ============================================================================\n\nconst QuotaUsageItem: FC<{ quota: QuotaAssignment }> = ({ quota }) => {\n const limits = quota.limits as { value?: number; type?: string } | null;\n const limit = limits?.value || 0;\n const used = quota.currentUsageSum || 0;\n const percentage = limit > 0 ? Math.min(100, (used / limit) * 100) : 0;\n\n return (\n <div className=\"p-4 rounded-lg bg-muted/30\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"font-medium text-foreground\">{quota.name}</span>\n <span className=\"text-sm text-muted-foreground\">\n {used} / {limit > 0 ? limit : '∞'}\n </span>\n </div>\n {limit > 0 && (\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full transition-all duration-300 ${getQuotaProgressColor(percentage)}`}\n style={{ width: `${percentage}%` }}\n />\n </div>\n )}\n <div className=\"flex items-center justify-between mt-2 text-xs text-muted-foreground\">\n <span>{quota.quotaType}</span>\n <span>{formatPercentage(percentage)} used</span>\n </div>\n </div>\n );\n};\n\nconst AddonItem: FC<{ addonSubscription: AddonSubscription }> = ({ addonSubscription }) => {\n const addon = addonSubscription.addon;\n return (\n <div className=\"flex items-center justify-between p-3 rounded-lg bg-muted/30\">\n <div>\n <p className=\"font-medium text-foreground\">{addon?.name || 'Unknown Addon'}</p>\n <p className=\"text-sm text-muted-foreground\">Qty: {addonSubscription.quantity}</p>\n </div>\n {addon && (\n <p className=\"text-sm font-medium text-foreground\">\n {formatCurrency(addon.price * addonSubscription.quantity, addon.currency)}\n </p>\n )}\n </div>\n );\n};\n\nconst TransactionItem: FC<{ transaction: Transaction }> = ({ transaction }) => {\n return (\n <div className=\"flex items-center justify-between px-6 py-4\">\n <div>\n <p className=\"font-medium text-foreground capitalize\">{transaction.type}</p>\n <p className=\"text-sm text-muted-foreground\">\n {formatDate(transaction.createdAt, 'short')}\n </p>\n </div>\n <div className=\"text-right\">\n <p\n className={`font-medium ${transaction.type === 'refund' ? 'text-status-error-text' : 'text-foreground'}`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(transaction.amount, transaction.currency)}\n </p>\n <p className=\"text-sm text-muted-foreground capitalize\">{transaction.status}</p>\n </div>\n </div>\n );\n};\n\nconst HistoryItem: FC<{ item: SubscriptionHistory }> = ({ item }) => {\n return (\n <div className=\"flex items-start gap-3\">\n <div className=\"size-2 rounded-full bg-muted-foreground mt-2 shrink-0\" />\n <div>\n <p className=\"text-sm text-foreground\">\n <span className=\"capitalize\">{item.fromStatus}</span>\n {' → '}\n <span className=\"capitalize font-medium\">{item.toStatus}</span>\n </p>\n {item.reason && <p className=\"text-xs text-muted-foreground mt-0.5\">{item.reason}</p>}\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {formatDate(item.createdAt, 'short')}\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsCA,IAAa,UAAmC;CAC9C,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,EAAE,sBAAmB,IAAuC,EAC5D,IAAa,GAAoB,EACjC,EAAE,aAAS,GAAwB,EACnC,EAAE,cAAU,GAAY,EACxB,IAAc,IAAuB,EACrC,EAAE,0BAAsB,EAA2B;EACvD;EACA,aAAa;EACb,cAAc;EACf,CAAC,EACI,EAAE,MAAM,OAA8B,EAA0C,EACpF,aAAa,eACd,CAAC,EACI,IACJ,MAAqB,IAA2B,0BAA0B,MAAM,KAAA,GAC5E,EAAE,iBAAc,eAAW,UAAO,eAAY,EAClD,GACA,EACD,EACK,EAAE,uBAAoB,gBAAa,4BAAyB,uBAChE,GAA0B,EAGtB,CAAC,GAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAsB,KAA2B,EAAS,GAAM,EACjE,CAAC,GAAc,KAAmB,EAAS,GAAG,EAC9C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAG7D,IAA2B,YAAY;AACtC,SAEL;KAAe,KAAK;AACpB,OAAI;AAcF,IAbA,MAAM,EAAmB;KACvB,kBAAkB,KAAoB,EAAa;KACnD,gBAAgB,EAAa;KAC7B,QAAQ,KAAgB;KACzB,CAAC,EACF,GAAK,kCAAkC;KACrC,OAAO;KACP,UAAU,EAAa;KACvB,QAAQ;KACR,QAAQ,KAAgB;KACzB,CAAC,EACF,EAAmB,GAAM,EACzB,EAAgB,GAAG,EACnB,MAAM,GAAS;YACR,GAAK;AACZ,MAAe,aAAe,QAAQ,EAAI,UAAU,gCAAgC;;;;AAKxF,KAAI,CAAC,EAAY,qBACf,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA;KACF,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eAAwC;KAAkB,CAAA;IACxE,kBAAC,KAAD;KAAG,WAAU;eAAyC;KAElD,CAAA;IACA;;EACF,CAAA;AAKV,KAAI,GACF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAK,WAAU,0CAA2C,CAAA,EAC1D,kBAAC,OAAD,EAAK,WAAU,2CAA4C,CAAA,CACvD;MACN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAK,WAAU,2CAA4C,CAAA,EAC3D,kBAAC,OAAD,EAAK,WAAU,6CAA8C,CAAA,CACzD;;IACF,CAAA;GACF,CAAA,CACF;;AAKV,KAAI,KAAS,CAAC,EACZ,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA;KACF,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,kCAAkC,yBAAyB;KAC5D,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,GAAO,WAAW;KACjB,CAAA;IACJ,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,EAAqB,kBAAkB,EAAiB,CAAC;KACnF,WAAU;eACX;KAEQ,CAAA;IACL;;EACF,CAAA;CAIV,IAAM,IAAO,EAAa,MACpB,IAAS,EAAa,UAAU,EAAE,EAClC,IAAS,EAAa,sBAAsB,EAAE,EAC9C,IAAe,EAAa,gBAAgB,EAAE,EAC9C,IAAU,EAAa,WAAW,EAAE,EACpC,KAAc,EAA2B,EAAa,OAAO,EAC7D,IAAW,EAAa,WAAY,UAGpC,IAAU,IAAI,KAAK,EAAa,QAAQ,EACxC,KAAY,IAAI,KAAK,EAAa,UAAU,EAC5C,qBAAM,IAAI,MAAM,EAChB,IAAY,KAAK,MAAM,EAAQ,SAAS,GAAG,GAAU,SAAS,KAAK,MAAO,KAAK,KAAK,IAAI,EACxF,IAAgB,KAAK,IACzB,GACA,KAAK,MAAM,EAAQ,SAAS,GAAG,GAAI,SAAS,KAAK,MAAO,KAAK,KAAK,IAAI,CACvE,EACK,KAAqB,KAAK,OAAQ,IAAY,KAAiB,IAAa,IAAI;AAEtF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,EAAqB,kBAAkB,EAAiB,CAAC;MACnF,WAAU;gBAEV,kBAAC,OAAD;OAAK,WAAU;OAAS,MAAK;OAAO,SAAQ;OAAY,QAAO;iBAC7D,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;QACF,CAAA;OACE,CAAA;MACC,CAAA,EACT,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAsC,GAAM,QAAQ;OAAoB,CAAA,EACtF,kBAAC,QAAD;OACE,WAAW,yCAAyC,EAAsB,GAAY;iBAErF,GAAyB,EAAa,OAAO;OACzC,CAAA,CACH;SACN,kBAAC,KAAD;MAAG,WAAU;gBACV,GAAM,iBAAiB,EAAa,YAAY,EAAK,eAClD,GAAG,EAAe,EAAK,cAAc,EAAK,SAAS,CAAC,YAAY,EAAmB,EAAK,SAAS,CAAC,KAAK,EAAa,aAAa,EAAK,YAAY,EAAE,WAAW,EAAe,EAAK,gBAAgB,EAAa,aAAa,EAAK,YAAY,IAAI,EAAK,SAAS,CAAC,KAAK,EAAmB,EAAK,SAAS,KACvS,IACE,GAAG,EAAe,EAAK,OAAO,EAAK,SAAS,CAAC,KAAK,EAAmB,EAAK,SAAS,KACnF;MACJ,CAAA,CACA,EAAA,CAAA,CACF;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KACC,EAAY,yBACZ,GAAM,iBAAiB,EAAa,YAClC,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAwB,GAAK;OAC5C,WAAU;iBACX;OAEQ,CAAA;MAEZ,KAAY,EAAY,yBACvB,kBAAC,UAAD;OACE,MAAK;OACL,eACE,EACE,EACE,kBAAkB,EAAa,GAAG,WAClC,KAAoB,EAAa,iBAClC,CACF;OAEH,WAAU;iBACX;OAEQ,CAAA;MAEV,KAAY,EAAY,yBAAyB,CAAC,EAAa,cAC9D,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAmB,GAAK;OACvC,UAAU;OACV,WAAU;iBAET,IAAc,iBAAiB;OACzB,CAAA;MAEV,EAAa,cACZ,kBAAC,QAAD;OAAM,WAAU;iBAAgG;OAEzG,CAAA;MAEL;OACF;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEG,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBAAwC;SAAmB,CAAA,EACzE,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACG,GAAc,kBACV;WACH;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UACE,WAAU;UACV,OAAO,EAAE,OAAO,GAAG,GAAmB,IAAI;UAC1C,CAAA;SACE,CAAA,EACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD,EAAA,UAAO,EAAW,EAAa,WAAW,QAAQ,EAAQ,CAAA,EAC1D,kBAAC,QAAD,EAAA,UAAO,EAAW,EAAa,SAAS,QAAQ,EAAQ,CAAA,CACpD;WACF;UACF;;MAIR,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,MAAD;SAAI,WAAU;mBAAd;UAAsD;UACtC,EAAO;UAAO;UACzB;;QACD,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAO,WAAW,IACjB,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAEzC,CAAA,GAEJ,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAO,KAAK,MACX,kBAAC,GAAD,EAAsC,UAAS,EAA1B,EAAM,GAAoB,CAC/C;SACE,CAAA;QAEJ,CAAA,CACF;;MAGL,EAAO,SAAS,KACf,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,MAAD;SAAI,WAAU;mBAAd;UAAsD;UAAS,EAAO;UAAO;UAAM;;QAC/E,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAO,KAAK,MACX,kBAAC,GAAD,EAA6B,mBAAmB,GAAY,EAA5C,EAAS,GAAmC,CAC5D;SACE,CAAA;QACF,CAAA,CACF;;MAIP,EAAa,SAAS,KACrB,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,MAAD;UAAI,WAAU;oBAAwC;UAAwB,CAAA;SAC1E,CAAA;QACN,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAa,MAAM,GAAG,EAAE,CAAC,KAAK,MAC7B,kBAAC,GAAD,EAAmD,gBAAe,EAA5C,EAAY,GAAgC,CAClE;SACE,CAAA;QACL,EAAa,SAAS,KACrB,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,UAAD;UAAQ,MAAK;UAAS,WAAU;oBAAuC;UAE9D,CAAA;SACL,CAAA;QAEJ;;MAEJ;QAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,MAAD;QAAI,WAAU;kBAAwC;QAAY,CAAA;OAC9D,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAmB,CAAA,EAChE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa;SACZ,CAAA,CACA,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAQ,CAAA,EACrD,kBAAC,KAAD;SAAG,WAAU;mBAAkC,GAAM,QAAQ;SAAc,CAAA,CACvE,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAmB,CAAA,EAChE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa,gBAAgB,QAAQ;SACpC,CAAA,CACA,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAc,CAAA,EAC3D,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAW,EAAa,UAAU;SACjC,CAAA,CACA,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAY,CAAA,EACzD,kBAAC,KAAD;SAAG,WAAU;mBAAkC,EAAW,EAAa,QAAQ;SAAK,CAAA,CAChF,EAAA,CAAA;QACL,EAAa,mBACZ,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAgB,CAAA,EAC7D,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAW,EAAa,gBAAgB;SACvC,CAAA,CACA,EAAA,CAAA;QAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAgB,CAAA,EAC7D,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa,cAAc,YAAY;SACtC,CAAA,CACA,EAAA,CAAA;QACL,EAAa,kBACZ,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAmB,CAAA,EAChE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAa;SACZ,CAAA,CACA,EAAA,CAAA;QAEJ;SACF;SAGL,EAAQ,SAAS,KAChB,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,MAAD;QAAI,WAAU;kBAAwC;QAAY,CAAA;OAC9D,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAQ,MAAM,GAAG,EAAE,CAAC,KAAK,MACxB,kBAAC,GAAD,EAAiC,SAAQ,EAAvB,EAAK,GAAkB,CACzC;QACE,CAAA;OACF,CAAA,CACF;QAEJ;OACF;;GAGL,KAAwB,KACvB,kBAAC,GAAD;IACgB;IACd,eAAe,EAAwB,GAAM;IAC7C,WAAW,OAAO,MAAc;AAE9B,KADA,MAAM,EAAwB;MAAE,gBAAgB,EAAa;MAAI;MAAW,CAAC,EAC7E,MAAM,GAAS;;IAEjB,WAAW;IACX,CAAA;GAIH,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KACE,WAAU;KACV,eAAe,EAAmB,GAAM;KACxC,CAAA,EACF,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,cAAW;KACX,mBAAgB;eAJlB;MAME,kBAAC,MAAD;OACE,IAAG;OACH,WAAU;iBACX;OAEI,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBAAb;QAAkD;QAEnC;QACb,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAW,EAAa,QAAQ;SAC5B,CAAA;;QAEL;;MAEJ,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QAAO,WAAU;kBAAiD;QAE1D,CAAA,EACR,kBAAC,YAAD;QACE,OAAO;QACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;QAChD,aAAY;QACZ,WAAU;QACV,MAAM;QACN,CAAA,CACE;;MAEL,KACC,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,KAAD;QAAG,WAAU;kBAA4B;QAAgB,CAAA;OACrD,CAAA;MAGR,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe;AAGb,SAFA,EAAmB,GAAM,EACzB,EAAgB,GAAG,EACnB,EAAe,KAAK;;QAEtB,WAAU;kBACX;QAEQ,CAAA,EACT,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU;QACV,WAAU;kBAET,IAAc,iBAAiB;QACzB,CAAA,CACL;;MACF;OACF;;GAEJ;;GAQJ,KAAkD,EAAE,eAAY;CAEpE,IAAM,IADS,EAAM,QACC,SAAS,GACzB,IAAO,EAAM,mBAAmB,GAChC,IAAa,IAAQ,IAAI,KAAK,IAAI,KAAM,IAAO,IAAS,IAAI,GAAG;AAErE,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAA+B,EAAM;KAAY,CAAA,EACjE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG;MAAK;MAAI,IAAQ,IAAI,IAAQ;MACzB;OACH;;GACL,IAAQ,KACP,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KACE,WAAW,sCAAsC,EAAsB,EAAW;KAClF,OAAO,EAAE,OAAO,GAAG,EAAW,IAAI;KAClC,CAAA;IACE,CAAA;GAER,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD,EAAA,UAAO,EAAM,WAAiB,CAAA,EAC9B,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAiB,EAAW,EAAC,QAAY,EAAA,CAAA,CAC5C;;GACF;;GAIJ,KAA2D,EAAE,2BAAwB;CACzF,IAAM,IAAQ,EAAkB;AAChC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;GAAG,WAAU;aAA+B,GAAO,QAAQ;GAAoB,CAAA,EAC/E,kBAAC,KAAD;GAAG,WAAU;aAAb,CAA6C,SAAM,EAAkB,SAAa;KAC9E,EAAA,CAAA,EACL,KACC,kBAAC,KAAD;GAAG,WAAU;aACV,EAAe,EAAM,QAAQ,EAAkB,UAAU,EAAM,SAAS;GACvE,CAAA,CAEF;;GAIJ,KAAqD,EAAE,qBAEzD,kBAAC,OAAD;CAAK,WAAU;WAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;EAAG,WAAU;YAA0C,EAAY;EAAS,CAAA,EAC5E,kBAAC,KAAD;EAAG,WAAU;YACV,EAAW,EAAY,WAAW,QAAQ;EACzC,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,KAAD;GACE,WAAW,eAAe,EAAY,SAAS,WAAW,2BAA2B;aADvF,CAGG,EAAY,SAAS,WAAW,MAAM,IACtC,EAAe,EAAY,QAAQ,EAAY,SAAS,CACvD;MACJ,kBAAC,KAAD;GAAG,WAAU;aAA4C,EAAY;GAAW,CAAA,CAC5E;IACF;IAIJ,KAAkD,EAAE,cAEtD,kBAAC,OAAD;CAAK,WAAU;WAAf,CACE,kBAAC,OAAD,EAAK,WAAU,yDAA0D,CAAA,EACzE,kBAAC,OAAD,EAAA,UAAA;EACE,kBAAC,KAAD;GAAG,WAAU;aAAb;IACE,kBAAC,QAAD;KAAM,WAAU;eAAc,EAAK;KAAkB,CAAA;IACpD;IACD,kBAAC,QAAD;KAAM,WAAU;eAA0B,EAAK;KAAgB,CAAA;IAC7D;;EACH,EAAK,UAAU,kBAAC,KAAD;GAAG,WAAU;aAAwC,EAAK;GAAW,CAAA;EACrF,kBAAC,KAAD;GAAG,WAAU;aACV,EAAW,EAAK,WAAW,QAAQ;GAClC,CAAA;EACA,EAAA,CAAA,CACF"}
1
+ {"version":3,"file":"SubscriptionDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionDetailPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscription Detail Page\n * Displays detailed information about a single subscription\n */\n\nimport { type FC, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { useSubscription, useSubscriptionMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useGetDefaultBillingAccountDashboardQuery } from '../../../../generated/global-operations';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n formatPercentage,\n} from '../../../shared/utils/format';\nimport {\n getSubscriptionStatusColor,\n getStatusBadgeClasses,\n getQuotaProgressColor,\n} from '../../../shared/utils/status';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n SubscriptionStatus,\n QuotaAssignment,\n AddonSubscription,\n SubscriptionHistory,\n Transaction,\n} from '../../../shared/types';\nimport { PricingModel } from '../../../shared/types';\nimport { ManageSeatsModal } from '../components/ManageSeatsModal';\nimport {\n WorkspaceQuotaAssignmentModal,\n type WorkspaceQuotaToAssign,\n} from '../../checkout/components/WorkspaceQuotaAssignmentModal';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport const SubscriptionDetailPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { subscriptionId } = useParams<{ subscriptionId: string }>();\n const navigateTo = useBillingNavigate();\n const { emit } = useBillingEventEmitter();\n const { orgId } = useBilling();\n const permissions = useBillingPermissions();\n const { selectedAccountId } = useBillingAccountSelection({\n orgId,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n const { data: defaultBillingAccountData } = useGetDefaultBillingAccountDashboardQuery({\n fetchPolicy: 'cache-first',\n });\n const billingAccountId =\n selectedAccountId ?? defaultBillingAccountData?.getDefaultBillingAccount?.id ?? undefined;\n const { subscription, isLoading, error, refetch } = useSubscription(\n subscriptionId,\n billingAccountId\n );\n const { cancelSubscription, isCanceling, updateSubscriptionSeats, isUpdatingSeats } =\n useSubscriptionMutations();\n\n const { workspaceId } = useBilling();\n\n // Modal state\n const [showCancelModal, setShowCancelModal] = useState(false);\n const [showManageSeatsModal, setShowManageSeatsModal] = useState(false);\n const [showAssignWorkspaceModal, setShowAssignWorkspaceModal] = useState(false);\n const [cancelReason, setCancelReason] = useState('');\n const [cancelError, setCancelError] = useState<string | null>(null);\n\n // Handle cancel subscription\n const handleCancelSubscription = async () => {\n if (!subscription) return;\n\n setCancelError(null);\n try {\n await cancelSubscription({\n billingAccountId: billingAccountId ?? subscription.billingAccountId,\n subscriptionId: subscription.id,\n reason: cancelReason || 'User requested cancellation',\n });\n emit('billing.subscription.cancelled', {\n route: '/billing/subscriptions',\n entityId: subscription.id,\n source: 'subscription-detail',\n reason: cancelReason || 'User requested cancellation',\n });\n setShowCancelModal(false);\n setCancelReason('');\n await refetch();\n } catch (err) {\n setCancelError(err instanceof Error ? err.message : 'Failed to cancel subscription');\n }\n };\n\n // Permission check\n if (!permissions.canViewSubscriptions) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-muted flex items-center justify-center\">\n <svg\n className=\"size-6 text-muted-foreground\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">Access Denied</h2>\n <p className=\"text-sm text-muted-foreground max-w-sm\">\n You don&apos;t have permission to view this subscription.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <div className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error || !subscription) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.subscriptions.notFound', 'Subscription not found')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {error?.message || 'The subscription you are looking for does not exist.'}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Back to Subscriptions\n </button>\n </div>\n </div>\n );\n }\n\n const plan = subscription.plan;\n const quotas = subscription.quotas || [];\n const addons = subscription.addonSubscriptions || [];\n const transactions = subscription.transactions || [];\n const history = subscription.history || [];\n const statusColor = getSubscriptionStatusColor(subscription.status);\n const isActive = subscription.status === ('active' as SubscriptionStatus);\n\n // Calculate days remaining\n const endDate = new Date(subscription.endDate);\n const startDate = new Date(subscription.startDate);\n const now = new Date();\n const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n const progressPercentage = Math.round(((totalDays - daysRemaining) / totalDays) * 100);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"p-2 -ml-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors\"\n >\n <svg className=\"size-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n <div>\n <div className=\"flex items-center gap-3\">\n <h1 className=\"text-2xl font-bold text-foreground\">{plan?.name || 'Subscription'}</h1>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatSubscriptionStatus(subscription.status)}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {plan?.pricingModel === PricingModel.PER_SEAT && plan.pricePerSeat\n ? `${formatCurrency(plan.pricePerSeat, plan.currency)} / seat / ${formatPlanDuration(plan.duration)} · ${subscription.seatCount ?? plan.minSeats ?? 1} seats = ${formatCurrency(plan.pricePerSeat * (subscription.seatCount ?? plan.minSeats ?? 1), plan.currency)} / ${formatPlanDuration(plan.duration)}`\n : plan\n ? `${formatCurrency(plan.price, plan.currency)} / ${formatPlanDuration(plan.duration)}`\n : 'Unknown plan'}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {isActive &&\n permissions.canUpdateSubscription &&\n plan?.pricingModel === PricingModel.PER_SEAT && (\n <button\n type=\"button\"\n onClick={() => setShowManageSeatsModal(true)}\n className=\"px-4 py-2 text-sm font-medium border border-border rounded-md hover:bg-accent text-foreground transition-colors\"\n >\n Manage Seats\n </button>\n )}\n {isActive && permissions.canUpdateSubscription && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}/upgrade`,\n billingAccountId ?? subscription.billingAccountId\n )\n )\n }\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Change Plan\n </button>\n )}\n {isActive && permissions.canCancelSubscription && !subscription.canceledAt && (\n <button\n type=\"button\"\n onClick={() => setShowCancelModal(true)}\n disabled={isCanceling}\n className=\"px-4 py-2 text-sm font-medium border border-border text-destructive rounded-md hover:bg-destructive/10 transition-colors disabled:opacity-50\"\n >\n {isCanceling ? 'Canceling...' : 'Cancel Subscription'}\n </button>\n )}\n {subscription.canceledAt && (\n <span className=\"px-4 py-2 text-sm font-medium text-status-warning-text bg-status-warning-bg-subtle rounded-md\">\n Cancellation Scheduled\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Period Progress */}\n {isActive && (\n <div className=\"border border-border rounded-lg bg-card p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-foreground\">Current Period</h2>\n <span className=\"text-sm text-muted-foreground\">\n {daysRemaining} days remaining\n </span>\n </div>\n <div className=\"space-y-2\">\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full bg-primary transition-all duration-300\"\n style={{ width: `${progressPercentage}%` }}\n />\n </div>\n <div className=\"flex items-center justify-between text-sm text-muted-foreground\">\n <span>{formatDate(subscription.startDate, 'short')}</span>\n <span>{formatDate(subscription.endDate, 'short')}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* Quotas */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n Quota Usage ({quotas.length})\n </h2>\n </div>\n <div className=\"p-6\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No quotas assigned to this subscription.\n </p>\n ) : (\n <div className=\"space-y-4\">\n {quotas.map((quota) => (\n <QuotaUsageItem key={quota.id} quota={quota} />\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Workspace Quota Assignment */}\n {isActive &&\n workspaceId &&\n (() => {\n const assigned = subscription.assignedWorkspaceId ?? null;\n // Empty placeholder quotas — modal is display-only here; actual quota list\n // is determined server-side by activateSubscriptionForWorkspace.\n const wsQuotas: WorkspaceQuotaToAssign[] = [];\n\n return (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n Workspace Quota Assignment\n </h2>\n {!assigned && (\n <button\n type=\"button\"\n onClick={() => setShowAssignWorkspaceModal(true)}\n className=\"px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Assign to Workspace\n </button>\n )}\n </div>\n <div className=\"p-6\">\n {assigned ? (\n <p className=\"text-sm text-muted-foreground\">\n Workspace quotas are active on workspace{' '}\n <span className=\"font-medium text-foreground font-mono text-xs\">\n {assigned}\n </span>\n .\n </p>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n The workspace quotas included in this plan haven&apos;t been activated yet.\n Click{' '}\n <span className=\"font-medium text-foreground\">Assign to Workspace</span> to\n activate them.\n </p>\n )}\n </div>\n\n {showAssignWorkspaceModal && (\n <WorkspaceQuotaAssignmentModal\n billingAccountId={billingAccountId ?? subscription.billingAccountId}\n subscriptionId={subscription.id}\n quotas={wsQuotas}\n workspaces={[{ id: workspaceId, name: 'Current workspace' }]}\n currentWorkspaceId={workspaceId}\n onAssigned={() => {\n setShowAssignWorkspaceModal(false);\n void refetch();\n }}\n onSkip={() => setShowAssignWorkspaceModal(false)}\n />\n )}\n </div>\n );\n })()}\n\n {/* Addons */}\n {addons.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Addons ({addons.length})</h2>\n </div>\n <div className=\"p-6\">\n <div className=\"space-y-3\">\n {addons.map((addonSub) => (\n <AddonItem key={addonSub.id} addonSubscription={addonSub} />\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Recent Transactions */}\n {transactions.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Recent Transactions</h2>\n </div>\n <div className=\"divide-y divide-border\">\n {transactions.slice(0, 5).map((transaction) => (\n <TransactionItem key={transaction.id} transaction={transaction} />\n ))}\n </div>\n {transactions.length > 5 && (\n <div className=\"p-4 text-center border-t border-border\">\n <button type=\"button\" className=\"text-sm text-primary hover:underline\">\n View all transactions\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Subscription Details */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Details</h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div>\n <p className=\"text-sm text-muted-foreground\">Subscription ID</p>\n <p className=\"font-mono text-sm text-foreground mt-0.5 break-all\">\n {subscription.id}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Plan</p>\n <p className=\"text-sm text-foreground mt-0.5\">{plan?.name || 'Unknown'}</p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Billing Account</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {subscription.billingAccount?.name || 'Unknown'}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Start Date</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {formatDate(subscription.startDate)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">End Date</p>\n <p className=\"text-sm text-foreground mt-0.5\">{formatDate(subscription.endDate)}</p>\n </div>\n {subscription.nextBillingDate && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Next Billing</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {formatDate(subscription.nextBillingDate)}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-sm text-muted-foreground\">Auto Renewal</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {subscription.autoRenewal ? 'Enabled' : 'Disabled'}\n </p>\n </div>\n {subscription.paymentGateway && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Payment Gateway</p>\n <p className=\"text-sm text-foreground mt-0.5 capitalize\">\n {subscription.paymentGateway}\n </p>\n </div>\n )}\n </div>\n </div>\n\n {/* History */}\n {history.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">History</h2>\n </div>\n <div className=\"p-6\">\n <div className=\"space-y-3\">\n {history.slice(0, 5).map((item) => (\n <HistoryItem key={item.id} item={item} />\n ))}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Manage Seats Modal */}\n {showManageSeatsModal && subscription && (\n <ManageSeatsModal\n subscription={subscription}\n onClose={() => setShowManageSeatsModal(false)}\n onConfirm={async (seatCount) => {\n await updateSubscriptionSeats({ subscriptionId: subscription.id, seatCount });\n await refetch();\n }}\n isLoading={isUpdatingSeats}\n />\n )}\n\n {/* Cancel Subscription Modal */}\n {showCancelModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div\n className=\"absolute inset-0 bg-overlay-scrim\"\n onClick={() => setShowCancelModal(false)}\n />\n <div\n className=\"relative bg-card border border-border rounded-lg shadow-lg w-full max-w-md mx-4 p-6\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"cancel-subscription-title\"\n >\n <h3\n id=\"cancel-subscription-title\"\n className=\"text-lg font-semibold text-foreground mb-2\"\n >\n Cancel Subscription\n </h3>\n <p className=\"text-sm text-muted-foreground mb-4\">\n Are you sure you want to cancel this subscription? Your subscription will remain\n active until{' '}\n <span className=\"font-medium text-foreground\">\n {formatDate(subscription.endDate)}\n </span>\n , but will not renew automatically.\n </p>\n\n <div className=\"mb-4\">\n <label className=\"block text-sm font-medium text-foreground mb-1\">\n Reason for cancellation (optional)\n </label>\n <textarea\n value={cancelReason}\n onChange={(e) => setCancelReason(e.target.value)}\n placeholder=\"Please share why you're canceling...\"\n className=\"w-full px-3 py-2 text-sm border border-border rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent resize-none\"\n rows={3}\n />\n </div>\n\n {cancelError && (\n <div className=\"mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md\">\n <p className=\"text-sm text-destructive\">{cancelError}</p>\n </div>\n )}\n\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => {\n setShowCancelModal(false);\n setCancelReason('');\n setCancelError(null);\n }}\n className=\"px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n Keep Subscription\n </button>\n <button\n type=\"button\"\n onClick={handleCancelSubscription}\n disabled={isCanceling}\n className=\"px-4 py-2 text-sm font-medium bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors disabled:opacity-50\"\n >\n {isCanceling ? 'Canceling...' : 'Confirm Cancellation'}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Sub-components\n// ============================================================================\n\nconst QuotaUsageItem: FC<{ quota: QuotaAssignment }> = ({ quota }) => {\n const limits = quota.limits as { value?: number; type?: string } | null;\n const limit = limits?.value || 0;\n const used = quota.currentUsageSum || 0;\n const percentage = limit > 0 ? Math.min(100, (used / limit) * 100) : 0;\n\n return (\n <div className=\"p-4 rounded-lg bg-muted/30\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"font-medium text-foreground\">{quota.name}</span>\n <span className=\"text-sm text-muted-foreground\">\n {used} / {limit > 0 ? limit : '∞'}\n </span>\n </div>\n {limit > 0 && (\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full transition-all duration-300 ${getQuotaProgressColor(percentage)}`}\n style={{ width: `${percentage}%` }}\n />\n </div>\n )}\n <div className=\"flex items-center justify-between mt-2 text-xs text-muted-foreground\">\n <span>{quota.quotaType}</span>\n <span>{formatPercentage(percentage)} used</span>\n </div>\n </div>\n );\n};\n\nconst AddonItem: FC<{ addonSubscription: AddonSubscription }> = ({ addonSubscription }) => {\n const addon = addonSubscription.addon;\n return (\n <div className=\"flex items-center justify-between p-3 rounded-lg bg-muted/30\">\n <div>\n <p className=\"font-medium text-foreground\">{addon?.name || 'Unknown Addon'}</p>\n <p className=\"text-sm text-muted-foreground\">Qty: {addonSubscription.quantity}</p>\n </div>\n {addon && (\n <p className=\"text-sm font-medium text-foreground\">\n {formatCurrency(addon.price * addonSubscription.quantity, addon.currency)}\n </p>\n )}\n </div>\n );\n};\n\nconst TransactionItem: FC<{ transaction: Transaction }> = ({ transaction }) => {\n return (\n <div className=\"flex items-center justify-between px-6 py-4\">\n <div>\n <p className=\"font-medium text-foreground capitalize\">{transaction.type}</p>\n <p className=\"text-sm text-muted-foreground\">\n {formatDate(transaction.createdAt, 'short')}\n </p>\n </div>\n <div className=\"text-right\">\n <p\n className={`font-medium ${transaction.type === 'refund' ? 'text-status-error-text' : 'text-foreground'}`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(transaction.amount, transaction.currency)}\n </p>\n <p className=\"text-sm text-muted-foreground capitalize\">{transaction.status}</p>\n </div>\n </div>\n );\n};\n\nconst HistoryItem: FC<{ item: SubscriptionHistory }> = ({ item }) => {\n return (\n <div className=\"flex items-start gap-3\">\n <div className=\"size-2 rounded-full bg-muted-foreground mt-2 shrink-0\" />\n <div>\n <p className=\"text-sm text-foreground\">\n <span className=\"capitalize\">{item.fromStatus}</span>\n {' → '}\n <span className=\"capitalize font-medium\">{item.toStatus}</span>\n </p>\n {item.reason && <p className=\"text-xs text-muted-foreground mt-0.5\">{item.reason}</p>}\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {formatDate(item.createdAt, 'short')}\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -37,31 +37,13 @@ var g = f`
37
37
  usagePercentage
38
38
  }
39
39
  }
40
- `, _ = f`
41
- mutation AssignWorkspaceQuota($input: AssignWorkspaceQuotaInput!) {
42
- assignWorkspaceQuota(input: $input) {
43
- id
44
- workspaceId
45
- quotaName
46
- productId
47
- quotaMode
48
- billingAccountId
49
- subscriptionId
50
- limit
51
- used
52
- noLimit
53
- resetPeriod
54
- isActive
55
- createdAt
56
- }
57
- }
58
40
  `;
59
41
  f`
60
42
  mutation UnassignWorkspaceQuota($id: ID!, $workspaceId: ID!) {
61
43
  unassignWorkspaceQuota(id: $id, workspaceId: $workspaceId)
62
44
  }
63
45
  `;
64
- function v(e) {
46
+ function _(e) {
65
47
  let { data: t, loading: n, error: r, refetch: i } = h(g, {
66
48
  client: p("workspace"),
67
49
  variables: { workspaceId: e },
@@ -75,10 +57,31 @@ function v(e) {
75
57
  refetch: i
76
58
  };
77
59
  }
60
+ var v = f`
61
+ mutation ActivateSubscriptionForWorkspace(
62
+ $subscriptionId: ID!
63
+ $billingAccountId: ID!
64
+ $workspaceId: ID!
65
+ ) {
66
+ activateSubscriptionForWorkspace(
67
+ subscriptionId: $subscriptionId
68
+ billingAccountId: $billingAccountId
69
+ workspaceId: $workspaceId
70
+ ) {
71
+ workspaceId
72
+ assignedQuotas
73
+ skippedQuotas
74
+ }
75
+ }
76
+ `;
78
77
  function y() {
79
- let [e, { loading: t, error: n }] = m(_, { client: p("workspace") });
78
+ let [e, { loading: t, error: n }] = m(v, { client: p("global") });
80
79
  return {
81
- assign: async (t) => (await e({ variables: { input: t } })).data?.assignWorkspaceQuota ?? null,
80
+ activate: async (t, n, r) => (await e({ variables: {
81
+ subscriptionId: t,
82
+ billingAccountId: n,
83
+ workspaceId: r
84
+ } })).data?.activateSubscriptionForWorkspace ?? null,
82
85
  isLoading: t,
83
86
  error: n ?? null
84
87
  };
@@ -446,6 +449,6 @@ function I({ billingAccountId: t, workspaceId: n, period: r = e.LAST_30_DAYS, se
446
449
  };
447
450
  }
448
451
  //#endregion
449
- export { y as useAssignWorkspaceQuota, A as useBillingAccountQuotasSummary, k as useBillingAccountsForUsage, I as useEnhancedUsagePage, F as useQuotaUsageDetails, M as useSubscriptionsWithQuotas, w as useUsageDashboard, C as useWorkspaceQuotaHistory, v as useWorkspaceQuotaOverview };
452
+ export { y as useActivateSubscriptionForWorkspace, A as useBillingAccountQuotasSummary, k as useBillingAccountsForUsage, I as useEnhancedUsagePage, F as useQuotaUsageDetails, M as useSubscriptionsWithQuotas, w as useUsageDashboard, C as useWorkspaceQuotaHistory, _ as useWorkspaceQuotaOverview };
450
453
 
451
454
  //# sourceMappingURL=useUsage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUsage.js","names":[],"sources":["../../../../../src/billing/modules/usage/hooks/useUsage.ts"],"sourcesContent":["/**\n * Usage Module - Hooks\n * Custom hooks for usage data fetching and mutations\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react';\nimport {\n useGetUsageDashboardQuery,\n useGetBillingAccountQuotasSummaryForUsageQuery,\n useGetSubscriptionsWithQuotasQuery,\n useGetPastSubscriptionsWithQuotasQuery,\n useGetBillingAccountsForUsageQuery,\n useGetQuotaAnalyticsForUsageQuery,\n type GetPastSubscriptionsWithQuotasQuery,\n} from '../../../../generated/global-operations';\nimport {\n useGetUsageRecordsQuery,\n type GetUsageRecordsQuery,\n type GetUsageRecordsQueryVariables,\n} from '../../../../generated/wspace-operations';\nimport { useGatewayClient, useQuery, useMutation, gql } from '@burdenoff/fe-libs/shared/graphql';\nimport { AnalyticsPeriod, QuotaStatus } from '../../../shared/types';\nimport type { UsageDashboard } from '../../../shared/types';\n\n// ============================================================================\n// Workspace Quota Assignment — managed by activity-svc\n// ============================================================================\n\nconst WORKSPACE_QUOTA_OVERVIEW_QUERY = gql`\n query WorkspaceQuotaOverview($workspaceId: ID!) {\n workspaceQuotaOverview(workspaceId: $workspaceId) {\n assignment {\n id\n workspaceId\n quotaName\n productId\n quotaMode\n billingAccountId\n subscriptionId\n limit\n used\n noLimit\n resetPeriod\n lastResetAt\n isActive\n createdAt\n }\n liveStatus {\n workspaceId\n quotaName\n productId\n currentValue\n limit\n noLimit\n scrapedAt\n }\n effectiveUsed\n remaining\n usagePercentage\n }\n }\n`;\n\nconst ASSIGN_WORKSPACE_QUOTA_MUTATION = gql`\n mutation AssignWorkspaceQuota($input: AssignWorkspaceQuotaInput!) {\n assignWorkspaceQuota(input: $input) {\n id\n workspaceId\n quotaName\n productId\n quotaMode\n billingAccountId\n subscriptionId\n limit\n used\n noLimit\n resetPeriod\n isActive\n createdAt\n }\n }\n`;\n\nconst UNASSIGN_WORKSPACE_QUOTA_MUTATION = gql`\n mutation UnassignWorkspaceQuota($id: ID!, $workspaceId: ID!) {\n unassignWorkspaceQuota(id: $id, workspaceId: $workspaceId)\n }\n`;\n\nexport interface WorkspaceQuotaAssignmentItem {\n id: string;\n workspaceId: string;\n quotaName: string;\n productId: string;\n quotaMode: 'dedicated' | 'pooled';\n billingAccountId: string | null;\n subscriptionId: string | null;\n limit: number | null;\n used: number;\n noLimit: boolean;\n resetPeriod: string;\n lastResetAt: string | null;\n isActive: boolean;\n createdAt: string;\n}\n\nexport interface WorkspaceQuotaOverviewItem {\n assignment: WorkspaceQuotaAssignmentItem;\n liveStatus: WorkspaceQuotaStatusItem | null;\n effectiveUsed: number;\n remaining: number | null;\n usagePercentage: number;\n}\n\nexport interface AssignWorkspaceQuotaInput {\n workspaceId: string;\n quotaName: string;\n productId: string;\n quotaMode?: 'dedicated' | 'pooled';\n billingAccountId?: string;\n subscriptionId?: string;\n limit?: number;\n noLimit?: boolean;\n resetPeriod?: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface UseWorkspaceQuotaOverviewResult {\n overview: WorkspaceQuotaOverviewItem[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useWorkspaceQuotaOverview(workspaceId?: string): UseWorkspaceQuotaOverviewResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error, refetch } = useQuery<{\n workspaceQuotaOverview: WorkspaceQuotaOverviewItem[];\n }>(WORKSPACE_QUOTA_OVERVIEW_QUERY, {\n client: wsClient,\n variables: { workspaceId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n overview: data?.workspaceQuotaOverview ?? [],\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\ninterface UseAssignWorkspaceQuotaResult {\n assign: (input: AssignWorkspaceQuotaInput) => Promise<WorkspaceQuotaAssignmentItem | null>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useAssignWorkspaceQuota(): UseAssignWorkspaceQuotaResult {\n const wsClient = useGatewayClient('workspace');\n const [mutate, { loading, error }] = useMutation<\n { assignWorkspaceQuota: WorkspaceQuotaAssignmentItem },\n { input: AssignWorkspaceQuotaInput }\n >(ASSIGN_WORKSPACE_QUOTA_MUTATION, { client: wsClient });\n\n const assign = async (input: AssignWorkspaceQuotaInput) => {\n const result = await mutate({ variables: { input } });\n return result.data?.assignWorkspaceQuota ?? null;\n };\n\n return { assign, isLoading: loading, error: error ?? null };\n}\n\ninterface UseUnassignWorkspaceQuotaResult {\n unassign: (id: string, workspaceId: string) => Promise<boolean>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useUnassignWorkspaceQuota(): UseUnassignWorkspaceQuotaResult {\n const wsClient = useGatewayClient('workspace');\n const [mutate, { loading, error }] = useMutation<\n { unassignWorkspaceQuota: boolean },\n { id: string; workspaceId: string }\n >(UNASSIGN_WORKSPACE_QUOTA_MUTATION, { client: wsClient });\n\n const unassign = async (id: string, workspaceId: string) => {\n const result = await mutate({ variables: { id, workspaceId } });\n return result.data?.unassignWorkspaceQuota ?? false;\n };\n\n return { unassign, isLoading: loading, error: error ?? null };\n}\n\n// ============================================================================\n// Workspace Quota Status — live values from activity-svc\n// ============================================================================\n\nconst WORKSPACE_QUOTA_STATUSES_QUERY = gql`\n query WorkspaceQuotaStatuses($workspaceId: ID!) {\n workspaceQuotaStatuses(workspaceId: $workspaceId) {\n workspaceId\n quotaName\n productId\n currentValue\n limit\n noLimit\n scrapedAt\n }\n }\n`;\n\nconst WORKSPACE_QUOTA_HISTORY_QUERY = gql`\n query WorkspaceQuotaHistory($workspaceId: ID!, $quotaName: String!, $limit: Int) {\n workspaceQuotaHistory(workspaceId: $workspaceId, quotaName: $quotaName, limit: $limit) {\n id\n workspaceId\n quotaName\n productId\n value\n limit\n noLimit\n snapshotAt\n }\n }\n`;\n\nexport interface WorkspaceQuotaStatusItem {\n workspaceId: string;\n quotaName: string;\n productId: string;\n currentValue: number;\n limit: number | null;\n noLimit: boolean;\n scrapedAt: string;\n}\n\nexport interface WorkspaceQuotaSnapshotItem {\n id: string;\n workspaceId: string;\n quotaName: string;\n productId: string;\n value: number;\n limit: number | null;\n noLimit: boolean;\n snapshotAt: string;\n}\n\ninterface UseWorkspaceQuotaStatusesResult {\n statuses: WorkspaceQuotaStatusItem[];\n statusMap: Map<string, WorkspaceQuotaStatusItem>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useWorkspaceQuotaStatuses(workspaceId?: string): UseWorkspaceQuotaStatusesResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error } = useQuery<{ workspaceQuotaStatuses: WorkspaceQuotaStatusItem[] }>(\n WORKSPACE_QUOTA_STATUSES_QUERY,\n {\n client: wsClient,\n variables: { workspaceId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n }\n );\n\n const { statuses, statusMap } = useMemo(() => {\n const list = data?.workspaceQuotaStatuses ?? [];\n return { statuses: list, statusMap: new Map(list.map((s) => [s.quotaName, s])) };\n }, [data]);\n\n return { statuses, statusMap, isLoading: loading, error: error ?? null };\n}\n\ninterface UseWorkspaceQuotaHistoryResult {\n snapshots: WorkspaceQuotaSnapshotItem[];\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useWorkspaceQuotaHistory(\n workspaceId?: string,\n quotaName?: string,\n limit: number = 48\n): UseWorkspaceQuotaHistoryResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error } = useQuery<{\n workspaceQuotaHistory: WorkspaceQuotaSnapshotItem[];\n }>(WORKSPACE_QUOTA_HISTORY_QUERY, {\n client: wsClient,\n variables: { workspaceId, quotaName, limit },\n skip: !workspaceId || !quotaName,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n snapshots: data?.workspaceQuotaHistory ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n// ============================================================================\n// Dashboard Hook\n// ============================================================================\n\ninterface UseUsageDashboardParams {\n billingAccountId?: string;\n period?: AnalyticsPeriod;\n}\n\ninterface UseUsageDashboardResult {\n dashboard: UsageDashboard | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useUsageDashboard({\n billingAccountId,\n period = AnalyticsPeriod.LAST_30_DAYS,\n}: UseUsageDashboardParams): UseUsageDashboardResult {\n const { data, loading, error, refetch } = useGetUsageDashboardQuery({\n variables: { billingAccountId: billingAccountId!, period },\n skip: !billingAccountId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n dashboard: data?.getUsageDashboard ?? null,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n// ============================================================================\n// Enhanced Usage Page Hooks\n// ============================================================================\n\n/**\n * Quota assignment from the API\n */\ninterface QuotaAssignmentSummary {\n id: string;\n name: string;\n unit: string;\n quotaType: 'POOLED' | 'PRODUCT_SPECIFIC';\n reusable: boolean;\n limit: number;\n limitLeft: number;\n used: number;\n usagePercentage: number;\n noLimit: boolean;\n subscriptionId?: string;\n context?: Record<string, unknown> | null;\n recentUsages: Array<{\n id: string;\n amount: number;\n description: string;\n timestamp: string;\n tags?: Record<string, unknown>;\n }>;\n createdAt: string;\n endtime: string;\n}\n\n/**\n * Aggregated quota for display (multiple quota assignments with same name combined)\n */\nexport interface AggregatedQuota {\n name: string;\n displayName?: string;\n description?: string;\n unit: string;\n totalLimit: number;\n totalUsed: number;\n usagePercentage: number;\n status: QuotaStatus;\n quotaCount: number;\n noLimit: boolean;\n reusable: boolean;\n /** True when usage comes from the activity-svc scraper, not billing recordUsage events. */\n isCollectorBased: boolean;\n quotaAssignments: QuotaAssignmentSummary[];\n}\n\n/**\n * Subscription with aggregated quotas\n */\nexport interface SubscriptionWithAggregatedQuotas {\n id: string;\n status: string;\n planName?: string;\n productId?: string;\n createdAt: string;\n aggregatedQuotas: AggregatedQuota[];\n}\n\n/**\n * Helper function to calculate quota status based on usage percentage\n */\nfunction calculateQuotaStatus(\n usagePercentage: number,\n limitLeft: number,\n noLimit = false\n): QuotaStatus {\n if (noLimit) return QuotaStatus.HEALTHY;\n if (limitLeft <= 0) return QuotaStatus.EXHAUSTED;\n if (usagePercentage >= 90) return QuotaStatus.CRITICAL;\n if (usagePercentage >= 75) return QuotaStatus.WARNING;\n return QuotaStatus.HEALTHY;\n}\n\n// Maps billing quota names to their corresponding scraper metric names in workspace_quota_statuses.\n// Scraper keys use dot-notation product prefix; billing keys use kebab-case.\nconst BILLING_TO_SCRAPER_METRIC: Record<string, string> = {\n 'agents-registered': 'vibecontrols.agents.count',\n 'vibes-created': 'vibecontrols.vibes.count',\n 'catalogs-created': 'vibecontrols.catalogs.count',\n 'tunnels-created': 'vibecontrols.tunnels.count',\n 'sessions-started': 'vibecontrols.sessions.starts',\n};\n\n/**\n * Merges live collector values from activity-svc into a list of aggregated quotas.\n * Overrides `totalUsed` (and derived fields) when a live value exists, and distributes\n * the live total across individual assignments (earliest-expiry first) so the\n * Quota Breakdown shows real per-assignment consumption instead of always-0.\n */\nfunction mergeLiveValues(\n aggregated: AggregatedQuota[],\n statusMap: Map<string, WorkspaceQuotaStatusItem>\n): AggregatedQuota[] {\n if (statusMap.size === 0) return aggregated;\n return aggregated.map((q) => {\n const scraperKey = BILLING_TO_SCRAPER_METRIC[q.name] ?? q.name;\n const live = statusMap.get(scraperKey);\n if (!live) return q;\n\n const totalUsed = live.currentValue;\n const { totalLimit, noLimit } = q;\n const usagePercentage = noLimit || totalLimit === 0 ? 0 : (totalUsed / totalLimit) * 100;\n const limitLeft = noLimit ? 0 : Math.max(0, totalLimit - totalUsed);\n\n // Distribute live total into assignments: fill earliest-expiry first, overflow to next.\n let remaining = totalUsed;\n const updatedAssignments = [...q.quotaAssignments]\n .sort((a, b) => {\n const dateA = a.endtime ? new Date(a.endtime).getTime() : Infinity;\n const dateB = b.endtime ? new Date(b.endtime).getTime() : Infinity;\n return dateA - dateB;\n })\n .map((a) => {\n const assignmentUsed = Math.min(remaining, a.limit || 0);\n remaining = Math.max(0, remaining - assignmentUsed);\n return { ...a, used: assignmentUsed };\n });\n\n return {\n ...q,\n totalUsed,\n usagePercentage,\n isCollectorBased: true,\n status: calculateQuotaStatus(usagePercentage, limitLeft, noLimit),\n quotaAssignments: updatedAssignments,\n };\n });\n}\n\n/**\n * Helper function to aggregate quotas by name\n */\nfunction aggregateQuotasByName(quotas: QuotaAssignmentSummary[]): AggregatedQuota[] {\n const grouped = new Map<string, QuotaAssignmentSummary[]>();\n\n for (const quota of quotas) {\n const existing = grouped.get(quota.name) || [];\n existing.push(quota);\n grouped.set(quota.name, existing);\n }\n\n const aggregated: AggregatedQuota[] = [];\n\n for (const [name, assignments] of grouped) {\n // Sum up limits and usage first\n const totalLimit = assignments.reduce((sum, q) => sum + (q.limit || 0), 0);\n const totalUsed = assignments.reduce((sum, q) => sum + (q.used || 0), 0);\n const totalLimitLeft = assignments.reduce((sum, q) => sum + (q.limitLeft || 0), 0);\n\n // Check if there's actually no limit (ignore noLimit flag if there's a numeric limit)\n // Only treat as unlimited if totalLimit is 0 or all assignments explicitly have noLimit AND no numeric limit\n const hasNoLimit =\n totalLimit === 0 && assignments.every((q) => q.noLimit || (q.limit || 0) === 0);\n\n // Calculate percentage\n const usagePercentage = hasNoLimit || totalLimit === 0 ? 0 : (totalUsed / totalLimit) * 100;\n\n // Get status\n const status = calculateQuotaStatus(usagePercentage, totalLimitLeft, hasNoLimit);\n\n // Get reusable, unit, and display info from first assignment\n const reusable = assignments[0]?.reusable || false;\n const unit = assignments[0]?.unit || '';\n const ctx = assignments[0]?.context;\n const displayName = typeof ctx?.displayName === 'string' ? ctx.displayName : undefined;\n const description = typeof ctx?.description === 'string' ? ctx.description : undefined;\n\n aggregated.push({\n name,\n displayName,\n description,\n unit,\n totalLimit: hasNoLimit ? 0 : totalLimit,\n totalUsed,\n usagePercentage,\n status,\n quotaCount: assignments.length,\n noLimit: hasNoLimit,\n isCollectorBased: false,\n reusable,\n quotaAssignments: assignments,\n });\n }\n\n // Sort by status priority (critical first) then by usage percentage\n const statusPriority: Record<QuotaStatus, number> = {\n EXHAUSTED: 0,\n CRITICAL: 1,\n WARNING: 2,\n HEALTHY: 3,\n INACTIVE: 4,\n };\n\n return aggregated.sort((a, b) => {\n const priorityDiff = statusPriority[a.status] - statusPriority[b.status];\n if (priorityDiff !== 0) return priorityDiff;\n return b.usagePercentage - a.usagePercentage;\n });\n}\n\n/**\n * Hook to get billing accounts for the selector\n */\ninterface UseBillingAccountsForUsageResult {\n billingAccounts: Array<{\n id: string;\n name: string;\n email: string;\n currency: string;\n creditAmount: number;\n isActive: boolean;\n isDefault: boolean;\n }>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useBillingAccountsForUsage(): UseBillingAccountsForUsageResult {\n const { data, loading, error } = useGetBillingAccountsForUsageQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n billingAccounts: data?.getBillingAccountsByOrg ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to get billing account quotas summary with aggregation\n * Only returns pooled quotas - subscription quotas come from useSubscriptionsWithQuotas\n */\ninterface UseBillingAccountQuotasSummaryResult {\n pooledQuotas: AggregatedQuota[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useBillingAccountQuotasSummary(\n billingAccountId: string | undefined,\n productId?: string\n): UseBillingAccountQuotasSummaryResult {\n const { data, loading, error, refetch } = useGetBillingAccountQuotasSummaryForUsageQuery({\n variables: { billingAccountId: billingAccountId!, productId },\n skip: !billingAccountId,\n fetchPolicy: 'cache-and-network',\n });\n\n const pooledQuotas = useMemo(() => {\n if (!data?.getBillingAccountQuotasSummary) {\n return [];\n }\n\n const summary = data.getBillingAccountQuotasSummary;\n return aggregateQuotasByName(summary.pooledQuotas || []);\n }, [data]);\n\n return {\n pooledQuotas,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Subscription from the API\n */\ninterface SubscriptionQuota {\n id: string;\n name: string;\n limits: { type: string; value: number; unit?: string; originalValue?: number };\n context?: Record<string, unknown> | null;\n currentUsageSum: number;\n usageCount: number;\n createdAt: string;\n endtime: string;\n}\n\ninterface SubscriptionFromAPI {\n id: string;\n status: string;\n createdAt: string;\n quotas: SubscriptionQuota[];\n}\n\n/**\n * Hook to get subscriptions with their quotas grouped by status\n * Uses lazy loading for past subscriptions and supports backend search\n */\ntype ViewMode = 'active' | 'past' | 'all';\n\nconst PAST_SUBSCRIPTIONS_PAGE_SIZE = 10;\n\ninterface UseSubscriptionsWithQuotasOptions {\n searchQuery?: string;\n viewMode?: ViewMode;\n}\n\ninterface UseSubscriptionsWithQuotasResult {\n activeSubscriptions: SubscriptionWithAggregatedQuotas[];\n pastSubscriptions: SubscriptionWithAggregatedQuotas[];\n isLoading: boolean;\n isPastSubscriptionsLoading: boolean;\n error: Error | null;\n refetch: () => void;\n // Pagination for past subscriptions\n loadMorePastSubscriptions: () => void;\n hasMorePastSubscriptions: boolean;\n isLoadingMorePast: boolean;\n pastSubscriptionsCount: number;\n}\n\nexport function useSubscriptionsWithQuotas(\n billingAccountId: string | undefined,\n options: UseSubscriptionsWithQuotasOptions = {}\n): UseSubscriptionsWithQuotasResult {\n const { searchQuery, viewMode = 'active' } = options;\n\n // Determine which subscriptions to fetch based on viewMode\n const shouldFetchActive = viewMode === 'active' || viewMode === 'all';\n const shouldFetchPast = viewMode === 'past' || viewMode === 'all';\n\n // Query for active subscriptions\n const { data, loading, error, refetch } = useGetSubscriptionsWithQuotasQuery({\n variables: {\n billingAccountId: billingAccountId!,\n searchQuery: searchQuery || null,\n },\n skip: !billingAccountId || !shouldFetchActive,\n fetchPolicy: 'cache-and-network',\n });\n\n // Query for past subscriptions with pagination - only when viewMode is 'past' or 'all'\n const {\n data: pastData,\n loading: pastLoading,\n refetch: refetchPast,\n fetchMore: fetchMorePast,\n } = useGetPastSubscriptionsWithQuotasQuery({\n variables: {\n billingAccountId: billingAccountId!,\n searchQuery: searchQuery || null,\n page: 1,\n pageSize: PAST_SUBSCRIPTIONS_PAGE_SIZE,\n },\n skip: !billingAccountId || !shouldFetchPast,\n fetchPolicy: 'cache-and-network',\n });\n\n // Track if we're loading more past subscriptions\n const [isLoadingMorePast, setIsLoadingMorePast] = useState(false);\n const [currentPastPage, setCurrentPastPage] = useState(1);\n\n // Helper function to process subscription data\n const processSubscription = useCallback(\n (sub: SubscriptionFromAPI): SubscriptionWithAggregatedQuotas => {\n const quotaSummaries: QuotaAssignmentSummary[] = (sub.quotas || []).map((q) => {\n const limit = q.limits?.value || 0;\n const used = q.currentUsageSum || 0;\n const limitLeft = Math.max(0, limit - used);\n const usagePercentage = limit > 0 ? (used / limit) * 100 : 0;\n const unit = q.limits?.unit || '';\n\n return {\n id: q.id,\n name: q.name,\n unit,\n quotaType: 'PRODUCT_SPECIFIC' as const,\n reusable: false,\n limit,\n limitLeft,\n used,\n usagePercentage,\n noLimit: q.limits?.type !== 'number' && q.limits?.type !== 'numeric',\n subscriptionId: sub.id,\n context: q.context,\n recentUsages: [],\n createdAt: q.createdAt,\n endtime: q.endtime,\n };\n });\n\n return {\n id: sub.id,\n status: sub.status,\n createdAt: sub.createdAt,\n aggregatedQuotas: aggregateQuotasByName(quotaSummaries),\n };\n },\n []\n );\n\n // Process active subscriptions\n const activeSubscriptions = useMemo(() => {\n if (!data?.getSubscriptionUsageOverview) {\n return [];\n }\n\n const subscriptions: SubscriptionFromAPI[] = data.getSubscriptionUsageOverview;\n return subscriptions.map(processSubscription);\n }, [data, processSubscription]);\n\n // Process past subscriptions\n const pastSubscriptions = useMemo(() => {\n if (!pastData?.getSubscriptionUsageOverview) {\n return [];\n }\n\n const subscriptions: SubscriptionFromAPI[] =\n pastData.getSubscriptionUsageOverview.subscriptions || pastData.getSubscriptionUsageOverview;\n return subscriptions.map(processSubscription);\n }, [pastData, processSubscription]);\n\n // Get pagination info for past subscriptions\n const pastSubscriptionsPagination = useMemo(() => {\n const overview = pastData?.getSubscriptionUsageOverview;\n if (!overview) {\n return { totalCount: 0, hasMore: false };\n }\n // If it's the new paginated format\n if (overview.totalCount !== undefined) {\n return {\n totalCount: overview.totalCount,\n hasMore: pastSubscriptions.length < overview.totalCount,\n };\n }\n // Legacy format - assume there might be more if we got a full page\n return {\n totalCount: pastSubscriptions.length,\n hasMore: pastSubscriptions.length >= PAST_SUBSCRIPTIONS_PAGE_SIZE * currentPastPage,\n };\n }, [pastData, pastSubscriptions.length, currentPastPage]);\n\n // Reset page when search query or viewMode changes\n useEffect(() => {\n setCurrentPastPage(1);\n }, [searchQuery, viewMode]);\n\n // Load more past subscriptions\n const loadMorePastSubscriptions = useCallback(async () => {\n if (!fetchMorePast || isLoadingMorePast || !pastSubscriptionsPagination.hasMore) return;\n\n setIsLoadingMorePast(true);\n const nextPage = currentPastPage + 1;\n\n try {\n await fetchMorePast({\n variables: {\n page: nextPage,\n pageSize: PAST_SUBSCRIPTIONS_PAGE_SIZE,\n },\n updateQuery: (\n prev: GetPastSubscriptionsWithQuotasQuery,\n { fetchMoreResult }: { fetchMoreResult: GetPastSubscriptionsWithQuotasQuery }\n ) => {\n if (!fetchMoreResult) return prev;\n\n const prevSubs = prev?.getSubscriptionUsageOverview ?? [];\n const newSubs = fetchMoreResult?.getSubscriptionUsageOverview ?? [];\n\n return {\n getSubscriptionUsageOverview: [...prevSubs, ...newSubs],\n };\n },\n });\n setCurrentPastPage(nextPage);\n } catch (err) {\n console.error('Failed to load more past subscriptions:', err);\n } finally {\n setIsLoadingMorePast(false);\n }\n }, [fetchMorePast, isLoadingMorePast, pastSubscriptionsPagination.hasMore, currentPastPage]);\n\n const combinedRefetch = useCallback(() => {\n setCurrentPastPage(1);\n if (shouldFetchActive) refetch();\n if (shouldFetchPast) refetchPast();\n }, [shouldFetchActive, shouldFetchPast, refetch, refetchPast]);\n\n return {\n activeSubscriptions,\n pastSubscriptions,\n isLoading: shouldFetchActive ? loading : false,\n isPastSubscriptionsLoading: shouldFetchPast ? pastLoading : false,\n error: error ?? null,\n refetch: combinedRefetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions,\n hasMorePastSubscriptions: pastSubscriptionsPagination.hasMore,\n isLoadingMorePast,\n pastSubscriptionsCount: pastSubscriptionsPagination.totalCount,\n };\n}\n\n/**\n * Hook to get quota usage details for drill-down using cursor-based pagination.\n *\n * The activity-svc API is cursor-based (limit / cursor → items / hasNextPage / nextCursor).\n * Callers should maintain a cursor stack:\n * - push nextCursor to go forward\n * - pop to go back\n * - pass null as cursor for the first page\n */\nexport interface QuotaUsageDetail {\n id: string;\n amount: number;\n description: string;\n tags?: Record<string, unknown>;\n productId?: string;\n userId?: string;\n timestamp: string;\n lastUpdated?: string;\n}\n\ninterface UseQuotaUsageDetailsResult {\n usages: QuotaUsageDetail[];\n hasNextPage: boolean;\n nextCursor: string | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\ntype UsageRecord = GetUsageRecordsQuery['getUsageRecords']['items'][number];\n\nexport function buildUsageRecordsVariables(\n quotaAssignmentId: string | undefined,\n pageSize: number,\n cursor: string | null,\n workspaceId?: string\n): GetUsageRecordsQueryVariables {\n return {\n filters: {\n quotaAssignmentId,\n workspaceId,\n },\n pagination: {\n limit: pageSize,\n ...(cursor ? { cursor } : {}),\n },\n };\n}\n\nexport function mapUsageRecordToQuotaUsageDetail(record: UsageRecord): QuotaUsageDetail {\n return {\n id: record.id,\n amount: record.amount,\n description: record.description ?? '',\n tags: (record.metadata as Record<string, unknown>) ?? {},\n productId: record.productId,\n userId: record.userId ?? undefined,\n timestamp: record.createdAt,\n lastUpdated: record.createdAt,\n };\n}\n\nexport function useQuotaUsageDetails(\n quotaAssignmentId: string | undefined,\n pageSize: number = 20,\n cursor: string | null = null,\n workspaceId?: string\n): UseQuotaUsageDetailsResult {\n // Query workspace gateway directly — usage records live in wspace-activity-svc.\n // Uses the dedicated workspace Apollo client so it doesn't conflict with the\n // shell's global Apollo client used by all other billing queries.\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error, refetch } = useGetUsageRecordsQuery({\n client: wsClient,\n variables: buildUsageRecordsVariables(quotaAssignmentId, pageSize, cursor, workspaceId),\n skip: !quotaAssignmentId || !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const items: GetUsageRecordsQuery['getUsageRecords']['items'] =\n data?.getUsageRecords?.items ?? [];\n\n return {\n usages: items.map(mapUsageRecordToQuotaUsageDetail),\n hasNextPage: data?.getUsageRecords?.hasNextPage ?? false,\n nextCursor: data?.getUsageRecords?.nextCursor ?? null,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to get quota analytics\n */\ninterface UseQuotaAnalyticsResult {\n analytics: {\n dailyUsage: Array<{ date: string; usage: number; count: number }>;\n usageByType: Array<{ resourceType: string; usage: number; count: number; percentage: number }>;\n trends?: {\n growthRate?: number;\n predictedUsage?: number;\n averageDailyUsage?: number;\n };\n peakUsage?: {\n maxDailyUsage: number;\n peakDate: string;\n peakHour?: number;\n };\n } | null;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useQuotaAnalytics(\n quotaAssignmentId: string | undefined,\n period: AnalyticsPeriod = AnalyticsPeriod.LAST_30_DAYS\n): UseQuotaAnalyticsResult {\n const { data, loading, error } = useGetQuotaAnalyticsForUsageQuery({\n variables: { quotaAssignmentId: quotaAssignmentId!, period },\n skip: !quotaAssignmentId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n analytics: data?.getQuotaAnalytics ?? null,\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Combined hook for the enhanced usage page\n */\ninterface UseEnhancedUsagePageParams {\n billingAccountId: string | undefined;\n workspaceId?: string;\n period?: AnalyticsPeriod;\n searchQuery?: string;\n viewMode?: ViewMode;\n}\n\ninterface UseEnhancedUsagePageResult {\n // Dashboard overview\n dashboard: UsageDashboard | null;\n\n // Pooled quotas (subscription quotas come from subscriptions directly)\n pooledQuotas: AggregatedQuota[];\n\n // Subscriptions with their quotas\n activeSubscriptions: SubscriptionWithAggregatedQuotas[];\n pastSubscriptions: SubscriptionWithAggregatedQuotas[];\n\n // Loading states\n isLoading: boolean;\n isPastSubscriptionsLoading: boolean;\n error: Error | null;\n\n // Actions\n refetch: () => void;\n\n // Pagination for past subscriptions\n loadMorePastSubscriptions: () => void;\n hasMorePastSubscriptions: boolean;\n isLoadingMorePast: boolean;\n pastSubscriptionsCount: number;\n}\n\nexport function useEnhancedUsagePage({\n billingAccountId,\n workspaceId,\n period = AnalyticsPeriod.LAST_30_DAYS,\n searchQuery,\n viewMode = 'active',\n}: UseEnhancedUsagePageParams): UseEnhancedUsagePageResult {\n // Dashboard for overview stats\n const dashboardResult = useUsageDashboard({\n billingAccountId,\n period,\n });\n\n // Quotas summary with aggregation\n const quotasSummaryResult = useBillingAccountQuotasSummary(billingAccountId);\n\n // Live collector values from activity-svc (source of truth for count/concurrent quotas)\n const { statusMap } = useWorkspaceQuotaStatuses(workspaceId);\n\n // Subscriptions with quotas - passes searchQuery and viewMode to backend\n const subscriptionsResult = useSubscriptionsWithQuotas(billingAccountId, {\n searchQuery,\n viewMode,\n });\n\n // Merge live values into both pooled quotas and subscription quotas\n const pooledQuotas = useMemo(\n () => mergeLiveValues(quotasSummaryResult.pooledQuotas, statusMap),\n [quotasSummaryResult.pooledQuotas, statusMap]\n );\n\n const activeSubscriptions = useMemo(\n () =>\n subscriptionsResult.activeSubscriptions.map((sub) => ({\n ...sub,\n aggregatedQuotas: mergeLiveValues(sub.aggregatedQuotas, statusMap),\n })),\n [subscriptionsResult.activeSubscriptions, statusMap]\n );\n\n const pastSubscriptions = useMemo(\n () =>\n subscriptionsResult.pastSubscriptions.map((sub) => ({\n ...sub,\n aggregatedQuotas: mergeLiveValues(sub.aggregatedQuotas, statusMap),\n })),\n [subscriptionsResult.pastSubscriptions, statusMap]\n );\n\n const refetch = useCallback(() => {\n dashboardResult.refetch();\n quotasSummaryResult.refetch();\n subscriptionsResult.refetch();\n }, [dashboardResult, quotasSummaryResult, subscriptionsResult]);\n\n return {\n dashboard: dashboardResult.dashboard,\n pooledQuotas,\n activeSubscriptions,\n pastSubscriptions,\n isLoading:\n dashboardResult.isLoading || quotasSummaryResult.isLoading || subscriptionsResult.isLoading,\n isPastSubscriptionsLoading: subscriptionsResult.isPastSubscriptionsLoading,\n error: dashboardResult.error || quotasSummaryResult.error || subscriptionsResult.error,\n refetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions: subscriptionsResult.loadMorePastSubscriptions,\n hasMorePastSubscriptions: subscriptionsResult.hasMorePastSubscriptions,\n isLoadingMorePast: subscriptionsResult.isLoadingMorePast,\n pastSubscriptionsCount: subscriptionsResult.pastSubscriptionsCount,\n };\n}\n"],"mappings":";;;;;;AA4BA,IAAM,IAAiC,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCpC,IAAkC,CAAG;;;;;;;;;;;;;;;;;;;AAoBD,CAAG;;;;;AAmD7C,SAAgB,EAA0B,GAAuD;CAE/F,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAEvC,GAAgC;EACjC,QAJe,EAAiB,YAAY;EAK5C,WAAW,EAAE,gBAAa;EAC1B,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAEF,QAAO;EACL,UAAU,GAAM,0BAA0B,EAAE;EAC5C,WAAW;EACX,OAAO,KAAS;EAChB;EACD;;AASH,SAAgB,IAAyD;CAEvE,IAAM,CAAC,GAAQ,EAAE,YAAS,cAAW,EAGnC,GAAiC,EAAE,QAJpB,EAAiB,YAAY,EAIS,CAAC;AAOxD,QAAO;EAAE,QALM,OAAO,OACL,MAAM,EAAO,EAAE,WAAW,EAAE,UAAO,EAAE,CAAC,EACvC,MAAM,wBAAwB;EAG7B,WAAW;EAAS,OAAO,KAAS;EAAM;;AA4B7D,IAAM,IAAiC,CAAG;;;;;;;;;;;;GAcpC,IAAgC,CAAG;;;;;;;;;;;;;;AA2CzC,SAAgB,EAA0B,GAAuD;CAE/F,IAAM,EAAE,SAAM,YAAS,aAAU,EAC/B,GACA;EACE,QAJa,EAAiB,YAAY;EAK1C,WAAW,EAAE,gBAAa;EAC1B,MAAM,CAAC;EACP,aAAa;EACd,CACF,EAEK,EAAE,aAAU,iBAAc,QAAc;EAC5C,IAAM,IAAO,GAAM,0BAA0B,EAAE;AAC/C,SAAO;GAAE,UAAU;GAAM,WAAW,IAAI,IAAI,EAAK,KAAK,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;GAAE;IAC/E,CAAC,EAAK,CAAC;AAEV,QAAO;EAAE;EAAU;EAAW,WAAW;EAAS,OAAO,KAAS;EAAM;;AAS1E,SAAgB,EACd,GACA,GACA,IAAgB,IACgB;CAEhC,IAAM,EAAE,SAAM,YAAS,aAAU,EAE9B,GAA+B;EAChC,QAJe,EAAiB,YAAY;EAK5C,WAAW;GAAE;GAAa;GAAW;GAAO;EAC5C,MAAM,CAAC,KAAe,CAAC;EACvB,aAAa;EACd,CAAC;AAEF,QAAO;EACL,WAAW,GAAM,yBAAyB,EAAE;EAC5C,WAAW;EACX,OAAO,KAAS;EACjB;;AAmBH,SAAgB,EAAkB,EAChC,qBACA,YAAS,EAAgB,gBAC0B;CACnD,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAA0B;EAClE,WAAW;GAAoB;GAAmB;GAAQ;EAC1D,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAEF,QAAO;EACL,WAAW,GAAM,qBAAqB;EACtC,WAAW;EACX,OAAO,KAAS;EAChB;EACD;;AAqEH,SAAS,EACP,GACA,GACA,IAAU,IACG;AAKb,QAJI,IAAgB,EAAY,UAC5B,KAAa,IAAU,EAAY,YACnC,KAAmB,KAAW,EAAY,WAC1C,KAAmB,KAAW,EAAY,UACvC,EAAY;;AAKrB,IAAM,IAAoD;CACxD,qBAAqB;CACrB,iBAAiB;CACjB,oBAAoB;CACpB,mBAAmB;CACnB,oBAAoB;CACrB;AAQD,SAAS,EACP,GACA,GACmB;AAEnB,QADI,EAAU,SAAS,IAAU,IAC1B,EAAW,KAAK,MAAM;EAC3B,IAAM,IAAa,EAA0B,EAAE,SAAS,EAAE,MACpD,IAAO,EAAU,IAAI,EAAW;AACtC,MAAI,CAAC,EAAM,QAAO;EAElB,IAAM,IAAY,EAAK,cACjB,EAAE,eAAY,eAAY,GAC1B,IAAkB,KAAW,MAAe,IAAI,IAAK,IAAY,IAAc,KAC/E,IAAY,IAAU,IAAI,KAAK,IAAI,GAAG,IAAa,EAAU,EAG/D,IAAY,GACV,IAAqB,CAAC,GAAG,EAAE,iBAAiB,CAC/C,MAAM,GAAG,OACM,EAAE,UAAU,IAAI,KAAK,EAAE,QAAQ,CAAC,SAAS,GAAG,aAC5C,EAAE,UAAU,IAAI,KAAK,EAAE,QAAQ,CAAC,SAAS,GAAG,UAE1D,CACD,KAAK,MAAM;GACV,IAAM,IAAiB,KAAK,IAAI,GAAW,EAAE,SAAS,EAAE;AAExD,UADA,IAAY,KAAK,IAAI,GAAG,IAAY,EAAe,EAC5C;IAAE,GAAG;IAAG,MAAM;IAAgB;IACrC;AAEJ,SAAO;GACL,GAAG;GACH;GACA;GACA,kBAAkB;GAClB,QAAQ,EAAqB,GAAiB,GAAW,EAAQ;GACjE,kBAAkB;GACnB;GACD;;AAMJ,SAAS,EAAsB,GAAqD;CAClF,IAAM,oBAAU,IAAI,KAAuC;AAE3D,MAAK,IAAM,KAAS,GAAQ;EAC1B,IAAM,IAAW,EAAQ,IAAI,EAAM,KAAK,IAAI,EAAE;AAE9C,EADA,EAAS,KAAK,EAAM,EACpB,EAAQ,IAAI,EAAM,MAAM,EAAS;;CAGnC,IAAM,IAAgC,EAAE;AAExC,MAAK,IAAM,CAAC,GAAM,MAAgB,GAAS;EAEzC,IAAM,IAAa,EAAY,QAAQ,GAAK,MAAM,KAAO,EAAE,SAAS,IAAI,EAAE,EACpE,IAAY,EAAY,QAAQ,GAAK,MAAM,KAAO,EAAE,QAAQ,IAAI,EAAE,EAClE,IAAiB,EAAY,QAAQ,GAAK,MAAM,KAAO,EAAE,aAAa,IAAI,EAAE,EAI5E,IACJ,MAAe,KAAK,EAAY,OAAO,MAAM,EAAE,YAAY,EAAE,SAAS,OAAO,EAAE,EAG3E,IAAkB,KAAc,MAAe,IAAI,IAAK,IAAY,IAAc,KAGlF,IAAS,EAAqB,GAAiB,GAAgB,EAAW,EAG1E,IAAW,EAAY,IAAI,YAAY,IACvC,IAAO,EAAY,IAAI,QAAQ,IAC/B,IAAM,EAAY,IAAI,SACtB,IAAc,OAAO,GAAK,eAAgB,WAAW,EAAI,cAAc,KAAA,GACvE,IAAc,OAAO,GAAK,eAAgB,WAAW,EAAI,cAAc,KAAA;AAE7E,IAAW,KAAK;GACd;GACA;GACA;GACA;GACA,YAAY,IAAa,IAAI;GAC7B;GACA;GACA;GACA,YAAY,EAAY;GACxB,SAAS;GACT,kBAAkB;GAClB;GACA,kBAAkB;GACnB,CAAC;;CAIJ,IAAM,IAA8C;EAClD,WAAW;EACX,UAAU;EACV,SAAS;EACT,SAAS;EACT,UAAU;EACX;AAED,QAAO,EAAW,MAAM,GAAG,MAAM;EAC/B,IAAM,IAAe,EAAe,EAAE,UAAU,EAAe,EAAE;AAEjE,SADI,MAAiB,IACd,EAAE,kBAAkB,EAAE,kBADE;GAE/B;;AAoBJ,SAAgB,IAA+D;CAC7E,IAAM,EAAE,SAAM,YAAS,aAAU,EAAmC,EAClE,aAAa,qBACd,CAAC;AAEF,QAAO;EACL,iBAAiB,GAAM,2BAA2B,EAAE;EACpD,WAAW;EACX,OAAO,KAAS;EACjB;;AAcH,SAAgB,EACd,GACA,GACsC;CACtC,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAA+C;EACvF,WAAW;GAAoB;GAAmB;GAAW;EAC7D,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAWF,QAAO;EACL,cAVmB,QAAc;AACjC,OAAI,CAAC,GAAM,+BACT,QAAO,EAAE;GAGX,IAAM,IAAU,EAAK;AACrB,UAAO,EAAsB,EAAQ,gBAAgB,EAAE,CAAC;KACvD,CAAC,EAAK,CAAC;EAIR,WAAW;EACX,OAAO,KAAS;EAChB;EACD;;AA8BH,IAAM,IAA+B;AAqBrC,SAAgB,EACd,GACA,IAA6C,EAAE,EACb;CAClC,IAAM,EAAE,gBAAa,cAAW,aAAa,GAGvC,IAAoB,MAAa,YAAY,MAAa,OAC1D,IAAkB,MAAa,UAAU,MAAa,OAGtD,EAAE,SAAM,YAAS,UAAO,eAAY,EAAmC;EAC3E,WAAW;GACS;GAClB,aAAa,KAAe;GAC7B;EACD,MAAM,CAAC,KAAoB,CAAC;EAC5B,aAAa;EACd,CAAC,EAGI,EACJ,MAAM,GACN,SAAS,GACT,SAAS,GACT,WAAW,MACT,EAAuC;EACzC,WAAW;GACS;GAClB,aAAa,KAAe;GAC5B,MAAM;GACN,UAAU;GACX;EACD,MAAM,CAAC,KAAoB,CAAC;EAC5B,aAAa;EACd,CAAC,EAGI,CAAC,GAAmB,KAAwB,EAAS,GAAM,EAC3D,CAAC,GAAiB,KAAsB,EAAS,EAAE,EAGnD,IAAsB,GACzB,MAA+D;EAC9D,IAAM,KAA4C,EAAI,UAAU,EAAE,EAAE,KAAK,MAAM;GAC7E,IAAM,IAAQ,EAAE,QAAQ,SAAS,GAC3B,IAAO,EAAE,mBAAmB,GAC5B,IAAY,KAAK,IAAI,GAAG,IAAQ,EAAK,EACrC,IAAkB,IAAQ,IAAK,IAAO,IAAS,MAAM,GACrD,IAAO,EAAE,QAAQ,QAAQ;AAE/B,UAAO;IACL,IAAI,EAAE;IACN,MAAM,EAAE;IACR;IACA,WAAW;IACX,UAAU;IACV;IACA;IACA;IACA;IACA,SAAS,EAAE,QAAQ,SAAS,YAAY,EAAE,QAAQ,SAAS;IAC3D,gBAAgB,EAAI;IACpB,SAAS,EAAE;IACX,cAAc,EAAE;IAChB,WAAW,EAAE;IACb,SAAS,EAAE;IACZ;IACD;AAEF,SAAO;GACL,IAAI,EAAI;GACR,QAAQ,EAAI;GACZ,WAAW,EAAI;GACf,kBAAkB,EAAsB,EAAe;GACxD;IAEH,EAAE,CACH,EAGK,IAAsB,QACrB,GAAM,+BAIkC,EAAK,6BAC7B,IAAI,EAAoB,GAJpC,EAAE,EAKV,CAAC,GAAM,EAAoB,CAAC,EAGzB,IAAoB,QACnB,GAAU,gCAKb,EAAS,6BAA6B,iBAAiB,EAAS,8BAC7C,IAAI,EAAoB,GALpC,EAAE,EAMV,CAAC,GAAU,EAAoB,CAAC,EAG7B,IAA8B,QAAc;EAChD,IAAM,IAAW,GAAU;AAY3B,SAXK,IAID,EAAS,eAAe,KAAA,IAOrB;GACL,YAAY,EAAkB;GAC9B,SAAS,EAAkB,UAAU,IAA+B;GACrE,GATQ;GACL,YAAY,EAAS;GACrB,SAAS,EAAkB,SAAS,EAAS;GAC9C,GAPM;GAAE,YAAY;GAAG,SAAS;GAAO;IAczC;EAAC;EAAU,EAAkB;EAAQ;EAAgB,CAAC;AAGzD,SAAgB;AACd,IAAmB,EAAE;IACpB,CAAC,GAAa,EAAS,CAAC;CAG3B,IAAM,IAA4B,EAAY,YAAY;AACxD,MAAI,CAAC,KAAiB,KAAqB,CAAC,EAA4B,QAAS;AAEjF,IAAqB,GAAK;EAC1B,IAAM,IAAW,IAAkB;AAEnC,MAAI;AAoBF,GAnBA,MAAM,EAAc;IAClB,WAAW;KACT,MAAM;KACN,UAAU;KACX;IACD,cACE,GACA,EAAE,yBACC;AACH,SAAI,CAAC,EAAiB,QAAO;KAE7B,IAAM,IAAW,GAAM,gCAAgC,EAAE,EACnD,IAAU,GAAiB,gCAAgC,EAAE;AAEnE,YAAO,EACL,8BAA8B,CAAC,GAAG,GAAU,GAAG,EAAQ,EACxD;;IAEJ,CAAC,EACF,EAAmB,EAAS;WACrB,GAAK;AACZ,WAAQ,MAAM,2CAA2C,EAAI;YACrD;AACR,KAAqB,GAAM;;IAE5B;EAAC;EAAe;EAAmB,EAA4B;EAAS;EAAgB,CAAC,EAEtF,IAAkB,QAAkB;AAGxC,EAFA,EAAmB,EAAE,EACjB,KAAmB,GAAS,EAC5B,KAAiB,GAAa;IACjC;EAAC;EAAmB;EAAiB;EAAS;EAAY,CAAC;AAE9D,QAAO;EACL;EACA;EACA,WAAW,IAAoB,IAAU;EACzC,4BAA4B,IAAkB,IAAc;EAC5D,OAAO,KAAS;EAChB,SAAS;EAET;EACA,0BAA0B,EAA4B;EACtD;EACA,wBAAwB,EAA4B;EACrD;;AAkCH,SAAgB,EACd,GACA,GACA,GACA,GAC+B;AAC/B,QAAO;EACL,SAAS;GACP;GACA;GACD;EACD,YAAY;GACV,OAAO;GACP,GAAI,IAAS,EAAE,WAAQ,GAAG,EAAE;GAC7B;EACF;;AAGH,SAAgB,EAAiC,GAAuC;AACtF,QAAO;EACL,IAAI,EAAO;EACX,QAAQ,EAAO;EACf,aAAa,EAAO,eAAe;EACnC,MAAO,EAAO,YAAwC,EAAE;EACxD,WAAW,EAAO;EAClB,QAAQ,EAAO,UAAU,KAAA;EACzB,WAAW,EAAO;EAClB,aAAa,EAAO;EACrB;;AAGH,SAAgB,EACd,GACA,IAAmB,IACnB,IAAwB,MACxB,GAC4B;CAK5B,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAAwB;EAChE,QAFe,EAAiB,YAAY;EAG5C,WAAW,EAA2B,GAAmB,GAAU,GAAQ,EAAY;EACvF,MAAM,CAAC,KAAqB,CAAC;EAC7B,aAAa;EACd,CAAC;AAKF,QAAO;EACL,SAHA,GAAM,iBAAiB,SAAS,EAAE,EAGpB,IAAI,EAAiC;EACnD,aAAa,GAAM,iBAAiB,eAAe;EACnD,YAAY,GAAM,iBAAiB,cAAc;EACjD,WAAW;EACX,OAAO,KAAS;EAChB;EACD;;AA+EH,SAAgB,EAAqB,EACnC,qBACA,gBACA,YAAS,EAAgB,cACzB,gBACA,cAAW,YAC8C;CAEzD,IAAM,IAAkB,EAAkB;EACxC;EACA;EACD,CAAC,EAGI,IAAsB,EAA+B,EAAiB,EAGtE,EAAE,iBAAc,EAA0B,EAAY,EAGtD,IAAsB,EAA2B,GAAkB;EACvE;EACA;EACD,CAAC,EAGI,IAAe,QACb,EAAgB,EAAoB,cAAc,EAAU,EAClE,CAAC,EAAoB,cAAc,EAAU,CAC9C,EAEK,IAAsB,QAExB,EAAoB,oBAAoB,KAAK,OAAS;EACpD,GAAG;EACH,kBAAkB,EAAgB,EAAI,kBAAkB,EAAU;EACnE,EAAE,EACL,CAAC,EAAoB,qBAAqB,EAAU,CACrD,EAEK,IAAoB,QAEtB,EAAoB,kBAAkB,KAAK,OAAS;EAClD,GAAG;EACH,kBAAkB,EAAgB,EAAI,kBAAkB,EAAU;EACnE,EAAE,EACL,CAAC,EAAoB,mBAAmB,EAAU,CACnD,EAEK,IAAU,QAAkB;AAGhC,EAFA,EAAgB,SAAS,EACzB,EAAoB,SAAS,EAC7B,EAAoB,SAAS;IAC5B;EAAC;EAAiB;EAAqB;EAAoB,CAAC;AAE/D,QAAO;EACL,WAAW,EAAgB;EAC3B;EACA;EACA;EACA,WACE,EAAgB,aAAa,EAAoB,aAAa,EAAoB;EACpF,4BAA4B,EAAoB;EAChD,OAAO,EAAgB,SAAS,EAAoB,SAAS,EAAoB;EACjF;EAEA,2BAA2B,EAAoB;EAC/C,0BAA0B,EAAoB;EAC9C,mBAAmB,EAAoB;EACvC,wBAAwB,EAAoB;EAC7C"}
1
+ {"version":3,"file":"useUsage.js","names":[],"sources":["../../../../../src/billing/modules/usage/hooks/useUsage.ts"],"sourcesContent":["/**\n * Usage Module - Hooks\n * Custom hooks for usage data fetching and mutations\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react';\nimport {\n useGetUsageDashboardQuery,\n useGetBillingAccountQuotasSummaryForUsageQuery,\n useGetSubscriptionsWithQuotasQuery,\n useGetPastSubscriptionsWithQuotasQuery,\n useGetBillingAccountsForUsageQuery,\n useGetQuotaAnalyticsForUsageQuery,\n type GetPastSubscriptionsWithQuotasQuery,\n} from '../../../../generated/global-operations';\nimport {\n useGetUsageRecordsQuery,\n type GetUsageRecordsQuery,\n type GetUsageRecordsQueryVariables,\n} from '../../../../generated/wspace-operations';\nimport { useGatewayClient, useQuery, useMutation, gql } from '@burdenoff/fe-libs/shared/graphql';\nimport { AnalyticsPeriod, QuotaStatus } from '../../../shared/types';\nimport type { UsageDashboard } from '../../../shared/types';\n\n// ============================================================================\n// Workspace Quota Assignment — managed by activity-svc\n// ============================================================================\n\nconst WORKSPACE_QUOTA_OVERVIEW_QUERY = gql`\n query WorkspaceQuotaOverview($workspaceId: ID!) {\n workspaceQuotaOverview(workspaceId: $workspaceId) {\n assignment {\n id\n workspaceId\n quotaName\n productId\n quotaMode\n billingAccountId\n subscriptionId\n limit\n used\n noLimit\n resetPeriod\n lastResetAt\n isActive\n createdAt\n }\n liveStatus {\n workspaceId\n quotaName\n productId\n currentValue\n limit\n noLimit\n scrapedAt\n }\n effectiveUsed\n remaining\n usagePercentage\n }\n }\n`;\n\nconst UNASSIGN_WORKSPACE_QUOTA_MUTATION = gql`\n mutation UnassignWorkspaceQuota($id: ID!, $workspaceId: ID!) {\n unassignWorkspaceQuota(id: $id, workspaceId: $workspaceId)\n }\n`;\n\nexport interface WorkspaceQuotaAssignmentItem {\n id: string;\n workspaceId: string;\n quotaName: string;\n productId: string;\n quotaMode: 'dedicated' | 'pooled';\n billingAccountId: string | null;\n subscriptionId: string | null;\n limit: number | null;\n used: number;\n noLimit: boolean;\n resetPeriod: string;\n lastResetAt: string | null;\n isActive: boolean;\n createdAt: string;\n}\n\nexport interface WorkspaceQuotaOverviewItem {\n assignment: WorkspaceQuotaAssignmentItem;\n liveStatus: WorkspaceQuotaStatusItem | null;\n effectiveUsed: number;\n remaining: number | null;\n usagePercentage: number;\n}\n\nexport interface AssignWorkspaceQuotaInput {\n workspaceId: string;\n quotaName: string;\n productId: string;\n quotaMode?: 'dedicated' | 'pooled';\n billingAccountId?: string;\n subscriptionId?: string;\n limit?: number;\n noLimit?: boolean;\n resetPeriod?: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface UseWorkspaceQuotaOverviewResult {\n overview: WorkspaceQuotaOverviewItem[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useWorkspaceQuotaOverview(workspaceId?: string): UseWorkspaceQuotaOverviewResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error, refetch } = useQuery<{\n workspaceQuotaOverview: WorkspaceQuotaOverviewItem[];\n }>(WORKSPACE_QUOTA_OVERVIEW_QUERY, {\n client: wsClient,\n variables: { workspaceId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n overview: data?.workspaceQuotaOverview ?? [],\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n// ============================================================================\n// activateSubscriptionForWorkspace — single backend call that assigns all\n// WORKSPACE-scoped quotas from the purchased plan. Replaces the N individual\n// assignWorkspaceQuota calls so the UI never sends quota names/limits to the server.\n// ============================================================================\n\nconst ACTIVATE_SUBSCRIPTION_FOR_WORKSPACE_MUTATION = gql`\n mutation ActivateSubscriptionForWorkspace(\n $subscriptionId: ID!\n $billingAccountId: ID!\n $workspaceId: ID!\n ) {\n activateSubscriptionForWorkspace(\n subscriptionId: $subscriptionId\n billingAccountId: $billingAccountId\n workspaceId: $workspaceId\n ) {\n workspaceId\n assignedQuotas\n skippedQuotas\n }\n }\n`;\n\nexport interface ActivateSubscriptionForWorkspaceResult {\n workspaceId: string;\n assignedQuotas: number;\n skippedQuotas: number;\n}\n\ninterface UseActivateSubscriptionForWorkspaceResult {\n activate: (\n subscriptionId: string,\n billingAccountId: string,\n workspaceId: string\n ) => Promise<ActivateSubscriptionForWorkspaceResult | null>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useActivateSubscriptionForWorkspace(): UseActivateSubscriptionForWorkspaceResult {\n const globalClient = useGatewayClient('global');\n const [mutate, { loading, error }] = useMutation<\n { activateSubscriptionForWorkspace: ActivateSubscriptionForWorkspaceResult },\n { subscriptionId: string; billingAccountId: string; workspaceId: string }\n >(ACTIVATE_SUBSCRIPTION_FOR_WORKSPACE_MUTATION, { client: globalClient });\n\n const activate = async (\n subscriptionId: string,\n billingAccountId: string,\n workspaceId: string\n ) => {\n const result = await mutate({ variables: { subscriptionId, billingAccountId, workspaceId } });\n return result.data?.activateSubscriptionForWorkspace ?? null;\n };\n\n return { activate, isLoading: loading, error: error ?? null };\n}\n\ninterface UseUnassignWorkspaceQuotaResult {\n unassign: (id: string, workspaceId: string) => Promise<boolean>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useUnassignWorkspaceQuota(): UseUnassignWorkspaceQuotaResult {\n const wsClient = useGatewayClient('workspace');\n const [mutate, { loading, error }] = useMutation<\n { unassignWorkspaceQuota: boolean },\n { id: string; workspaceId: string }\n >(UNASSIGN_WORKSPACE_QUOTA_MUTATION, { client: wsClient });\n\n const unassign = async (id: string, workspaceId: string) => {\n const result = await mutate({ variables: { id, workspaceId } });\n return result.data?.unassignWorkspaceQuota ?? false;\n };\n\n return { unassign, isLoading: loading, error: error ?? null };\n}\n\n// ============================================================================\n// Workspace Quota Status — live values from activity-svc\n// ============================================================================\n\nconst WORKSPACE_QUOTA_STATUSES_QUERY = gql`\n query WorkspaceQuotaStatuses($workspaceId: ID!) {\n workspaceQuotaStatuses(workspaceId: $workspaceId) {\n workspaceId\n quotaName\n productId\n currentValue\n limit\n noLimit\n scrapedAt\n }\n }\n`;\n\nconst WORKSPACE_QUOTA_HISTORY_QUERY = gql`\n query WorkspaceQuotaHistory($workspaceId: ID!, $quotaName: String!, $limit: Int) {\n workspaceQuotaHistory(workspaceId: $workspaceId, quotaName: $quotaName, limit: $limit) {\n id\n workspaceId\n quotaName\n productId\n value\n limit\n noLimit\n snapshotAt\n }\n }\n`;\n\nexport interface WorkspaceQuotaStatusItem {\n workspaceId: string;\n quotaName: string;\n productId: string;\n currentValue: number;\n limit: number | null;\n noLimit: boolean;\n scrapedAt: string;\n}\n\nexport interface WorkspaceQuotaSnapshotItem {\n id: string;\n workspaceId: string;\n quotaName: string;\n productId: string;\n value: number;\n limit: number | null;\n noLimit: boolean;\n snapshotAt: string;\n}\n\ninterface UseWorkspaceQuotaStatusesResult {\n statuses: WorkspaceQuotaStatusItem[];\n statusMap: Map<string, WorkspaceQuotaStatusItem>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useWorkspaceQuotaStatuses(workspaceId?: string): UseWorkspaceQuotaStatusesResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error } = useQuery<{ workspaceQuotaStatuses: WorkspaceQuotaStatusItem[] }>(\n WORKSPACE_QUOTA_STATUSES_QUERY,\n {\n client: wsClient,\n variables: { workspaceId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n }\n );\n\n const { statuses, statusMap } = useMemo(() => {\n const list = data?.workspaceQuotaStatuses ?? [];\n return { statuses: list, statusMap: new Map(list.map((s) => [s.quotaName, s])) };\n }, [data]);\n\n return { statuses, statusMap, isLoading: loading, error: error ?? null };\n}\n\ninterface UseWorkspaceQuotaHistoryResult {\n snapshots: WorkspaceQuotaSnapshotItem[];\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useWorkspaceQuotaHistory(\n workspaceId?: string,\n quotaName?: string,\n limit: number = 48\n): UseWorkspaceQuotaHistoryResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error } = useQuery<{\n workspaceQuotaHistory: WorkspaceQuotaSnapshotItem[];\n }>(WORKSPACE_QUOTA_HISTORY_QUERY, {\n client: wsClient,\n variables: { workspaceId, quotaName, limit },\n skip: !workspaceId || !quotaName,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n snapshots: data?.workspaceQuotaHistory ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n// ============================================================================\n// Dashboard Hook\n// ============================================================================\n\ninterface UseUsageDashboardParams {\n billingAccountId?: string;\n period?: AnalyticsPeriod;\n}\n\ninterface UseUsageDashboardResult {\n dashboard: UsageDashboard | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useUsageDashboard({\n billingAccountId,\n period = AnalyticsPeriod.LAST_30_DAYS,\n}: UseUsageDashboardParams): UseUsageDashboardResult {\n const { data, loading, error, refetch } = useGetUsageDashboardQuery({\n variables: { billingAccountId: billingAccountId!, period },\n skip: !billingAccountId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n dashboard: data?.getUsageDashboard ?? null,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n// ============================================================================\n// Enhanced Usage Page Hooks\n// ============================================================================\n\n/**\n * Quota assignment from the API\n */\ninterface QuotaAssignmentSummary {\n id: string;\n name: string;\n unit: string;\n quotaType: 'POOLED' | 'PRODUCT_SPECIFIC';\n reusable: boolean;\n limit: number;\n limitLeft: number;\n used: number;\n usagePercentage: number;\n noLimit: boolean;\n subscriptionId?: string;\n context?: Record<string, unknown> | null;\n recentUsages: Array<{\n id: string;\n amount: number;\n description: string;\n timestamp: string;\n tags?: Record<string, unknown>;\n }>;\n createdAt: string;\n endtime: string;\n}\n\n/**\n * Aggregated quota for display (multiple quota assignments with same name combined)\n */\nexport interface AggregatedQuota {\n name: string;\n displayName?: string;\n description?: string;\n unit: string;\n totalLimit: number;\n totalUsed: number;\n usagePercentage: number;\n status: QuotaStatus;\n quotaCount: number;\n noLimit: boolean;\n reusable: boolean;\n /** True when usage comes from the activity-svc scraper, not billing recordUsage events. */\n isCollectorBased: boolean;\n quotaAssignments: QuotaAssignmentSummary[];\n}\n\n/**\n * Subscription with aggregated quotas\n */\nexport interface SubscriptionWithAggregatedQuotas {\n id: string;\n status: string;\n planName?: string;\n productId?: string;\n createdAt: string;\n aggregatedQuotas: AggregatedQuota[];\n}\n\n/**\n * Helper function to calculate quota status based on usage percentage\n */\nfunction calculateQuotaStatus(\n usagePercentage: number,\n limitLeft: number,\n noLimit = false\n): QuotaStatus {\n if (noLimit) return QuotaStatus.HEALTHY;\n if (limitLeft <= 0) return QuotaStatus.EXHAUSTED;\n if (usagePercentage >= 90) return QuotaStatus.CRITICAL;\n if (usagePercentage >= 75) return QuotaStatus.WARNING;\n return QuotaStatus.HEALTHY;\n}\n\n// Maps billing quota names to their corresponding scraper metric names in workspace_quota_statuses.\n// Scraper keys use dot-notation product prefix; billing keys use kebab-case.\nconst BILLING_TO_SCRAPER_METRIC: Record<string, string> = {\n 'agents-registered': 'vibecontrols.agents.count',\n 'vibes-created': 'vibecontrols.vibes.count',\n 'catalogs-created': 'vibecontrols.catalogs.count',\n 'tunnels-created': 'vibecontrols.tunnels.count',\n 'sessions-started': 'vibecontrols.sessions.starts',\n};\n\n/**\n * Merges live collector values from activity-svc into a list of aggregated quotas.\n * Overrides `totalUsed` (and derived fields) when a live value exists, and distributes\n * the live total across individual assignments (earliest-expiry first) so the\n * Quota Breakdown shows real per-assignment consumption instead of always-0.\n */\nfunction mergeLiveValues(\n aggregated: AggregatedQuota[],\n statusMap: Map<string, WorkspaceQuotaStatusItem>\n): AggregatedQuota[] {\n if (statusMap.size === 0) return aggregated;\n return aggregated.map((q) => {\n const scraperKey = BILLING_TO_SCRAPER_METRIC[q.name] ?? q.name;\n const live = statusMap.get(scraperKey);\n if (!live) return q;\n\n const totalUsed = live.currentValue;\n const { totalLimit, noLimit } = q;\n const usagePercentage = noLimit || totalLimit === 0 ? 0 : (totalUsed / totalLimit) * 100;\n const limitLeft = noLimit ? 0 : Math.max(0, totalLimit - totalUsed);\n\n // Distribute live total into assignments: fill earliest-expiry first, overflow to next.\n let remaining = totalUsed;\n const updatedAssignments = [...q.quotaAssignments]\n .sort((a, b) => {\n const dateA = a.endtime ? new Date(a.endtime).getTime() : Infinity;\n const dateB = b.endtime ? new Date(b.endtime).getTime() : Infinity;\n return dateA - dateB;\n })\n .map((a) => {\n const assignmentUsed = Math.min(remaining, a.limit || 0);\n remaining = Math.max(0, remaining - assignmentUsed);\n return { ...a, used: assignmentUsed };\n });\n\n return {\n ...q,\n totalUsed,\n usagePercentage,\n isCollectorBased: true,\n status: calculateQuotaStatus(usagePercentage, limitLeft, noLimit),\n quotaAssignments: updatedAssignments,\n };\n });\n}\n\n/**\n * Helper function to aggregate quotas by name\n */\nfunction aggregateQuotasByName(quotas: QuotaAssignmentSummary[]): AggregatedQuota[] {\n const grouped = new Map<string, QuotaAssignmentSummary[]>();\n\n for (const quota of quotas) {\n const existing = grouped.get(quota.name) || [];\n existing.push(quota);\n grouped.set(quota.name, existing);\n }\n\n const aggregated: AggregatedQuota[] = [];\n\n for (const [name, assignments] of grouped) {\n // Sum up limits and usage first\n const totalLimit = assignments.reduce((sum, q) => sum + (q.limit || 0), 0);\n const totalUsed = assignments.reduce((sum, q) => sum + (q.used || 0), 0);\n const totalLimitLeft = assignments.reduce((sum, q) => sum + (q.limitLeft || 0), 0);\n\n // Check if there's actually no limit (ignore noLimit flag if there's a numeric limit)\n // Only treat as unlimited if totalLimit is 0 or all assignments explicitly have noLimit AND no numeric limit\n const hasNoLimit =\n totalLimit === 0 && assignments.every((q) => q.noLimit || (q.limit || 0) === 0);\n\n // Calculate percentage\n const usagePercentage = hasNoLimit || totalLimit === 0 ? 0 : (totalUsed / totalLimit) * 100;\n\n // Get status\n const status = calculateQuotaStatus(usagePercentage, totalLimitLeft, hasNoLimit);\n\n // Get reusable, unit, and display info from first assignment\n const reusable = assignments[0]?.reusable || false;\n const unit = assignments[0]?.unit || '';\n const ctx = assignments[0]?.context;\n const displayName = typeof ctx?.displayName === 'string' ? ctx.displayName : undefined;\n const description = typeof ctx?.description === 'string' ? ctx.description : undefined;\n\n aggregated.push({\n name,\n displayName,\n description,\n unit,\n totalLimit: hasNoLimit ? 0 : totalLimit,\n totalUsed,\n usagePercentage,\n status,\n quotaCount: assignments.length,\n noLimit: hasNoLimit,\n isCollectorBased: false,\n reusable,\n quotaAssignments: assignments,\n });\n }\n\n // Sort by status priority (critical first) then by usage percentage\n const statusPriority: Record<QuotaStatus, number> = {\n EXHAUSTED: 0,\n CRITICAL: 1,\n WARNING: 2,\n HEALTHY: 3,\n INACTIVE: 4,\n };\n\n return aggregated.sort((a, b) => {\n const priorityDiff = statusPriority[a.status] - statusPriority[b.status];\n if (priorityDiff !== 0) return priorityDiff;\n return b.usagePercentage - a.usagePercentage;\n });\n}\n\n/**\n * Hook to get billing accounts for the selector\n */\ninterface UseBillingAccountsForUsageResult {\n billingAccounts: Array<{\n id: string;\n name: string;\n email: string;\n currency: string;\n creditAmount: number;\n isActive: boolean;\n isDefault: boolean;\n }>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useBillingAccountsForUsage(): UseBillingAccountsForUsageResult {\n const { data, loading, error } = useGetBillingAccountsForUsageQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n billingAccounts: data?.getBillingAccountsByOrg ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to get billing account quotas summary with aggregation\n * Only returns pooled quotas - subscription quotas come from useSubscriptionsWithQuotas\n */\ninterface UseBillingAccountQuotasSummaryResult {\n pooledQuotas: AggregatedQuota[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useBillingAccountQuotasSummary(\n billingAccountId: string | undefined,\n productId?: string\n): UseBillingAccountQuotasSummaryResult {\n const { data, loading, error, refetch } = useGetBillingAccountQuotasSummaryForUsageQuery({\n variables: { billingAccountId: billingAccountId!, productId },\n skip: !billingAccountId,\n fetchPolicy: 'cache-and-network',\n });\n\n const pooledQuotas = useMemo(() => {\n if (!data?.getBillingAccountQuotasSummary) {\n return [];\n }\n\n const summary = data.getBillingAccountQuotasSummary;\n return aggregateQuotasByName(summary.pooledQuotas || []);\n }, [data]);\n\n return {\n pooledQuotas,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Subscription from the API\n */\ninterface SubscriptionQuota {\n id: string;\n name: string;\n limits: { type: string; value: number; unit?: string; originalValue?: number };\n context?: Record<string, unknown> | null;\n currentUsageSum: number;\n usageCount: number;\n createdAt: string;\n endtime: string;\n}\n\ninterface SubscriptionFromAPI {\n id: string;\n status: string;\n createdAt: string;\n quotas: SubscriptionQuota[];\n}\n\n/**\n * Hook to get subscriptions with their quotas grouped by status\n * Uses lazy loading for past subscriptions and supports backend search\n */\ntype ViewMode = 'active' | 'past' | 'all';\n\nconst PAST_SUBSCRIPTIONS_PAGE_SIZE = 10;\n\ninterface UseSubscriptionsWithQuotasOptions {\n searchQuery?: string;\n viewMode?: ViewMode;\n}\n\ninterface UseSubscriptionsWithQuotasResult {\n activeSubscriptions: SubscriptionWithAggregatedQuotas[];\n pastSubscriptions: SubscriptionWithAggregatedQuotas[];\n isLoading: boolean;\n isPastSubscriptionsLoading: boolean;\n error: Error | null;\n refetch: () => void;\n // Pagination for past subscriptions\n loadMorePastSubscriptions: () => void;\n hasMorePastSubscriptions: boolean;\n isLoadingMorePast: boolean;\n pastSubscriptionsCount: number;\n}\n\nexport function useSubscriptionsWithQuotas(\n billingAccountId: string | undefined,\n options: UseSubscriptionsWithQuotasOptions = {}\n): UseSubscriptionsWithQuotasResult {\n const { searchQuery, viewMode = 'active' } = options;\n\n // Determine which subscriptions to fetch based on viewMode\n const shouldFetchActive = viewMode === 'active' || viewMode === 'all';\n const shouldFetchPast = viewMode === 'past' || viewMode === 'all';\n\n // Query for active subscriptions\n const { data, loading, error, refetch } = useGetSubscriptionsWithQuotasQuery({\n variables: {\n billingAccountId: billingAccountId!,\n searchQuery: searchQuery || null,\n },\n skip: !billingAccountId || !shouldFetchActive,\n fetchPolicy: 'cache-and-network',\n });\n\n // Query for past subscriptions with pagination - only when viewMode is 'past' or 'all'\n const {\n data: pastData,\n loading: pastLoading,\n refetch: refetchPast,\n fetchMore: fetchMorePast,\n } = useGetPastSubscriptionsWithQuotasQuery({\n variables: {\n billingAccountId: billingAccountId!,\n searchQuery: searchQuery || null,\n page: 1,\n pageSize: PAST_SUBSCRIPTIONS_PAGE_SIZE,\n },\n skip: !billingAccountId || !shouldFetchPast,\n fetchPolicy: 'cache-and-network',\n });\n\n // Track if we're loading more past subscriptions\n const [isLoadingMorePast, setIsLoadingMorePast] = useState(false);\n const [currentPastPage, setCurrentPastPage] = useState(1);\n\n // Helper function to process subscription data\n const processSubscription = useCallback(\n (sub: SubscriptionFromAPI): SubscriptionWithAggregatedQuotas => {\n const quotaSummaries: QuotaAssignmentSummary[] = (sub.quotas || []).map((q) => {\n const limit = q.limits?.value || 0;\n const used = q.currentUsageSum || 0;\n const limitLeft = Math.max(0, limit - used);\n const usagePercentage = limit > 0 ? (used / limit) * 100 : 0;\n const unit = q.limits?.unit || '';\n\n return {\n id: q.id,\n name: q.name,\n unit,\n quotaType: 'PRODUCT_SPECIFIC' as const,\n reusable: false,\n limit,\n limitLeft,\n used,\n usagePercentage,\n noLimit: q.limits?.type !== 'number' && q.limits?.type !== 'numeric',\n subscriptionId: sub.id,\n context: q.context,\n recentUsages: [],\n createdAt: q.createdAt,\n endtime: q.endtime,\n };\n });\n\n return {\n id: sub.id,\n status: sub.status,\n createdAt: sub.createdAt,\n aggregatedQuotas: aggregateQuotasByName(quotaSummaries),\n };\n },\n []\n );\n\n // Process active subscriptions\n const activeSubscriptions = useMemo(() => {\n if (!data?.getSubscriptionUsageOverview) {\n return [];\n }\n\n const subscriptions: SubscriptionFromAPI[] = data.getSubscriptionUsageOverview;\n return subscriptions.map(processSubscription);\n }, [data, processSubscription]);\n\n // Process past subscriptions\n const pastSubscriptions = useMemo(() => {\n if (!pastData?.getSubscriptionUsageOverview) {\n return [];\n }\n\n const subscriptions: SubscriptionFromAPI[] =\n pastData.getSubscriptionUsageOverview.subscriptions || pastData.getSubscriptionUsageOverview;\n return subscriptions.map(processSubscription);\n }, [pastData, processSubscription]);\n\n // Get pagination info for past subscriptions\n const pastSubscriptionsPagination = useMemo(() => {\n const overview = pastData?.getSubscriptionUsageOverview;\n if (!overview) {\n return { totalCount: 0, hasMore: false };\n }\n // If it's the new paginated format\n if (overview.totalCount !== undefined) {\n return {\n totalCount: overview.totalCount,\n hasMore: pastSubscriptions.length < overview.totalCount,\n };\n }\n // Legacy format - assume there might be more if we got a full page\n return {\n totalCount: pastSubscriptions.length,\n hasMore: pastSubscriptions.length >= PAST_SUBSCRIPTIONS_PAGE_SIZE * currentPastPage,\n };\n }, [pastData, pastSubscriptions.length, currentPastPage]);\n\n // Reset page when search query or viewMode changes\n useEffect(() => {\n setCurrentPastPage(1);\n }, [searchQuery, viewMode]);\n\n // Load more past subscriptions\n const loadMorePastSubscriptions = useCallback(async () => {\n if (!fetchMorePast || isLoadingMorePast || !pastSubscriptionsPagination.hasMore) return;\n\n setIsLoadingMorePast(true);\n const nextPage = currentPastPage + 1;\n\n try {\n await fetchMorePast({\n variables: {\n page: nextPage,\n pageSize: PAST_SUBSCRIPTIONS_PAGE_SIZE,\n },\n updateQuery: (\n prev: GetPastSubscriptionsWithQuotasQuery,\n { fetchMoreResult }: { fetchMoreResult: GetPastSubscriptionsWithQuotasQuery }\n ) => {\n if (!fetchMoreResult) return prev;\n\n const prevSubs = prev?.getSubscriptionUsageOverview ?? [];\n const newSubs = fetchMoreResult?.getSubscriptionUsageOverview ?? [];\n\n return {\n getSubscriptionUsageOverview: [...prevSubs, ...newSubs],\n };\n },\n });\n setCurrentPastPage(nextPage);\n } catch (err) {\n console.error('Failed to load more past subscriptions:', err);\n } finally {\n setIsLoadingMorePast(false);\n }\n }, [fetchMorePast, isLoadingMorePast, pastSubscriptionsPagination.hasMore, currentPastPage]);\n\n const combinedRefetch = useCallback(() => {\n setCurrentPastPage(1);\n if (shouldFetchActive) refetch();\n if (shouldFetchPast) refetchPast();\n }, [shouldFetchActive, shouldFetchPast, refetch, refetchPast]);\n\n return {\n activeSubscriptions,\n pastSubscriptions,\n isLoading: shouldFetchActive ? loading : false,\n isPastSubscriptionsLoading: shouldFetchPast ? pastLoading : false,\n error: error ?? null,\n refetch: combinedRefetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions,\n hasMorePastSubscriptions: pastSubscriptionsPagination.hasMore,\n isLoadingMorePast,\n pastSubscriptionsCount: pastSubscriptionsPagination.totalCount,\n };\n}\n\n/**\n * Hook to get quota usage details for drill-down using cursor-based pagination.\n *\n * The activity-svc API is cursor-based (limit / cursor → items / hasNextPage / nextCursor).\n * Callers should maintain a cursor stack:\n * - push nextCursor to go forward\n * - pop to go back\n * - pass null as cursor for the first page\n */\nexport interface QuotaUsageDetail {\n id: string;\n amount: number;\n description: string;\n tags?: Record<string, unknown>;\n productId?: string;\n userId?: string;\n timestamp: string;\n lastUpdated?: string;\n}\n\ninterface UseQuotaUsageDetailsResult {\n usages: QuotaUsageDetail[];\n hasNextPage: boolean;\n nextCursor: string | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\ntype UsageRecord = GetUsageRecordsQuery['getUsageRecords']['items'][number];\n\nexport function buildUsageRecordsVariables(\n quotaAssignmentId: string | undefined,\n pageSize: number,\n cursor: string | null,\n workspaceId?: string\n): GetUsageRecordsQueryVariables {\n return {\n filters: {\n quotaAssignmentId,\n workspaceId,\n },\n pagination: {\n limit: pageSize,\n ...(cursor ? { cursor } : {}),\n },\n };\n}\n\nexport function mapUsageRecordToQuotaUsageDetail(record: UsageRecord): QuotaUsageDetail {\n return {\n id: record.id,\n amount: record.amount,\n description: record.description ?? '',\n tags: (record.metadata as Record<string, unknown>) ?? {},\n productId: record.productId,\n userId: record.userId ?? undefined,\n timestamp: record.createdAt,\n lastUpdated: record.createdAt,\n };\n}\n\nexport function useQuotaUsageDetails(\n quotaAssignmentId: string | undefined,\n pageSize: number = 20,\n cursor: string | null = null,\n workspaceId?: string\n): UseQuotaUsageDetailsResult {\n // Query workspace gateway directly — usage records live in wspace-activity-svc.\n // Uses the dedicated workspace Apollo client so it doesn't conflict with the\n // shell's global Apollo client used by all other billing queries.\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error, refetch } = useGetUsageRecordsQuery({\n client: wsClient,\n variables: buildUsageRecordsVariables(quotaAssignmentId, pageSize, cursor, workspaceId),\n skip: !quotaAssignmentId || !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const items: GetUsageRecordsQuery['getUsageRecords']['items'] =\n data?.getUsageRecords?.items ?? [];\n\n return {\n usages: items.map(mapUsageRecordToQuotaUsageDetail),\n hasNextPage: data?.getUsageRecords?.hasNextPage ?? false,\n nextCursor: data?.getUsageRecords?.nextCursor ?? null,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to get quota analytics\n */\ninterface UseQuotaAnalyticsResult {\n analytics: {\n dailyUsage: Array<{ date: string; usage: number; count: number }>;\n usageByType: Array<{ resourceType: string; usage: number; count: number; percentage: number }>;\n trends?: {\n growthRate?: number;\n predictedUsage?: number;\n averageDailyUsage?: number;\n };\n peakUsage?: {\n maxDailyUsage: number;\n peakDate: string;\n peakHour?: number;\n };\n } | null;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useQuotaAnalytics(\n quotaAssignmentId: string | undefined,\n period: AnalyticsPeriod = AnalyticsPeriod.LAST_30_DAYS\n): UseQuotaAnalyticsResult {\n const { data, loading, error } = useGetQuotaAnalyticsForUsageQuery({\n variables: { quotaAssignmentId: quotaAssignmentId!, period },\n skip: !quotaAssignmentId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n analytics: data?.getQuotaAnalytics ?? null,\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Combined hook for the enhanced usage page\n */\ninterface UseEnhancedUsagePageParams {\n billingAccountId: string | undefined;\n workspaceId?: string;\n period?: AnalyticsPeriod;\n searchQuery?: string;\n viewMode?: ViewMode;\n}\n\ninterface UseEnhancedUsagePageResult {\n // Dashboard overview\n dashboard: UsageDashboard | null;\n\n // Pooled quotas (subscription quotas come from subscriptions directly)\n pooledQuotas: AggregatedQuota[];\n\n // Subscriptions with their quotas\n activeSubscriptions: SubscriptionWithAggregatedQuotas[];\n pastSubscriptions: SubscriptionWithAggregatedQuotas[];\n\n // Loading states\n isLoading: boolean;\n isPastSubscriptionsLoading: boolean;\n error: Error | null;\n\n // Actions\n refetch: () => void;\n\n // Pagination for past subscriptions\n loadMorePastSubscriptions: () => void;\n hasMorePastSubscriptions: boolean;\n isLoadingMorePast: boolean;\n pastSubscriptionsCount: number;\n}\n\nexport function useEnhancedUsagePage({\n billingAccountId,\n workspaceId,\n period = AnalyticsPeriod.LAST_30_DAYS,\n searchQuery,\n viewMode = 'active',\n}: UseEnhancedUsagePageParams): UseEnhancedUsagePageResult {\n // Dashboard for overview stats\n const dashboardResult = useUsageDashboard({\n billingAccountId,\n period,\n });\n\n // Quotas summary with aggregation\n const quotasSummaryResult = useBillingAccountQuotasSummary(billingAccountId);\n\n // Live collector values from activity-svc (source of truth for count/concurrent quotas)\n const { statusMap } = useWorkspaceQuotaStatuses(workspaceId);\n\n // Subscriptions with quotas - passes searchQuery and viewMode to backend\n const subscriptionsResult = useSubscriptionsWithQuotas(billingAccountId, {\n searchQuery,\n viewMode,\n });\n\n // Merge live values into both pooled quotas and subscription quotas\n const pooledQuotas = useMemo(\n () => mergeLiveValues(quotasSummaryResult.pooledQuotas, statusMap),\n [quotasSummaryResult.pooledQuotas, statusMap]\n );\n\n const activeSubscriptions = useMemo(\n () =>\n subscriptionsResult.activeSubscriptions.map((sub) => ({\n ...sub,\n aggregatedQuotas: mergeLiveValues(sub.aggregatedQuotas, statusMap),\n })),\n [subscriptionsResult.activeSubscriptions, statusMap]\n );\n\n const pastSubscriptions = useMemo(\n () =>\n subscriptionsResult.pastSubscriptions.map((sub) => ({\n ...sub,\n aggregatedQuotas: mergeLiveValues(sub.aggregatedQuotas, statusMap),\n })),\n [subscriptionsResult.pastSubscriptions, statusMap]\n );\n\n const refetch = useCallback(() => {\n dashboardResult.refetch();\n quotasSummaryResult.refetch();\n subscriptionsResult.refetch();\n }, [dashboardResult, quotasSummaryResult, subscriptionsResult]);\n\n return {\n dashboard: dashboardResult.dashboard,\n pooledQuotas,\n activeSubscriptions,\n pastSubscriptions,\n isLoading:\n dashboardResult.isLoading || quotasSummaryResult.isLoading || subscriptionsResult.isLoading,\n isPastSubscriptionsLoading: subscriptionsResult.isPastSubscriptionsLoading,\n error: dashboardResult.error || quotasSummaryResult.error || subscriptionsResult.error,\n refetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions: subscriptionsResult.loadMorePastSubscriptions,\n hasMorePastSubscriptions: subscriptionsResult.hasMorePastSubscriptions,\n isLoadingMorePast: subscriptionsResult.isLoadingMorePast,\n pastSubscriptionsCount: subscriptionsResult.pastSubscriptionsCount,\n };\n}\n"],"mappings":";;;;;;AA4BA,IAAM,IAAiC,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,CAAG;;;;;AAmD7C,SAAgB,EAA0B,GAAuD;CAE/F,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAEvC,GAAgC;EACjC,QAJe,EAAiB,YAAY;EAK5C,WAAW,EAAE,gBAAa;EAC1B,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAEF,QAAO;EACL,UAAU,GAAM,0BAA0B,EAAE;EAC5C,WAAW;EACX,OAAO,KAAS;EAChB;EACD;;AASH,IAAM,IAA+C,CAAG;;;;;;;;;;;;;;;;;AAkCxD,SAAgB,IAAiF;CAE/F,IAAM,CAAC,GAAQ,EAAE,YAAS,cAAW,EAGnC,GAA8C,EAAE,QAJ7B,EAAiB,SAAS,EAIyB,CAAC;AAWzE,QAAO;EAAE,UATQ,OACf,GACA,GACA,OAEe,MAAM,EAAO,EAAE,WAAW;GAAE;GAAgB;GAAkB;GAAa,EAAE,CAAC,EAC/E,MAAM,oCAAoC;EAGvC,WAAW;EAAS,OAAO,KAAS;EAAM;;AA4B/D,IAAM,IAAiC,CAAG;;;;;;;;;;;;GAcpC,IAAgC,CAAG;;;;;;;;;;;;;;AA2CzC,SAAgB,EAA0B,GAAuD;CAE/F,IAAM,EAAE,SAAM,YAAS,aAAU,EAC/B,GACA;EACE,QAJa,EAAiB,YAAY;EAK1C,WAAW,EAAE,gBAAa;EAC1B,MAAM,CAAC;EACP,aAAa;EACd,CACF,EAEK,EAAE,aAAU,iBAAc,QAAc;EAC5C,IAAM,IAAO,GAAM,0BAA0B,EAAE;AAC/C,SAAO;GAAE,UAAU;GAAM,WAAW,IAAI,IAAI,EAAK,KAAK,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;GAAE;IAC/E,CAAC,EAAK,CAAC;AAEV,QAAO;EAAE;EAAU;EAAW,WAAW;EAAS,OAAO,KAAS;EAAM;;AAS1E,SAAgB,EACd,GACA,GACA,IAAgB,IACgB;CAEhC,IAAM,EAAE,SAAM,YAAS,aAAU,EAE9B,GAA+B;EAChC,QAJe,EAAiB,YAAY;EAK5C,WAAW;GAAE;GAAa;GAAW;GAAO;EAC5C,MAAM,CAAC,KAAe,CAAC;EACvB,aAAa;EACd,CAAC;AAEF,QAAO;EACL,WAAW,GAAM,yBAAyB,EAAE;EAC5C,WAAW;EACX,OAAO,KAAS;EACjB;;AAmBH,SAAgB,EAAkB,EAChC,qBACA,YAAS,EAAgB,gBAC0B;CACnD,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAA0B;EAClE,WAAW;GAAoB;GAAmB;GAAQ;EAC1D,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAEF,QAAO;EACL,WAAW,GAAM,qBAAqB;EACtC,WAAW;EACX,OAAO,KAAS;EAChB;EACD;;AAqEH,SAAS,EACP,GACA,GACA,IAAU,IACG;AAKb,QAJI,IAAgB,EAAY,UAC5B,KAAa,IAAU,EAAY,YACnC,KAAmB,KAAW,EAAY,WAC1C,KAAmB,KAAW,EAAY,UACvC,EAAY;;AAKrB,IAAM,IAAoD;CACxD,qBAAqB;CACrB,iBAAiB;CACjB,oBAAoB;CACpB,mBAAmB;CACnB,oBAAoB;CACrB;AAQD,SAAS,EACP,GACA,GACmB;AAEnB,QADI,EAAU,SAAS,IAAU,IAC1B,EAAW,KAAK,MAAM;EAC3B,IAAM,IAAa,EAA0B,EAAE,SAAS,EAAE,MACpD,IAAO,EAAU,IAAI,EAAW;AACtC,MAAI,CAAC,EAAM,QAAO;EAElB,IAAM,IAAY,EAAK,cACjB,EAAE,eAAY,eAAY,GAC1B,IAAkB,KAAW,MAAe,IAAI,IAAK,IAAY,IAAc,KAC/E,IAAY,IAAU,IAAI,KAAK,IAAI,GAAG,IAAa,EAAU,EAG/D,IAAY,GACV,IAAqB,CAAC,GAAG,EAAE,iBAAiB,CAC/C,MAAM,GAAG,OACM,EAAE,UAAU,IAAI,KAAK,EAAE,QAAQ,CAAC,SAAS,GAAG,aAC5C,EAAE,UAAU,IAAI,KAAK,EAAE,QAAQ,CAAC,SAAS,GAAG,UAE1D,CACD,KAAK,MAAM;GACV,IAAM,IAAiB,KAAK,IAAI,GAAW,EAAE,SAAS,EAAE;AAExD,UADA,IAAY,KAAK,IAAI,GAAG,IAAY,EAAe,EAC5C;IAAE,GAAG;IAAG,MAAM;IAAgB;IACrC;AAEJ,SAAO;GACL,GAAG;GACH;GACA;GACA,kBAAkB;GAClB,QAAQ,EAAqB,GAAiB,GAAW,EAAQ;GACjE,kBAAkB;GACnB;GACD;;AAMJ,SAAS,EAAsB,GAAqD;CAClF,IAAM,oBAAU,IAAI,KAAuC;AAE3D,MAAK,IAAM,KAAS,GAAQ;EAC1B,IAAM,IAAW,EAAQ,IAAI,EAAM,KAAK,IAAI,EAAE;AAE9C,EADA,EAAS,KAAK,EAAM,EACpB,EAAQ,IAAI,EAAM,MAAM,EAAS;;CAGnC,IAAM,IAAgC,EAAE;AAExC,MAAK,IAAM,CAAC,GAAM,MAAgB,GAAS;EAEzC,IAAM,IAAa,EAAY,QAAQ,GAAK,MAAM,KAAO,EAAE,SAAS,IAAI,EAAE,EACpE,IAAY,EAAY,QAAQ,GAAK,MAAM,KAAO,EAAE,QAAQ,IAAI,EAAE,EAClE,IAAiB,EAAY,QAAQ,GAAK,MAAM,KAAO,EAAE,aAAa,IAAI,EAAE,EAI5E,IACJ,MAAe,KAAK,EAAY,OAAO,MAAM,EAAE,YAAY,EAAE,SAAS,OAAO,EAAE,EAG3E,IAAkB,KAAc,MAAe,IAAI,IAAK,IAAY,IAAc,KAGlF,IAAS,EAAqB,GAAiB,GAAgB,EAAW,EAG1E,IAAW,EAAY,IAAI,YAAY,IACvC,IAAO,EAAY,IAAI,QAAQ,IAC/B,IAAM,EAAY,IAAI,SACtB,IAAc,OAAO,GAAK,eAAgB,WAAW,EAAI,cAAc,KAAA,GACvE,IAAc,OAAO,GAAK,eAAgB,WAAW,EAAI,cAAc,KAAA;AAE7E,IAAW,KAAK;GACd;GACA;GACA;GACA;GACA,YAAY,IAAa,IAAI;GAC7B;GACA;GACA;GACA,YAAY,EAAY;GACxB,SAAS;GACT,kBAAkB;GAClB;GACA,kBAAkB;GACnB,CAAC;;CAIJ,IAAM,IAA8C;EAClD,WAAW;EACX,UAAU;EACV,SAAS;EACT,SAAS;EACT,UAAU;EACX;AAED,QAAO,EAAW,MAAM,GAAG,MAAM;EAC/B,IAAM,IAAe,EAAe,EAAE,UAAU,EAAe,EAAE;AAEjE,SADI,MAAiB,IACd,EAAE,kBAAkB,EAAE,kBADE;GAE/B;;AAoBJ,SAAgB,IAA+D;CAC7E,IAAM,EAAE,SAAM,YAAS,aAAU,EAAmC,EAClE,aAAa,qBACd,CAAC;AAEF,QAAO;EACL,iBAAiB,GAAM,2BAA2B,EAAE;EACpD,WAAW;EACX,OAAO,KAAS;EACjB;;AAcH,SAAgB,EACd,GACA,GACsC;CACtC,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAA+C;EACvF,WAAW;GAAoB;GAAmB;GAAW;EAC7D,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAWF,QAAO;EACL,cAVmB,QAAc;AACjC,OAAI,CAAC,GAAM,+BACT,QAAO,EAAE;GAGX,IAAM,IAAU,EAAK;AACrB,UAAO,EAAsB,EAAQ,gBAAgB,EAAE,CAAC;KACvD,CAAC,EAAK,CAAC;EAIR,WAAW;EACX,OAAO,KAAS;EAChB;EACD;;AA8BH,IAAM,IAA+B;AAqBrC,SAAgB,EACd,GACA,IAA6C,EAAE,EACb;CAClC,IAAM,EAAE,gBAAa,cAAW,aAAa,GAGvC,IAAoB,MAAa,YAAY,MAAa,OAC1D,IAAkB,MAAa,UAAU,MAAa,OAGtD,EAAE,SAAM,YAAS,UAAO,eAAY,EAAmC;EAC3E,WAAW;GACS;GAClB,aAAa,KAAe;GAC7B;EACD,MAAM,CAAC,KAAoB,CAAC;EAC5B,aAAa;EACd,CAAC,EAGI,EACJ,MAAM,GACN,SAAS,GACT,SAAS,GACT,WAAW,MACT,EAAuC;EACzC,WAAW;GACS;GAClB,aAAa,KAAe;GAC5B,MAAM;GACN,UAAU;GACX;EACD,MAAM,CAAC,KAAoB,CAAC;EAC5B,aAAa;EACd,CAAC,EAGI,CAAC,GAAmB,KAAwB,EAAS,GAAM,EAC3D,CAAC,GAAiB,KAAsB,EAAS,EAAE,EAGnD,IAAsB,GACzB,MAA+D;EAC9D,IAAM,KAA4C,EAAI,UAAU,EAAE,EAAE,KAAK,MAAM;GAC7E,IAAM,IAAQ,EAAE,QAAQ,SAAS,GAC3B,IAAO,EAAE,mBAAmB,GAC5B,IAAY,KAAK,IAAI,GAAG,IAAQ,EAAK,EACrC,IAAkB,IAAQ,IAAK,IAAO,IAAS,MAAM,GACrD,IAAO,EAAE,QAAQ,QAAQ;AAE/B,UAAO;IACL,IAAI,EAAE;IACN,MAAM,EAAE;IACR;IACA,WAAW;IACX,UAAU;IACV;IACA;IACA;IACA;IACA,SAAS,EAAE,QAAQ,SAAS,YAAY,EAAE,QAAQ,SAAS;IAC3D,gBAAgB,EAAI;IACpB,SAAS,EAAE;IACX,cAAc,EAAE;IAChB,WAAW,EAAE;IACb,SAAS,EAAE;IACZ;IACD;AAEF,SAAO;GACL,IAAI,EAAI;GACR,QAAQ,EAAI;GACZ,WAAW,EAAI;GACf,kBAAkB,EAAsB,EAAe;GACxD;IAEH,EAAE,CACH,EAGK,IAAsB,QACrB,GAAM,+BAIkC,EAAK,6BAC7B,IAAI,EAAoB,GAJpC,EAAE,EAKV,CAAC,GAAM,EAAoB,CAAC,EAGzB,IAAoB,QACnB,GAAU,gCAKb,EAAS,6BAA6B,iBAAiB,EAAS,8BAC7C,IAAI,EAAoB,GALpC,EAAE,EAMV,CAAC,GAAU,EAAoB,CAAC,EAG7B,IAA8B,QAAc;EAChD,IAAM,IAAW,GAAU;AAY3B,SAXK,IAID,EAAS,eAAe,KAAA,IAOrB;GACL,YAAY,EAAkB;GAC9B,SAAS,EAAkB,UAAU,IAA+B;GACrE,GATQ;GACL,YAAY,EAAS;GACrB,SAAS,EAAkB,SAAS,EAAS;GAC9C,GAPM;GAAE,YAAY;GAAG,SAAS;GAAO;IAczC;EAAC;EAAU,EAAkB;EAAQ;EAAgB,CAAC;AAGzD,SAAgB;AACd,IAAmB,EAAE;IACpB,CAAC,GAAa,EAAS,CAAC;CAG3B,IAAM,IAA4B,EAAY,YAAY;AACxD,MAAI,CAAC,KAAiB,KAAqB,CAAC,EAA4B,QAAS;AAEjF,IAAqB,GAAK;EAC1B,IAAM,IAAW,IAAkB;AAEnC,MAAI;AAoBF,GAnBA,MAAM,EAAc;IAClB,WAAW;KACT,MAAM;KACN,UAAU;KACX;IACD,cACE,GACA,EAAE,yBACC;AACH,SAAI,CAAC,EAAiB,QAAO;KAE7B,IAAM,IAAW,GAAM,gCAAgC,EAAE,EACnD,IAAU,GAAiB,gCAAgC,EAAE;AAEnE,YAAO,EACL,8BAA8B,CAAC,GAAG,GAAU,GAAG,EAAQ,EACxD;;IAEJ,CAAC,EACF,EAAmB,EAAS;WACrB,GAAK;AACZ,WAAQ,MAAM,2CAA2C,EAAI;YACrD;AACR,KAAqB,GAAM;;IAE5B;EAAC;EAAe;EAAmB,EAA4B;EAAS;EAAgB,CAAC,EAEtF,IAAkB,QAAkB;AAGxC,EAFA,EAAmB,EAAE,EACjB,KAAmB,GAAS,EAC5B,KAAiB,GAAa;IACjC;EAAC;EAAmB;EAAiB;EAAS;EAAY,CAAC;AAE9D,QAAO;EACL;EACA;EACA,WAAW,IAAoB,IAAU;EACzC,4BAA4B,IAAkB,IAAc;EAC5D,OAAO,KAAS;EAChB,SAAS;EAET;EACA,0BAA0B,EAA4B;EACtD;EACA,wBAAwB,EAA4B;EACrD;;AAkCH,SAAgB,EACd,GACA,GACA,GACA,GAC+B;AAC/B,QAAO;EACL,SAAS;GACP;GACA;GACD;EACD,YAAY;GACV,OAAO;GACP,GAAI,IAAS,EAAE,WAAQ,GAAG,EAAE;GAC7B;EACF;;AAGH,SAAgB,EAAiC,GAAuC;AACtF,QAAO;EACL,IAAI,EAAO;EACX,QAAQ,EAAO;EACf,aAAa,EAAO,eAAe;EACnC,MAAO,EAAO,YAAwC,EAAE;EACxD,WAAW,EAAO;EAClB,QAAQ,EAAO,UAAU,KAAA;EACzB,WAAW,EAAO;EAClB,aAAa,EAAO;EACrB;;AAGH,SAAgB,EACd,GACA,IAAmB,IACnB,IAAwB,MACxB,GAC4B;CAK5B,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAAwB;EAChE,QAFe,EAAiB,YAAY;EAG5C,WAAW,EAA2B,GAAmB,GAAU,GAAQ,EAAY;EACvF,MAAM,CAAC,KAAqB,CAAC;EAC7B,aAAa;EACd,CAAC;AAKF,QAAO;EACL,SAHA,GAAM,iBAAiB,SAAS,EAAE,EAGpB,IAAI,EAAiC;EACnD,aAAa,GAAM,iBAAiB,eAAe;EACnD,YAAY,GAAM,iBAAiB,cAAc;EACjD,WAAW;EACX,OAAO,KAAS;EAChB;EACD;;AA+EH,SAAgB,EAAqB,EACnC,qBACA,gBACA,YAAS,EAAgB,cACzB,gBACA,cAAW,YAC8C;CAEzD,IAAM,IAAkB,EAAkB;EACxC;EACA;EACD,CAAC,EAGI,IAAsB,EAA+B,EAAiB,EAGtE,EAAE,iBAAc,EAA0B,EAAY,EAGtD,IAAsB,EAA2B,GAAkB;EACvE;EACA;EACD,CAAC,EAGI,IAAe,QACb,EAAgB,EAAoB,cAAc,EAAU,EAClE,CAAC,EAAoB,cAAc,EAAU,CAC9C,EAEK,IAAsB,QAExB,EAAoB,oBAAoB,KAAK,OAAS;EACpD,GAAG;EACH,kBAAkB,EAAgB,EAAI,kBAAkB,EAAU;EACnE,EAAE,EACL,CAAC,EAAoB,qBAAqB,EAAU,CACrD,EAEK,IAAoB,QAEtB,EAAoB,kBAAkB,KAAK,OAAS;EAClD,GAAG;EACH,kBAAkB,EAAgB,EAAI,kBAAkB,EAAU;EACnE,EAAE,EACL,CAAC,EAAoB,mBAAmB,EAAU,CACnD,EAEK,IAAU,QAAkB;AAGhC,EAFA,EAAgB,SAAS,EACzB,EAAoB,SAAS,EAC7B,EAAoB,SAAS;IAC5B;EAAC;EAAiB;EAAqB;EAAoB,CAAC;AAE/D,QAAO;EACL,WAAW,EAAgB;EAC3B;EACA;EACA;EACA,WACE,EAAgB,aAAa,EAAoB,aAAa,EAAoB;EACpF,4BAA4B,EAAoB;EAChD,OAAO,EAAgB,SAAS,EAAoB,SAAS,EAAoB;EACjF;EAEA,2BAA2B,EAAoB;EAC/C,0BAA0B,EAAoB;EAC9C,mBAAmB,EAAoB;EACvC,wBAAwB,EAAoB;EAC7C"}