@burdenoff/microfe-billing 2026.516.1 → 2026.517.1
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.
- package/dist/billing/BillingUserRoutes.js +1 -1
- package/dist/billing/BillingUserRoutes.js.map +1 -1
- package/dist/billing/modules/billing/hooks/useInvoices.d.ts +0 -1
- package/dist/billing/modules/billing/hooks/useInvoices.d.ts.map +1 -1
- package/dist/billing/modules/billing/hooks/useInvoices.js +20 -26
- package/dist/billing/modules/billing/hooks/useInvoices.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.d.ts.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +176 -170
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +4 -4
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.d.ts.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +2 -2
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.d.ts.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +178 -209
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +2 -2
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +2 -2
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/api.d.ts +0 -4
- package/dist/billing/modules/earnings/api.d.ts.map +1 -1
- package/dist/billing/modules/earnings/api.js +10 -14
- package/dist/billing/modules/earnings/api.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.d.ts.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +233 -523
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.d.ts.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +44 -101
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.d.ts.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +46 -105
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/hooks/useSubscriptions.d.ts +1 -1
- package/dist/billing/modules/subscriptions/hooks/useSubscriptions.d.ts.map +1 -1
- package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js +15 -16
- package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.d.ts.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +165 -158
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.d.ts.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +3 -3
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/types/graphql.d.ts +0 -2
- package/dist/billing/shared/types/graphql.d.ts.map +1 -1
- package/dist/billing/shared/types/graphql.js.map +1 -1
- package/dist/generated/global-operations.d.ts +0 -48
- package/dist/generated/global-operations.d.ts.map +1 -1
- package/dist/generated/global-operations.js +293 -310
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.d.ts +0 -9
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/global-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriptionsListPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionsListPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscriptions List Page\n * Displays paginated subscriptions for the selected billing account\n */\n\nimport { useState, useEffect, useRef, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingAccounts, usePaginatedSubscriptions } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { BillingSubscription, SubscriptionStatus, BillingAccount } from '../../../shared/types';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n} from '../../../shared/utils/format';\nimport { getSubscriptionStatusColor, getStatusBadgeClasses } from '../../../shared/utils/status';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype FilterStatus = 'all' | 'active' | 'canceled' | 'inactive';\n\n// Statuses that count as \"inactive\" (historical/past subscriptions)\nconst INACTIVE_STATUSES = ['expired', 'upgraded'] as const;\n\nconst PAGE_SIZE = 10;\n\nexport const SubscriptionsListPage: 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 navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { orgId } = useBilling();\n\n // Fetch all billing accounts for the selector\n const { billingAccounts, isLoading: isLoadingAccounts } = useBillingAccounts();\n\n // State\n const {\n selectedAccountId: selectedBillingAccountId,\n setSelectedAccountId: setSelectedBillingAccountId,\n } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(() => {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n setDebouncedSearch(searchQuery);\n setCurrentPage(1);\n }, 400);\n return () => clearTimeout(debounceTimer.current);\n }, [searchQuery]);\n\n // Fetch paginated subscriptions for selected billing account\n const {\n subscriptions,\n activeSubscriptions,\n totalCount,\n totalPages,\n isLoading: isLoadingSubscriptions,\n error,\n refetch,\n } = usePaginatedSubscriptions(\n selectedBillingAccountId ?? undefined,\n currentPage,\n PAGE_SIZE,\n debouncedSearch || undefined\n );\n\n // Get selected billing account info from the list\n const selectedBillingAccount = billingAccounts.find((a) => a.id === selectedBillingAccountId);\n\n // Handle account selection\n const handleSelectAccount = (account: BillingAccount) => {\n setSelectedBillingAccountId(account.id);\n setIsAccountSelectorOpen(false);\n setCurrentPage(1); // Reset to first page when switching accounts\n };\n\n // Handle page change\n const handlePageChange = (newPage: number) => {\n setCurrentPage(newPage);\n };\n\n // Client-side status filter only; search is handled by the backend\n const filteredSubscriptions = subscriptions.filter((subscription) => {\n if (filterStatus === 'all') return true;\n if (filterStatus === 'inactive') {\n return INACTIVE_STATUSES.includes(subscription.status as (typeof INACTIVE_STATUSES)[number]);\n }\n return subscription.status === filterStatus;\n });\n\n const isLoading = isLoadingAccounts || isLoadingSubscriptions;\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=\"w-12 h-12 mx-auto rounded-full bg-muted flex items-center justify-center\">\n <svg\n className=\"w-6 h-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't have permission to view subscriptions.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !selectedBillingAccount) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"h-12 w-12 bg-muted animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-48 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\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=\"w-12 h-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <svg\n className=\"w-6 h-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\">Failed to load subscriptions</h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n onClick={() => refetch()}\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 Try Again\n </button>\n </div>\n </div>\n );\n }\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-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.subscriptions.title', 'Subscriptions')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.subscriptions.subtitle', 'Manage your active and past subscriptions')}\n </p>\n </div>\n\n {permissions.canCreateSubscription && (\n <button\n onClick={() => navigateTo('/plans')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n New Subscription\n </button>\n )}\n </div>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\">\n <button\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border rounded-lg bg-card hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center\">\n <svg\n className=\"w-4 h-4 text-primary\"\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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-foreground\">\n {selectedBillingAccount?.name || 'Select Account'}\n </p>\n <p className=\"text-xs text-muted-foreground\">{selectedBillingAccount?.email}</p>\n </div>\n </div>\n <svg\n className={`w-4 h-4 text-muted-foreground transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\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=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-popover border border-border rounded-lg shadow-lg z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-muted transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-8 h-8 rounded-lg bg-muted flex items-center justify-center flex-shrink-0\">\n <svg\n className=\"w-4 h-4 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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-foreground truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">{account.email}</p>\n </div>\n </div>\n {selectedBillingAccount?.id === account.id && (\n <svg\n className=\"w-4 h-4 text-primary flex-shrink-0\"\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=\"M5 13l4 4L19 7\"\n />\n </svg>\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Account Summary */}\n {selectedBillingAccount && (\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Account</p>\n <p className=\"text-lg font-semibold text-foreground mt-1\">\n {selectedBillingAccount.name}\n </p>\n </div>\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Active Subscriptions</p>\n <p className=\"text-lg font-semibold text-status-success-text mt-1\">\n {activeSubscriptions.length}\n </p>\n </div>\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Credit Balance</p>\n <p className=\"text-lg font-semibold text-foreground mt-1\">\n {(selectedBillingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row flex-wrap items-stretch sm:items-center gap-3 sm:gap-4 pb-4 border-b border-border\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50 flex-wrap\">\n {(['all', 'active', 'canceled', 'inactive'] as FilterStatus[]).map((status) => (\n <button\n key={status}\n onClick={() => setFilterStatus(status)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors capitalize ${\n filterStatus === status\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {status === 'inactive' ? 'Inactive' : status}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <div className=\"relative flex-1 min-w-0 w-full sm:max-w-md\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 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=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n <input\n type=\"text\"\n placeholder={tr('billing.subscriptions.searchPlaceholder', 'Search subscriptions...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 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\"\n />\n </div>\n\n {/* Total Count */}\n <div className=\"text-sm text-muted-foreground\">\n {totalCount} subscription{totalCount !== 1 ? 's' : ''} total\n </div>\n </div>\n\n {/* Subscriptions List */}\n {filteredSubscriptions.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"w-6 h-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=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {tr('billing.subscriptions.noSubscriptionsFound', 'No subscriptions found')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {filterStatus !== 'all' || debouncedSearch\n ? 'No subscriptions match your filters.'\n : \"You don't have any subscriptions yet.\"}\n </p>\n {permissions.canCreateSubscription && filterStatus === 'all' && !debouncedSearch && (\n <button\n onClick={() => navigateTo('/plans')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Browse Plans\n </button>\n )}\n </div>\n ) : (\n <div className=\"space-y-4\">\n {filteredSubscriptions.map((subscription) => (\n <SubscriptionCard\n key={subscription.id}\n subscription={subscription}\n onView={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}`,\n subscription.billingAccountId\n )\n )\n }\n canCancel={permissions.canCancelSubscription}\n />\n ))}\n </div>\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"flex items-center justify-center gap-2 pt-4\">\n <button\n onClick={() => handlePageChange(currentPage - 1)}\n disabled={currentPage === 1}\n className=\"px-3 py-1.5 text-sm border border-border rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted transition-colors\"\n >\n <svg className=\"w-4 h-4\" 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\n <div className=\"flex items-center gap-1\">\n {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => {\n // Show first, last, current, and adjacent pages\n const showPage =\n page === 1 || page === totalPages || Math.abs(page - currentPage) <= 1;\n\n // Show ellipsis\n const showEllipsisBefore = page === currentPage - 2 && currentPage > 3;\n const showEllipsisAfter = page === currentPage + 2 && currentPage < totalPages - 2;\n\n if (showEllipsisBefore || showEllipsisAfter) {\n return (\n <span key={page} className=\"px-2 text-muted-foreground\">\n ...\n </span>\n );\n }\n\n if (!showPage) return null;\n\n return (\n <button\n key={page}\n onClick={() => handlePageChange(page)}\n className={`px-3 py-1.5 text-sm rounded-md transition-colors ${\n page === currentPage\n ? 'bg-primary text-primary-foreground'\n : 'border border-border hover:bg-muted'\n }`}\n >\n {page}\n </button>\n );\n })}\n </div>\n\n <button\n onClick={() => handlePageChange(currentPage + 1)}\n disabled={currentPage === totalPages}\n className=\"px-3 py-1.5 text-sm border border-border rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Subscription Card Component\n// ============================================================================\n\ninterface SubscriptionCardProps {\n subscription: BillingSubscription;\n onView: () => void;\n canCancel: boolean;\n}\n\nconst SubscriptionCard: FC<SubscriptionCardProps> = ({\n subscription,\n onView,\n canCancel: _canCancel,\n}) => {\n const plan = subscription.plan;\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 now = new Date();\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n\n return (\n <div\n className=\"border border-border rounded-lg bg-card hover:shadow-md transition-shadow cursor-pointer\"\n onClick={onView}\n >\n <div className=\"p-6\">\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-4\">\n {/* Plan Icon */}\n <div className=\"w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center shrink-0\">\n <svg\n className=\"w-6 h-6 text-primary\"\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=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"\n />\n </svg>\n </div>\n\n {/* Subscription Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-foreground\">{plan?.name || 'Unknown Plan'}</h3>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatSubscriptionStatus(subscription.status)}\n </span>\n </div>\n <div className=\"flex items-center gap-4 mt-1 text-sm text-muted-foreground\">\n <span>\n {plan ? formatCurrency(plan.price, plan.currency) : '—'} /{' '}\n {plan ? formatPlanDuration(plan.duration) : '—'}\n </span>\n <span className=\"hidden sm:inline\">•</span>\n <span className=\"hidden sm:inline\">\n Started {formatDate(subscription.startDate, 'short')}\n </span>\n </div>\n </div>\n\n {/* Right Side Info */}\n <div className=\"flex items-center gap-4\">\n {isActive && (\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-foreground\">{daysRemaining} days</p>\n <p className=\"text-xs text-muted-foreground\">remaining</p>\n </div>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n onView();\n }}\n className=\"px-3 py-1.5 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View\n </button>\n </div>\n </div>\n\n {/* Additional Info */}\n {subscription.addonSubscriptions && subscription.addonSubscriptions.length > 0 && (\n <div className=\"mt-4 pt-4 border-t border-border\">\n <p className=\"text-sm text-muted-foreground\">\n {subscription.addonSubscriptions.length} addon\n {subscription.addonSubscriptions.length !== 1 ? 's' : ''} attached\n </p>\n </div>\n )}\n\n {/* Cancellation Info */}\n {subscription.canceledAt && (\n <div className=\"mt-4 pt-4 border-t border-border\">\n <p className=\"text-sm text-muted-foreground\">\n Canceled on {formatDate(subscription.canceledAt)}\n {subscription.cancellationReason && <span> — {subscription.cancellationReason}</span>}\n </p>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SubscriptionsListPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionsListPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscriptions List Page\n * Displays paginated subscriptions for the selected billing account\n */\n\nimport { useState, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingAccounts, usePaginatedSubscriptions } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { BillingSubscription, SubscriptionStatus, BillingAccount } from '../../../shared/types';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n} from '../../../shared/utils/format';\nimport { getSubscriptionStatusColor, getStatusBadgeClasses } from '../../../shared/utils/status';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype FilterStatus = 'all' | 'active' | 'canceled' | 'inactive';\n\n// Statuses that count as \"inactive\" (historical/past subscriptions)\nconst INACTIVE_STATUSES = ['expired', 'upgraded'] as const;\n\nconst PAGE_SIZE = 10;\n\nexport const SubscriptionsListPage: 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 navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { orgId } = useBilling();\n\n // Fetch all billing accounts for the selector\n const { billingAccounts, isLoading: isLoadingAccounts } = useBillingAccounts();\n\n // State\n const {\n selectedAccountId: selectedBillingAccountId,\n setSelectedAccountId: setSelectedBillingAccountId,\n } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n\n // Fetch paginated subscriptions for selected billing account\n const {\n subscriptions,\n activeSubscriptions,\n totalCount,\n totalPages,\n isLoading: isLoadingSubscriptions,\n error,\n refetch,\n } = usePaginatedSubscriptions(selectedBillingAccountId ?? undefined, currentPage, PAGE_SIZE);\n\n // Get selected billing account info from the list\n const selectedBillingAccount = billingAccounts.find((a) => a.id === selectedBillingAccountId);\n\n // Handle account selection\n const handleSelectAccount = (account: BillingAccount) => {\n setSelectedBillingAccountId(account.id);\n setIsAccountSelectorOpen(false);\n setCurrentPage(1); // Reset to first page when switching accounts\n };\n\n // Handle page change\n const handlePageChange = (newPage: number) => {\n setCurrentPage(newPage);\n };\n\n // Filter subscriptions (client-side filtering for status and search)\n const filteredSubscriptions = subscriptions.filter((subscription) => {\n // Status filter\n if (filterStatus !== 'all') {\n if (filterStatus === 'inactive') {\n if (\n !INACTIVE_STATUSES.includes(subscription.status as (typeof INACTIVE_STATUSES)[number])\n ) {\n return false;\n }\n } else if (subscription.status !== filterStatus) {\n return false;\n }\n }\n\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const planName = subscription.plan?.name?.toLowerCase() || '';\n return planName.includes(query) || subscription.id.toLowerCase().includes(query);\n }\n\n return true;\n });\n\n const isLoading = isLoadingAccounts || isLoadingSubscriptions;\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=\"w-12 h-12 mx-auto rounded-full bg-muted flex items-center justify-center\">\n <svg\n className=\"w-6 h-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't have permission to view subscriptions.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !selectedBillingAccount) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"h-12 w-12 bg-muted animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-48 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\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=\"w-12 h-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <svg\n className=\"w-6 h-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\">Failed to load subscriptions</h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n onClick={() => refetch()}\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 Try Again\n </button>\n </div>\n </div>\n );\n }\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-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.subscriptions.title', 'Subscriptions')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.subscriptions.subtitle', 'Manage your active and past subscriptions')}\n </p>\n </div>\n\n {permissions.canCreateSubscription && (\n <button\n onClick={() => navigateTo('/plans')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n New Subscription\n </button>\n )}\n </div>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\">\n <button\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border rounded-lg bg-card hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center\">\n <svg\n className=\"w-4 h-4 text-primary\"\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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-foreground\">\n {selectedBillingAccount?.name || 'Select Account'}\n </p>\n <p className=\"text-xs text-muted-foreground\">{selectedBillingAccount?.email}</p>\n </div>\n </div>\n <svg\n className={`w-4 h-4 text-muted-foreground transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\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=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-popover border border-border rounded-lg shadow-lg z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-muted transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-8 h-8 rounded-lg bg-muted flex items-center justify-center flex-shrink-0\">\n <svg\n className=\"w-4 h-4 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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-foreground truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">{account.email}</p>\n </div>\n </div>\n {selectedBillingAccount?.id === account.id && (\n <svg\n className=\"w-4 h-4 text-primary flex-shrink-0\"\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=\"M5 13l4 4L19 7\"\n />\n </svg>\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Account Summary */}\n {selectedBillingAccount && (\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Account</p>\n <p className=\"text-lg font-semibold text-foreground mt-1\">\n {selectedBillingAccount.name}\n </p>\n </div>\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Active Subscriptions</p>\n <p className=\"text-lg font-semibold text-status-success-text mt-1\">\n {activeSubscriptions.length}\n </p>\n </div>\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Credit Balance</p>\n <p className=\"text-lg font-semibold text-foreground mt-1\">\n {(selectedBillingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n {(['all', 'active', 'canceled', 'inactive'] as FilterStatus[]).map((status) => (\n <button\n key={status}\n onClick={() => setFilterStatus(status)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors capitalize ${\n filterStatus === status\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {status === 'inactive' ? 'Inactive' : status}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <div className=\"relative flex-1 min-w-[200px] max-w-md\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 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=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n <input\n type=\"text\"\n placeholder={tr('billing.subscriptions.searchPlaceholder', 'Search subscriptions...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 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\"\n />\n </div>\n\n {/* Total Count */}\n <div className=\"text-sm text-muted-foreground\">\n {totalCount} subscription{totalCount !== 1 ? 's' : ''} total\n </div>\n </div>\n\n {/* Subscriptions List */}\n {filteredSubscriptions.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"w-6 h-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=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {tr('billing.subscriptions.noSubscriptionsFound', 'No subscriptions found')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {filterStatus !== 'all' || searchQuery\n ? 'No subscriptions match your filters.'\n : \"You don't have any subscriptions yet.\"}\n </p>\n {permissions.canCreateSubscription && filterStatus === 'all' && !searchQuery && (\n <button\n onClick={() => navigateTo('/plans')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Browse Plans\n </button>\n )}\n </div>\n ) : (\n <div className=\"space-y-4\">\n {filteredSubscriptions.map((subscription) => (\n <SubscriptionCard\n key={subscription.id}\n subscription={subscription}\n onView={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}`,\n subscription.billingAccountId\n )\n )\n }\n canCancel={permissions.canCancelSubscription}\n />\n ))}\n </div>\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"flex items-center justify-center gap-2 pt-4\">\n <button\n onClick={() => handlePageChange(currentPage - 1)}\n disabled={currentPage === 1}\n className=\"px-3 py-1.5 text-sm border border-border rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted transition-colors\"\n >\n <svg className=\"w-4 h-4\" 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\n <div className=\"flex items-center gap-1\">\n {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => {\n // Show first, last, current, and adjacent pages\n const showPage =\n page === 1 || page === totalPages || Math.abs(page - currentPage) <= 1;\n\n // Show ellipsis\n const showEllipsisBefore = page === currentPage - 2 && currentPage > 3;\n const showEllipsisAfter = page === currentPage + 2 && currentPage < totalPages - 2;\n\n if (showEllipsisBefore || showEllipsisAfter) {\n return (\n <span key={page} className=\"px-2 text-muted-foreground\">\n ...\n </span>\n );\n }\n\n if (!showPage) return null;\n\n return (\n <button\n key={page}\n onClick={() => handlePageChange(page)}\n className={`px-3 py-1.5 text-sm rounded-md transition-colors ${\n page === currentPage\n ? 'bg-primary text-primary-foreground'\n : 'border border-border hover:bg-muted'\n }`}\n >\n {page}\n </button>\n );\n })}\n </div>\n\n <button\n onClick={() => handlePageChange(currentPage + 1)}\n disabled={currentPage === totalPages}\n className=\"px-3 py-1.5 text-sm border border-border rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Subscription Card Component\n// ============================================================================\n\ninterface SubscriptionCardProps {\n subscription: BillingSubscription;\n onView: () => void;\n canCancel: boolean;\n}\n\nconst SubscriptionCard: FC<SubscriptionCardProps> = ({\n subscription,\n onView,\n canCancel: _canCancel,\n}) => {\n const plan = subscription.plan;\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 now = new Date();\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n\n return (\n <div\n className=\"border border-border rounded-lg bg-card hover:shadow-md transition-shadow cursor-pointer\"\n onClick={onView}\n >\n <div className=\"p-6\">\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-4\">\n {/* Plan Icon */}\n <div className=\"w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center shrink-0\">\n <svg\n className=\"w-6 h-6 text-primary\"\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=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"\n />\n </svg>\n </div>\n\n {/* Subscription Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-foreground\">{plan?.name || 'Unknown Plan'}</h3>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatSubscriptionStatus(subscription.status)}\n </span>\n </div>\n <div className=\"flex items-center gap-4 mt-1 text-sm text-muted-foreground\">\n <span>\n {plan ? formatCurrency(plan.price, plan.currency) : '—'} /{' '}\n {plan ? formatPlanDuration(plan.duration) : '—'}\n </span>\n <span className=\"hidden sm:inline\">•</span>\n <span className=\"hidden sm:inline\">\n Started {formatDate(subscription.startDate, 'short')}\n </span>\n </div>\n </div>\n\n {/* Right Side Info */}\n <div className=\"flex items-center gap-4\">\n {isActive && (\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-foreground\">{daysRemaining} days</p>\n <p className=\"text-xs text-muted-foreground\">remaining</p>\n </div>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n onView();\n }}\n className=\"px-3 py-1.5 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View\n </button>\n </div>\n </div>\n\n {/* Additional Info */}\n {subscription.addonSubscriptions && subscription.addonSubscriptions.length > 0 && (\n <div className=\"mt-4 pt-4 border-t border-border\">\n <p className=\"text-sm text-muted-foreground\">\n {subscription.addonSubscriptions.length} addon\n {subscription.addonSubscriptions.length !== 1 ? 's' : ''} attached\n </p>\n </div>\n )}\n\n {/* Cancellation Info */}\n {subscription.canceledAt && (\n <div className=\"mt-4 pt-4 border-t border-border\">\n <p className=\"text-sm text-muted-foreground\">\n Canceled on {formatDate(subscription.canceledAt)}\n {subscription.cancellationReason && <span> — {subscription.cancellationReason}</span>}\n </p>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UsagePage.d.ts","sourceRoot":"","sources":["../../../../../src/billing/modules/usage/pages/UsagePage.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAoD,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"UsagePage.d.ts","sourceRoot":"","sources":["../../../../../src/billing/modules/usage/pages/UsagePage.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAoD,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAmmBlF,eAAO,MAAM,SAAS,EAAE,EAmkBvB,CAAC"}
|
|
@@ -90,7 +90,7 @@ var te = ({ status: e, className: n = "w-4 h-4" }) => {
|
|
|
90
90
|
className: "cursor-pointer hover:bg-muted/40 transition-colors border-b border-border last:border-b-0",
|
|
91
91
|
children: [
|
|
92
92
|
/* @__PURE__ */ k("td", {
|
|
93
|
-
className: "px-4 py-3",
|
|
93
|
+
className: "px-4 py-3 min-w-[180px]",
|
|
94
94
|
children: [/* @__PURE__ */ O("span", {
|
|
95
95
|
className: "text-sm font-medium text-primary hover:underline capitalize",
|
|
96
96
|
children: e.name.replace(/_/g, " ")
|
|
@@ -104,7 +104,7 @@ var te = ({ status: e, className: n = "w-4 h-4" }) => {
|
|
|
104
104
|
children: t
|
|
105
105
|
}),
|
|
106
106
|
/* @__PURE__ */ O("td", {
|
|
107
|
-
className: "px-4 py-3",
|
|
107
|
+
className: "px-4 py-3 min-w-[180px]",
|
|
108
108
|
children: /* @__PURE__ */ k("div", {
|
|
109
109
|
className: "space-y-1.5",
|
|
110
110
|
children: [/* @__PURE__ */ k("span", {
|
|
@@ -540,7 +540,7 @@ var te = ({ status: e, className: n = "w-4 h-4" }) => {
|
|
|
540
540
|
children: [I.length > 1 && /* @__PURE__ */ O("select", {
|
|
541
541
|
value: L || "",
|
|
542
542
|
onChange: (e) => c(e.target.value),
|
|
543
|
-
className: "px-3 py-2 text-sm border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary
|
|
543
|
+
className: "px-3 py-2 text-sm border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary min-w-[200px]",
|
|
544
544
|
children: I.map((e) => /* @__PURE__ */ O("option", {
|
|
545
545
|
value: e.id,
|
|
546
546
|
children: e.name
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UsagePage.js","names":[],"sources":["../../../../../src/billing/modules/usage/pages/UsagePage.tsx"],"sourcesContent":["/**\n * Usage Module - Enhanced Usage Page\n * Displays usage dashboard with billing account selector, aggregated quotas by subscription,\n * pooled quotas, and lazy-loaded past subscriptions\n */\n\nimport { useState, useMemo, useDeferredValue, useCallback, type FC } from 'react';\nimport {\n ChevronDown,\n ChevronRight,\n AlertTriangle,\n CheckCircle,\n XCircle,\n AlertCircle,\n Layers,\n Package,\n Clock,\n RefreshCw,\n Info,\n ChevronLeft,\n X,\n Search,\n} from 'lucide-react';\nimport {\n useEnhancedUsagePage,\n useBillingAccountsForUsage,\n useQuotaUsageDetails,\n type AggregatedQuota,\n type SubscriptionWithAggregatedQuotas,\n} from '../hooks/useUsage';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { formatNumber, formatRelativeTime, formatDateTime } from '../../../shared/utils/format';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport type { QuotaStatus } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface QuotaDetailModalProps {\n quota: AggregatedQuota;\n onClose: () => void;\n}\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\nconst StatusIcon: FC<{ status: QuotaStatus; className?: string }> = ({\n status,\n className = 'w-4 h-4',\n}) => {\n switch (status) {\n case 'EXHAUSTED':\n return <XCircle className={`${className} ${statusTokens.error.icon}`} />;\n case 'CRITICAL':\n return <AlertTriangle className={`${className} ${statusTokens.error.icon}`} />;\n case 'WARNING':\n return <AlertCircle className={`${className} ${statusTokens.warning.icon}`} />;\n case 'HEALTHY':\n return <CheckCircle className={`${className} ${statusTokens.success.icon}`} />;\n default:\n return <Info className={`${className} ${statusTokens.neutral.icon}`} />;\n }\n};\n\nconst StatusBadge: FC<{ status: QuotaStatus }> = ({ status }) => {\n const colors: Record<QuotaStatus, string> = {\n EXHAUSTED: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n CRITICAL: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n WARNING: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n HEALTHY: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n INACTIVE: `${statusTokens.neutral.bg} ${statusTokens.neutral.text}`,\n };\n\n return (\n <span className={`px-2 py-0.5 text-xs font-medium rounded ${colors[status]}`}>{status}</span>\n );\n};\n\nconst ProgressBar: FC<{ percentage: number; status: QuotaStatus }> = ({ percentage, status }) => {\n const clampedPercentage = Math.min(100, Math.max(0, percentage));\n\n const barColors: Record<QuotaStatus, string> = {\n EXHAUSTED: statusTokens.error.dot,\n CRITICAL: statusTokens.error.dot,\n WARNING: statusTokens.warning.dot,\n HEALTHY: statusTokens.success.dot,\n INACTIVE: statusTokens.neutral.dot,\n };\n\n return (\n <div className=\"w-full bg-muted rounded-full h-2.5 overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all duration-300 ${barColors[status]}`}\n style={{ width: `${clampedPercentage}%` }}\n />\n </div>\n );\n};\n\n// ============================================================================\n// Stat Card Component\n// ============================================================================\n\ntype StatusFilter = 'all' | 'healthy' | 'warning' | 'critical';\n\ninterface StatCardProps {\n label: string;\n value: string | number;\n icon: React.ReactNode;\n color: 'blue' | 'emerald' | 'amber' | 'red' | 'gray';\n isActive?: boolean;\n onClick?: () => void;\n}\n\nconst StatCard: FC<StatCardProps> = ({ label, value, icon, color, isActive, onClick }) => {\n // Map color prop to semantic status tokens\n const colorToStatus = {\n blue: statusTokens.info,\n emerald: statusTokens.success,\n amber: statusTokens.warning,\n red: statusTokens.error,\n gray: statusTokens.neutral,\n };\n\n const tokens = colorToStatus[color];\n\n const colors = {\n blue: `${tokens.bg} ${tokens.border}`,\n emerald: `${tokens.bg} ${tokens.border}`,\n amber: `${tokens.bg} ${tokens.border}`,\n red: `${tokens.bg} ${tokens.border}`,\n gray: `${tokens.bg} ${tokens.border}`,\n };\n\n // Active ring colors - using semantic dot color for consistency\n const activeColors = {\n blue: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.info.border.replace('border-', 'ring-')}`,\n emerald: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.success.border.replace('border-', 'ring-')}`,\n amber: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.warning.border.replace('border-', 'ring-')}`,\n red: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.error.border.replace('border-', 'ring-')}`,\n gray: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.neutral.border.replace('border-', 'ring-')}`,\n };\n\n const iconColors = {\n blue: statusTokens.info.icon,\n emerald: statusTokens.success.icon,\n amber: statusTokens.warning.icon,\n red: statusTokens.error.icon,\n gray: statusTokens.neutral.icon,\n };\n\n return (\n <button\n onClick={onClick}\n className={`border rounded-lg p-4 w-full text-left transition-all ${colors[color]} ${\n onClick ? 'cursor-pointer hover:shadow-md' : ''\n } ${isActive ? activeColors[color] : ''}`}\n >\n <div className=\"flex items-center gap-2 mb-2\">\n <span className={iconColors[color]}>{icon}</span>\n <span className=\"text-sm text-muted-foreground\">{label}</span>\n </div>\n <p className=\"text-2xl font-bold text-foreground\">{value}</p>\n </button>\n );\n};\n\n// ============================================================================\n// Quota Table Components\n// ============================================================================\n\ninterface QuotaTableRowProps {\n quota: AggregatedQuota;\n planLabel?: string;\n onClick: () => void;\n}\n\nconst QuotaTableRow: FC<QuotaTableRowProps> = ({ quota, planLabel, onClick }) => {\n const hasLimit = quota.totalLimit != null && quota.totalLimit > 0 && !quota.noLimit;\n\n return (\n <tr\n onClick={onClick}\n className=\"cursor-pointer hover:bg-muted/40 transition-colors border-b border-border last:border-b-0\"\n >\n {/* Quota name */}\n <td className=\"px-4 py-3\">\n <span className=\"text-sm font-medium text-primary hover:underline capitalize\">\n {quota.name.replace(/_/g, ' ')}\n </span>\n {quota.quotaCount > 1 && (\n <span className=\"block text-xs text-muted-foreground mt-0.5\">\n {quota.quotaCount} assignments\n </span>\n )}\n </td>\n\n {/* Plan / type */}\n {planLabel !== undefined && (\n <td className=\"px-4 py-3 text-sm text-muted-foreground whitespace-nowrap\">{planLabel}</td>\n )}\n\n {/* Current usage: value + inline progress bar */}\n <td className=\"px-4 py-3\">\n <div className=\"space-y-1.5\">\n <span className=\"text-sm text-foreground tabular-nums\">\n {formatNumber(quota.totalUsed)} / \n {hasLimit ? formatNumber(quota.totalLimit) : '∞'}\n {quota.unit ? ` ${quota.unit}` : ''}\n </span>\n <ProgressBar percentage={hasLimit ? quota.usagePercentage : 0} status={quota.status} />\n </div>\n </td>\n\n {/* Limit */}\n <td className=\"px-4 py-3 text-sm text-muted-foreground tabular-nums whitespace-nowrap\">\n {hasLimit ? formatNumber(quota.totalLimit) : '∞'}\n </td>\n\n {/* Status */}\n <td className=\"px-4 py-3\">\n <StatusBadge status={quota.status} />\n </td>\n </tr>\n );\n};\n\ninterface QuotaTableProps {\n quotas: AggregatedQuota[];\n /** When provided, renders a \"Plan\" column with this value per row */\n planLabel?: string;\n showPlanColumn?: boolean;\n onQuotaClick: (quota: AggregatedQuota) => void;\n}\n\nconst QuotaTable: FC<QuotaTableProps> = ({\n quotas,\n planLabel,\n showPlanColumn = true,\n onQuotaClick,\n}) => (\n <div className=\"overflow-x-auto rounded-lg border border-border bg-card\">\n <table className=\"w-full text-left\">\n <thead>\n <tr className=\"border-b border-border bg-muted/30\">\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Quotas\n </th>\n {showPlanColumn && (\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Plan\n </th>\n )}\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Current Usage\n </th>\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Limit\n </th>\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Status\n </th>\n </tr>\n </thead>\n <tbody>\n {quotas.map((quota) => (\n <QuotaTableRow\n key={quota.name}\n quota={quota}\n planLabel={showPlanColumn ? planLabel : undefined}\n onClick={() => onQuotaClick(quota)}\n />\n ))}\n </tbody>\n </table>\n </div>\n);\n\n// ============================================================================\n// Subscription Section Component\n// ============================================================================\n\ninterface SubscriptionSectionProps {\n subscription: SubscriptionWithAggregatedQuotas;\n onQuotaClick: (quota: AggregatedQuota) => void;\n defaultExpanded?: boolean;\n}\n\nconst SubscriptionSection: FC<SubscriptionSectionProps> = ({\n subscription,\n onQuotaClick,\n defaultExpanded = true,\n}) => {\n const [isExpanded, setIsExpanded] = useState(defaultExpanded);\n\n const statusColors: Record<string, string> = {\n active: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n trialing: `${statusTokens.info.bg} ${statusTokens.info.text}`,\n past_due: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n canceled: `${statusTokens.neutral.bg} ${statusTokens.neutral.text}`,\n expired: `${statusTokens.neutral.bg} ${statusTokens.neutral.text}`,\n upgraded: `${statusTokens.primary.bg} ${statusTokens.primary.text}`,\n };\n\n return (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <button\n onClick={() => setIsExpanded(!isExpanded)}\n className=\"w-full flex items-center justify-between p-4 hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <Package className=\"w-5 h-5 text-primary\" />\n <div className=\"text-left\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-foreground\">Subscription</span>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded capitalize ${statusColors[subscription.status.toLowerCase()] || statusColors.active}`}\n >\n {subscription.status}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {subscription.aggregatedQuotas.length} quota type\n {subscription.aggregatedQuotas.length !== 1 ? 's' : ''}\n </p>\n </div>\n </div>\n {isExpanded ? (\n <ChevronDown className=\"w-5 h-5 text-muted-foreground\" />\n ) : (\n <ChevronRight className=\"w-5 h-5 text-muted-foreground\" />\n )}\n </button>\n\n {isExpanded && subscription.aggregatedQuotas.length > 0 && (\n <div className=\"border-t border-border\">\n <QuotaTable\n quotas={subscription.aggregatedQuotas}\n planLabel={subscription.planName || subscription.productId || undefined}\n showPlanColumn={!!(subscription.planName || subscription.productId)}\n onQuotaClick={onQuotaClick}\n />\n </div>\n )}\n\n {isExpanded && subscription.aggregatedQuotas.length === 0 && (\n <div className=\"border-t border-border p-6 text-center\">\n <p className=\"text-sm text-muted-foreground\">No quotas assigned to this subscription</p>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Quota Detail Modal Component\n// ============================================================================\n\nconst QuotaDetailModal: FC<QuotaDetailModalProps> = ({ quota, onClose }) => {\n // Cursor stack: [null] = page 1, [null, c1] = page 2, etc.\n const [cursorStack, setCursorStack] = useState<Array<string | null>>([null]);\n const currentCursor = cursorStack[cursorStack.length - 1] ?? null;\n const currentPage = cursorStack.length;\n const { workspaceId } = useBilling();\n\n // Get usage details for the first quota assignment directly from activity-svc\n const firstAssignmentId = quota.quotaAssignments[0]?.id;\n const { usages, hasNextPage, nextCursor, isLoading } = useQuotaUsageDetails(\n firstAssignmentId,\n 10,\n currentCursor,\n workspaceId\n );\n\n const handleNext = () => {\n if (nextCursor) setCursorStack((prev) => [...prev, nextCursor]);\n };\n\n const handlePrev = () => {\n if (cursorStack.length > 1) setCursorStack((prev) => prev.slice(0, -1));\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={onClose} />\n <div\n className=\"relative bg-background border border-border rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-auto\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"quota-detail-title\"\n >\n {/* Header */}\n <div className=\"sticky top-0 bg-background border-b border-border p-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <StatusIcon status={quota.status} className=\"w-5 h-5\" />\n <div>\n <h2 id=\"quota-detail-title\" className=\"text-lg font-semibold capitalize\">\n {quota.name.replace(/_/g, ' ')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {quota.quotaCount} quota assignment{quota.quotaCount !== 1 ? 's' : ''}\n </p>\n </div>\n </div>\n <button\n onClick={onClose}\n className=\"p-1 rounded-md hover:bg-muted/50 transition-colors\"\n aria-label=\"Close quota details\"\n >\n <X className=\"w-5 h-5\" />\n </button>\n </div>\n\n {/* Summary */}\n <div className=\"p-4 border-b border-border\">\n <div className=\"mb-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Usage</span>\n <StatusBadge status={quota.status} />\n </div>\n <ProgressBar percentage={quota.usagePercentage} status={quota.status} />\n <div className=\"flex items-center justify-between mt-2 text-sm\">\n <span className=\"font-medium\">\n {formatNumber(quota.totalUsed)} /{' '}\n {quota.totalLimit != null && quota.totalLimit > 0\n ? formatNumber(quota.totalLimit)\n : '∞'}\n {quota.unit && ` ${quota.unit}`}\n </span>\n <span className=\"text-muted-foreground\">\n {quota.totalLimit != null && quota.totalLimit > 0\n ? `${Math.round(quota.usagePercentage)}% used`\n : 'Unlimited'}\n </span>\n </div>\n </div>\n\n {/* Quota Assignments Breakdown - sorted by expiry date (earliest first) */}\n {quota.quotaCount > 1 && (\n <div className=\"mt-4\">\n <h3 className=\"text-sm font-medium text-foreground mb-3\">Quota Breakdown</h3>\n <div className=\"border border-border rounded-lg overflow-hidden divide-y divide-border\">\n {[...quota.quotaAssignments]\n .sort((a, b) => {\n // Sort by endtime ascending (earliest expiry first)\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((assignment) => {\n const expiryDate = assignment.endtime ? new Date(assignment.endtime) : null;\n const now = new Date();\n const isExpired = expiryDate && expiryDate < now;\n const daysUntilExpiry = expiryDate\n ? Math.ceil((expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000))\n : null;\n const isExpiringSoon =\n daysUntilExpiry !== null && daysUntilExpiry > 0 && daysUntilExpiry <= 7;\n\n return (\n <div\n key={assignment.id}\n className={`flex items-center justify-between px-3 py-2.5 ${\n isExpired\n ? 'bg-muted/30'\n : isExpiringSoon\n ? statusTokens.warning.bg\n : 'bg-background'\n }`}\n >\n <div className=\"flex items-center gap-2\">\n {isExpiringSoon && !isExpired && (\n <span\n className={`w-2 h-2 rounded-full ${statusTokens.warning.dot} animate-pulse`}\n />\n )}\n <div className=\"flex flex-col\">\n <span\n className={`text-sm ${isExpired ? 'text-muted-foreground' : 'text-foreground'}`}\n >\n {expiryDate\n ? expiryDate.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n : 'No expiry'}\n </span>\n <span\n className={`text-xs ${\n isExpired\n ? 'text-destructive'\n : isExpiringSoon\n ? statusTokens.warning.text\n : 'text-muted-foreground'\n }`}\n >\n {isExpired\n ? 'Expired'\n : daysUntilExpiry !== null\n ? daysUntilExpiry === 0\n ? 'Expires today'\n : daysUntilExpiry === 1\n ? 'Expires tomorrow'\n : `${daysUntilExpiry} days left`\n : 'Never expires'}\n </span>\n </div>\n </div>\n <div className=\"text-right\">\n <span\n className={`text-sm font-semibold ${isExpired ? 'text-muted-foreground' : 'text-foreground'}`}\n >\n {formatNumber(assignment.used)}\n <span className=\"text-muted-foreground font-normal\">\n {' / '}\n {assignment.limit != null && assignment.limit > 0\n ? formatNumber(assignment.limit)\n : '∞'}\n {assignment.unit && ` ${assignment.unit}`}\n </span>\n </span>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n )}\n </div>\n\n {/* Usage History */}\n <div className=\"p-4\">\n <h3 className=\"text-sm font-medium text-foreground mb-3\">Recent Usage</h3>\n\n {isLoading ? (\n <div className=\"space-y-2\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-12 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n ) : usages.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-6\">No usage records found</p>\n ) : (\n <div className=\"space-y-2\">\n {usages.map((usage) => (\n <div\n key={usage.id}\n className=\"flex items-center justify-between p-3 border border-border rounded-lg\"\n >\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {usage.description || 'Usage recorded'}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {formatRelativeTime(usage.timestamp)} • {formatDateTime(usage.timestamp)}\n </p>\n </div>\n <span className=\"text-sm font-medium text-foreground ml-4\">\n {formatNumber(usage.amount)}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Pagination */}\n {(cursorStack.length > 1 || hasNextPage) && (\n <div className=\"flex items-center justify-center gap-2 mt-4\">\n <button\n onClick={handlePrev}\n disabled={cursorStack.length === 1}\n className=\"px-3 py-1.5 text-sm border border-border rounded-md disabled:opacity-50\"\n aria-label=\"Previous usage page\"\n >\n <ChevronLeft className=\"w-4 h-4\" />\n </button>\n <span className=\"text-sm text-muted-foreground\">Page {currentPage}</span>\n <button\n onClick={handleNext}\n disabled={!hasNextPage}\n className=\"px-3 py-1.5 text-sm border border-border rounded-md disabled:opacity-50\"\n aria-label=\"Next usage page\"\n >\n <ChevronRight className=\"w-4 h-4\" />\n </button>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"sticky bottom-0 bg-background border-t border-border p-4\">\n <button\n onClick={onClose}\n className=\"w-full py-2 px-4 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Close\n </button>\n </div>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Main Usage Page Component\n// ============================================================================\n\nexport const UsagePage: 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 permissions = useBillingPermissions();\n\n // Helper to get billing account ID from URL (for navigation from Overview page)\n const getInitialBillingAccountId = (): string | undefined => {\n if (typeof window === 'undefined') return undefined;\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get('billingAccountId') || undefined;\n };\n\n // State - initialize with URL param if present\n const [selectedBillingAccountId, setSelectedBillingAccountId] = useState<string | undefined>(\n getInitialBillingAccountId\n );\n const [selectedQuota, setSelectedQuota] = useState<AggregatedQuota | null>(null);\n\n // Search and filter state\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');\n\n // View mode: 'active' | 'past' | 'all'\n type ViewMode = 'active' | 'past' | 'all';\n const [viewMode, setViewMode] = useState<ViewMode>('active');\n\n // Debounce search query to avoid too many API calls\n const deferredSearchQuery = useDeferredValue(searchQuery);\n\n // Fetch billing accounts\n const { billingAccounts, isLoading: isLoadingAccounts } = useBillingAccountsForUsage();\n\n // Prefer the default account, fall back to first\n const defaultBillingAccountId = (billingAccounts.find((a) => a.isDefault) ?? billingAccounts[0])\n ?.id;\n // ?? so that an explicit empty string (cleared URL param) still falls back to default\n const effectiveBillingAccountId = selectedBillingAccountId ?? defaultBillingAccountId;\n\n // Fetch usage data - passes searchQuery and viewMode to backend for server-side filtering\n const {\n dashboard,\n pooledQuotas,\n activeSubscriptions,\n pastSubscriptions,\n isLoading,\n isPastSubscriptionsLoading,\n error,\n refetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions,\n hasMorePastSubscriptions,\n isLoadingMorePast,\n pastSubscriptionsCount,\n } = useEnhancedUsagePage({\n billingAccountId: effectiveBillingAccountId,\n searchQuery: deferredSearchQuery || undefined,\n viewMode, // 'active' | 'past' | 'all'\n });\n\n // Filter quotas by status (search is now handled by backend)\n const filterQuotaByStatus = useCallback(\n (quota: AggregatedQuota): boolean => {\n if (statusFilter === 'all') return true;\n if (statusFilter === 'healthy') return quota.status === 'HEALTHY';\n if (statusFilter === 'warning') return quota.status === 'WARNING';\n if (statusFilter === 'critical')\n return quota.status === 'CRITICAL' || quota.status === 'EXHAUSTED';\n return true;\n },\n [statusFilter]\n );\n\n // Filtered data - only status filter is client-side, search is backend\n const filteredPooledQuotas = useMemo(() => {\n return pooledQuotas.filter(filterQuotaByStatus);\n }, [pooledQuotas, filterQuotaByStatus]);\n\n const filteredActiveSubscriptions = useMemo(() => {\n if (statusFilter === 'all') {\n return activeSubscriptions;\n }\n return activeSubscriptions\n .map((sub) => ({\n ...sub,\n aggregatedQuotas: sub.aggregatedQuotas.filter(filterQuotaByStatus),\n }))\n .filter((sub) => sub.aggregatedQuotas.length > 0);\n }, [activeSubscriptions, statusFilter, filterQuotaByStatus]);\n\n const filteredPastSubscriptions = useMemo(() => {\n if (statusFilter === 'all') {\n return pastSubscriptions;\n }\n return pastSubscriptions\n .map((sub) => ({\n ...sub,\n aggregatedQuotas: sub.aggregatedQuotas.filter(filterQuotaByStatus),\n }))\n .filter((sub) => sub.aggregatedQuotas.length > 0);\n }, [pastSubscriptions, statusFilter, filterQuotaByStatus]);\n\n // Subscriptions to display based on view mode\n const displayedSubscriptions = useMemo(() => {\n if (viewMode === 'active') {\n return { active: filteredActiveSubscriptions, past: [] };\n }\n if (viewMode === 'past') {\n return { active: [], past: filteredPastSubscriptions };\n }\n // 'all' - show both\n return { active: filteredActiveSubscriptions, past: filteredPastSubscriptions };\n }, [viewMode, filteredActiveSubscriptions, filteredPastSubscriptions]);\n\n const hasActiveFilters = searchQuery !== '' || statusFilter !== 'all';\n const isSearching = deferredSearchQuery !== searchQuery; // Show loading indicator while debouncing\n\n const clearFilters = () => {\n setSearchQuery('');\n setStatusFilter('all');\n };\n\n // Toggle status filter (click same to deselect)\n const toggleStatusFilter = (filter: StatusFilter) => {\n setStatusFilter((prev) => (prev === filter ? 'all' : filter));\n };\n\n // Permission check\n if (!permissions.canViewUsage) {\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=\"w-12 h-12 mx-auto rounded-full bg-muted flex items-center justify-center\">\n <AlertTriangle className=\"w-6 h-6 text-muted-foreground\" />\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't have permission to view usage data.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if ((isLoadingAccounts || isLoading) && !dashboard) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n {[1, 2, 3, 4].map((i) => (\n <div key={i} className=\"h-24 bg-muted animate-pulse rounded-lg\" />\n ))}\n </div>\n <div className=\"h-64 bg-muted animate-pulse rounded-lg\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\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=\"w-12 h-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <AlertTriangle className=\"w-6 h-6 text-destructive\" />\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">Failed to load usage data</h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n onClick={() => refetch()}\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 Try Again\n </button>\n </div>\n </div>\n );\n }\n\n const selectedAccount = billingAccounts.find((a) => a.id === effectiveBillingAccountId);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header with Billing Account Selector */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.usage.title', 'Usage')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Monitor your resource consumption and quota usage\n </p>\n </div>\n\n <div className=\"flex items-center gap-3\">\n {/* Billing Account Selector */}\n {billingAccounts.length > 1 && (\n <select\n value={effectiveBillingAccountId || ''}\n onChange={(e) => setSelectedBillingAccountId(e.target.value)}\n className=\"px-3 py-2 text-sm border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary w-full sm:min-w-[200px]\"\n >\n {billingAccounts.map((account) => (\n <option key={account.id} value={account.id}>\n {account.name}\n </option>\n ))}\n </select>\n )}\n\n {/* Refresh Button */}\n <button\n onClick={() => refetch()}\n className=\"p-2 border border-border rounded-md hover:bg-muted/50 transition-colors\"\n title=\"Refresh\"\n >\n <RefreshCw className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n\n {/* Search and Filters */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n {/* Search Input */}\n <div className=\"relative flex-1 max-w-md\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground\" />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={`Search ${viewMode === 'past' ? 'past' : viewMode === 'all' ? 'all' : 'active'} quotas...`}\n className=\"w-full pl-9 pr-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\"\n />\n {(searchQuery || isSearching) && (\n <button\n onClick={() => setSearchQuery('')}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded hover:bg-muted/50\"\n >\n {isSearching ? (\n <RefreshCw className=\"w-3 h-3 text-muted-foreground animate-spin\" />\n ) : (\n <X className=\"w-3 h-3 text-muted-foreground\" />\n )}\n </button>\n )}\n </div>\n\n {/* View Mode Tabs */}\n <div className=\"flex items-center bg-muted/50 rounded-lg p-1 border border-border\">\n <button\n onClick={() => setViewMode('active')}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-all ${\n viewMode === 'active'\n ? 'bg-background text-foreground shadow-sm font-medium'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n <Package className=\"w-3.5 h-3.5\" />\n Active\n </button>\n <button\n onClick={() => setViewMode('past')}\n disabled={isPastSubscriptionsLoading && viewMode !== 'past'}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-all ${\n viewMode === 'past'\n ? 'bg-background text-foreground shadow-sm font-medium'\n : 'text-muted-foreground hover:text-foreground'\n } ${isPastSubscriptionsLoading && viewMode !== 'past' ? 'opacity-70' : ''}`}\n >\n {isPastSubscriptionsLoading && viewMode === 'past' ? (\n <RefreshCw className=\"w-3.5 h-3.5 animate-spin\" />\n ) : (\n <Clock className=\"w-3.5 h-3.5\" />\n )}\n Past\n </button>\n </div>\n\n {/* Active Filters Indicator */}\n {hasActiveFilters && (\n <button\n onClick={clearFilters}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground border border-border rounded-md hover:bg-muted/50 transition-colors\"\n >\n <X className=\"w-3 h-3\" />\n Clear filters\n </button>\n )}\n </div>\n\n {/* Billing Account Info */}\n {selectedAccount && (\n <div className=\"text-sm text-muted-foreground\">\n Viewing usage for{' '}\n <span className=\"font-medium text-foreground\">{selectedAccount.name}</span>\n </div>\n )}\n\n {/* Overview Stats - Clickable to filter */}\n {dashboard && (\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <StatCard\n label=\"Total Quotas\"\n value={dashboard.totalQuotas}\n icon={<Layers className=\"w-5 h-5\" />}\n color=\"blue\"\n isActive={statusFilter === 'all'}\n onClick={() => setStatusFilter('all')}\n />\n <StatCard\n label=\"Healthy\"\n value={dashboard.healthyQuotas}\n icon={<CheckCircle className=\"w-5 h-5\" />}\n color=\"emerald\"\n isActive={statusFilter === 'healthy'}\n onClick={() => toggleStatusFilter('healthy')}\n />\n <StatCard\n label=\"Warning\"\n value={dashboard.warningQuotas}\n icon={<AlertCircle className=\"w-5 h-5\" />}\n color=\"amber\"\n isActive={statusFilter === 'warning'}\n onClick={() => toggleStatusFilter('warning')}\n />\n <StatCard\n label=\"Critical / Exhausted\"\n value={dashboard.criticalQuotas + dashboard.exhaustedQuotas}\n icon={<AlertTriangle className=\"w-5 h-5\" />}\n color=\"red\"\n isActive={statusFilter === 'critical'}\n onClick={() => toggleStatusFilter('critical')}\n />\n </div>\n )}\n\n {/* Filter Results Info */}\n {(hasActiveFilters || viewMode !== 'active') && (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground flex-wrap\">\n <span>\n Showing{' '}\n {displayedSubscriptions.active.reduce((acc, s) => acc + s.aggregatedQuotas.length, 0) +\n displayedSubscriptions.past.reduce((acc, s) => acc + s.aggregatedQuotas.length, 0) +\n (viewMode !== 'past' ? filteredPooledQuotas.length : 0)}{' '}\n quotas\n </span>\n <span className=\"px-2 py-0.5 bg-primary/10 text-primary rounded text-xs capitalize\">\n {viewMode === 'active' ? 'Active' : viewMode === 'past' ? 'Past' : 'All'} subscriptions\n </span>\n {statusFilter !== 'all' && (\n <span className=\"px-2 py-0.5 bg-muted rounded text-xs capitalize\">\n {statusFilter === 'critical' ? 'Critical / Exhausted' : statusFilter}\n </span>\n )}\n {searchQuery && (\n <span className=\"px-2 py-0.5 bg-muted rounded text-xs\">\n “{searchQuery}”\n </span>\n )}\n </div>\n )}\n\n {/* Active Subscriptions Section */}\n {displayedSubscriptions.active.length > 0 && (\n <section>\n <h2 className=\"text-lg font-semibold text-foreground mb-4 flex items-center gap-2\">\n <Package className=\"w-5 h-5 text-primary\" />\n {viewMode === 'all' ? 'Active Subscription Quotas' : 'Subscription Quotas'}\n </h2>\n <div className=\"space-y-4\">\n {displayedSubscriptions.active.map((subscription) => (\n <SubscriptionSection\n key={subscription.id}\n subscription={subscription}\n onQuotaClick={setSelectedQuota}\n defaultExpanded={true}\n />\n ))}\n </div>\n </section>\n )}\n\n {/* Past Subscriptions Section (when viewMode is 'past' or 'all') */}\n {displayedSubscriptions.past.length > 0 && (\n <section>\n <h2 className=\"text-lg font-semibold text-foreground mb-4 flex items-center gap-2\">\n <Clock className=\"w-5 h-5 text-muted-foreground\" />\n Past Subscription Quotas\n <span className=\"text-xs font-normal text-muted-foreground\">\n (Canceled / Expired / Upgraded)\n </span>\n {pastSubscriptionsCount > 0 && (\n <span className=\"text-xs font-normal px-2 py-0.5 bg-muted rounded-full\">\n {displayedSubscriptions.past.length}\n {hasMorePastSubscriptions ? '+' : ''} of {pastSubscriptionsCount}\n </span>\n )}\n </h2>\n <div className=\"space-y-4\">\n {displayedSubscriptions.past.map((subscription) => (\n <SubscriptionSection\n key={subscription.id}\n subscription={subscription}\n onQuotaClick={setSelectedQuota}\n defaultExpanded={viewMode === 'past'}\n />\n ))}\n\n {/* Load More Button */}\n {hasMorePastSubscriptions && (\n <div className=\"flex justify-center pt-2\">\n <button\n onClick={loadMorePastSubscriptions}\n disabled={isLoadingMorePast}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground border border-border rounded-md hover:bg-muted/50 transition-colors disabled:opacity-50\"\n >\n {isLoadingMorePast ? (\n <>\n <RefreshCw className=\"w-4 h-4 animate-spin\" />\n Loading...\n </>\n ) : (\n <>\n <ChevronDown className=\"w-4 h-4\" />\n Load more past subscriptions\n </>\n )}\n </button>\n </div>\n )}\n </div>\n </section>\n )}\n\n {/* Pooled Quotas Section - Only show for active/all views (pooled quotas are account-level) */}\n {viewMode !== 'past' && filteredPooledQuotas.length > 0 && (\n <section>\n <h2 className=\"text-lg font-semibold text-foreground mb-4 flex items-center gap-2\">\n <Layers className={`w-5 h-5 ${statusTokens.info.icon}`} />\n Pooled Quotas\n <span className=\"text-xs font-normal text-muted-foreground\">\n (Shared across all products)\n </span>\n </h2>\n <QuotaTable\n quotas={filteredPooledQuotas}\n showPlanColumn={false}\n onQuotaClick={setSelectedQuota}\n />\n </section>\n )}\n\n {/* Empty State - No quotas at all (when viewing active) */}\n {viewMode === 'active' &&\n activeSubscriptions.length === 0 &&\n pooledQuotas.length === 0 &&\n !hasActiveFilters && (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <Layers className=\"w-6 h-6 text-muted-foreground\" />\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">No active quotas found</h3>\n <p className=\"text-sm text-muted-foreground mt-1 text-center max-w-sm\">\n Quotas will appear here once you have active subscriptions or pooled allocations.\n </p>\n </div>\n )}\n\n {/* Empty State - No past subscriptions */}\n {viewMode === 'past' &&\n pastSubscriptions.length === 0 &&\n !hasActiveFilters &&\n !isPastSubscriptionsLoading && (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <Clock className=\"w-6 h-6 text-muted-foreground\" />\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">No past subscriptions</h3>\n <p className=\"text-sm text-muted-foreground mt-1 text-center max-w-sm\">\n You don't have any canceled, expired, or upgraded subscriptions yet.\n </p>\n <button\n onClick={() => setViewMode('active')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n View active subscriptions\n </button>\n </div>\n )}\n\n {/* Loading State for Past Subscriptions */}\n {viewMode === 'past' && isPastSubscriptionsLoading && (\n <div className=\"space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-muted animate-pulse rounded-lg\" />\n ))}\n </div>\n )}\n\n {/* Empty State - No results from search/filter */}\n {hasActiveFilters &&\n displayedSubscriptions.active.length === 0 &&\n displayedSubscriptions.past.length === 0 &&\n (viewMode === 'past' || filteredPooledQuotas.length === 0) && (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n {statusFilter !== 'all' && !searchQuery ? (\n <StatusIcon\n status={\n (statusFilter === 'healthy'\n ? 'HEALTHY'\n : statusFilter === 'warning'\n ? 'WARNING'\n : 'CRITICAL') as QuotaStatus\n }\n className=\"w-6 h-6\"\n />\n ) : (\n <Search className=\"w-6 h-6 text-muted-foreground\" />\n )}\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {statusFilter !== 'all' && !searchQuery\n ? `No ${statusFilter === 'critical' ? 'critical or exhausted' : statusFilter} quotas`\n : 'No matching quotas'}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1 text-center max-w-sm\">\n {statusFilter !== 'all' && !searchQuery ? (\n <>\n All your quotas are in a different status.\n {dashboard && (\n <span className=\"block mt-1\">\n You have {dashboard.healthyQuotas} healthy, {dashboard.warningQuotas} warning,\n and {dashboard.criticalQuotas + dashboard.exhaustedQuotas} critical quotas.\n </span>\n )}\n </>\n ) : (\n <>\n No quotas match your current search or filter criteria in{' '}\n {viewMode === 'past' ? 'past' : viewMode === 'all' ? 'any' : 'active'}{' '}\n subscriptions.\n {viewMode === 'active' && !statusFilter.includes('all') && (\n <span className=\"block mt-1\">\n Try switching to “Past” or “All” to search across more\n subscriptions.\n </span>\n )}\n </>\n )}\n </p>\n <div className=\"flex gap-2 mt-4\">\n <button\n onClick={clearFilters}\n className=\"px-4 py-2 text-sm font-medium border border-border rounded-md hover:bg-muted/50 transition-colors\"\n >\n {statusFilter !== 'all' ? 'Show all quotas' : 'Clear filters'}\n </button>\n {viewMode === 'active' && searchQuery && (\n <button\n onClick={() => setViewMode('all')}\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 Search all\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Quota Detail Modal */}\n {selectedQuota && (\n <QuotaDetailModal quota={selectedQuota} onClose={() => setSelectedQuota(null)} />\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;AAkDA,IAAM,MAA+D,EACnE,WACA,eAAY,gBACR;AACJ,SAAQ,GAAR;EACE,KAAK,YACH,QAAO,kBAAC,GAAD,EAAS,WAAW,GAAG,EAAU,GAAG,EAAa,MAAM,QAAU,CAAA;EAC1E,KAAK,WACH,QAAO,kBAAC,GAAD,EAAe,WAAW,GAAG,EAAU,GAAG,EAAa,MAAM,QAAU,CAAA;EAChF,KAAK,UACH,QAAO,kBAAC,GAAD,EAAa,WAAW,GAAG,EAAU,GAAG,EAAa,QAAQ,QAAU,CAAA;EAChF,KAAK,UACH,QAAO,kBAAC,GAAD,EAAa,WAAW,GAAG,EAAU,GAAG,EAAa,QAAQ,QAAU,CAAA;EAChF,QACE,QAAO,kBAAC,GAAD,EAAM,WAAW,GAAG,EAAU,GAAG,EAAa,QAAQ,QAAU,CAAA;;GAIvE,KAA4C,EAAE,gBAUhD,kBAAC,QAAD;CAAM,WAAW,2CATyB;EAC1C,WAAW,GAAG,EAAa,MAAM,GAAG,GAAG,EAAa,MAAM;EAC1D,UAAU,GAAG,EAAa,MAAM,GAAG,GAAG,EAAa,MAAM;EACzD,SAAS,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;EAC5D,SAAS,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;EAC5D,UAAU,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;EAC9D,CAGoE;WAAY;CAAc,CAAA,EAI3F,KAAgE,EAAE,eAAY,gBAAa;CAC/F,IAAM,IAAoB,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,EAAW,CAAC;AAUhE,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GACE,WAAW,mDAX8B;IAC7C,WAAW,EAAa,MAAM;IAC9B,UAAU,EAAa,MAAM;IAC7B,SAAS,EAAa,QAAQ;IAC9B,SAAS,EAAa,QAAQ;IAC9B,UAAU,EAAa,QAAQ;IAChC,CAK6E;GACxE,OAAO,EAAE,OAAO,GAAG,EAAkB,IAAI;GACzC,CAAA;EACE,CAAA;GAmBJ,KAA+B,EAAE,UAAO,UAAO,SAAM,UAAO,aAAU,iBAAc;CAUxF,IAAM,IARgB;EACpB,MAAM,EAAa;EACnB,SAAS,EAAa;EACtB,OAAO,EAAa;EACpB,KAAK,EAAa;EAClB,MAAM,EAAa;EACpB,CAE4B,IAEvB,IAAS;EACb,MAAM,GAAG,EAAO,GAAG,GAAG,EAAO;EAC7B,SAAS,GAAG,EAAO,GAAG,GAAG,EAAO;EAChC,OAAO,GAAG,EAAO,GAAG,GAAG,EAAO;EAC9B,KAAK,GAAG,EAAO,GAAG,GAAG,EAAO;EAC5B,MAAM,GAAG,EAAO,GAAG,GAAG,EAAO;EAC9B,EAGK,IAAe;EACnB,MAAM,oDAAoD,EAAa,KAAK,OAAO,QAAQ,WAAW,QAAQ;EAC9G,SAAS,oDAAoD,EAAa,QAAQ,OAAO,QAAQ,WAAW,QAAQ;EACpH,OAAO,oDAAoD,EAAa,QAAQ,OAAO,QAAQ,WAAW,QAAQ;EAClH,KAAK,oDAAoD,EAAa,MAAM,OAAO,QAAQ,WAAW,QAAQ;EAC9G,MAAM,oDAAoD,EAAa,QAAQ,OAAO,QAAQ,WAAW,QAAQ;EAClH,EAEK,IAAa;EACjB,MAAM,EAAa,KAAK;EACxB,SAAS,EAAa,QAAQ;EAC9B,OAAO,EAAa,QAAQ;EAC5B,KAAK,EAAa,MAAM;EACxB,MAAM,EAAa,QAAQ;EAC5B;AAED,QACE,kBAAC,UAAD;EACW;EACT,WAAW,yDAAyD,EAAO,GAAO,GAChF,IAAU,mCAAmC,GAC9C,GAAG,IAAW,EAAa,KAAS;YAJvC,CAME,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAW,EAAW;cAAS;IAAY,CAAA,EACjD,kBAAC,QAAD;IAAM,WAAU;cAAiC;IAAa,CAAA,CAC1D;MACN,kBAAC,KAAD;GAAG,WAAU;aAAsC;GAAU,CAAA,CACtD;;GAcP,KAAyC,EAAE,UAAO,cAAW,iBAAc;CAC/E,IAAM,IAAW,EAAM,cAAc,QAAQ,EAAM,aAAa,KAAK,CAAC,EAAM;AAE5E,QACE,kBAAC,MAAD;EACW;EACT,WAAU;YAFZ;GAKE,kBAAC,MAAD;IAAI,WAAU;cAAd,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAM,KAAK,QAAQ,MAAM,IAAI;KACzB,CAAA,EACN,EAAM,aAAa,KAClB,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACG,EAAM,YAAW,eACb;OAEN;;GAGJ,MAAc,KAAA,KACb,kBAAC,MAAD;IAAI,WAAU;cAA6D;IAAe,CAAA;GAI5F,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACG,EAAa,EAAM,UAAU;OAAC;OAC9B,IAAW,EAAa,EAAM,WAAW,GAAG;OAC5C,EAAM,OAAO,IAAI,EAAM,SAAS;OAC5B;SACP,kBAAC,GAAD;MAAa,YAAY,IAAW,EAAM,kBAAkB;MAAG,QAAQ,EAAM;MAAU,CAAA,CACnF;;IACH,CAAA;GAGL,kBAAC,MAAD;IAAI,WAAU;cACX,IAAW,EAAa,EAAM,WAAW,GAAG;IAC1C,CAAA;GAGL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,GAAD,EAAa,QAAQ,EAAM,QAAU,CAAA;IAClC,CAAA;GACF;;GAYH,MAAmC,EACvC,WACA,cACA,oBAAiB,IACjB,sBAEA,kBAAC,OAAD;CAAK,WAAU;WACb,kBAAC,SAAD;EAAO,WAAU;YAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;GAAI,WAAU;aAAd;IACE,kBAAC,MAAD;KAAI,WAAU;eAAmF;KAE5F,CAAA;IACJ,KACC,kBAAC,MAAD;KAAI,WAAU;eAAmF;KAE5F,CAAA;IAEP,kBAAC,MAAD;KAAI,WAAU;eAAmF;KAE5F,CAAA;IACL,kBAAC,MAAD;KAAI,WAAU;eAAmF;KAE5F,CAAA;IACL,kBAAC,MAAD;KAAI,WAAU;eAAmF;KAE5F,CAAA;IACF;MACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAO,KAAK,MACX,kBAAC,GAAD;GAES;GACP,WAAW,IAAiB,IAAY,KAAA;GACxC,eAAe,EAAa,EAAM;GAClC,EAJK,EAAM,KAIX,CACF,EACI,CAAA,CACF;;CACJ,CAAA,EAaF,MAAqD,EACzD,iBACA,iBACA,qBAAkB,SACd;CACJ,IAAM,CAAC,GAAY,KAAiB,EAAS,EAAgB,EAEvD,IAAuC;EAC3C,QAAQ,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;EAC3D,UAAU,GAAG,EAAa,KAAK,GAAG,GAAG,EAAa,KAAK;EACvD,UAAU,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;EAC7D,UAAU,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;EAC7D,SAAS,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;EAC5D,UAAU,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;EAC9D;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,UAAD;IACE,eAAe,EAAc,CAAC,EAAW;IACzC,WAAU;cAFZ,CAIE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAC5C,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAA8B;QAAmB,CAAA,EACjE,kBAAC,QAAD;QACE,WAAW,sDAAsD,EAAa,EAAa,OAAO,aAAa,KAAK,EAAa;kBAEhI,EAAa;QACT,CAAA,CACH;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAa,iBAAiB;QAAO;QACrC,EAAa,iBAAiB,WAAW,IAAU,KAAN;QAC5C;SACA;QACF;QAEJ,EADD,IACE,IAEA,GAFD,EAAa,WAAU,iCAAkC,CAEC,CAErD;;GAER,KAAc,EAAa,iBAAiB,SAAS,KACpD,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,IAAD;KACE,QAAQ,EAAa;KACrB,WAAW,EAAa,YAAY,EAAa,aAAa,KAAA;KAC9D,gBAAgB,CAAC,EAAE,EAAa,YAAY,EAAa;KAC3C;KACd,CAAA;IACE,CAAA;GAGP,KAAc,EAAa,iBAAiB,WAAW,KACtD,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eAAgC;KAA2C,CAAA;IACpF,CAAA;GAEJ;;GAQJ,MAA+C,EAAE,UAAO,iBAAc;CAE1E,IAAM,CAAC,GAAa,KAAkB,EAA+B,CAAC,KAAK,CAAC,EACtE,IAAgB,EAAY,EAAY,SAAS,MAAM,MACvD,IAAc,EAAY,QAC1B,EAAE,mBAAgB,GAAY,EAG9B,IAAoB,EAAM,iBAAiB,IAAI,IAC/C,EAAE,WAAQ,gBAAa,eAAY,iBAAc,EACrD,GACA,IACA,GACA,EACD,EAEK,UAAmB;AACvB,EAAI,KAAY,GAAgB,MAAS,CAAC,GAAG,GAAM,EAAW,CAAC;IAG3D,UAAmB;AACvB,EAAI,EAAY,SAAS,KAAG,GAAgB,MAAS,EAAK,MAAM,GAAG,GAAG,CAAC;;AAGzE,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;GAAoC,SAAS;GAAW,CAAA,EACvE,kBAAC,OAAD;GACE,WAAU;GACV,MAAK;GACL,cAAW;GACX,mBAAgB;aAJlB;IAOE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,IAAD;OAAY,QAAQ,EAAM;OAAQ,WAAU;OAAY,CAAA,EACxD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,IAAG;OAAqB,WAAU;iBACnC,EAAM,KAAK,QAAQ,MAAM,IAAI;OAC3B,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAM;QAAW;QAAkB,EAAM,eAAe,IAAU,KAAN;QAC3D;SACA,EAAA,CAAA,CACF;SACN,kBAAC,UAAD;MACE,SAAS;MACT,WAAU;MACV,cAAW;gBAEX,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MAClB,CAAA,CACL;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAgC;SAAY,CAAA,EAC5D,kBAAC,GAAD,EAAa,QAAQ,EAAM,QAAU,CAAA,CACjC;;OACN,kBAAC,GAAD;QAAa,YAAY,EAAM;QAAiB,QAAQ,EAAM;QAAU,CAAA;OACxE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAhB;UACG,EAAa,EAAM,UAAU;UAAC;UAAG;UACjC,EAAM,cAAc,QAAQ,EAAM,aAAa,IAC5C,EAAa,EAAM,WAAW,GAC9B;UACH,EAAM,QAAQ,IAAI,EAAM;UACpB;YACP,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAM,cAAc,QAAQ,EAAM,aAAa,IAC5C,GAAG,KAAK,MAAM,EAAM,gBAAgB,CAAC,UACrC;SACC,CAAA,CACH;;OACF;SAGL,EAAM,aAAa,KAClB,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA2C;OAAoB,CAAA,EAC7E,kBAAC,OAAD;OAAK,WAAU;iBACZ,CAAC,GAAG,EAAM,iBAAiB,CACzB,MAAM,GAAG,OAEM,EAAE,UAAU,IAAI,KAAK,EAAE,QAAQ,CAAC,SAAS,GAAG,aAC5C,EAAE,UAAU,IAAI,KAAK,EAAE,QAAQ,CAAC,SAAS,GAAG,UAE1D,CACD,KAAK,MAAe;QACnB,IAAM,IAAa,EAAW,UAAU,IAAI,KAAK,EAAW,QAAQ,GAAG,MACjE,oBAAM,IAAI,MAAM,EAChB,IAAY,KAAc,IAAa,GACvC,IAAkB,IACpB,KAAK,MAAM,EAAW,SAAS,GAAG,EAAI,SAAS,KAAK,OAAU,KAAK,KAAM,GACzE,MACE,IACJ,MAAoB,QAAQ,IAAkB,KAAK,KAAmB;AAExE,eACE,kBAAC,OAAD;SAEE,WAAW,iDACT,IACI,gBACA,IACE,EAAa,QAAQ,KACrB;mBAPV,CAUE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACG,KAAkB,CAAC,KAClB,kBAAC,QAAD,EACE,WAAW,wBAAwB,EAAa,QAAQ,IAAI,iBAC5D,CAAA,EAEJ,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YACE,WAAW,WAAW,IAAY,0BAA0B;sBAE3D,IACG,EAAW,mBAAmB,SAAS;aACrC,OAAO;aACP,KAAK;aACL,MAAM;aACP,CAAC,GACF;YACC,CAAA,EACP,kBAAC,QAAD;YACE,WAAW,WACT,IACI,qBACA,IACE,EAAa,QAAQ,OACrB;sBAGP,IACG,YACA,MAAoB,OAMlB,kBALA,MAAoB,IAClB,kBACA,MAAoB,IAClB,qBACA,GAAG,EAAgB;YAExB,CAAA,CACH;aACF;aACN,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,QAAD;WACE,WAAW,yBAAyB,IAAY,0BAA0B;qBAD5E,CAGG,EAAa,EAAW,KAAK,EAC9B,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aACG;aACA,EAAW,SAAS,QAAQ,EAAW,QAAQ,IAC5C,EAAa,EAAW,MAAM,GAC9B;aACH,EAAW,QAAQ,IAAI,EAAW;aAC9B;cACF;;UACH,CAAA,CACF;WA9DC,EAAW,GA8DZ;SAER;OACA,CAAA,CACF;QAEJ;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,WAAU;iBAA2C;OAAiB,CAAA;MAEzE,IACC,kBAAC,OAAD;OAAK,WAAU;iBACZ;QAAC;QAAG;QAAG;QAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAAa,WAAU,uCAAwC,EAArD,EAAqD,CAC/D;OACE,CAAA,GACJ,EAAO,WAAW,IACpB,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAA0B,CAAA,GAExF,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAO,KAAK,MACX,kBAAC,OAAD;QAEE,WAAU;kBAFZ,CAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAM,eAAe;UACpB,CAAA,EACJ,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACG,EAAmB,EAAM,UAAU;WAAC;WAAI,EAAe,EAAM,UAAU;WACtE;YACA;YACN,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAa,EAAM,OAAO;SACtB,CAAA,CACH;UAdC,EAAM,GAcP,CACN;OACE,CAAA;OAIN,EAAY,SAAS,KAAK,MAC1B,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,SAAS;SACT,UAAU,EAAY,WAAW;SACjC,WAAU;SACV,cAAW;mBAEX,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;SAC5B,CAAA;QACT,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CAAgD,SAAM,EAAmB;;QACzE,kBAAC,UAAD;SACE,SAAS;SACT,UAAU,CAAC;SACX,WAAU;SACV,cAAW;mBAEX,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;SAC7B,CAAA;QACL;;MAEJ;;IAGN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,UAAD;MACE,SAAS;MACT,WAAU;gBACX;MAEQ,CAAA;KACL,CAAA;IACF;KACF;;GAQG,UAAsB;CACjC,IAAM,EAAE,SAAM,IAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,IAAc,GAAuB,EAUrC,CAAC,GAA0B,KAA+B,QAPH;AACvD,eAAO,SAAW,KAEtB,QADkB,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAC5C,IAAI,mBAAmB,IAAI,KAAA;GAM7C,EACK,CAAC,GAAe,KAAoB,EAAiC,KAAK,EAG1E,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAc,KAAmB,EAAuB,MAAM,EAI/D,CAAC,GAAU,KAAe,EAAmB,SAAS,EAGtD,IAAsB,EAAiB,EAAY,EAGnD,EAAE,oBAAiB,WAAW,OAAsB,GAA4B,EAGhF,MAA2B,EAAgB,MAAM,MAAM,EAAE,UAAU,IAAI,EAAgB,KACzF,IAEE,IAA4B,KAA4B,IAGxD,EACJ,cACA,iBACA,wBACA,sBACA,eACA,+BACA,UACA,YAEA,+BACA,6BACA,sBACA,+BACE,EAAqB;EACvB,kBAAkB;EAClB,aAAa,KAAuB,KAAA;EACpC;EACD,CAAC,EAGI,IAAsB,GACzB,MACK,MAAiB,QAAc,KAC/B,MAAiB,YAAkB,EAAM,WAAW,YACpD,MAAiB,YAAkB,EAAM,WAAW,YACpD,MAAiB,aACZ,EAAM,WAAW,cAAc,EAAM,WAAW,cAClD,IAET,CAAC,EAAa,CACf,EAGK,IAAuB,QACpB,EAAa,OAAO,EAAoB,EAC9C,CAAC,GAAc,EAAoB,CAAC,EAEjC,IAA8B,QAC9B,MAAiB,QACZ,IAEF,EACJ,KAAK,OAAS;EACb,GAAG;EACH,kBAAkB,EAAI,iBAAiB,OAAO,EAAoB;EACnE,EAAE,CACF,QAAQ,MAAQ,EAAI,iBAAiB,SAAS,EAAE,EAClD;EAAC;EAAqB;EAAc;EAAoB,CAAC,EAEtD,IAA4B,QAC5B,MAAiB,QACZ,IAEF,EACJ,KAAK,OAAS;EACb,GAAG;EACH,kBAAkB,EAAI,iBAAiB,OAAO,EAAoB;EACnE,EAAE,CACF,QAAQ,MAAQ,EAAI,iBAAiB,SAAS,EAAE,EAClD;EAAC;EAAmB;EAAc;EAAoB,CAAC,EAGpD,IAAyB,QACzB,MAAa,WACR;EAAE,QAAQ;EAA6B,MAAM,EAAE;EAAE,GAEtD,MAAa,SACR;EAAE,QAAQ,EAAE;EAAE,MAAM;EAA2B,GAGjD;EAAE,QAAQ;EAA6B,MAAM;EAA2B,EAC9E;EAAC;EAAU;EAA6B;EAA0B,CAAC,EAEhE,IAAmB,MAAgB,MAAM,MAAiB,OAC1D,KAAc,MAAwB,GAEtC,WAAqB;AAEzB,EADA,EAAe,GAAG,EAClB,EAAgB,MAAM;IAIlB,KAAsB,MAAyB;AACnD,KAAiB,MAAU,MAAS,IAAS,QAAQ,EAAQ;;AAI/D,KAAI,CAAC,EAAY,aACf,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAe,WAAU,iCAAkC,CAAA;KACvD,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eAAwC;KAAkB,CAAA;IACxE,kBAAC,KAAD;KAAG,WAAU;eAAyC;KAElD,CAAA;IACA;;EACF,CAAA;AAKV,MAAK,MAAqB,OAAc,CAAC,EACvC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAK,WAAU,2CAA4C,CAAA;GAC3D,kBAAC,OAAD;IAAK,WAAU;cACZ;KAAC;KAAG;KAAG;KAAG;KAAE,CAAC,KAAK,MACjB,kBAAC,OAAD,EAAa,WAAU,0CAA2C,EAAxD,EAAwD,CAClE;IACE,CAAA;GACN,kBAAC,OAAD,EAAK,WAAU,0CAA2C,CAAA;GACtD;;AAKV,KAAI,EACF,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAe,WAAU,4BAA6B,CAAA;KAClD,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eAAwC;KAA8B,CAAA;IACpF,kBAAC,KAAD;KAAG,WAAU;eAAiC,EAAM;KAAY,CAAA;IAChE,kBAAC,UAAD;KACE,eAAe,GAAS;KACxB,WAAU;eACX;KAEQ,CAAA;IACL;;EACF,CAAA;CAIV,IAAM,KAAkB,EAAgB,MAAM,MAAM,EAAE,OAAO,EAA0B;AAEvF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,uBAAuB,QAAQ;KAChC,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eAAqC;KAE9C,CAAA,CACA,EAAA,CAAA,EAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEG,EAAgB,SAAS,KACxB,kBAAC,UAAD;MACE,OAAO,KAA6B;MACpC,WAAW,MAAM,EAA4B,EAAE,OAAO,MAAM;MAC5D,WAAU;gBAET,EAAgB,KAAK,MACpB,kBAAC,UAAD;OAAyB,OAAO,EAAQ;iBACrC,EAAQ;OACF,EAFI,EAAQ,GAEZ,CACT;MACK,CAAA,EAIX,kBAAC,UAAD;MACE,eAAe,GAAS;MACxB,WAAU;MACV,OAAM;gBAEN,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MAC1B,CAAA,CACL;OACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAQ,WAAU,0EAA2E,CAAA;OAC7F,kBAAC,SAAD;QACE,MAAK;QACL,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,aAAa,UAAU,MAAa,SAAS,SAAS,MAAa,QAAQ,QAAQ,SAAS;QAC5F,WAAU;QACV,CAAA;QACA,KAAe,OACf,kBAAC,UAAD;QACE,eAAe,EAAe,GAAG;QACjC,WAAU;kBAET,KACC,kBAAC,GAAD,EAAW,WAAU,8CAA+C,CAAA,GAEpE,kBAAC,GAAD,EAAG,WAAU,iCAAkC,CAAA;QAE1C,CAAA;OAEP;;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,eAAe,EAAY,SAAS;OACpC,WAAW,2EACT,MAAa,WACT,wDACA;iBALR,CAQE,kBAAC,GAAD,EAAS,WAAU,eAAgB,CAAA,EAAA,SAE5B;UACT,kBAAC,UAAD;OACE,eAAe,EAAY,OAAO;OAClC,UAAU,KAA8B,MAAa;OACrD,WAAW,2EACT,MAAa,SACT,wDACA,8CACL,GAAG,KAA8B,MAAa,SAAS,eAAe;iBAPzE,CASG,KAA8B,MAAa,SAC1C,kBAAC,GAAD,EAAW,WAAU,4BAA6B,CAAA,GAElD,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA,EACjC,OAEK;SACL;;KAGL,KACC,kBAAC,UAAD;MACE,SAAS;MACT,WAAU;gBAFZ,CAIE,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA,EAAA,gBAElB;;KAEP;;GAGL,MACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KAA+C;KAC3B;KAClB,kBAAC,QAAD;MAAM,WAAU;gBAA+B,GAAgB;MAAY,CAAA;KACvE;;GAIP,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAU;MACjB,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;MACpC,OAAM;MACN,UAAU,MAAiB;MAC3B,eAAe,EAAgB,MAAM;MACrC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAU;MACjB,MAAM,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;MACzC,OAAM;MACN,UAAU,MAAiB;MAC3B,eAAe,EAAmB,UAAU;MAC5C,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAU;MACjB,MAAM,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;MACzC,OAAM;MACN,UAAU,MAAiB;MAC3B,eAAe,EAAmB,UAAU;MAC5C,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAU,iBAAiB,EAAU;MAC5C,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;MAC3C,OAAM;MACN,UAAU,MAAiB;MAC3B,eAAe,EAAmB,WAAW;MAC7C,CAAA;KACE;;IAIN,KAAoB,MAAa,aACjC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD,EAAA,UAAA;MAAM;MACI;MACP,EAAuB,OAAO,QAAQ,GAAK,MAAM,IAAM,EAAE,iBAAiB,QAAQ,EAAE,GACnF,EAAuB,KAAK,QAAQ,GAAK,MAAM,IAAM,EAAE,iBAAiB,QAAQ,EAAE,IACjF,MAAa,SAAuC,IAA9B,EAAqB;MAAa;MAAI;MAE1D,EAAA,CAAA;KACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,MAAa,WAAW,WAAW,MAAa,SAAS,SAAS,OAAM,iBACpE;;KACN,MAAiB,SAChB,kBAAC,QAAD;MAAM,WAAU;gBACb,MAAiB,aAAa,yBAAyB;MACnD,CAAA;KAER,KACC,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OAAuD;OAC7C;OAAY;OACf;;KAEL;;GAIP,EAAuB,OAAO,SAAS,KACtC,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAAd,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAC3C,MAAa,QAAQ,+BAA+B,sBAClD;OACL,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAuB,OAAO,KAAK,MAClC,kBAAC,IAAD;KAEgB;KACd,cAAc;KACd,iBAAiB;KACjB,EAJK,EAAa,GAIlB,CACF;IACE,CAAA,CACE,EAAA,CAAA;GAIX,EAAuB,KAAK,SAAS,KACpC,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAAd;KACE,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA;;KAEnD,kBAAC,QAAD;MAAM,WAAU;gBAA4C;MAErD,CAAA;KACN,KAAyB,KACxB,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACG,EAAuB,KAAK;OAC5B,IAA2B,MAAM;OAAG;OAAK;OACrC;;KAEN;OACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAuB,KAAK,KAAK,MAChC,kBAAC,IAAD;KAEgB;KACd,cAAc;KACd,iBAAiB,MAAa;KAC9B,EAJK,EAAa,GAIlB,CACF,EAGD,KACC,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,UAAD;MACE,SAAS;MACT,UAAU;MACV,WAAU;gBAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAW,WAAU,wBAAyB,CAAA,EAAA,aAE7C,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA,EAAA,+BAElC,EAAA,CAAA;MAEE,CAAA;KACL,CAAA,CAEJ;MACE,EAAA,CAAA;GAIX,MAAa,UAAU,EAAqB,SAAS,KACpD,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAAd;KACE,kBAAC,GAAD,EAAQ,WAAW,WAAW,EAAa,KAAK,QAAU,CAAA;;KAE1D,kBAAC,QAAD;MAAM,WAAU;gBAA4C;MAErD,CAAA;KACJ;OACL,kBAAC,IAAD;IACE,QAAQ;IACR,gBAAgB;IAChB,cAAc;IACd,CAAA,CACM,EAAA,CAAA;GAIX,MAAa,YACZ,EAAoB,WAAW,KAC/B,EAAa,WAAW,KACxB,CAAC,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAQ,WAAU,iCAAkC,CAAA;MAChD,CAAA;KACN,kBAAC,MAAD;MAAI,WAAU;gBAAsC;MAA2B,CAAA;KAC/E,kBAAC,KAAD;MAAG,WAAU;gBAA0D;MAEnE,CAAA;KACA;;GAIT,MAAa,UACZ,EAAkB,WAAW,KAC7B,CAAC,KACD,CAAC,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA;MAC/C,CAAA;KACN,kBAAC,MAAD;MAAI,WAAU;gBAAsC;MAA0B,CAAA;KAC9E,kBAAC,KAAD;MAAG,WAAU;gBAA0D;MAEnE,CAAA;KACJ,kBAAC,UAAD;MACE,eAAe,EAAY,SAAS;MACpC,WAAU;gBACX;MAEQ,CAAA;KACL;;GAIT,MAAa,UAAU,KACtB,kBAAC,OAAD;IAAK,WAAU;cACZ;KAAC;KAAG;KAAG;KAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAAa,WAAU,0CAA2C,EAAxD,EAAwD,CAClE;IACE,CAAA;GAIP,KACC,EAAuB,OAAO,WAAW,KACzC,EAAuB,KAAK,WAAW,MACtC,MAAa,UAAU,EAAqB,WAAW,MACtD,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACZ,MAAiB,SAAS,CAAC,IAC1B,kBAAC,IAAD;OACE,QACG,MAAiB,YACd,YACA,MAAiB,YACf,YACA;OAER,WAAU;OACV,CAAA,GAEF,kBAAC,GAAD,EAAQ,WAAU,iCAAkC,CAAA;MAElD,CAAA;KACN,kBAAC,MAAD;MAAI,WAAU;gBACX,MAAiB,SAAS,CAAC,IACxB,MAAM,MAAiB,aAAa,0BAA0B,EAAa,WAC3E;MACD,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBACV,MAAiB,SAAS,CAAC,IAC1B,kBAAA,GAAA,EAAA,UAAA,CAAE,8CAEC,KACC,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QAA6B;QACjB,EAAU;QAAc;QAAW,EAAU;QAAc;QAChE,EAAU,iBAAiB,EAAU;QAAgB;QACrD;SAER,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA;OAAE;OAC0D;OACzD,MAAa,SAAS,SAAS,MAAa,QAAQ,QAAQ;OAAU;OAAI;OAE1E,MAAa,YAAY,CAAC,EAAa,SAAS,MAAM,IACrD,kBAAC,QAAD;QAAM,WAAU;kBAAa;QAGtB,CAAA;OAER,EAAA,CAAA;MAEH,CAAA;KACJ,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,SAAS;OACT,WAAU;iBAET,MAAiB,QAA4B,kBAApB;OACnB,CAAA,EACR,MAAa,YAAY,KACxB,kBAAC,UAAD;OACE,eAAe,EAAY,MAAM;OACjC,WAAU;iBACX;OAEQ,CAAA,CAEP;;KACF;;GAIT,KACC,kBAAC,IAAD;IAAkB,OAAO;IAAe,eAAe,EAAiB,KAAK;IAAI,CAAA;GAE/E"}
|
|
1
|
+
{"version":3,"file":"UsagePage.js","names":[],"sources":["../../../../../src/billing/modules/usage/pages/UsagePage.tsx"],"sourcesContent":["/**\n * Usage Module - Enhanced Usage Page\n * Displays usage dashboard with billing account selector, aggregated quotas by subscription,\n * pooled quotas, and lazy-loaded past subscriptions\n */\n\nimport { useState, useMemo, useDeferredValue, useCallback, type FC } from 'react';\nimport {\n ChevronDown,\n ChevronRight,\n AlertTriangle,\n CheckCircle,\n XCircle,\n AlertCircle,\n Layers,\n Package,\n Clock,\n RefreshCw,\n Info,\n ChevronLeft,\n X,\n Search,\n} from 'lucide-react';\nimport {\n useEnhancedUsagePage,\n useBillingAccountsForUsage,\n useQuotaUsageDetails,\n type AggregatedQuota,\n type SubscriptionWithAggregatedQuotas,\n} from '../hooks/useUsage';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { formatNumber, formatRelativeTime, formatDateTime } from '../../../shared/utils/format';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport type { QuotaStatus } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface QuotaDetailModalProps {\n quota: AggregatedQuota;\n onClose: () => void;\n}\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\nconst StatusIcon: FC<{ status: QuotaStatus; className?: string }> = ({\n status,\n className = 'w-4 h-4',\n}) => {\n switch (status) {\n case 'EXHAUSTED':\n return <XCircle className={`${className} ${statusTokens.error.icon}`} />;\n case 'CRITICAL':\n return <AlertTriangle className={`${className} ${statusTokens.error.icon}`} />;\n case 'WARNING':\n return <AlertCircle className={`${className} ${statusTokens.warning.icon}`} />;\n case 'HEALTHY':\n return <CheckCircle className={`${className} ${statusTokens.success.icon}`} />;\n default:\n return <Info className={`${className} ${statusTokens.neutral.icon}`} />;\n }\n};\n\nconst StatusBadge: FC<{ status: QuotaStatus }> = ({ status }) => {\n const colors: Record<QuotaStatus, string> = {\n EXHAUSTED: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n CRITICAL: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n WARNING: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n HEALTHY: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n INACTIVE: `${statusTokens.neutral.bg} ${statusTokens.neutral.text}`,\n };\n\n return (\n <span className={`px-2 py-0.5 text-xs font-medium rounded ${colors[status]}`}>{status}</span>\n );\n};\n\nconst ProgressBar: FC<{ percentage: number; status: QuotaStatus }> = ({ percentage, status }) => {\n const clampedPercentage = Math.min(100, Math.max(0, percentage));\n\n const barColors: Record<QuotaStatus, string> = {\n EXHAUSTED: statusTokens.error.dot,\n CRITICAL: statusTokens.error.dot,\n WARNING: statusTokens.warning.dot,\n HEALTHY: statusTokens.success.dot,\n INACTIVE: statusTokens.neutral.dot,\n };\n\n return (\n <div className=\"w-full bg-muted rounded-full h-2.5 overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all duration-300 ${barColors[status]}`}\n style={{ width: `${clampedPercentage}%` }}\n />\n </div>\n );\n};\n\n// ============================================================================\n// Stat Card Component\n// ============================================================================\n\ntype StatusFilter = 'all' | 'healthy' | 'warning' | 'critical';\n\ninterface StatCardProps {\n label: string;\n value: string | number;\n icon: React.ReactNode;\n color: 'blue' | 'emerald' | 'amber' | 'red' | 'gray';\n isActive?: boolean;\n onClick?: () => void;\n}\n\nconst StatCard: FC<StatCardProps> = ({ label, value, icon, color, isActive, onClick }) => {\n // Map color prop to semantic status tokens\n const colorToStatus = {\n blue: statusTokens.info,\n emerald: statusTokens.success,\n amber: statusTokens.warning,\n red: statusTokens.error,\n gray: statusTokens.neutral,\n };\n\n const tokens = colorToStatus[color];\n\n const colors = {\n blue: `${tokens.bg} ${tokens.border}`,\n emerald: `${tokens.bg} ${tokens.border}`,\n amber: `${tokens.bg} ${tokens.border}`,\n red: `${tokens.bg} ${tokens.border}`,\n gray: `${tokens.bg} ${tokens.border}`,\n };\n\n // Active ring colors - using semantic dot color for consistency\n const activeColors = {\n blue: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.info.border.replace('border-', 'ring-')}`,\n emerald: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.success.border.replace('border-', 'ring-')}`,\n amber: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.warning.border.replace('border-', 'ring-')}`,\n red: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.error.border.replace('border-', 'ring-')}`,\n gray: `ring-2 ring-offset-2 dark:ring-offset-background ${statusTokens.neutral.border.replace('border-', 'ring-')}`,\n };\n\n const iconColors = {\n blue: statusTokens.info.icon,\n emerald: statusTokens.success.icon,\n amber: statusTokens.warning.icon,\n red: statusTokens.error.icon,\n gray: statusTokens.neutral.icon,\n };\n\n return (\n <button\n onClick={onClick}\n className={`border rounded-lg p-4 w-full text-left transition-all ${colors[color]} ${\n onClick ? 'cursor-pointer hover:shadow-md' : ''\n } ${isActive ? activeColors[color] : ''}`}\n >\n <div className=\"flex items-center gap-2 mb-2\">\n <span className={iconColors[color]}>{icon}</span>\n <span className=\"text-sm text-muted-foreground\">{label}</span>\n </div>\n <p className=\"text-2xl font-bold text-foreground\">{value}</p>\n </button>\n );\n};\n\n// ============================================================================\n// Quota Table Components\n// ============================================================================\n\ninterface QuotaTableRowProps {\n quota: AggregatedQuota;\n planLabel?: string;\n onClick: () => void;\n}\n\nconst QuotaTableRow: FC<QuotaTableRowProps> = ({ quota, planLabel, onClick }) => {\n const hasLimit = quota.totalLimit != null && quota.totalLimit > 0 && !quota.noLimit;\n\n return (\n <tr\n onClick={onClick}\n className=\"cursor-pointer hover:bg-muted/40 transition-colors border-b border-border last:border-b-0\"\n >\n {/* Quota name */}\n <td className=\"px-4 py-3 min-w-[180px]\">\n <span className=\"text-sm font-medium text-primary hover:underline capitalize\">\n {quota.name.replace(/_/g, ' ')}\n </span>\n {quota.quotaCount > 1 && (\n <span className=\"block text-xs text-muted-foreground mt-0.5\">\n {quota.quotaCount} assignments\n </span>\n )}\n </td>\n\n {/* Plan / type */}\n {planLabel !== undefined && (\n <td className=\"px-4 py-3 text-sm text-muted-foreground whitespace-nowrap\">\n {planLabel}\n </td>\n )}\n\n {/* Current usage: value + inline progress bar */}\n <td className=\"px-4 py-3 min-w-[180px]\">\n <div className=\"space-y-1.5\">\n <span className=\"text-sm text-foreground tabular-nums\">\n {formatNumber(quota.totalUsed)} / \n {hasLimit ? formatNumber(quota.totalLimit) : '∞'}\n {quota.unit ? ` ${quota.unit}` : ''}\n </span>\n <ProgressBar\n percentage={hasLimit ? quota.usagePercentage : 0}\n status={quota.status}\n />\n </div>\n </td>\n\n {/* Limit */}\n <td className=\"px-4 py-3 text-sm text-muted-foreground tabular-nums whitespace-nowrap\">\n {hasLimit ? formatNumber(quota.totalLimit) : '∞'}\n </td>\n\n {/* Status */}\n <td className=\"px-4 py-3\">\n <StatusBadge status={quota.status} />\n </td>\n </tr>\n );\n};\n\ninterface QuotaTableProps {\n quotas: AggregatedQuota[];\n /** When provided, renders a \"Plan\" column with this value per row */\n planLabel?: string;\n showPlanColumn?: boolean;\n onQuotaClick: (quota: AggregatedQuota) => void;\n}\n\nconst QuotaTable: FC<QuotaTableProps> = ({\n quotas,\n planLabel,\n showPlanColumn = true,\n onQuotaClick,\n}) => (\n <div className=\"overflow-x-auto rounded-lg border border-border bg-card\">\n <table className=\"w-full text-left\">\n <thead>\n <tr className=\"border-b border-border bg-muted/30\">\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Quotas\n </th>\n {showPlanColumn && (\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Plan\n </th>\n )}\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Current Usage\n </th>\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Limit\n </th>\n <th className=\"px-4 py-2.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider\">\n Status\n </th>\n </tr>\n </thead>\n <tbody>\n {quotas.map((quota) => (\n <QuotaTableRow\n key={quota.name}\n quota={quota}\n planLabel={showPlanColumn ? planLabel : undefined}\n onClick={() => onQuotaClick(quota)}\n />\n ))}\n </tbody>\n </table>\n </div>\n);\n\n// ============================================================================\n// Subscription Section Component\n// ============================================================================\n\ninterface SubscriptionSectionProps {\n subscription: SubscriptionWithAggregatedQuotas;\n onQuotaClick: (quota: AggregatedQuota) => void;\n defaultExpanded?: boolean;\n}\n\nconst SubscriptionSection: FC<SubscriptionSectionProps> = ({\n subscription,\n onQuotaClick,\n defaultExpanded = true,\n}) => {\n const [isExpanded, setIsExpanded] = useState(defaultExpanded);\n\n const statusColors: Record<string, string> = {\n active: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n trialing: `${statusTokens.info.bg} ${statusTokens.info.text}`,\n past_due: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n canceled: `${statusTokens.neutral.bg} ${statusTokens.neutral.text}`,\n expired: `${statusTokens.neutral.bg} ${statusTokens.neutral.text}`,\n upgraded: `${statusTokens.primary.bg} ${statusTokens.primary.text}`,\n };\n\n return (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <button\n onClick={() => setIsExpanded(!isExpanded)}\n className=\"w-full flex items-center justify-between p-4 hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <Package className=\"w-5 h-5 text-primary\" />\n <div className=\"text-left\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-foreground\">Subscription</span>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded capitalize ${statusColors[subscription.status.toLowerCase()] || statusColors.active}`}\n >\n {subscription.status}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {subscription.aggregatedQuotas.length} quota type\n {subscription.aggregatedQuotas.length !== 1 ? 's' : ''}\n </p>\n </div>\n </div>\n {isExpanded ? (\n <ChevronDown className=\"w-5 h-5 text-muted-foreground\" />\n ) : (\n <ChevronRight className=\"w-5 h-5 text-muted-foreground\" />\n )}\n </button>\n\n {isExpanded && subscription.aggregatedQuotas.length > 0 && (\n <div className=\"border-t border-border\">\n <QuotaTable\n quotas={subscription.aggregatedQuotas}\n planLabel={subscription.planName || subscription.productId || undefined}\n showPlanColumn={!!(subscription.planName || subscription.productId)}\n onQuotaClick={onQuotaClick}\n />\n </div>\n )}\n\n {isExpanded && subscription.aggregatedQuotas.length === 0 && (\n <div className=\"border-t border-border p-6 text-center\">\n <p className=\"text-sm text-muted-foreground\">No quotas assigned to this subscription</p>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Quota Detail Modal Component\n// ============================================================================\n\nconst QuotaDetailModal: FC<QuotaDetailModalProps> = ({ quota, onClose }) => {\n // Cursor stack: [null] = page 1, [null, c1] = page 2, etc.\n const [cursorStack, setCursorStack] = useState<Array<string | null>>([null]);\n const currentCursor = cursorStack[cursorStack.length - 1] ?? null;\n const currentPage = cursorStack.length;\n const { workspaceId } = useBilling();\n\n // Get usage details for the first quota assignment directly from activity-svc\n const firstAssignmentId = quota.quotaAssignments[0]?.id;\n const { usages, hasNextPage, nextCursor, isLoading } = useQuotaUsageDetails(\n firstAssignmentId,\n 10,\n currentCursor,\n workspaceId\n );\n\n const handleNext = () => {\n if (nextCursor) setCursorStack((prev) => [...prev, nextCursor]);\n };\n\n const handlePrev = () => {\n if (cursorStack.length > 1) setCursorStack((prev) => prev.slice(0, -1));\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={onClose} />\n <div\n className=\"relative bg-background border border-border rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-auto\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"quota-detail-title\"\n >\n {/* Header */}\n <div className=\"sticky top-0 bg-background border-b border-border p-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <StatusIcon status={quota.status} className=\"w-5 h-5\" />\n <div>\n <h2 id=\"quota-detail-title\" className=\"text-lg font-semibold capitalize\">\n {quota.name.replace(/_/g, ' ')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {quota.quotaCount} quota assignment{quota.quotaCount !== 1 ? 's' : ''}\n </p>\n </div>\n </div>\n <button\n onClick={onClose}\n className=\"p-1 rounded-md hover:bg-muted/50 transition-colors\"\n aria-label=\"Close quota details\"\n >\n <X className=\"w-5 h-5\" />\n </button>\n </div>\n\n {/* Summary */}\n <div className=\"p-4 border-b border-border\">\n <div className=\"mb-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Usage</span>\n <StatusBadge status={quota.status} />\n </div>\n <ProgressBar percentage={quota.usagePercentage} status={quota.status} />\n <div className=\"flex items-center justify-between mt-2 text-sm\">\n <span className=\"font-medium\">\n {formatNumber(quota.totalUsed)} /{' '}\n {quota.totalLimit != null && quota.totalLimit > 0\n ? formatNumber(quota.totalLimit)\n : '∞'}\n {quota.unit && ` ${quota.unit}`}\n </span>\n <span className=\"text-muted-foreground\">\n {quota.totalLimit != null && quota.totalLimit > 0\n ? `${Math.round(quota.usagePercentage)}% used`\n : 'Unlimited'}\n </span>\n </div>\n </div>\n\n {/* Quota Assignments Breakdown - sorted by expiry date (earliest first) */}\n {quota.quotaCount > 1 && (\n <div className=\"mt-4\">\n <h3 className=\"text-sm font-medium text-foreground mb-3\">Quota Breakdown</h3>\n <div className=\"border border-border rounded-lg overflow-hidden divide-y divide-border\">\n {[...quota.quotaAssignments]\n .sort((a, b) => {\n // Sort by endtime ascending (earliest expiry first)\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((assignment) => {\n const expiryDate = assignment.endtime ? new Date(assignment.endtime) : null;\n const now = new Date();\n const isExpired = expiryDate && expiryDate < now;\n const daysUntilExpiry = expiryDate\n ? Math.ceil((expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000))\n : null;\n const isExpiringSoon =\n daysUntilExpiry !== null && daysUntilExpiry > 0 && daysUntilExpiry <= 7;\n\n return (\n <div\n key={assignment.id}\n className={`flex items-center justify-between px-3 py-2.5 ${\n isExpired\n ? 'bg-muted/30'\n : isExpiringSoon\n ? statusTokens.warning.bg\n : 'bg-background'\n }`}\n >\n <div className=\"flex items-center gap-2\">\n {isExpiringSoon && !isExpired && (\n <span\n className={`w-2 h-2 rounded-full ${statusTokens.warning.dot} animate-pulse`}\n />\n )}\n <div className=\"flex flex-col\">\n <span\n className={`text-sm ${isExpired ? 'text-muted-foreground' : 'text-foreground'}`}\n >\n {expiryDate\n ? expiryDate.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n : 'No expiry'}\n </span>\n <span\n className={`text-xs ${\n isExpired\n ? 'text-destructive'\n : isExpiringSoon\n ? statusTokens.warning.text\n : 'text-muted-foreground'\n }`}\n >\n {isExpired\n ? 'Expired'\n : daysUntilExpiry !== null\n ? daysUntilExpiry === 0\n ? 'Expires today'\n : daysUntilExpiry === 1\n ? 'Expires tomorrow'\n : `${daysUntilExpiry} days left`\n : 'Never expires'}\n </span>\n </div>\n </div>\n <div className=\"text-right\">\n <span\n className={`text-sm font-semibold ${isExpired ? 'text-muted-foreground' : 'text-foreground'}`}\n >\n {formatNumber(assignment.used)}\n <span className=\"text-muted-foreground font-normal\">\n {' / '}\n {assignment.limit != null && assignment.limit > 0\n ? formatNumber(assignment.limit)\n : '∞'}\n {assignment.unit && ` ${assignment.unit}`}\n </span>\n </span>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n )}\n </div>\n\n {/* Usage History */}\n <div className=\"p-4\">\n <h3 className=\"text-sm font-medium text-foreground mb-3\">Recent Usage</h3>\n\n {isLoading ? (\n <div className=\"space-y-2\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-12 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n ) : usages.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-6\">No usage records found</p>\n ) : (\n <div className=\"space-y-2\">\n {usages.map((usage) => (\n <div\n key={usage.id}\n className=\"flex items-center justify-between p-3 border border-border rounded-lg\"\n >\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {usage.description || 'Usage recorded'}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {formatRelativeTime(usage.timestamp)} • {formatDateTime(usage.timestamp)}\n </p>\n </div>\n <span className=\"text-sm font-medium text-foreground ml-4\">\n {formatNumber(usage.amount)}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Pagination */}\n {(cursorStack.length > 1 || hasNextPage) && (\n <div className=\"flex items-center justify-center gap-2 mt-4\">\n <button\n onClick={handlePrev}\n disabled={cursorStack.length === 1}\n className=\"px-3 py-1.5 text-sm border border-border rounded-md disabled:opacity-50\"\n aria-label=\"Previous usage page\"\n >\n <ChevronLeft className=\"w-4 h-4\" />\n </button>\n <span className=\"text-sm text-muted-foreground\">Page {currentPage}</span>\n <button\n onClick={handleNext}\n disabled={!hasNextPage}\n className=\"px-3 py-1.5 text-sm border border-border rounded-md disabled:opacity-50\"\n aria-label=\"Next usage page\"\n >\n <ChevronRight className=\"w-4 h-4\" />\n </button>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"sticky bottom-0 bg-background border-t border-border p-4\">\n <button\n onClick={onClose}\n className=\"w-full py-2 px-4 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Close\n </button>\n </div>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Main Usage Page Component\n// ============================================================================\n\nexport const UsagePage: 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 permissions = useBillingPermissions();\n\n // Helper to get billing account ID from URL (for navigation from Overview page)\n const getInitialBillingAccountId = (): string | undefined => {\n if (typeof window === 'undefined') return undefined;\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get('billingAccountId') || undefined;\n };\n\n // State - initialize with URL param if present\n const [selectedBillingAccountId, setSelectedBillingAccountId] = useState<string | undefined>(\n getInitialBillingAccountId\n );\n const [selectedQuota, setSelectedQuota] = useState<AggregatedQuota | null>(null);\n\n // Search and filter state\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');\n\n // View mode: 'active' | 'past' | 'all'\n type ViewMode = 'active' | 'past' | 'all';\n const [viewMode, setViewMode] = useState<ViewMode>('active');\n\n // Debounce search query to avoid too many API calls\n const deferredSearchQuery = useDeferredValue(searchQuery);\n\n // Fetch billing accounts\n const { billingAccounts, isLoading: isLoadingAccounts } = useBillingAccountsForUsage();\n\n // Prefer the default account, fall back to first\n const defaultBillingAccountId = (billingAccounts.find((a) => a.isDefault) ?? billingAccounts[0])\n ?.id;\n // ?? so that an explicit empty string (cleared URL param) still falls back to default\n const effectiveBillingAccountId = selectedBillingAccountId ?? defaultBillingAccountId;\n\n // Fetch usage data - passes searchQuery and viewMode to backend for server-side filtering\n const {\n dashboard,\n pooledQuotas,\n activeSubscriptions,\n pastSubscriptions,\n isLoading,\n isPastSubscriptionsLoading,\n error,\n refetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions,\n hasMorePastSubscriptions,\n isLoadingMorePast,\n pastSubscriptionsCount,\n } = useEnhancedUsagePage({\n billingAccountId: effectiveBillingAccountId,\n searchQuery: deferredSearchQuery || undefined,\n viewMode, // 'active' | 'past' | 'all'\n });\n\n // Filter quotas by status (search is now handled by backend)\n const filterQuotaByStatus = useCallback(\n (quota: AggregatedQuota): boolean => {\n if (statusFilter === 'all') return true;\n if (statusFilter === 'healthy') return quota.status === 'HEALTHY';\n if (statusFilter === 'warning') return quota.status === 'WARNING';\n if (statusFilter === 'critical')\n return quota.status === 'CRITICAL' || quota.status === 'EXHAUSTED';\n return true;\n },\n [statusFilter]\n );\n\n // Filtered data - only status filter is client-side, search is backend\n const filteredPooledQuotas = useMemo(() => {\n return pooledQuotas.filter(filterQuotaByStatus);\n }, [pooledQuotas, filterQuotaByStatus]);\n\n const filteredActiveSubscriptions = useMemo(() => {\n if (statusFilter === 'all') {\n return activeSubscriptions;\n }\n return activeSubscriptions\n .map((sub) => ({\n ...sub,\n aggregatedQuotas: sub.aggregatedQuotas.filter(filterQuotaByStatus),\n }))\n .filter((sub) => sub.aggregatedQuotas.length > 0);\n }, [activeSubscriptions, statusFilter, filterQuotaByStatus]);\n\n const filteredPastSubscriptions = useMemo(() => {\n if (statusFilter === 'all') {\n return pastSubscriptions;\n }\n return pastSubscriptions\n .map((sub) => ({\n ...sub,\n aggregatedQuotas: sub.aggregatedQuotas.filter(filterQuotaByStatus),\n }))\n .filter((sub) => sub.aggregatedQuotas.length > 0);\n }, [pastSubscriptions, statusFilter, filterQuotaByStatus]);\n\n // Subscriptions to display based on view mode\n const displayedSubscriptions = useMemo(() => {\n if (viewMode === 'active') {\n return { active: filteredActiveSubscriptions, past: [] };\n }\n if (viewMode === 'past') {\n return { active: [], past: filteredPastSubscriptions };\n }\n // 'all' - show both\n return { active: filteredActiveSubscriptions, past: filteredPastSubscriptions };\n }, [viewMode, filteredActiveSubscriptions, filteredPastSubscriptions]);\n\n const hasActiveFilters = searchQuery !== '' || statusFilter !== 'all';\n const isSearching = deferredSearchQuery !== searchQuery; // Show loading indicator while debouncing\n\n const clearFilters = () => {\n setSearchQuery('');\n setStatusFilter('all');\n };\n\n // Toggle status filter (click same to deselect)\n const toggleStatusFilter = (filter: StatusFilter) => {\n setStatusFilter((prev) => (prev === filter ? 'all' : filter));\n };\n\n // Permission check\n if (!permissions.canViewUsage) {\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=\"w-12 h-12 mx-auto rounded-full bg-muted flex items-center justify-center\">\n <AlertTriangle className=\"w-6 h-6 text-muted-foreground\" />\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't have permission to view usage data.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if ((isLoadingAccounts || isLoading) && !dashboard) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n {[1, 2, 3, 4].map((i) => (\n <div key={i} className=\"h-24 bg-muted animate-pulse rounded-lg\" />\n ))}\n </div>\n <div className=\"h-64 bg-muted animate-pulse rounded-lg\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\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=\"w-12 h-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <AlertTriangle className=\"w-6 h-6 text-destructive\" />\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">Failed to load usage data</h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n onClick={() => refetch()}\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 Try Again\n </button>\n </div>\n </div>\n );\n }\n\n const selectedAccount = billingAccounts.find((a) => a.id === effectiveBillingAccountId);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header with Billing Account Selector */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.usage.title', 'Usage')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Monitor your resource consumption and quota usage\n </p>\n </div>\n\n <div className=\"flex items-center gap-3\">\n {/* Billing Account Selector */}\n {billingAccounts.length > 1 && (\n <select\n value={effectiveBillingAccountId || ''}\n onChange={(e) => setSelectedBillingAccountId(e.target.value)}\n className=\"px-3 py-2 text-sm border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary min-w-[200px]\"\n >\n {billingAccounts.map((account) => (\n <option key={account.id} value={account.id}>\n {account.name}\n </option>\n ))}\n </select>\n )}\n\n {/* Refresh Button */}\n <button\n onClick={() => refetch()}\n className=\"p-2 border border-border rounded-md hover:bg-muted/50 transition-colors\"\n title=\"Refresh\"\n >\n <RefreshCw className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n\n {/* Search and Filters */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n {/* Search Input */}\n <div className=\"relative flex-1 max-w-md\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground\" />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={`Search ${viewMode === 'past' ? 'past' : viewMode === 'all' ? 'all' : 'active'} quotas...`}\n className=\"w-full pl-9 pr-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\"\n />\n {(searchQuery || isSearching) && (\n <button\n onClick={() => setSearchQuery('')}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded hover:bg-muted/50\"\n >\n {isSearching ? (\n <RefreshCw className=\"w-3 h-3 text-muted-foreground animate-spin\" />\n ) : (\n <X className=\"w-3 h-3 text-muted-foreground\" />\n )}\n </button>\n )}\n </div>\n\n {/* View Mode Tabs */}\n <div className=\"flex items-center bg-muted/50 rounded-lg p-1 border border-border\">\n <button\n onClick={() => setViewMode('active')}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-all ${\n viewMode === 'active'\n ? 'bg-background text-foreground shadow-sm font-medium'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n <Package className=\"w-3.5 h-3.5\" />\n Active\n </button>\n <button\n onClick={() => setViewMode('past')}\n disabled={isPastSubscriptionsLoading && viewMode !== 'past'}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-all ${\n viewMode === 'past'\n ? 'bg-background text-foreground shadow-sm font-medium'\n : 'text-muted-foreground hover:text-foreground'\n } ${isPastSubscriptionsLoading && viewMode !== 'past' ? 'opacity-70' : ''}`}\n >\n {isPastSubscriptionsLoading && viewMode === 'past' ? (\n <RefreshCw className=\"w-3.5 h-3.5 animate-spin\" />\n ) : (\n <Clock className=\"w-3.5 h-3.5\" />\n )}\n Past\n </button>\n </div>\n\n {/* Active Filters Indicator */}\n {hasActiveFilters && (\n <button\n onClick={clearFilters}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground border border-border rounded-md hover:bg-muted/50 transition-colors\"\n >\n <X className=\"w-3 h-3\" />\n Clear filters\n </button>\n )}\n </div>\n\n {/* Billing Account Info */}\n {selectedAccount && (\n <div className=\"text-sm text-muted-foreground\">\n Viewing usage for{' '}\n <span className=\"font-medium text-foreground\">{selectedAccount.name}</span>\n </div>\n )}\n\n {/* Overview Stats - Clickable to filter */}\n {dashboard && (\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <StatCard\n label=\"Total Quotas\"\n value={dashboard.totalQuotas}\n icon={<Layers className=\"w-5 h-5\" />}\n color=\"blue\"\n isActive={statusFilter === 'all'}\n onClick={() => setStatusFilter('all')}\n />\n <StatCard\n label=\"Healthy\"\n value={dashboard.healthyQuotas}\n icon={<CheckCircle className=\"w-5 h-5\" />}\n color=\"emerald\"\n isActive={statusFilter === 'healthy'}\n onClick={() => toggleStatusFilter('healthy')}\n />\n <StatCard\n label=\"Warning\"\n value={dashboard.warningQuotas}\n icon={<AlertCircle className=\"w-5 h-5\" />}\n color=\"amber\"\n isActive={statusFilter === 'warning'}\n onClick={() => toggleStatusFilter('warning')}\n />\n <StatCard\n label=\"Critical / Exhausted\"\n value={dashboard.criticalQuotas + dashboard.exhaustedQuotas}\n icon={<AlertTriangle className=\"w-5 h-5\" />}\n color=\"red\"\n isActive={statusFilter === 'critical'}\n onClick={() => toggleStatusFilter('critical')}\n />\n </div>\n )}\n\n {/* Filter Results Info */}\n {(hasActiveFilters || viewMode !== 'active') && (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground flex-wrap\">\n <span>\n Showing{' '}\n {displayedSubscriptions.active.reduce((acc, s) => acc + s.aggregatedQuotas.length, 0) +\n displayedSubscriptions.past.reduce((acc, s) => acc + s.aggregatedQuotas.length, 0) +\n (viewMode !== 'past' ? filteredPooledQuotas.length : 0)}{' '}\n quotas\n </span>\n <span className=\"px-2 py-0.5 bg-primary/10 text-primary rounded text-xs capitalize\">\n {viewMode === 'active' ? 'Active' : viewMode === 'past' ? 'Past' : 'All'} subscriptions\n </span>\n {statusFilter !== 'all' && (\n <span className=\"px-2 py-0.5 bg-muted rounded text-xs capitalize\">\n {statusFilter === 'critical' ? 'Critical / Exhausted' : statusFilter}\n </span>\n )}\n {searchQuery && (\n <span className=\"px-2 py-0.5 bg-muted rounded text-xs\">\n “{searchQuery}”\n </span>\n )}\n </div>\n )}\n\n {/* Active Subscriptions Section */}\n {displayedSubscriptions.active.length > 0 && (\n <section>\n <h2 className=\"text-lg font-semibold text-foreground mb-4 flex items-center gap-2\">\n <Package className=\"w-5 h-5 text-primary\" />\n {viewMode === 'all' ? 'Active Subscription Quotas' : 'Subscription Quotas'}\n </h2>\n <div className=\"space-y-4\">\n {displayedSubscriptions.active.map((subscription) => (\n <SubscriptionSection\n key={subscription.id}\n subscription={subscription}\n onQuotaClick={setSelectedQuota}\n defaultExpanded={true}\n />\n ))}\n </div>\n </section>\n )}\n\n {/* Past Subscriptions Section (when viewMode is 'past' or 'all') */}\n {displayedSubscriptions.past.length > 0 && (\n <section>\n <h2 className=\"text-lg font-semibold text-foreground mb-4 flex items-center gap-2\">\n <Clock className=\"w-5 h-5 text-muted-foreground\" />\n Past Subscription Quotas\n <span className=\"text-xs font-normal text-muted-foreground\">\n (Canceled / Expired / Upgraded)\n </span>\n {pastSubscriptionsCount > 0 && (\n <span className=\"text-xs font-normal px-2 py-0.5 bg-muted rounded-full\">\n {displayedSubscriptions.past.length}\n {hasMorePastSubscriptions ? '+' : ''} of {pastSubscriptionsCount}\n </span>\n )}\n </h2>\n <div className=\"space-y-4\">\n {displayedSubscriptions.past.map((subscription) => (\n <SubscriptionSection\n key={subscription.id}\n subscription={subscription}\n onQuotaClick={setSelectedQuota}\n defaultExpanded={viewMode === 'past'}\n />\n ))}\n\n {/* Load More Button */}\n {hasMorePastSubscriptions && (\n <div className=\"flex justify-center pt-2\">\n <button\n onClick={loadMorePastSubscriptions}\n disabled={isLoadingMorePast}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground border border-border rounded-md hover:bg-muted/50 transition-colors disabled:opacity-50\"\n >\n {isLoadingMorePast ? (\n <>\n <RefreshCw className=\"w-4 h-4 animate-spin\" />\n Loading...\n </>\n ) : (\n <>\n <ChevronDown className=\"w-4 h-4\" />\n Load more past subscriptions\n </>\n )}\n </button>\n </div>\n )}\n </div>\n </section>\n )}\n\n {/* Pooled Quotas Section - Only show for active/all views (pooled quotas are account-level) */}\n {viewMode !== 'past' && filteredPooledQuotas.length > 0 && (\n <section>\n <h2 className=\"text-lg font-semibold text-foreground mb-4 flex items-center gap-2\">\n <Layers className={`w-5 h-5 ${statusTokens.info.icon}`} />\n Pooled Quotas\n <span className=\"text-xs font-normal text-muted-foreground\">\n (Shared across all products)\n </span>\n </h2>\n <QuotaTable\n quotas={filteredPooledQuotas}\n showPlanColumn={false}\n onQuotaClick={setSelectedQuota}\n />\n </section>\n )}\n\n {/* Empty State - No quotas at all (when viewing active) */}\n {viewMode === 'active' &&\n activeSubscriptions.length === 0 &&\n pooledQuotas.length === 0 &&\n !hasActiveFilters && (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <Layers className=\"w-6 h-6 text-muted-foreground\" />\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">No active quotas found</h3>\n <p className=\"text-sm text-muted-foreground mt-1 text-center max-w-sm\">\n Quotas will appear here once you have active subscriptions or pooled allocations.\n </p>\n </div>\n )}\n\n {/* Empty State - No past subscriptions */}\n {viewMode === 'past' &&\n pastSubscriptions.length === 0 &&\n !hasActiveFilters &&\n !isPastSubscriptionsLoading && (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <Clock className=\"w-6 h-6 text-muted-foreground\" />\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">No past subscriptions</h3>\n <p className=\"text-sm text-muted-foreground mt-1 text-center max-w-sm\">\n You don't have any canceled, expired, or upgraded subscriptions yet.\n </p>\n <button\n onClick={() => setViewMode('active')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n View active subscriptions\n </button>\n </div>\n )}\n\n {/* Loading State for Past Subscriptions */}\n {viewMode === 'past' && isPastSubscriptionsLoading && (\n <div className=\"space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-muted animate-pulse rounded-lg\" />\n ))}\n </div>\n )}\n\n {/* Empty State - No results from search/filter */}\n {hasActiveFilters &&\n displayedSubscriptions.active.length === 0 &&\n displayedSubscriptions.past.length === 0 &&\n (viewMode === 'past' || filteredPooledQuotas.length === 0) && (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n {statusFilter !== 'all' && !searchQuery ? (\n <StatusIcon\n status={\n (statusFilter === 'healthy'\n ? 'HEALTHY'\n : statusFilter === 'warning'\n ? 'WARNING'\n : 'CRITICAL') as QuotaStatus\n }\n className=\"w-6 h-6\"\n />\n ) : (\n <Search className=\"w-6 h-6 text-muted-foreground\" />\n )}\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {statusFilter !== 'all' && !searchQuery\n ? `No ${statusFilter === 'critical' ? 'critical or exhausted' : statusFilter} quotas`\n : 'No matching quotas'}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1 text-center max-w-sm\">\n {statusFilter !== 'all' && !searchQuery ? (\n <>\n All your quotas are in a different status.\n {dashboard && (\n <span className=\"block mt-1\">\n You have {dashboard.healthyQuotas} healthy, {dashboard.warningQuotas} warning,\n and {dashboard.criticalQuotas + dashboard.exhaustedQuotas} critical quotas.\n </span>\n )}\n </>\n ) : (\n <>\n No quotas match your current search or filter criteria in{' '}\n {viewMode === 'past' ? 'past' : viewMode === 'all' ? 'any' : 'active'}{' '}\n subscriptions.\n {viewMode === 'active' && !statusFilter.includes('all') && (\n <span className=\"block mt-1\">\n Try switching to “Past” or “All” to search across more\n subscriptions.\n </span>\n )}\n </>\n )}\n </p>\n <div className=\"flex gap-2 mt-4\">\n <button\n onClick={clearFilters}\n className=\"px-4 py-2 text-sm font-medium border border-border rounded-md hover:bg-muted/50 transition-colors\"\n >\n {statusFilter !== 'all' ? 'Show all quotas' : 'Clear filters'}\n </button>\n {viewMode === 'active' && searchQuery && (\n <button\n onClick={() => setViewMode('all')}\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 Search all\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Quota Detail Modal */}\n {selectedQuota && (\n <QuotaDetailModal quota={selectedQuota} onClose={() => setSelectedQuota(null)} />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -856,7 +856,6 @@ export interface CreateQuotaInput {
|
|
|
856
856
|
quotaType?: QuotaType;
|
|
857
857
|
reusable: boolean;
|
|
858
858
|
resetPeriod?: QuotaResetPeriod;
|
|
859
|
-
context?: Record<string, unknown>;
|
|
860
859
|
}
|
|
861
860
|
export interface UpdateQuotaInput {
|
|
862
861
|
id: string;
|
|
@@ -865,7 +864,6 @@ export interface UpdateQuotaInput {
|
|
|
865
864
|
reusable?: boolean;
|
|
866
865
|
isActive: boolean;
|
|
867
866
|
resetPeriod?: QuotaResetPeriod;
|
|
868
|
-
context?: Record<string, unknown>;
|
|
869
867
|
}
|
|
870
868
|
export interface SpendCreditsInput {
|
|
871
869
|
billingAccountId: string;
|