@burdenoff/microfe-subscriberbot 2026.1030.7 → 2026.1102.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/README.md +26 -0
- package/dist/stories/pages/AgentDetailPage.stories.d.ts +16 -0
- package/dist/stories/pages/AgentDetailPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/AgentNewPage.stories.d.ts +12 -0
- package/dist/stories/pages/AgentNewPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/AgentsListPage.stories.d.ts +16 -0
- package/dist/stories/pages/AgentsListPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/AssetVaultPage.stories.d.ts +14 -0
- package/dist/stories/pages/AssetVaultPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/DashboardPage.stories.d.ts +12 -0
- package/dist/stories/pages/DashboardPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/DecisionDetailPage.stories.d.ts +13 -0
- package/dist/stories/pages/DecisionDetailPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/DecisionsPage.stories.d.ts +14 -0
- package/dist/stories/pages/DecisionsPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/EnterpriseSpendPage.stories.d.ts +13 -0
- package/dist/stories/pages/EnterpriseSpendPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/InboxPage.stories.d.ts +20 -0
- package/dist/stories/pages/InboxPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/InsightsPage.stories.d.ts +12 -0
- package/dist/stories/pages/InsightsPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/InvoicesPage.stories.d.ts +16 -0
- package/dist/stories/pages/InvoicesPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/NegotiationDetailPage.stories.d.ts +11 -0
- package/dist/stories/pages/NegotiationDetailPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/NegotiationNewPage.stories.d.ts +9 -0
- package/dist/stories/pages/NegotiationNewPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/NegotiationsPage.stories.d.ts +11 -0
- package/dist/stories/pages/NegotiationsPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/ProviderDetailPage.stories.d.ts +15 -0
- package/dist/stories/pages/ProviderDetailPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/ProviderNewPage.stories.d.ts +12 -0
- package/dist/stories/pages/ProviderNewPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/ProvidersListPage.stories.d.ts +17 -0
- package/dist/stories/pages/ProvidersListPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/RelationshipDetailPage.stories.d.ts +15 -0
- package/dist/stories/pages/RelationshipDetailPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/RelationshipNewPage.stories.d.ts +12 -0
- package/dist/stories/pages/RelationshipNewPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/RelationshipsListPage.stories.d.ts +14 -0
- package/dist/stories/pages/RelationshipsListPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/SavingsPage.stories.d.ts +13 -0
- package/dist/stories/pages/SavingsPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/WorkflowsPage.stories.d.ts +16 -0
- package/dist/stories/pages/WorkflowsPage.stories.d.ts.map +1 -0
- package/dist/stories/support/PageHarness.d.ts +28 -0
- package/dist/stories/support/PageHarness.d.ts.map +1 -0
- package/dist/stories/support/fixtures.d.ts +28 -0
- package/dist/stories/support/fixtures.d.ts.map +1 -0
- package/dist/stories/support/graphqlMock.d.ts +34 -0
- package/dist/stories/support/graphqlMock.d.ts.map +1 -0
- package/dist/stories/support/handlers.d.ts +8 -0
- package/dist/stories/support/handlers.d.ts.map +1 -0
- package/dist/stories/support/negotiationFixtures.d.ts +14 -0
- package/dist/stories/support/negotiationFixtures.d.ts.map +1 -0
- package/dist/stories/support/play.d.ts +7 -0
- package/dist/stories/support/play.d.ts.map +1 -0
- package/dist/subscriberbot/SubscriberBotRoutes.d.ts.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoutes.js +18 -6
- package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
- package/dist/subscriberbot/admin/AdminDashboardPage.d.ts.map +1 -1
- package/dist/subscriberbot/admin/AdminDashboardPage.js +61 -51
- package/dist/subscriberbot/admin/AdminDashboardPage.js.map +1 -1
- package/dist/subscriberbot/admin/NegotiationDeskPanel.d.ts +4 -0
- package/dist/subscriberbot/admin/NegotiationDeskPanel.d.ts.map +1 -0
- package/dist/subscriberbot/admin/NegotiationDeskPanel.js +210 -0
- package/dist/subscriberbot/admin/NegotiationDeskPanel.js.map +1 -0
- package/dist/subscriberbot/components/Modal.d.ts.map +1 -1
- package/dist/subscriberbot/components/Modal.js +9 -6
- package/dist/subscriberbot/components/Modal.js.map +1 -1
- package/dist/subscriberbot/components/negotiations/NegotiationStatusPill.d.ts +8 -0
- package/dist/subscriberbot/components/negotiations/NegotiationStatusPill.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/NegotiationStatusPill.js +18 -0
- package/dist/subscriberbot/components/negotiations/NegotiationStatusPill.js.map +1 -0
- package/dist/subscriberbot/components/negotiations/NegotiationTimeline.d.ts +8 -0
- package/dist/subscriberbot/components/negotiations/NegotiationTimeline.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/NegotiationTimeline.js +49 -0
- package/dist/subscriberbot/components/negotiations/NegotiationTimeline.js.map +1 -0
- package/dist/subscriberbot/components/negotiations/OutcomeForm.d.ts +13 -0
- package/dist/subscriberbot/components/negotiations/OutcomeForm.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/OutcomeForm.js +112 -0
- package/dist/subscriberbot/components/negotiations/OutcomeForm.js.map +1 -0
- package/dist/subscriberbot/components/negotiations/index.d.ts +5 -0
- package/dist/subscriberbot/components/negotiations/index.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/index.js +4 -0
- package/dist/subscriberbot/components/negotiations/labels.d.ts +16 -0
- package/dist/subscriberbot/components/negotiations/labels.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/labels.js +179 -0
- package/dist/subscriberbot/components/negotiations/labels.js.map +1 -0
- package/dist/subscriberbot/hooks/useNegotiationApi.d.ts +51 -0
- package/dist/subscriberbot/hooks/useNegotiationApi.d.ts.map +1 -0
- package/dist/subscriberbot/hooks/useNegotiationApi.js +106 -0
- package/dist/subscriberbot/hooks/useNegotiationApi.js.map +1 -0
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts +1 -0
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.js +1 -0
- package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -1
- package/dist/subscriberbot/pages/enterprise/EnterpriseSpendPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/enterprise/EnterpriseSpendPage.js +1 -0
- package/dist/subscriberbot/pages/enterprise/EnterpriseSpendPage.js.map +1 -1
- package/dist/subscriberbot/pages/negotiations/NegotiationDetailPage.d.ts +11 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationDetailPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationDetailPage.js +312 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationDetailPage.js.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationNewPage.d.ts +11 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationNewPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationNewPage.js +375 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationNewPage.js.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationsPage.d.ts +10 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationsPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationsPage.js +155 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationsPage.js.map +1 -0
- package/dist/subscriberbot/pages/providers/ProvidersListPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js +1 -0
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +321 -307
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
- package/dist/subscriberbot/types/negotiation.d.ts +101 -0
- package/dist/subscriberbot/types/negotiation.d.ts.map +1 -0
- package/dist/subscriberbot/utils/formatters.d.ts +1 -0
- package/dist/subscriberbot/utils/formatters.d.ts.map +1 -1
- package/dist/subscriberbot/utils/formatters.js +4 -3
- package/dist/subscriberbot/utils/formatters.js.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoicesPage.js","names":[],"sources":["../../../src/subscriberbot/pages/InvoicesPage.tsx"],"sourcesContent":["/**\n * Invoices\n * @module subscriberbot/pages\n *\n * The Universal Billing Layer's invoice ledger across every relationship, plus\n * the passive payment-method annotations that pay for them. Filter by status,\n * mark issued/overdue invoices paid, create invoices, and manage payment\n * methods. Reads `subListInvoices` / `subListPaymentInstruments` /\n * `subListRelationships` (to name the subscription each invoice bills for);\n * writes `subMarkInvoicePaid`, `subCreateInvoice`, `subCreatePaymentInstrument`,\n * `subRemovePaymentInstrument`.\n */\n\nimport { useMemo, useState } from 'react';\nimport {\n Receipt,\n FileText,\n Plus,\n CreditCard,\n Trash2,\n Star,\n AlertTriangle,\n CheckCircle2,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { EmptyState } from '../components/EmptyState';\nimport { Modal } from '../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../components/StateViews';\nimport { Donut, Legend, CHART_COLORS } from '../components/viz';\nimport type { Segment } from '../components/viz';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport {\n useInvoices,\n useMarkInvoicePaid,\n useCreateInvoice,\n useRelationships,\n usePaymentInstruments,\n useCreatePaymentInstrument,\n useRemovePaymentInstrument,\n} from '../hooks/useSubscriberBotApi';\nimport { useTr } from '../hooks/useTr';\nimport { cn } from '../utils/cn';\nimport { formatCurrency, formatDate, formatEnumLabel } from '../utils/formatters';\nimport type { SubInvoiceStatus, SubPaymentInstrumentType } from '../types';\n\nconst PAGE_SIZE = 25;\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst STATUS_KEY: Record<SubInvoiceStatus, string> = {\n ISSUED: 'invoices.filters.issued',\n OVERDUE: 'invoices.filters.overdue',\n PAID: 'invoices.filters.paid',\n FAILED: 'invoices.filters.failed',\n DRAFT: 'invoices.filters.draft',\n CANCELLED: 'invoices.filters.cancelled',\n REFUNDED: 'invoices.filters.refunded',\n};\n\nconst INVOICE_STATUSES: SubInvoiceStatus[] = [\n 'ISSUED',\n 'DRAFT',\n 'OVERDUE',\n 'PAID',\n 'FAILED',\n 'CANCELLED',\n 'REFUNDED',\n];\n\nconst PAYMENT_TYPES: SubPaymentInstrumentType[] = [\n 'CARD',\n 'BANK_ACCOUNT',\n 'PAYPAL',\n 'UPI',\n 'WALLET',\n 'OTHER',\n];\n\nconst PAYABLE = new Set<SubInvoiceStatus>(['ISSUED', 'OVERDUE', 'FAILED']);\n\n// Semantic tints for the status-distribution donut (fall back to chart tokens).\nconst STATUS_TINT: Partial<Record<SubInvoiceStatus, string>> = {\n PAID: 'var(--color-status-success-text)',\n OVERDUE: 'var(--color-status-error-text)',\n FAILED: 'var(--color-status-error-text)',\n ISSUED: 'var(--color-chart-1)',\n DRAFT: 'var(--color-text-secondary)',\n CANCELLED: 'var(--color-chart-4)',\n REFUNDED: 'var(--color-status-warning-text)',\n};\n\n// ---------------------------------------------------------------------------\n// Create invoice modal → subCreateInvoice\n// ---------------------------------------------------------------------------\n\nfunction CreateInvoiceModal({ onClose }: { onClose: () => void }) {\n const tr = useTr();\n const create = useCreateInvoice();\n const relationshipsQ = useRelationships({ pagination: { limit: 100, offset: 0 } });\n const relationships = relationshipsQ.data?.items ?? [];\n\n const [relationshipId, setRelationshipId] = useState('');\n const [number, setNumber] = useState('');\n const [status, setStatus] = useState<SubInvoiceStatus>('ISSUED');\n const [currency, setCurrency] = useState('USD');\n const [subtotal, setSubtotal] = useState('');\n const [taxAmount, setTaxAmount] = useState('');\n const [total, setTotal] = useState('');\n const [issueDate, setIssueDate] = useState('');\n const [dueDate, setDueDate] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!relationshipId) {\n setFormError(\n tr(\n 'invoices.createModal.relationshipRequired',\n 'Select the relationship this invoice belongs to.'\n )\n );\n return;\n }\n create.mutate(\n {\n relationshipId,\n status,\n currency: currency.trim() || 'USD',\n ...(number.trim() ? { number: number.trim() } : {}),\n ...(subtotal.trim() ? { subtotal: Number(subtotal) } : {}),\n ...(taxAmount.trim() ? { taxAmount: Number(taxAmount) } : {}),\n ...(total.trim() ? { total: Number(total) } : {}),\n ...(issueDate ? { issueDate: new Date(issueDate).toISOString() } : {}),\n ...(dueDate ? { dueDate: new Date(dueDate).toISOString() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(\n err instanceof Error\n ? err.message\n : tr('invoices.createModal.error', 'Could not create invoice.')\n ),\n }\n );\n };\n\n return (\n <Modal\n title={tr('invoices.createModal.title', 'Create invoice')}\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"inv-relationship\" className={labelCls}>\n {tr('invoices.createModal.relationshipLabel', 'Relationship')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <select\n id=\"inv-relationship\"\n value={relationshipId}\n onChange={(e) => setRelationshipId(e.target.value)}\n className={inputCls}\n required\n >\n <option value=\"\">\n {tr('invoices.createModal.relationshipPlaceholder', '— Select a relationship —')}\n </option>\n {relationships.map((r) => (\n <option key={r.id} value={r.id}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"inv-number\" className={labelCls}>\n {tr('invoices.createModal.numberLabel', 'Number')}\n </label>\n <input\n id=\"inv-number\"\n type=\"text\"\n value={number}\n onChange={(e) => setNumber(e.target.value)}\n placeholder={tr('invoices.createModal.numberPlaceholder', 'INV-0001')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-status\" className={labelCls}>\n {tr('invoices.createModal.statusLabel', 'Status')}\n </label>\n <select\n id=\"inv-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value as SubInvoiceStatus)}\n className={inputCls}\n >\n {INVOICE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {tr(STATUS_KEY[s], s)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"inv-currency\" className={labelCls}>\n {tr('invoices.createModal.currencyLabel', 'Currency')}\n </label>\n <input\n id=\"inv-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"inv-subtotal\" className={labelCls}>\n {tr('invoices.createModal.subtotalLabel', 'Subtotal')}\n </label>\n <input\n id=\"inv-subtotal\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={subtotal}\n onChange={(e) => setSubtotal(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-tax\" className={labelCls}>\n {tr('invoices.createModal.taxLabel', 'Tax')}\n </label>\n <input\n id=\"inv-tax\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={taxAmount}\n onChange={(e) => setTaxAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-total\" className={labelCls}>\n {tr('invoices.createModal.totalLabel', 'Total')}\n </label>\n <input\n id=\"inv-total\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={total}\n onChange={(e) => setTotal(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"inv-issue\" className={labelCls}>\n {tr('invoices.createModal.issueDateLabel', 'Issue date')}\n </label>\n <input\n id=\"inv-issue\"\n type=\"date\"\n value={issueDate}\n onChange={(e) => setIssueDate(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-due\" className={labelCls}>\n {tr('invoices.createModal.dueDateLabel', 'Due date')}\n </label>\n <input\n id=\"inv-due\"\n type=\"date\"\n value={dueDate}\n onChange={(e) => setDueDate(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n {tr('invoices.createModal.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending\n ? tr('invoices.creating', 'Creating…')\n : tr('invoices.createModal.submit', 'Create invoice')}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add payment method modal → subCreatePaymentInstrument\n// ---------------------------------------------------------------------------\n\nfunction AddPaymentMethodModal({ onClose }: { onClose: () => void }) {\n const tr = useTr();\n const create = useCreatePaymentInstrument();\n const [label, setLabel] = useState('');\n const [type, setType] = useState<SubPaymentInstrumentType>('CARD');\n const [brand, setBrand] = useState('');\n const [last4, setLast4] = useState('');\n const [expiryMonth, setExpiryMonth] = useState('');\n const [expiryYear, setExpiryYear] = useState('');\n const [isDefault, setIsDefault] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError(tr('invoices.addPaymentMethodModal.labelRequired', 'A label is required.'));\n return;\n }\n create.mutate(\n {\n label: label.trim(),\n type,\n isDefault,\n ...(brand.trim() ? { brand: brand.trim() } : {}),\n ...(last4.trim() ? { last4: last4.trim() } : {}),\n ...(expiryMonth.trim() ? { expiryMonth: Number(expiryMonth) } : {}),\n ...(expiryYear.trim() ? { expiryYear: Number(expiryYear) } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(\n err instanceof Error\n ? err.message\n : tr('invoices.addPaymentMethodModal.error', 'Could not add payment method.')\n ),\n }\n );\n };\n\n return (\n <Modal\n title={tr('invoices.addPaymentMethodModal.title', 'Add payment method')}\n description={tr(\n 'invoices.addPaymentMethodModal.description',\n 'A passive annotation of which external card/account pays for a subscription — no full card numbers or CVV.'\n )}\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"pm-label\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.labelLabel', 'Label')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"pm-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder={tr(\n 'invoices.addPaymentMethodModal.labelPlaceholder',\n 'e.g. Personal Visa'\n )}\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"pm-type\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.typeLabel', 'Type')}\n </label>\n <select\n id=\"pm-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubPaymentInstrumentType)}\n className={inputCls}\n >\n {PAYMENT_TYPES.map((t) => (\n <option key={t} value={t}>\n {formatEnumLabel(t)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"pm-brand\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.brandLabel', 'Brand')}\n </label>\n <input\n id=\"pm-brand\"\n type=\"text\"\n value={brand}\n onChange={(e) => setBrand(e.target.value)}\n placeholder={tr('invoices.addPaymentMethodModal.brandPlaceholder', 'Visa, Amex…')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"pm-last4\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.last4Label', 'Last 4')}\n </label>\n <input\n id=\"pm-last4\"\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={4}\n value={last4}\n onChange={(e) => setLast4(e.target.value.replace(/\\D/g, ''))}\n placeholder={tr('invoices.addPaymentMethodModal.last4Placeholder', '4242')}\n className={inputCls}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-2\">\n <div>\n <label htmlFor=\"pm-exp-month\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.expMonthLabel', 'Exp. mo.')}\n </label>\n <input\n id=\"pm-exp-month\"\n type=\"number\"\n min=\"1\"\n max=\"12\"\n value={expiryMonth}\n onChange={(e) => setExpiryMonth(e.target.value)}\n placeholder={tr('invoices.addPaymentMethodModal.expMonthPlaceholder', '12')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"pm-exp-year\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.expYearLabel', 'Exp. yr.')}\n </label>\n <input\n id=\"pm-exp-year\"\n type=\"number\"\n min=\"2024\"\n max=\"2099\"\n value={expiryYear}\n onChange={(e) => setExpiryYear(e.target.value)}\n placeholder={tr('invoices.addPaymentMethodModal.expYearPlaceholder', '2028')}\n className={inputCls}\n />\n </div>\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isDefault}\n onChange={(e) => setIsDefault(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n {tr('invoices.addPaymentMethodModal.defaultLabel', 'Set as default')}\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n {tr('invoices.addPaymentMethodModal.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending\n ? tr('invoices.addPaymentMethodModal.adding', 'Adding…')\n : tr('invoices.addPaymentMethodModal.submit', 'Add payment method')}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Payment methods panel\n// ---------------------------------------------------------------------------\n\nfunction PaymentMethodsSection() {\n const tr = useTr();\n const { data, isLoading, isError, refetch } = usePaymentInstruments();\n const remove = useRemovePaymentInstrument();\n const [showAdd, setShowAdd] = useState(false);\n\n const instruments = data ?? [];\n\n return (\n <Section\n title={tr('invoices.paymentMethods.title', 'Payment methods')}\n description={tr(\n 'invoices.paymentMethods.description',\n 'Which external cards and accounts pay for your subscriptions'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => setShowAdd(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> {tr('invoices.paymentMethods.add', 'Add payment method')}\n </button>\n }\n >\n {isLoading ? (\n <LoadingView label={tr('invoices.paymentMethods.loading', 'Loading payment methods…')} />\n ) : isError ? (\n <ErrorView\n message={tr('invoices.paymentMethods.error', 'Could not load payment methods.')}\n onRetry={() => void refetch()}\n />\n ) : instruments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CreditCard className=\"h-12 w-12\" />}\n title={tr('invoices.paymentMethods.emptyTitle', 'No payment methods')}\n description={tr(\n 'invoices.paymentMethods.emptyDescription',\n 'Annotate which card or account pays for each subscription. No full card numbers are stored.'\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {instruments.map((pi) => {\n const meta = [\n pi.expiryMonth && pi.expiryYear\n ? `Exp ${String(pi.expiryMonth).padStart(2, '0')}/${String(pi.expiryYear).slice(-2)}`\n : null,\n ].filter(Boolean);\n return (\n <GlassCard key={pi.id} treatment=\"metric\" className=\"flex flex-col gap-4 p-4\">\n {/* Card face */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-3\">\n <span className=\"grid h-9 w-12 place-items-center rounded-md bg-[var(--color-accent-soft)] text-text-primary\">\n <CreditCard className=\"h-4 w-4\" aria-hidden />\n </span>\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-1.5 truncate text-sm font-semibold text-text-primary\">\n {pi.brand || pi.label}\n {pi.isDefault && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-status-success-text\">\n <Star className=\"h-2.5 w-2.5 fill-current\" aria-hidden />\n {tr('invoices.paymentMethods.default', 'Default')}\n </span>\n )}\n </p>\n <p className=\"font-mono text-sm tracking-[0.15em] text-text-secondary\">\n {pi.last4 ? `•••• ${pi.last4}` : formatEnumLabel(pi.type)}\n </p>\n </div>\n </div>\n <StatusBadge status={pi.status} />\n </div>\n\n <div className=\"mt-auto flex items-center justify-between border-t border-border-subtle pt-3\">\n <p className=\"truncate text-xs text-text-secondary\">\n {[pi.label, ...meta].filter(Boolean).join(' · ')}\n </p>\n <button\n type=\"button\"\n aria-label={tr('invoices.paymentMethods.removeAria', 'Remove {{label}}', {\n label: pi.label,\n })}\n disabled={remove.isPending}\n onClick={() => remove.mutate(pi.id)}\n className=\"inline-flex shrink-0 items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-secondary hover:border-status-error-text/40 hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" aria-hidden />\n {tr('invoices.paymentMethods.remove', 'Remove')}\n </button>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n\n {showAdd && <AddPaymentMethodModal onClose={() => setShowAdd(false)} />}\n </Section>\n );\n}\n\nexport function InvoicesPage() {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [status, setStatus] = useState<'' | SubInvoiceStatus>('');\n const [showCreate, setShowCreate] = useState(false);\n\n const statusOptions: Array<{ value: '' | SubInvoiceStatus; label: string }> = [\n { value: '', label: tr('invoices.filters.allStatuses', 'All statuses') },\n ...INVOICE_STATUSES.map((s) => ({ value: s, label: tr(STATUS_KEY[s], s) })),\n ];\n\n const { data, isLoading, isError, refetch, isFetching } = useInvoices({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n const markPaid = useMarkInvoicePaid();\n\n // An invoice only carries `relationshipId`. Resolve the subscription it bills\n // for from the real relationship list (same join the Asset Vault uses) so the\n // ledger names the subscription instead of an opaque id.\n const relationshipsQ = useRelationships({ pagination: { limit: 100, offset: 0 } });\n const relationshipLabels = useMemo(() => {\n const map = new Map<string, string>();\n for (const r of relationshipsQ.data?.items ?? []) map.set(r.id, r.label);\n return map;\n }, [relationshipsQ.data]);\n\n const items = useMemo(() => data?.items ?? [], [data]);\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n const totals = useMemo(() => {\n const outstanding = items.filter((i) => PAYABLE.has(i.status));\n const currency = items[0]?.currency ?? 'USD';\n\n // Status distribution → donut segments (only statuses actually present).\n const byStatus = new Map<SubInvoiceStatus, number>();\n for (const i of items) byStatus.set(i.status, (byStatus.get(i.status) ?? 0) + 1);\n const distribution: Segment[] = Array.from(byStatus.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([s, count], idx) => ({\n label: tr(STATUS_KEY[s], s.charAt(0) + s.slice(1).toLowerCase()),\n value: count,\n color: STATUS_TINT[s] ?? CHART_COLORS[idx % CHART_COLORS.length],\n }));\n\n return {\n outstandingCount: outstanding.length,\n outstandingTotal: outstanding.reduce((sum, i) => sum + i.total, 0),\n paidCount: items.filter((i) => i.status === 'PAID').length,\n overdueCount: items.filter((i) => i.status === 'OVERDUE').length,\n distribution,\n currency,\n };\n }, [items, tr]);\n\n return (\n <PageLayout\n title={tr('invoices.title', 'Invoices')}\n description={tr('invoices.description', 'Your billing ledger across every relationship')}\n actions={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('invoices.create', 'Create invoice')}\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n {tr(\n 'invoices.purpose',\n 'Every invoice from your recurring relationships in one ledger. Filter by status and settle issued or overdue invoices directly.'\n )}\n </PagePurpose>\n\n {!isLoading && !isError && items.length > 0 && (\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:col-span-2\">\n <MetricsCard\n featured\n icon={<Receipt />}\n value={formatCurrency(totals.outstandingTotal, totals.currency, {\n maximumFractionDigits: 0,\n })}\n label={tr('invoices.metrics.outstanding', 'Outstanding · this page')}\n />\n <MetricsCard\n icon={<FileText />}\n value={totals.outstandingCount}\n label={tr('invoices.metrics.openInvoices', 'Open invoices')}\n />\n <MetricsCard\n icon={<AlertTriangle />}\n value={totals.overdueCount}\n label={tr('invoices.metrics.overdue', 'Overdue')}\n {...(totals.overdueCount > 0 ? { trend: 'down' as const } : {})}\n />\n <MetricsCard\n icon={<CheckCircle2 />}\n value={totals.paidCount}\n label={tr('invoices.metrics.paid', 'Paid')}\n {...(totals.paidCount > 0 ? { trend: 'up' as const } : {})}\n />\n </div>\n\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('invoices.metrics.statusBreakdown', 'Status breakdown')}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr('invoices.metrics.statusBreakdownHint', 'Across the invoices on this page')}\n </p>\n <div className=\"mt-4 flex flex-col items-center gap-5 sm:flex-row sm:items-center lg:flex-col xl:flex-row\">\n <Donut\n data={totals.distribution}\n size={132}\n thickness={16}\n centerLabel={String(items.length)}\n centerSub={tr('invoices.metrics.invoices', 'invoices')}\n className=\"shrink-0\"\n />\n <Legend\n data={totals.distribution}\n className=\"w-full min-w-0 flex-1\"\n valueFormat={(n) => String(n)}\n />\n </div>\n </GlassCard>\n </div>\n )}\n\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <select\n aria-label={tr('invoices.filters.statusLabel', 'Filter by status')}\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as '' | SubInvoiceStatus);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {statusOptions.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label={tr('invoices.loading', 'Loading invoices…')} />\n ) : isError ? (\n <ErrorView\n message={tr('invoices.error', 'Could not load invoices.')}\n onRetry={() => void refetch()}\n />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<Receipt className=\"h-12 w-12\" />}\n title={tr('invoices.emptyTitle', 'No invoices')}\n description={\n status\n ? tr('invoices.emptyDescriptionFiltered', 'No invoices match this status filter.')\n : tr(\n 'invoices.emptyDescription',\n 'Invoices from your relationships will appear here — or create one manually.'\n )\n }\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('invoices.create', 'Create invoice')}\n </button>\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 overflow-hidden rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"hidden grid-cols-[1.3fr_1.2fr_0.8fr_0.8fr_0.8fr_auto] gap-4 border-b border-border-subtle bg-bg-sunken px-4 py-2.5 text-xs font-medium text-text-secondary sm:grid\">\n <span>{tr('invoices.table.invoice', 'Invoice')}</span>\n <span>{tr('invoices.table.subscription', 'Subscription')}</span>\n <span>{tr('invoices.table.status', 'Status')}</span>\n <span className=\"text-right\">{tr('invoices.table.total', 'Total')}</span>\n <span className=\"text-right\">{tr('invoices.table.due', 'Due')}</span>\n <span className=\"text-right\">{tr('invoices.table.action', 'Action')}</span>\n </div>\n <div className=\"divide-y divide-border-subtle\">\n {items.map((inv) => (\n <div\n key={inv.id}\n className=\"grid grid-cols-1 gap-2 px-4 py-3 transition-colors hover:bg-bg-sunken/50 sm:grid-cols-[1.3fr_1.2fr_0.8fr_0.8fr_0.8fr_auto] sm:items-center sm:gap-4\"\n >\n <button\n type=\"button\"\n onClick={() => navigateTo(`/relationships/${inv.relationshipId}`)}\n className=\"min-w-0 text-left\"\n >\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {inv.number ||\n `${tr('invoices.table.invoice', 'Invoice')} ${inv.id.slice(0, 8)}`}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {inv.issueDate\n ? tr('invoices.table.issued', 'Issued {{date}}', {\n date: formatDate(inv.issueDate),\n })\n : '—'}\n </p>\n </button>\n <span className=\"min-w-0 truncate text-sm text-text-secondary\">\n {relationshipLabels.get(inv.relationshipId) ?? '—'}\n </span>\n <span>\n <StatusBadge status={inv.status} />\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary sm:text-right\">\n {formatCurrency(inv.total, inv.currency)}\n </span>\n <span className=\"text-xs text-text-secondary sm:text-right\">\n {inv.dueDate ? formatDate(inv.dueDate) : '—'}\n </span>\n <span className=\"sm:text-right\">\n {PAYABLE.has(inv.status) ? (\n <button\n type=\"button\"\n disabled={markPaid.isPending}\n onClick={() => markPaid.mutate(inv.id)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle px-3 py-1.5 text-xs font-medium text-status-success-text hover:opacity-90 disabled:opacity-50\"\n >\n <CheckCircle2 className=\"h-3.5 w-3.5\" aria-hidden />\n {tr('invoices.table.markPaid', 'Mark paid')}\n </button>\n ) : inv.paidAt ? (\n <span className=\"text-xs text-text-secondary\">\n {tr('invoices.table.paidAt', 'Paid {{date}}', {\n date: formatDate(inv.paidAt),\n })}\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">—</span>\n )}\n </span>\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n {tr('invoices.pagination.page', 'Page {{page}}', { page: page + 1 })}\n {isFetching ? ` · ${tr('invoices.pagination.refreshing', 'refreshing…')}` : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr('invoices.pagination.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr('invoices.pagination.next', 'Next')}\n </button>\n </div>\n </div>\n </>\n )}\n\n <PaymentMethodsSection />\n\n {showCreate && <CreateInvoiceModal onClose={() => setShowCreate(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InvoicesPage.js","names":[],"sources":["../../../src/subscriberbot/pages/InvoicesPage.tsx"],"sourcesContent":["/**\n * Invoices\n * @module subscriberbot/pages\n *\n * The Universal Billing Layer's invoice ledger across every relationship, plus\n * the passive payment-method annotations that pay for them. Filter by status,\n * mark issued/overdue invoices paid, create invoices, and manage payment\n * methods. Reads `subListInvoices` / `subListPaymentInstruments` /\n * `subListRelationships` (to name the subscription each invoice bills for);\n * writes `subMarkInvoicePaid`, `subCreateInvoice`, `subCreatePaymentInstrument`,\n * `subRemovePaymentInstrument`.\n */\n\nimport { useMemo, useState } from 'react';\nimport {\n Receipt,\n FileText,\n Plus,\n CreditCard,\n Trash2,\n Star,\n AlertTriangle,\n CheckCircle2,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { EmptyState } from '../components/EmptyState';\nimport { Modal } from '../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../components/StateViews';\nimport { Donut, Legend, CHART_COLORS } from '../components/viz';\nimport type { Segment } from '../components/viz';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport {\n useInvoices,\n useMarkInvoicePaid,\n useCreateInvoice,\n useRelationships,\n usePaymentInstruments,\n useCreatePaymentInstrument,\n useRemovePaymentInstrument,\n} from '../hooks/useSubscriberBotApi';\nimport { useTr } from '../hooks/useTr';\nimport { cn } from '../utils/cn';\nimport { formatCurrency, formatDate, formatEnumLabel } from '../utils/formatters';\nimport type { SubInvoiceStatus, SubPaymentInstrumentType } from '../types';\n\nconst PAGE_SIZE = 25;\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst STATUS_KEY: Record<SubInvoiceStatus, string> = {\n ISSUED: 'invoices.filters.issued',\n OVERDUE: 'invoices.filters.overdue',\n PAID: 'invoices.filters.paid',\n FAILED: 'invoices.filters.failed',\n DRAFT: 'invoices.filters.draft',\n CANCELLED: 'invoices.filters.cancelled',\n REFUNDED: 'invoices.filters.refunded',\n};\n\nconst INVOICE_STATUSES: SubInvoiceStatus[] = [\n 'ISSUED',\n 'DRAFT',\n 'OVERDUE',\n 'PAID',\n 'FAILED',\n 'CANCELLED',\n 'REFUNDED',\n];\n\nconst PAYMENT_TYPES: SubPaymentInstrumentType[] = [\n 'CARD',\n 'BANK_ACCOUNT',\n 'PAYPAL',\n 'UPI',\n 'WALLET',\n 'OTHER',\n];\n\nconst PAYABLE = new Set<SubInvoiceStatus>(['ISSUED', 'OVERDUE', 'FAILED']);\n\n// Semantic tints for the status-distribution donut (fall back to chart tokens).\nconst STATUS_TINT: Partial<Record<SubInvoiceStatus, string>> = {\n PAID: 'var(--color-status-success-text)',\n OVERDUE: 'var(--color-status-error-text)',\n FAILED: 'var(--color-status-error-text)',\n ISSUED: 'var(--color-chart-1)',\n DRAFT: 'var(--color-text-secondary)',\n CANCELLED: 'var(--color-chart-4)',\n REFUNDED: 'var(--color-status-warning-text)',\n};\n\n// ---------------------------------------------------------------------------\n// Create invoice modal → subCreateInvoice\n// ---------------------------------------------------------------------------\n\nfunction CreateInvoiceModal({ onClose }: { onClose: () => void }) {\n const tr = useTr();\n const create = useCreateInvoice();\n const relationshipsQ = useRelationships({ pagination: { limit: 100, offset: 0 } });\n const relationships = relationshipsQ.data?.items ?? [];\n\n const [relationshipId, setRelationshipId] = useState('');\n const [number, setNumber] = useState('');\n const [status, setStatus] = useState<SubInvoiceStatus>('ISSUED');\n const [currency, setCurrency] = useState('USD');\n const [subtotal, setSubtotal] = useState('');\n const [taxAmount, setTaxAmount] = useState('');\n const [total, setTotal] = useState('');\n const [issueDate, setIssueDate] = useState('');\n const [dueDate, setDueDate] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!relationshipId) {\n setFormError(\n tr(\n 'invoices.createModal.relationshipRequired',\n 'Select the relationship this invoice belongs to.'\n )\n );\n return;\n }\n create.mutate(\n {\n relationshipId,\n status,\n currency: currency.trim() || 'USD',\n ...(number.trim() ? { number: number.trim() } : {}),\n ...(subtotal.trim() ? { subtotal: Number(subtotal) } : {}),\n ...(taxAmount.trim() ? { taxAmount: Number(taxAmount) } : {}),\n ...(total.trim() ? { total: Number(total) } : {}),\n ...(issueDate ? { issueDate: new Date(issueDate).toISOString() } : {}),\n ...(dueDate ? { dueDate: new Date(dueDate).toISOString() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(\n err instanceof Error\n ? err.message\n : tr('invoices.createModal.error', 'Could not create invoice.')\n ),\n }\n );\n };\n\n return (\n <Modal\n title={tr('invoices.createModal.title', 'Create invoice')}\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"inv-relationship\" className={labelCls}>\n {tr('invoices.createModal.relationshipLabel', 'Relationship')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <select\n id=\"inv-relationship\"\n value={relationshipId}\n onChange={(e) => setRelationshipId(e.target.value)}\n className={inputCls}\n required\n >\n <option value=\"\">\n {tr('invoices.createModal.relationshipPlaceholder', '— Select a relationship —')}\n </option>\n {relationships.map((r) => (\n <option key={r.id} value={r.id}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"inv-number\" className={labelCls}>\n {tr('invoices.createModal.numberLabel', 'Number')}\n </label>\n <input\n id=\"inv-number\"\n type=\"text\"\n value={number}\n onChange={(e) => setNumber(e.target.value)}\n placeholder={tr('invoices.createModal.numberPlaceholder', 'INV-0001')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-status\" className={labelCls}>\n {tr('invoices.createModal.statusLabel', 'Status')}\n </label>\n <select\n id=\"inv-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value as SubInvoiceStatus)}\n className={inputCls}\n >\n {INVOICE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {tr(STATUS_KEY[s], s)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"inv-currency\" className={labelCls}>\n {tr('invoices.createModal.currencyLabel', 'Currency')}\n </label>\n <input\n id=\"inv-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"inv-subtotal\" className={labelCls}>\n {tr('invoices.createModal.subtotalLabel', 'Subtotal')}\n </label>\n <input\n id=\"inv-subtotal\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={subtotal}\n onChange={(e) => setSubtotal(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-tax\" className={labelCls}>\n {tr('invoices.createModal.taxLabel', 'Tax')}\n </label>\n <input\n id=\"inv-tax\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={taxAmount}\n onChange={(e) => setTaxAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-total\" className={labelCls}>\n {tr('invoices.createModal.totalLabel', 'Total')}\n </label>\n <input\n id=\"inv-total\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={total}\n onChange={(e) => setTotal(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"inv-issue\" className={labelCls}>\n {tr('invoices.createModal.issueDateLabel', 'Issue date')}\n </label>\n <input\n id=\"inv-issue\"\n type=\"date\"\n value={issueDate}\n onChange={(e) => setIssueDate(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-due\" className={labelCls}>\n {tr('invoices.createModal.dueDateLabel', 'Due date')}\n </label>\n <input\n id=\"inv-due\"\n type=\"date\"\n value={dueDate}\n onChange={(e) => setDueDate(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n {tr('invoices.createModal.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending\n ? tr('invoices.creating', 'Creating…')\n : tr('invoices.createModal.submit', 'Create invoice')}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add payment method modal → subCreatePaymentInstrument\n// ---------------------------------------------------------------------------\n\nfunction AddPaymentMethodModal({ onClose }: { onClose: () => void }) {\n const tr = useTr();\n const create = useCreatePaymentInstrument();\n const [label, setLabel] = useState('');\n const [type, setType] = useState<SubPaymentInstrumentType>('CARD');\n const [brand, setBrand] = useState('');\n const [last4, setLast4] = useState('');\n const [expiryMonth, setExpiryMonth] = useState('');\n const [expiryYear, setExpiryYear] = useState('');\n const [isDefault, setIsDefault] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError(tr('invoices.addPaymentMethodModal.labelRequired', 'A label is required.'));\n return;\n }\n create.mutate(\n {\n label: label.trim(),\n type,\n isDefault,\n ...(brand.trim() ? { brand: brand.trim() } : {}),\n ...(last4.trim() ? { last4: last4.trim() } : {}),\n ...(expiryMonth.trim() ? { expiryMonth: Number(expiryMonth) } : {}),\n ...(expiryYear.trim() ? { expiryYear: Number(expiryYear) } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(\n err instanceof Error\n ? err.message\n : tr('invoices.addPaymentMethodModal.error', 'Could not add payment method.')\n ),\n }\n );\n };\n\n return (\n <Modal\n title={tr('invoices.addPaymentMethodModal.title', 'Add payment method')}\n description={tr(\n 'invoices.addPaymentMethodModal.description',\n 'A passive annotation of which external card/account pays for a subscription — no full card numbers or CVV.'\n )}\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"pm-label\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.labelLabel', 'Label')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"pm-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder={tr(\n 'invoices.addPaymentMethodModal.labelPlaceholder',\n 'e.g. Personal Visa'\n )}\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"pm-type\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.typeLabel', 'Type')}\n </label>\n <select\n id=\"pm-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubPaymentInstrumentType)}\n className={inputCls}\n >\n {PAYMENT_TYPES.map((t) => (\n <option key={t} value={t}>\n {formatEnumLabel(t)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"pm-brand\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.brandLabel', 'Brand')}\n </label>\n <input\n id=\"pm-brand\"\n type=\"text\"\n value={brand}\n onChange={(e) => setBrand(e.target.value)}\n placeholder={tr('invoices.addPaymentMethodModal.brandPlaceholder', 'Visa, Amex…')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"pm-last4\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.last4Label', 'Last 4')}\n </label>\n <input\n id=\"pm-last4\"\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={4}\n value={last4}\n onChange={(e) => setLast4(e.target.value.replace(/\\D/g, ''))}\n placeholder={tr('invoices.addPaymentMethodModal.last4Placeholder', '4242')}\n className={inputCls}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-2\">\n <div>\n <label htmlFor=\"pm-exp-month\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.expMonthLabel', 'Exp. mo.')}\n </label>\n <input\n id=\"pm-exp-month\"\n type=\"number\"\n min=\"1\"\n max=\"12\"\n value={expiryMonth}\n onChange={(e) => setExpiryMonth(e.target.value)}\n placeholder={tr('invoices.addPaymentMethodModal.expMonthPlaceholder', '12')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"pm-exp-year\" className={labelCls}>\n {tr('invoices.addPaymentMethodModal.expYearLabel', 'Exp. yr.')}\n </label>\n <input\n id=\"pm-exp-year\"\n type=\"number\"\n min=\"2024\"\n max=\"2099\"\n value={expiryYear}\n onChange={(e) => setExpiryYear(e.target.value)}\n placeholder={tr('invoices.addPaymentMethodModal.expYearPlaceholder', '2028')}\n className={inputCls}\n />\n </div>\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isDefault}\n onChange={(e) => setIsDefault(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n {tr('invoices.addPaymentMethodModal.defaultLabel', 'Set as default')}\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n {tr('invoices.addPaymentMethodModal.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending\n ? tr('invoices.addPaymentMethodModal.adding', 'Adding…')\n : tr('invoices.addPaymentMethodModal.submit', 'Add payment method')}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Payment methods panel\n// ---------------------------------------------------------------------------\n\nfunction PaymentMethodsSection() {\n const tr = useTr();\n const { data, isLoading, isError, refetch } = usePaymentInstruments();\n const remove = useRemovePaymentInstrument();\n const [showAdd, setShowAdd] = useState(false);\n\n const instruments = data ?? [];\n\n return (\n <Section\n title={tr('invoices.paymentMethods.title', 'Payment methods')}\n description={tr(\n 'invoices.paymentMethods.description',\n 'Which external cards and accounts pay for your subscriptions'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => setShowAdd(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> {tr('invoices.paymentMethods.add', 'Add payment method')}\n </button>\n }\n >\n {isLoading ? (\n <LoadingView label={tr('invoices.paymentMethods.loading', 'Loading payment methods…')} />\n ) : isError ? (\n <ErrorView\n message={tr('invoices.paymentMethods.error', 'Could not load payment methods.')}\n onRetry={() => void refetch()}\n />\n ) : instruments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CreditCard className=\"h-12 w-12\" />}\n title={tr('invoices.paymentMethods.emptyTitle', 'No payment methods')}\n description={tr(\n 'invoices.paymentMethods.emptyDescription',\n 'Annotate which card or account pays for each subscription. No full card numbers are stored.'\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {instruments.map((pi) => {\n const meta = [\n pi.expiryMonth && pi.expiryYear\n ? `Exp ${String(pi.expiryMonth).padStart(2, '0')}/${String(pi.expiryYear).slice(-2)}`\n : null,\n ].filter(Boolean);\n return (\n <GlassCard key={pi.id} treatment=\"metric\" className=\"flex flex-col gap-4 p-4\">\n {/* Card face */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-3\">\n <span className=\"grid h-9 w-12 place-items-center rounded-md bg-[var(--color-accent-soft)] text-text-primary\">\n <CreditCard className=\"h-4 w-4\" aria-hidden />\n </span>\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-1.5 truncate text-sm font-semibold text-text-primary\">\n {pi.brand || pi.label}\n {pi.isDefault && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-status-success-text\">\n <Star className=\"h-2.5 w-2.5 fill-current\" aria-hidden />\n {tr('invoices.paymentMethods.default', 'Default')}\n </span>\n )}\n </p>\n <p className=\"font-mono text-sm tracking-[0.15em] text-text-secondary\">\n {pi.last4 ? `•••• ${pi.last4}` : formatEnumLabel(pi.type)}\n </p>\n </div>\n </div>\n <StatusBadge status={pi.status} />\n </div>\n\n <div className=\"mt-auto flex items-center justify-between border-t border-border-subtle pt-3\">\n <p className=\"truncate text-xs text-text-secondary\">\n {[pi.label, ...meta].filter(Boolean).join(' · ')}\n </p>\n <button\n type=\"button\"\n aria-label={tr('invoices.paymentMethods.removeAria', 'Remove {{label}}', {\n label: pi.label,\n })}\n disabled={remove.isPending}\n onClick={() => remove.mutate(pi.id)}\n className=\"inline-flex shrink-0 items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-secondary hover:border-status-error-text/40 hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" aria-hidden />\n {tr('invoices.paymentMethods.remove', 'Remove')}\n </button>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n\n {showAdd && <AddPaymentMethodModal onClose={() => setShowAdd(false)} />}\n </Section>\n );\n}\n\nexport function InvoicesPage() {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [status, setStatus] = useState<'' | SubInvoiceStatus>('');\n const [showCreate, setShowCreate] = useState(false);\n\n const statusOptions: Array<{ value: '' | SubInvoiceStatus; label: string }> = [\n { value: '', label: tr('invoices.filters.allStatuses', 'All statuses') },\n ...INVOICE_STATUSES.map((s) => ({ value: s, label: tr(STATUS_KEY[s], s) })),\n ];\n\n const { data, isLoading, isError, refetch, isFetching } = useInvoices({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n const markPaid = useMarkInvoicePaid();\n\n // An invoice only carries `relationshipId`. Resolve the subscription it bills\n // for from the real relationship list (same join the Asset Vault uses) so the\n // ledger names the subscription instead of an opaque id.\n const relationshipsQ = useRelationships({ pagination: { limit: 100, offset: 0 } });\n const relationshipLabels = useMemo(() => {\n const map = new Map<string, string>();\n for (const r of relationshipsQ.data?.items ?? []) map.set(r.id, r.label);\n return map;\n }, [relationshipsQ.data]);\n\n const items = useMemo(() => data?.items ?? [], [data]);\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n const totals = useMemo(() => {\n const outstanding = items.filter((i) => PAYABLE.has(i.status));\n const currency = items[0]?.currency ?? 'USD';\n\n // Status distribution → donut segments (only statuses actually present).\n const byStatus = new Map<SubInvoiceStatus, number>();\n for (const i of items) byStatus.set(i.status, (byStatus.get(i.status) ?? 0) + 1);\n const distribution: Segment[] = Array.from(byStatus.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([s, count], idx) => ({\n label: tr(STATUS_KEY[s], s.charAt(0) + s.slice(1).toLowerCase()),\n value: count,\n color: STATUS_TINT[s] ?? CHART_COLORS[idx % CHART_COLORS.length],\n }));\n\n return {\n outstandingCount: outstanding.length,\n outstandingTotal: outstanding.reduce((sum, i) => sum + i.total, 0),\n paidCount: items.filter((i) => i.status === 'PAID').length,\n overdueCount: items.filter((i) => i.status === 'OVERDUE').length,\n distribution,\n currency,\n };\n }, [items, tr]);\n\n return (\n <PageLayout\n title={tr('invoices.title', 'Invoices')}\n description={tr('invoices.description', 'Your billing ledger across every relationship')}\n actions={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('invoices.create', 'Create invoice')}\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n {tr(\n 'invoices.purpose',\n 'Every invoice from your recurring relationships in one ledger. Filter by status and settle issued or overdue invoices directly.'\n )}\n </PagePurpose>\n\n {!isLoading && !isError && items.length > 0 && (\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:col-span-2\">\n <MetricsCard\n featured\n icon={<Receipt />}\n value={formatCurrency(totals.outstandingTotal, totals.currency, {\n maximumFractionDigits: 0,\n })}\n label={tr('invoices.metrics.outstanding', 'Outstanding · this page')}\n />\n <MetricsCard\n icon={<FileText />}\n value={totals.outstandingCount}\n label={tr('invoices.metrics.openInvoices', 'Open invoices')}\n />\n <MetricsCard\n icon={<AlertTriangle />}\n value={totals.overdueCount}\n label={tr('invoices.metrics.overdue', 'Overdue')}\n {...(totals.overdueCount > 0 ? { trend: 'down' as const } : {})}\n />\n <MetricsCard\n icon={<CheckCircle2 />}\n value={totals.paidCount}\n label={tr('invoices.metrics.paid', 'Paid')}\n {...(totals.paidCount > 0 ? { trend: 'up' as const } : {})}\n />\n </div>\n\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('invoices.metrics.statusBreakdown', 'Status breakdown')}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr('invoices.metrics.statusBreakdownHint', 'Across the invoices on this page')}\n </p>\n <div className=\"mt-4 flex flex-col items-center gap-5 sm:flex-row sm:items-center lg:flex-col xl:flex-row\">\n <Donut\n data={totals.distribution}\n size={132}\n thickness={16}\n centerLabel={String(items.length)}\n centerSub={tr('invoices.metrics.invoices', 'invoices')}\n className=\"shrink-0\"\n />\n <Legend\n data={totals.distribution}\n className=\"w-full min-w-0 flex-1\"\n valueFormat={(n) => String(n)}\n />\n </div>\n </GlassCard>\n </div>\n )}\n\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <select\n aria-label={tr('invoices.filters.statusLabel', 'Filter by status')}\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as '' | SubInvoiceStatus);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {statusOptions.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label={tr('invoices.loading', 'Loading invoices…')} />\n ) : isError ? (\n <ErrorView\n message={tr('invoices.error', 'Could not load invoices.')}\n onRetry={() => void refetch()}\n />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<Receipt className=\"h-12 w-12\" />}\n title={tr('invoices.emptyTitle', 'No invoices')}\n description={\n status\n ? tr('invoices.emptyDescriptionFiltered', 'No invoices match this status filter.')\n : tr(\n 'invoices.emptyDescription',\n 'Invoices from your relationships will appear here — or create one manually.'\n )\n }\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('invoices.create', 'Create invoice')}\n </button>\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 overflow-hidden rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"hidden grid-cols-[1.3fr_1.2fr_0.8fr_0.8fr_0.8fr_auto] gap-4 border-b border-border-subtle bg-bg-sunken px-4 py-2.5 text-xs font-medium text-text-secondary sm:grid\">\n <span>{tr('invoices.table.invoice', 'Invoice')}</span>\n <span>{tr('invoices.table.subscription', 'Subscription')}</span>\n <span>{tr('invoices.table.status', 'Status')}</span>\n <span className=\"text-right\">{tr('invoices.table.total', 'Total')}</span>\n <span className=\"text-right\">{tr('invoices.table.due', 'Due')}</span>\n <span className=\"text-right\">{tr('invoices.table.action', 'Action')}</span>\n </div>\n <div className=\"divide-y divide-border-subtle\">\n {items.map((inv) => (\n <div\n key={inv.id}\n className=\"grid grid-cols-1 gap-2 px-4 py-3 transition-colors hover:bg-bg-sunken/50 sm:grid-cols-[1.3fr_1.2fr_0.8fr_0.8fr_0.8fr_auto] sm:items-center sm:gap-4\"\n >\n <button\n type=\"button\"\n onClick={() => navigateTo(`/relationships/${inv.relationshipId}`)}\n className=\"min-w-0 text-left\"\n >\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {inv.number ||\n `${tr('invoices.table.invoice', 'Invoice')} ${inv.id.slice(0, 8)}`}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {inv.issueDate\n ? tr('invoices.table.issued', 'Issued {{date}}', {\n date: formatDate(inv.issueDate),\n })\n : '—'}\n </p>\n </button>\n <span className=\"min-w-0 truncate text-sm text-text-secondary\">\n {relationshipLabels.get(inv.relationshipId) ?? '—'}\n </span>\n <span>\n <StatusBadge status={inv.status} />\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary sm:text-right\">\n {formatCurrency(inv.total, inv.currency)}\n </span>\n <span className=\"text-xs text-text-secondary sm:text-right\">\n {inv.dueDate ? formatDate(inv.dueDate) : '—'}\n </span>\n <span className=\"sm:text-right\">\n {PAYABLE.has(inv.status) ? (\n <button\n type=\"button\"\n disabled={markPaid.isPending}\n onClick={() => markPaid.mutate(inv.id)}\n aria-label={tr('invoices.table.markPaidAria', 'Mark {{invoice}} paid', {\n invoice:\n inv.number ||\n `${tr('invoices.table.invoice', 'Invoice')} ${inv.id.slice(0, 8)}`,\n })}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle px-3 py-1.5 text-xs font-medium text-status-success-text hover:opacity-90 disabled:opacity-50\"\n >\n <CheckCircle2 className=\"h-3.5 w-3.5\" aria-hidden />\n {tr('invoices.table.markPaid', 'Mark paid')}\n </button>\n ) : inv.paidAt ? (\n <span className=\"text-xs text-text-secondary\">\n {tr('invoices.table.paidAt', 'Paid {{date}}', {\n date: formatDate(inv.paidAt),\n })}\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">—</span>\n )}\n </span>\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n {tr('invoices.pagination.page', 'Page {{page}}', { page: page + 1 })}\n {isFetching ? ` · ${tr('invoices.pagination.refreshing', 'refreshing…')}` : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr('invoices.pagination.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr('invoices.pagination.next', 'Next')}\n </button>\n </div>\n </div>\n </>\n )}\n\n <PaymentMethodsSection />\n\n {showCreate && <CreateInvoiceModal onClose={() => setShowCreate(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnterpriseSpendPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/enterprise/EnterpriseSpendPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"EnterpriseSpendPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/enterprise/EnterpriseSpendPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA4RH,wBAAgB,mBAAmB,gCAgmBlC"}
|
|
@@ -194,6 +194,7 @@ var U = ({ status: e, fmt: n, tr: r }) => /* @__PURE__ */ M("div", {
|
|
|
194
194
|
type: "button",
|
|
195
195
|
onClick: () => b(e.department, null),
|
|
196
196
|
disabled: o.isPending,
|
|
197
|
+
"aria-label": r("enterprise.budgets.removeAria", "Remove the {{name}} budget", { name: e.department ?? r("enterprise.budgets.companyWide", "Company-wide") }),
|
|
197
198
|
className: "text-xs font-medium text-status-error-text hover:underline disabled:opacity-60",
|
|
198
199
|
children: r("enterprise.budgets.remove", "Remove")
|
|
199
200
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnterpriseSpendPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/enterprise/EnterpriseSpendPage.tsx"],"sourcesContent":["/**\n * Enterprise SaaS spend management\n * @module subscriberbot/pages/enterprise\n *\n * `/subscriberbot/enterprise/spend` (BOFF-4008) — the finance / IT leadership\n * view of recurring spend: company-wide spend and trend, budget variance,\n * spend by department, duplicate tools, shadow IT, the quarterly renewal\n * pipeline, compliance / risk flags and the licence table. Every figure comes\n * from the real `subEnterpriseSpendDashboard` query, which aggregates the\n * live portfolio server-side; nothing on this page is mocked.\n */\n\nimport { useMemo, useState, type FC, type ReactNode } from 'react';\nimport {\n AlertTriangle,\n Building2,\n CalendarClock,\n Copy,\n EyeOff,\n Layers,\n PiggyBank,\n ShieldAlert,\n Target,\n Users,\n Wallet,\n} from 'lucide-react';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n BarList,\n EmptyState,\n ErrorView,\n LoadingView,\n MetricsCard,\n MiniProgress,\n Modal,\n PageLayout,\n Section,\n Sparkline,\n} from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useEnterpriseSpendDashboard,\n useSetSpendBudget,\n useSpendBudgets,\n} from '../../hooks/useSubscriberBotApi';\nimport { useTr } from '../../hooks/useTr';\nimport { formatCurrency, formatDate } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type {\n SubBudgetStatus,\n SubEnterpriseSpendDashboard,\n SubOverlapType,\n SubShadowItReason,\n SubSpendRiskKind,\n SubSpendRiskSeverity,\n} from '../../types';\n\ntype Tr = ReturnType<typeof useTr>;\n\nconst SHADOW_REASON: Record<SubShadowItReason, { key: string; fallback: string }> = {\n UNCATALOGUED: { key: 'enterprise.shadow.reasons.uncatalogued', fallback: 'Not in the catalogue' },\n UNVERIFIED_PROVIDER: {\n key: 'enterprise.shadow.reasons.unverified',\n fallback: 'Unverified provider',\n },\n DEPRECATED_PROVIDER: {\n key: 'enterprise.shadow.reasons.deprecated',\n fallback: 'Deprecated provider',\n },\n};\n\nconst OVERLAP_TYPE: Record<SubOverlapType, { key: string; fallback: string }> = {\n EXACT_DUPLICATE: { key: 'enterprise.duplicates.types.exact', fallback: 'Same vendor' },\n CAPABILITY: { key: 'enterprise.duplicates.types.capability', fallback: 'Same capability' },\n ENTITLEMENT: {\n key: 'enterprise.duplicates.types.entitlement',\n fallback: 'Covered by another plan',\n },\n};\n\nconst RISK_KIND: Record<SubSpendRiskKind, { key: string; fallback: string }> = {\n PAST_DUE: { key: 'enterprise.risks.kinds.pastDue', fallback: 'Payment failed' },\n MISSING_INVOICE: { key: 'enterprise.risks.kinds.missingInvoice', fallback: 'Missing invoice' },\n UNTRACKED_TRIAL: { key: 'enterprise.risks.kinds.untrackedTrial', fallback: 'Untracked trial' },\n HIGH_COST_AI: { key: 'enterprise.risks.kinds.highCostAi', fallback: 'High-cost AI service' },\n};\n\nconst SEVERITY_STYLE: Record<SubSpendRiskSeverity, string> = {\n HIGH: 'bg-status-error-bg-subtle text-status-error-text',\n MEDIUM: 'bg-status-warning-bg-subtle text-status-warning-text',\n LOW: 'bg-bg-sunken text-text-secondary',\n};\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1.5 block text-xs font-medium text-text-secondary';\nconst linkCls =\n 'truncate text-left text-sm font-medium text-text-primary hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded';\n\nfunction departmentName(tr: Tr, department: string | null): string {\n return department ?? tr('enterprise.departments.unassigned', 'Unassigned');\n}\n\n/** \"62% of 5,000.00 budget\" pill for a budget status. */\nconst BudgetMeter: FC<{ status: SubBudgetStatus; fmt: (n: number) => string; tr: Tr }> = ({\n status,\n fmt,\n tr,\n}) => (\n <div className=\"space-y-1.5\">\n <MiniProgress\n value={Math.min(status.utilizationPct, 100)}\n tone={status.overBudget ? 'negative' : status.utilizationPct >= 85 ? 'warning' : 'positive'}\n />\n <p\n className={cn(\n 'text-xs tabular-nums',\n status.overBudget ? 'text-status-error-text' : 'text-text-secondary'\n )}\n >\n {status.overBudget\n ? tr('enterprise.budget.over', '{{amount}} over a {{budget}} budget', {\n amount: fmt(-status.variance),\n budget: fmt(status.monthlyBudget),\n })\n : tr('enterprise.budget.under', '{{amount}} left of {{budget}} ({{pct}}% used)', {\n amount: fmt(status.variance),\n budget: fmt(status.monthlyBudget),\n pct: status.utilizationPct,\n })}\n </p>\n </div>\n);\n\nconst Card: FC<{ title: string; icon: ReactNode; children: ReactNode; testId?: string }> = ({\n title,\n icon,\n children,\n testId,\n}) => (\n <GlassCard treatment=\"plain\" className=\"p-6\" data-testid={testId}>\n <div className=\"mb-4 flex items-center gap-2\">\n <span className=\"text-text-secondary\">{icon}</span>\n <h3 className=\"text-sm font-semibold text-text-primary\">{title}</h3>\n </div>\n {children}\n </GlassCard>\n);\n\n/** Budgets editor — company-wide and per-department monthly budgets. */\nconst BudgetsModal: FC<{\n currency: string;\n departments: string[];\n onClose: () => void;\n}> = ({ currency, departments, onClose }) => {\n const tr = useTr();\n const budgetsQ = useSpendBudgets();\n const setBudget = useSetSpendBudget();\n const [department, setDepartment] = useState('');\n const [amount, setAmount] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const budgets = (budgetsQ.data ?? []).filter((b) => b.currency === currency);\n\n const save = (dept: string | null, monthlyAmount: number | null) => {\n setError(null);\n setBudget.mutate(\n { department: dept, currency, monthlyAmount },\n {\n onSuccess: () => {\n setAmount('');\n setDepartment('');\n },\n onError: (err) =>\n setError(\n err instanceof Error\n ? err.message\n : tr('enterprise.budgets.error', 'Could not save the budget.')\n ),\n }\n );\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const value = Number(amount);\n if (!amount.trim() || !Number.isFinite(value) || value <= 0) {\n setError(tr('enterprise.budgets.amountRequired', 'Enter a monthly amount above zero.'));\n return;\n }\n save(department.trim() || null, value);\n };\n\n return (\n <Modal\n title={tr('enterprise.budgets.title', 'Monthly budgets')}\n description={tr(\n 'enterprise.budgets.description',\n 'Budgets apply to spend billed in {{currency}}. Leave the department empty for the company-wide budget.',\n { currency }\n )}\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {error && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-[1fr_10rem_auto] sm:items-end\">\n <div>\n <label htmlFor=\"budget-department\" className={labelCls}>\n {tr('enterprise.budgets.department', 'Department (optional)')}\n </label>\n <input\n id=\"budget-department\"\n list=\"budget-department-options\"\n value={department}\n maxLength={120}\n onChange={(e) => setDepartment(e.target.value)}\n placeholder={tr('enterprise.budgets.companyWide', 'Company-wide')}\n className={inputCls}\n />\n <datalist id=\"budget-department-options\">\n {departments.map((d) => (\n <option key={d} value={d} />\n ))}\n </datalist>\n </div>\n <div>\n <label htmlFor=\"budget-amount\" className={labelCls}>\n {tr('enterprise.budgets.amount', 'Per month ({{currency}})', { currency })}\n </label>\n <input\n id=\"budget-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n inputMode=\"decimal\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <button\n type=\"submit\"\n disabled={setBudget.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-60\"\n >\n {tr('enterprise.budgets.save', 'Save budget')}\n </button>\n </div>\n </form>\n\n <div className=\"mt-6\">\n {budgetsQ.isLoading ? (\n <LoadingView label={tr('enterprise.budgets.loading', 'Loading budgets…')} />\n ) : budgets.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('enterprise.budgets.none', 'No budgets set for {{currency}} yet.', { currency })}\n </p>\n ) : (\n <ul className=\"divide-y divide-border-subtle rounded-lg border border-border-subtle\">\n {budgets.map((b) => (\n <li key={b.id} className=\"flex items-center justify-between gap-3 px-4 py-3\">\n <span className=\"truncate text-sm text-text-primary\">\n {b.department ?? tr('enterprise.budgets.companyWide', 'Company-wide')}\n </span>\n <span className=\"flex items-center gap-3\">\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatCurrency(b.monthlyAmount, b.currency)}\n </span>\n <button\n type=\"button\"\n onClick={() => save(b.department, null)}\n disabled={setBudget.isPending}\n className=\"text-xs font-medium text-status-error-text hover:underline disabled:opacity-60\"\n >\n {tr('enterprise.budgets.remove', 'Remove')}\n </button>\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n </Modal>\n );\n};\n\nexport function EnterpriseSpendPage() {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const [currency, setCurrency] = useState<string | null>(null);\n const [budgetsOpen, setBudgetsOpen] = useState(false);\n const query = useEnterpriseSpendDashboard(currency);\n const data: SubEnterpriseSpendDashboard | undefined = query.data;\n\n const fmt = useMemo(() => {\n const code = data?.currency ?? 'USD';\n return (n: number) => formatCurrency(n, code);\n }, [data?.currency]);\n\n const openRelationship = (id: string) => navigateTo(`/relationships/${id}`);\n\n if (query.isLoading)\n return <LoadingView label={tr('enterprise.loading', 'Aggregating company-wide spend…')} />;\n if (query.isError || !data)\n return (\n <ErrorView\n message={tr('enterprise.error', 'Could not load the enterprise spend dashboard.')}\n onRetry={() => void query.refetch()}\n />\n );\n\n const nextQuarter = data.renewalPipeline[0];\n const change = data.monthlySpendChangePct;\n const currencyOptions = [data.currency, ...data.otherCurrencies.map((c) => c.currency)];\n const departmentNames = data.departments.map((d) => d.department).filter((d): d is string => !!d);\n const isEmpty =\n data.activeRelationshipCount === 0 &&\n data.shadowItCount === 0 &&\n data.riskFlagCount === 0 &&\n data.departments.length === 0;\n\n return (\n <PageLayout\n title={tr('enterprise.title', 'SaaS spend management')}\n description={tr(\n 'enterprise.description',\n 'Company-wide recurring spend by department, with duplicate tools, shadow IT, renewals and risks.'\n )}\n actions={\n <div className=\"flex items-center gap-2\">\n {currencyOptions.length > 1 && (\n <label className=\"flex items-center gap-2 text-xs text-text-secondary\">\n {tr('enterprise.currency', 'Currency')}\n <select\n aria-label={tr('enterprise.currency', 'Currency')}\n value={data.currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-2 py-1.5 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {currencyOptions.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </select>\n </label>\n )}\n <button\n type=\"button\"\n onClick={() => setBudgetsOpen(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Target className=\"h-4 w-4\" aria-hidden /> {tr('enterprise.manageBudgets', 'Budgets')}\n </button>\n </div>\n }\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n 'enterprise.purpose',\n 'For finance, IT and procurement. Spend is the monthly run-rate of every subscription billing right now, computed live from your portfolio in one currency at a time. Allocate subscriptions to a department on the subscription itself.'\n )}\n </PagePurpose>\n\n {data.truncated && (\n <p className=\"mb-4 rounded-lg bg-status-warning-bg-subtle px-4 py-2 text-sm text-status-warning-text\">\n {tr(\n 'enterprise.truncated',\n 'This workspace has more subscriptions than one dashboard aggregates; figures cover the most recent ones.'\n )}\n </p>\n )}\n\n {isEmpty ? (\n <EmptyState\n icon={<Building2 className=\"h-8 w-8\" />}\n title={tr('enterprise.emptyTitle', 'No company spend to analyse yet')}\n description={tr(\n 'enterprise.emptyDescription',\n 'Add your SaaS subscriptions and allocate them to departments to see spend, duplicates, shadow IT and renewals here.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('enterprise.emptyAction', 'Add a subscription')}\n </button>\n }\n />\n ) : (\n <div className=\"space-y-6\">\n {/* KPI header */}\n <div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\"\n data-testid=\"enterprise-kpis\"\n >\n <MetricsCard\n featured\n icon={<Wallet className=\"h-5 w-5\" />}\n value={fmt(data.annualSpend)}\n label={tr(\n 'enterprise.kpis.annualSpend',\n 'Total SaaS spend / year ({{monthly}} / month)',\n {\n monthly: fmt(data.monthlySpend),\n }\n )}\n trend={\n change == null ? undefined : change > 0 ? 'up' : change < 0 ? 'down' : 'neutral'\n }\n trendValue={change == null ? undefined : `${change > 0 ? '+' : ''}${change}%`}\n />\n <MetricsCard\n icon={<Layers className=\"h-5 w-5\" />}\n value={data.activeRelationshipCount}\n label={tr('enterprise.kpis.activeLicenses', 'Active licences')}\n />\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={data.seatUtilizationPct != null ? `${data.seatUtilizationPct}%` : '—'}\n label={\n data.seatUtilizationPct != null\n ? tr('enterprise.kpis.seats', 'Seats utilised ({{used}} of {{assigned}})', {\n used: data.seatsUsed,\n assigned: data.seatsAssigned,\n })\n : tr('enterprise.kpis.seatsUntracked', 'Seats utilised (no seat data yet)')\n }\n />\n <MetricsCard\n icon={<CalendarClock className=\"h-5 w-5\" />}\n value={nextQuarter ? nextQuarter.relationshipCount : 0}\n label={tr('enterprise.kpis.renewals', 'Renewals this quarter ({{amount}})', {\n amount: fmt(nextQuarter?.amount ?? 0),\n })}\n />\n </div>\n\n {/* Trend + budget */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <Card\n title={tr('enterprise.trend.title', 'Monthly run-rate, last 150 days')}\n icon={<Wallet className=\"h-4 w-4\" />}\n >\n <Sparkline\n points={data.trend.map((p) => p.monthlySpend)}\n positive={(change ?? 0) <= 0}\n className=\"w-full\"\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr('enterprise.trend.vs', '{{now}} now vs {{before}} 30 days ago', {\n now: fmt(data.monthlySpend),\n before: fmt(data.previousMonthlySpend),\n })}\n </p>\n </Card>\n <Card\n title={tr('enterprise.budget.title', 'Company budget')}\n icon={<Target className=\"h-4 w-4\" />}\n testId=\"enterprise-budget\"\n >\n {data.budget ? (\n <BudgetMeter status={data.budget} fmt={fmt} tr={tr} />\n ) : (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr('enterprise.budget.none', 'No company-wide budget set for {{currency}}.', {\n currency: data.currency,\n })}\n </p>\n <button\n type=\"button\"\n onClick={() => setBudgetsOpen(true)}\n className=\"text-sm font-medium text-text-primary underline\"\n >\n {tr('enterprise.budget.set', 'Set a budget')}\n </button>\n </div>\n )}\n </Card>\n <Card\n title={tr('enterprise.savings.title', 'Consolidation opportunity')}\n icon={<PiggyBank className=\"h-4 w-4\" />}\n >\n <p className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {fmt(data.duplicatePotentialMonthlySavings)}\n <span className=\"text-sm font-normal text-text-secondary\">\n {tr('enterprise.perMonth', '/mo')}\n </span>\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'enterprise.savings.detail',\n 'Upper bound if each duplicated tool is consolidated into one subscription; shadow IT costs {{shadow}} / month.',\n { shadow: fmt(data.shadowItMonthlySpend) }\n )}\n </p>\n </Card>\n </div>\n\n {/* Departments */}\n <Section\n title={tr('enterprise.departments.title', 'Spend by department')}\n description={tr(\n 'enterprise.departments.description',\n 'Monthly run-rate per department, with each department budget.'\n )}\n >\n <GlassCard treatment=\"plain\" className=\"p-6\" data-testid=\"enterprise-departments\">\n {data.departments.length ? (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <BarList\n data={data.departments.map((d) => ({\n label: departmentName(tr, d.department),\n value: d.monthlySpend,\n }))}\n valueFormat={fmt}\n />\n <ul className=\"divide-y divide-border-subtle\">\n {data.departments.map((d) => (\n <li key={d.department ?? '__unassigned'} className=\"py-3 first:pt-0\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"truncate font-medium text-text-primary\">\n {departmentName(tr, d.department)}\n </span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {tr('enterprise.departments.row', '{{count}} subs · {{share}}%', {\n count: d.relationshipCount,\n share: d.sharePct,\n })}\n </span>\n </div>\n {d.budget && (\n <div className=\"mt-2\">\n <BudgetMeter status={d.budget} fmt={fmt} tr={tr} />\n </div>\n )}\n </li>\n ))}\n </ul>\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {tr('enterprise.departments.none', 'No spend is billing right now.')}\n </p>\n )}\n </GlassCard>\n </Section>\n\n {/* Duplicates + shadow IT */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <Card\n title={tr('enterprise.duplicates.title', 'Duplicate tools ({{count}})', {\n count: data.duplicateTools.length,\n })}\n icon={<Copy className=\"h-4 w-4\" />}\n testId=\"enterprise-duplicates\"\n >\n {data.duplicateTools.length ? (\n <ul className=\"space-y-3\">\n {data.duplicateTools.map((g) => (\n <li\n key={g.key}\n className=\"rounded-lg border border-status-warning-text/30 bg-status-warning-bg-subtle p-3\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n {g.providerId ? (\n <button\n type=\"button\"\n className={linkCls}\n onClick={() => navigateTo(`/providers/${g.providerId}`)}\n >\n {g.name}\n </button>\n ) : (\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {g.name}\n </p>\n )}\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'enterprise.duplicates.summary',\n '{{count}} subscriptions · {{teams}}',\n {\n count: g.relationshipCount,\n teams: g.departments.length\n ? g.departments.join(', ')\n : tr('enterprise.departments.unassigned', 'Unassigned'),\n }\n )}\n </p>\n </div>\n <span className=\"shrink-0 text-right text-xs text-status-warning-text\">\n {tr('enterprise.duplicates.save', 'Save up to {{amount}}/mo', {\n amount: fmt(g.potentialMonthlySavings),\n })}\n </span>\n </div>\n <ul className=\"mt-2 flex flex-wrap gap-1.5\">\n {g.relationships.map((m) => (\n <li key={m.relationshipId}>\n <button\n type=\"button\"\n onClick={() => openRelationship(m.relationshipId)}\n className={cn(\n 'rounded-md px-2 py-1 text-xs hover:bg-bg-sunken',\n m.keep\n ? 'border border-status-success-text/40 bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-surface text-text-primary'\n )}\n >\n {m.keep ? `${tr('enterprise.duplicates.keep', 'Keep')}: ` : ''}\n {m.label} · {fmt(m.monthlySpend)}\n </button>\n </li>\n ))}\n </ul>\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(OVERLAP_TYPE[g.type].key, OVERLAP_TYPE[g.type].fallback)} ·{' '}\n {g.rationale}\n </p>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {tr('enterprise.duplicates.none', 'No duplicate or overlapping tools found.')}\n </p>\n )}\n </Card>\n\n <Card\n title={tr('enterprise.shadow.title', 'Shadow IT ({{count}})', {\n count: data.shadowItCount,\n })}\n icon={<EyeOff className=\"h-4 w-4\" />}\n testId=\"enterprise-shadow-it\"\n >\n {data.shadowIt.length ? (\n <ul className=\"divide-y divide-border-subtle\">\n {data.shadowIt.map((s) => (\n <li\n key={s.relationshipId}\n className=\"flex items-center justify-between gap-3 py-2.5\"\n >\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className={linkCls}\n onClick={() => openRelationship(s.relationshipId)}\n >\n {s.label}\n </button>\n <p className=\"text-xs text-text-secondary\">\n {tr(SHADOW_REASON[s.reason].key, SHADOW_REASON[s.reason].fallback)}\n {' · '}\n {departmentName(tr, s.department)}\n {' · '}\n {tr('enterprise.shadow.found', 'found {{date}}', {\n date: formatDate(s.detectedAt),\n })}\n </p>\n </div>\n <span className=\"shrink-0 text-sm font-semibold tabular-nums text-text-primary\">\n {fmt(s.monthlySpend)}\n </span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'enterprise.shadow.none',\n 'Every discovered tool is linked to an approved provider.'\n )}\n </p>\n )}\n </Card>\n </div>\n\n {/* Renewal pipeline */}\n <Section\n title={tr('enterprise.renewals.title', 'Renewal pipeline')}\n description={tr(\n 'enterprise.renewals.description',\n 'Renewal charges due by quarter, for planning renegotiations.'\n )}\n >\n <div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\"\n data-testid=\"enterprise-renewals\"\n >\n {data.renewalPipeline.map((q) => (\n <GlassCard key={q.label} treatment=\"plain\" className=\"p-5\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {q.label}\n </p>\n <p className=\"mt-1 text-xl font-bold tabular-nums text-text-primary\">\n {fmt(q.amount)}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'enterprise.renewals.counts',\n '{{renewals}} renewals · {{subs}} subscriptions',\n {\n renewals: q.renewalCount,\n subs: q.relationshipCount,\n }\n )}\n </p>\n {q.topRenewals.length > 0 && (\n <ul className=\"mt-3 space-y-1.5\">\n {q.topRenewals.map((r) => (\n <li\n key={r.relationshipId}\n className=\"flex items-center justify-between gap-2\"\n >\n <button\n type=\"button\"\n className={cn(linkCls, 'text-xs')}\n onClick={() => openRelationship(r.relationshipId)}\n >\n {r.label}\n </button>\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {formatDate(r.renewsAt)} · {fmt(r.amount)}\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n ))}\n </div>\n </Section>\n\n {/* Risk flags */}\n <Section\n title={tr('enterprise.risks.title', 'Compliance and risk flags ({{count}})', {\n count: data.riskFlagCount,\n })}\n >\n <GlassCard treatment=\"plain\" className=\"p-2\" data-testid=\"enterprise-risks\">\n {data.riskFlags.length ? (\n <ul className=\"divide-y divide-border-subtle\">\n {data.riskFlags.map((f) => (\n <li\n key={`${f.kind}-${f.relationshipId}`}\n className=\"flex flex-col gap-2 p-4 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex min-w-0 items-start gap-3\">\n <span\n className={cn(\n 'shrink-0 rounded-md px-2 py-0.5 text-xs font-medium',\n SEVERITY_STYLE[f.severity]\n )}\n >\n {tr(RISK_KIND[f.kind].key, RISK_KIND[f.kind].fallback)}\n </span>\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className={linkCls}\n onClick={() => openRelationship(f.relationshipId)}\n >\n {f.label}\n </button>\n <p className=\"text-xs text-text-secondary\">{f.detail}</p>\n </div>\n </div>\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {departmentName(tr, f.department)} · {fmt(f.monthlySpend)}\n {tr('enterprise.perMonth', '/mo')}\n </span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"flex items-center gap-2 p-4 text-sm text-text-secondary\">\n <ShieldAlert className=\"h-4 w-4\" aria-hidden />\n {tr('enterprise.risks.none', 'No compliance or risk issues found.')}\n </p>\n )}\n </GlassCard>\n </Section>\n\n {/* Licences */}\n <Section title={tr('enterprise.licenses.title', 'Licences')}>\n <GlassCard\n treatment=\"plain\"\n className=\"overflow-x-auto\"\n data-testid=\"enterprise-licenses\"\n >\n <table className=\"w-full min-w-[640px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-xs text-text-secondary\">\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.app', 'App')}\n </th>\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.department', 'Department')}\n </th>\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.seats', 'Seats used / assigned')}\n </th>\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.utilization', 'Utilisation')}\n </th>\n <th className=\"px-4 py-3 text-right font-medium\">\n {tr('enterprise.licenses.annualCost', 'Annual cost')}\n </th>\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.renews', 'Renews')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-subtle\">\n {data.licenses.map((l) => (\n <tr key={l.relationshipId}>\n <td className=\"max-w-[16rem] px-4 py-3\">\n <button\n type=\"button\"\n className={linkCls}\n onClick={() => openRelationship(l.relationshipId)}\n >\n {l.label}\n </button>\n {l.providerName && (\n <p className=\"truncate text-xs text-text-secondary\">{l.providerName}</p>\n )}\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {departmentName(tr, l.department)}\n </td>\n <td className=\"px-4 py-3 tabular-nums text-text-primary\">\n {l.seatsAssigned != null ? `${l.seatsUsed ?? 0} / ${l.seatsAssigned}` : '—'}\n </td>\n <td className=\"w-40 px-4 py-3\">\n {l.utilizationPct != null ? (\n <MiniProgress\n value={l.utilizationPct}\n tone={l.utilizationPct >= 70 ? 'positive' : 'warning'}\n />\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </td>\n <td className=\"px-4 py-3 text-right font-semibold tabular-nums text-text-primary\">\n {fmt(l.annualCost)}\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {l.renewsAt ? formatDate(l.renewsAt) : '—'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n {data.licenses.length === 0 && (\n <p className=\"p-4 text-sm text-text-secondary\">\n {tr('enterprise.licenses.none', 'No subscription is billing right now.')}\n </p>\n )}\n </GlassCard>\n </Section>\n\n {data.otherCurrencies.length > 0 && (\n <p className=\"flex items-start gap-2 text-xs text-text-secondary\">\n <AlertTriangle className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" aria-hidden />\n {tr(\n 'enterprise.otherCurrencies',\n 'Not included above (amounts are never converted): {{list}}. Switch currency to see them.',\n {\n list: data.otherCurrencies\n .map((c) => `${formatCurrency(c.monthlySpend, c.currency)}/mo`)\n .join(', '),\n }\n )}\n </p>\n )}\n </div>\n )}\n\n {budgetsOpen && (\n <BudgetsModal\n currency={data.currency}\n departments={departmentNames}\n onClose={() => setBudgetsOpen(false)}\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EnterpriseSpendPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/enterprise/EnterpriseSpendPage.tsx"],"sourcesContent":["/**\n * Enterprise SaaS spend management\n * @module subscriberbot/pages/enterprise\n *\n * `/subscriberbot/enterprise/spend` (BOFF-4008) — the finance / IT leadership\n * view of recurring spend: company-wide spend and trend, budget variance,\n * spend by department, duplicate tools, shadow IT, the quarterly renewal\n * pipeline, compliance / risk flags and the licence table. Every figure comes\n * from the real `subEnterpriseSpendDashboard` query, which aggregates the\n * live portfolio server-side; nothing on this page is mocked.\n */\n\nimport { useMemo, useState, type FC, type ReactNode } from 'react';\nimport {\n AlertTriangle,\n Building2,\n CalendarClock,\n Copy,\n EyeOff,\n Layers,\n PiggyBank,\n ShieldAlert,\n Target,\n Users,\n Wallet,\n} from 'lucide-react';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n BarList,\n EmptyState,\n ErrorView,\n LoadingView,\n MetricsCard,\n MiniProgress,\n Modal,\n PageLayout,\n Section,\n Sparkline,\n} from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useEnterpriseSpendDashboard,\n useSetSpendBudget,\n useSpendBudgets,\n} from '../../hooks/useSubscriberBotApi';\nimport { useTr } from '../../hooks/useTr';\nimport { formatCurrency, formatDate } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type {\n SubBudgetStatus,\n SubEnterpriseSpendDashboard,\n SubOverlapType,\n SubShadowItReason,\n SubSpendRiskKind,\n SubSpendRiskSeverity,\n} from '../../types';\n\ntype Tr = ReturnType<typeof useTr>;\n\nconst SHADOW_REASON: Record<SubShadowItReason, { key: string; fallback: string }> = {\n UNCATALOGUED: { key: 'enterprise.shadow.reasons.uncatalogued', fallback: 'Not in the catalogue' },\n UNVERIFIED_PROVIDER: {\n key: 'enterprise.shadow.reasons.unverified',\n fallback: 'Unverified provider',\n },\n DEPRECATED_PROVIDER: {\n key: 'enterprise.shadow.reasons.deprecated',\n fallback: 'Deprecated provider',\n },\n};\n\nconst OVERLAP_TYPE: Record<SubOverlapType, { key: string; fallback: string }> = {\n EXACT_DUPLICATE: { key: 'enterprise.duplicates.types.exact', fallback: 'Same vendor' },\n CAPABILITY: { key: 'enterprise.duplicates.types.capability', fallback: 'Same capability' },\n ENTITLEMENT: {\n key: 'enterprise.duplicates.types.entitlement',\n fallback: 'Covered by another plan',\n },\n};\n\nconst RISK_KIND: Record<SubSpendRiskKind, { key: string; fallback: string }> = {\n PAST_DUE: { key: 'enterprise.risks.kinds.pastDue', fallback: 'Payment failed' },\n MISSING_INVOICE: { key: 'enterprise.risks.kinds.missingInvoice', fallback: 'Missing invoice' },\n UNTRACKED_TRIAL: { key: 'enterprise.risks.kinds.untrackedTrial', fallback: 'Untracked trial' },\n HIGH_COST_AI: { key: 'enterprise.risks.kinds.highCostAi', fallback: 'High-cost AI service' },\n};\n\nconst SEVERITY_STYLE: Record<SubSpendRiskSeverity, string> = {\n HIGH: 'bg-status-error-bg-subtle text-status-error-text',\n MEDIUM: 'bg-status-warning-bg-subtle text-status-warning-text',\n LOW: 'bg-bg-sunken text-text-secondary',\n};\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1.5 block text-xs font-medium text-text-secondary';\nconst linkCls =\n 'truncate text-left text-sm font-medium text-text-primary hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded';\n\nfunction departmentName(tr: Tr, department: string | null): string {\n return department ?? tr('enterprise.departments.unassigned', 'Unassigned');\n}\n\n/** \"62% of 5,000.00 budget\" pill for a budget status. */\nconst BudgetMeter: FC<{ status: SubBudgetStatus; fmt: (n: number) => string; tr: Tr }> = ({\n status,\n fmt,\n tr,\n}) => (\n <div className=\"space-y-1.5\">\n <MiniProgress\n value={Math.min(status.utilizationPct, 100)}\n tone={status.overBudget ? 'negative' : status.utilizationPct >= 85 ? 'warning' : 'positive'}\n />\n <p\n className={cn(\n 'text-xs tabular-nums',\n status.overBudget ? 'text-status-error-text' : 'text-text-secondary'\n )}\n >\n {status.overBudget\n ? tr('enterprise.budget.over', '{{amount}} over a {{budget}} budget', {\n amount: fmt(-status.variance),\n budget: fmt(status.monthlyBudget),\n })\n : tr('enterprise.budget.under', '{{amount}} left of {{budget}} ({{pct}}% used)', {\n amount: fmt(status.variance),\n budget: fmt(status.monthlyBudget),\n pct: status.utilizationPct,\n })}\n </p>\n </div>\n);\n\nconst Card: FC<{ title: string; icon: ReactNode; children: ReactNode; testId?: string }> = ({\n title,\n icon,\n children,\n testId,\n}) => (\n <GlassCard treatment=\"plain\" className=\"p-6\" data-testid={testId}>\n <div className=\"mb-4 flex items-center gap-2\">\n <span className=\"text-text-secondary\">{icon}</span>\n <h3 className=\"text-sm font-semibold text-text-primary\">{title}</h3>\n </div>\n {children}\n </GlassCard>\n);\n\n/** Budgets editor — company-wide and per-department monthly budgets. */\nconst BudgetsModal: FC<{\n currency: string;\n departments: string[];\n onClose: () => void;\n}> = ({ currency, departments, onClose }) => {\n const tr = useTr();\n const budgetsQ = useSpendBudgets();\n const setBudget = useSetSpendBudget();\n const [department, setDepartment] = useState('');\n const [amount, setAmount] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const budgets = (budgetsQ.data ?? []).filter((b) => b.currency === currency);\n\n const save = (dept: string | null, monthlyAmount: number | null) => {\n setError(null);\n setBudget.mutate(\n { department: dept, currency, monthlyAmount },\n {\n onSuccess: () => {\n setAmount('');\n setDepartment('');\n },\n onError: (err) =>\n setError(\n err instanceof Error\n ? err.message\n : tr('enterprise.budgets.error', 'Could not save the budget.')\n ),\n }\n );\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const value = Number(amount);\n if (!amount.trim() || !Number.isFinite(value) || value <= 0) {\n setError(tr('enterprise.budgets.amountRequired', 'Enter a monthly amount above zero.'));\n return;\n }\n save(department.trim() || null, value);\n };\n\n return (\n <Modal\n title={tr('enterprise.budgets.title', 'Monthly budgets')}\n description={tr(\n 'enterprise.budgets.description',\n 'Budgets apply to spend billed in {{currency}}. Leave the department empty for the company-wide budget.',\n { currency }\n )}\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {error && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-[1fr_10rem_auto] sm:items-end\">\n <div>\n <label htmlFor=\"budget-department\" className={labelCls}>\n {tr('enterprise.budgets.department', 'Department (optional)')}\n </label>\n <input\n id=\"budget-department\"\n list=\"budget-department-options\"\n value={department}\n maxLength={120}\n onChange={(e) => setDepartment(e.target.value)}\n placeholder={tr('enterprise.budgets.companyWide', 'Company-wide')}\n className={inputCls}\n />\n <datalist id=\"budget-department-options\">\n {departments.map((d) => (\n <option key={d} value={d} />\n ))}\n </datalist>\n </div>\n <div>\n <label htmlFor=\"budget-amount\" className={labelCls}>\n {tr('enterprise.budgets.amount', 'Per month ({{currency}})', { currency })}\n </label>\n <input\n id=\"budget-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n inputMode=\"decimal\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <button\n type=\"submit\"\n disabled={setBudget.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-60\"\n >\n {tr('enterprise.budgets.save', 'Save budget')}\n </button>\n </div>\n </form>\n\n <div className=\"mt-6\">\n {budgetsQ.isLoading ? (\n <LoadingView label={tr('enterprise.budgets.loading', 'Loading budgets…')} />\n ) : budgets.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('enterprise.budgets.none', 'No budgets set for {{currency}} yet.', { currency })}\n </p>\n ) : (\n <ul className=\"divide-y divide-border-subtle rounded-lg border border-border-subtle\">\n {budgets.map((b) => (\n <li key={b.id} className=\"flex items-center justify-between gap-3 px-4 py-3\">\n <span className=\"truncate text-sm text-text-primary\">\n {b.department ?? tr('enterprise.budgets.companyWide', 'Company-wide')}\n </span>\n <span className=\"flex items-center gap-3\">\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatCurrency(b.monthlyAmount, b.currency)}\n </span>\n <button\n type=\"button\"\n onClick={() => save(b.department, null)}\n disabled={setBudget.isPending}\n aria-label={tr('enterprise.budgets.removeAria', 'Remove the {{name}} budget', {\n name: b.department ?? tr('enterprise.budgets.companyWide', 'Company-wide'),\n })}\n className=\"text-xs font-medium text-status-error-text hover:underline disabled:opacity-60\"\n >\n {tr('enterprise.budgets.remove', 'Remove')}\n </button>\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n </Modal>\n );\n};\n\nexport function EnterpriseSpendPage() {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const [currency, setCurrency] = useState<string | null>(null);\n const [budgetsOpen, setBudgetsOpen] = useState(false);\n const query = useEnterpriseSpendDashboard(currency);\n const data: SubEnterpriseSpendDashboard | undefined = query.data;\n\n const fmt = useMemo(() => {\n const code = data?.currency ?? 'USD';\n return (n: number) => formatCurrency(n, code);\n }, [data?.currency]);\n\n const openRelationship = (id: string) => navigateTo(`/relationships/${id}`);\n\n if (query.isLoading)\n return <LoadingView label={tr('enterprise.loading', 'Aggregating company-wide spend…')} />;\n if (query.isError || !data)\n return (\n <ErrorView\n message={tr('enterprise.error', 'Could not load the enterprise spend dashboard.')}\n onRetry={() => void query.refetch()}\n />\n );\n\n const nextQuarter = data.renewalPipeline[0];\n const change = data.monthlySpendChangePct;\n const currencyOptions = [data.currency, ...data.otherCurrencies.map((c) => c.currency)];\n const departmentNames = data.departments.map((d) => d.department).filter((d): d is string => !!d);\n const isEmpty =\n data.activeRelationshipCount === 0 &&\n data.shadowItCount === 0 &&\n data.riskFlagCount === 0 &&\n data.departments.length === 0;\n\n return (\n <PageLayout\n title={tr('enterprise.title', 'SaaS spend management')}\n description={tr(\n 'enterprise.description',\n 'Company-wide recurring spend by department, with duplicate tools, shadow IT, renewals and risks.'\n )}\n actions={\n <div className=\"flex items-center gap-2\">\n {currencyOptions.length > 1 && (\n <label className=\"flex items-center gap-2 text-xs text-text-secondary\">\n {tr('enterprise.currency', 'Currency')}\n <select\n aria-label={tr('enterprise.currency', 'Currency')}\n value={data.currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-2 py-1.5 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {currencyOptions.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </select>\n </label>\n )}\n <button\n type=\"button\"\n onClick={() => setBudgetsOpen(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Target className=\"h-4 w-4\" aria-hidden /> {tr('enterprise.manageBudgets', 'Budgets')}\n </button>\n </div>\n }\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n 'enterprise.purpose',\n 'For finance, IT and procurement. Spend is the monthly run-rate of every subscription billing right now, computed live from your portfolio in one currency at a time. Allocate subscriptions to a department on the subscription itself.'\n )}\n </PagePurpose>\n\n {data.truncated && (\n <p className=\"mb-4 rounded-lg bg-status-warning-bg-subtle px-4 py-2 text-sm text-status-warning-text\">\n {tr(\n 'enterprise.truncated',\n 'This workspace has more subscriptions than one dashboard aggregates; figures cover the most recent ones.'\n )}\n </p>\n )}\n\n {isEmpty ? (\n <EmptyState\n icon={<Building2 className=\"h-8 w-8\" />}\n title={tr('enterprise.emptyTitle', 'No company spend to analyse yet')}\n description={tr(\n 'enterprise.emptyDescription',\n 'Add your SaaS subscriptions and allocate them to departments to see spend, duplicates, shadow IT and renewals here.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('enterprise.emptyAction', 'Add a subscription')}\n </button>\n }\n />\n ) : (\n <div className=\"space-y-6\">\n {/* KPI header */}\n <div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\"\n data-testid=\"enterprise-kpis\"\n >\n <MetricsCard\n featured\n icon={<Wallet className=\"h-5 w-5\" />}\n value={fmt(data.annualSpend)}\n label={tr(\n 'enterprise.kpis.annualSpend',\n 'Total SaaS spend / year ({{monthly}} / month)',\n {\n monthly: fmt(data.monthlySpend),\n }\n )}\n trend={\n change == null ? undefined : change > 0 ? 'up' : change < 0 ? 'down' : 'neutral'\n }\n trendValue={change == null ? undefined : `${change > 0 ? '+' : ''}${change}%`}\n />\n <MetricsCard\n icon={<Layers className=\"h-5 w-5\" />}\n value={data.activeRelationshipCount}\n label={tr('enterprise.kpis.activeLicenses', 'Active licences')}\n />\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={data.seatUtilizationPct != null ? `${data.seatUtilizationPct}%` : '—'}\n label={\n data.seatUtilizationPct != null\n ? tr('enterprise.kpis.seats', 'Seats utilised ({{used}} of {{assigned}})', {\n used: data.seatsUsed,\n assigned: data.seatsAssigned,\n })\n : tr('enterprise.kpis.seatsUntracked', 'Seats utilised (no seat data yet)')\n }\n />\n <MetricsCard\n icon={<CalendarClock className=\"h-5 w-5\" />}\n value={nextQuarter ? nextQuarter.relationshipCount : 0}\n label={tr('enterprise.kpis.renewals', 'Renewals this quarter ({{amount}})', {\n amount: fmt(nextQuarter?.amount ?? 0),\n })}\n />\n </div>\n\n {/* Trend + budget */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <Card\n title={tr('enterprise.trend.title', 'Monthly run-rate, last 150 days')}\n icon={<Wallet className=\"h-4 w-4\" />}\n >\n <Sparkline\n points={data.trend.map((p) => p.monthlySpend)}\n positive={(change ?? 0) <= 0}\n className=\"w-full\"\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr('enterprise.trend.vs', '{{now}} now vs {{before}} 30 days ago', {\n now: fmt(data.monthlySpend),\n before: fmt(data.previousMonthlySpend),\n })}\n </p>\n </Card>\n <Card\n title={tr('enterprise.budget.title', 'Company budget')}\n icon={<Target className=\"h-4 w-4\" />}\n testId=\"enterprise-budget\"\n >\n {data.budget ? (\n <BudgetMeter status={data.budget} fmt={fmt} tr={tr} />\n ) : (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr('enterprise.budget.none', 'No company-wide budget set for {{currency}}.', {\n currency: data.currency,\n })}\n </p>\n <button\n type=\"button\"\n onClick={() => setBudgetsOpen(true)}\n className=\"text-sm font-medium text-text-primary underline\"\n >\n {tr('enterprise.budget.set', 'Set a budget')}\n </button>\n </div>\n )}\n </Card>\n <Card\n title={tr('enterprise.savings.title', 'Consolidation opportunity')}\n icon={<PiggyBank className=\"h-4 w-4\" />}\n >\n <p className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {fmt(data.duplicatePotentialMonthlySavings)}\n <span className=\"text-sm font-normal text-text-secondary\">\n {tr('enterprise.perMonth', '/mo')}\n </span>\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'enterprise.savings.detail',\n 'Upper bound if each duplicated tool is consolidated into one subscription; shadow IT costs {{shadow}} / month.',\n { shadow: fmt(data.shadowItMonthlySpend) }\n )}\n </p>\n </Card>\n </div>\n\n {/* Departments */}\n <Section\n title={tr('enterprise.departments.title', 'Spend by department')}\n description={tr(\n 'enterprise.departments.description',\n 'Monthly run-rate per department, with each department budget.'\n )}\n >\n <GlassCard treatment=\"plain\" className=\"p-6\" data-testid=\"enterprise-departments\">\n {data.departments.length ? (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <BarList\n data={data.departments.map((d) => ({\n label: departmentName(tr, d.department),\n value: d.monthlySpend,\n }))}\n valueFormat={fmt}\n />\n <ul className=\"divide-y divide-border-subtle\">\n {data.departments.map((d) => (\n <li key={d.department ?? '__unassigned'} className=\"py-3 first:pt-0\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"truncate font-medium text-text-primary\">\n {departmentName(tr, d.department)}\n </span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {tr('enterprise.departments.row', '{{count}} subs · {{share}}%', {\n count: d.relationshipCount,\n share: d.sharePct,\n })}\n </span>\n </div>\n {d.budget && (\n <div className=\"mt-2\">\n <BudgetMeter status={d.budget} fmt={fmt} tr={tr} />\n </div>\n )}\n </li>\n ))}\n </ul>\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {tr('enterprise.departments.none', 'No spend is billing right now.')}\n </p>\n )}\n </GlassCard>\n </Section>\n\n {/* Duplicates + shadow IT */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <Card\n title={tr('enterprise.duplicates.title', 'Duplicate tools ({{count}})', {\n count: data.duplicateTools.length,\n })}\n icon={<Copy className=\"h-4 w-4\" />}\n testId=\"enterprise-duplicates\"\n >\n {data.duplicateTools.length ? (\n <ul className=\"space-y-3\">\n {data.duplicateTools.map((g) => (\n <li\n key={g.key}\n className=\"rounded-lg border border-status-warning-text/30 bg-status-warning-bg-subtle p-3\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n {g.providerId ? (\n <button\n type=\"button\"\n className={linkCls}\n onClick={() => navigateTo(`/providers/${g.providerId}`)}\n >\n {g.name}\n </button>\n ) : (\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {g.name}\n </p>\n )}\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'enterprise.duplicates.summary',\n '{{count}} subscriptions · {{teams}}',\n {\n count: g.relationshipCount,\n teams: g.departments.length\n ? g.departments.join(', ')\n : tr('enterprise.departments.unassigned', 'Unassigned'),\n }\n )}\n </p>\n </div>\n <span className=\"shrink-0 text-right text-xs text-status-warning-text\">\n {tr('enterprise.duplicates.save', 'Save up to {{amount}}/mo', {\n amount: fmt(g.potentialMonthlySavings),\n })}\n </span>\n </div>\n <ul className=\"mt-2 flex flex-wrap gap-1.5\">\n {g.relationships.map((m) => (\n <li key={m.relationshipId}>\n <button\n type=\"button\"\n onClick={() => openRelationship(m.relationshipId)}\n className={cn(\n 'rounded-md px-2 py-1 text-xs hover:bg-bg-sunken',\n m.keep\n ? 'border border-status-success-text/40 bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-surface text-text-primary'\n )}\n >\n {m.keep ? `${tr('enterprise.duplicates.keep', 'Keep')}: ` : ''}\n {m.label} · {fmt(m.monthlySpend)}\n </button>\n </li>\n ))}\n </ul>\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(OVERLAP_TYPE[g.type].key, OVERLAP_TYPE[g.type].fallback)} ·{' '}\n {g.rationale}\n </p>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {tr('enterprise.duplicates.none', 'No duplicate or overlapping tools found.')}\n </p>\n )}\n </Card>\n\n <Card\n title={tr('enterprise.shadow.title', 'Shadow IT ({{count}})', {\n count: data.shadowItCount,\n })}\n icon={<EyeOff className=\"h-4 w-4\" />}\n testId=\"enterprise-shadow-it\"\n >\n {data.shadowIt.length ? (\n <ul className=\"divide-y divide-border-subtle\">\n {data.shadowIt.map((s) => (\n <li\n key={s.relationshipId}\n className=\"flex items-center justify-between gap-3 py-2.5\"\n >\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className={linkCls}\n onClick={() => openRelationship(s.relationshipId)}\n >\n {s.label}\n </button>\n <p className=\"text-xs text-text-secondary\">\n {tr(SHADOW_REASON[s.reason].key, SHADOW_REASON[s.reason].fallback)}\n {' · '}\n {departmentName(tr, s.department)}\n {' · '}\n {tr('enterprise.shadow.found', 'found {{date}}', {\n date: formatDate(s.detectedAt),\n })}\n </p>\n </div>\n <span className=\"shrink-0 text-sm font-semibold tabular-nums text-text-primary\">\n {fmt(s.monthlySpend)}\n </span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'enterprise.shadow.none',\n 'Every discovered tool is linked to an approved provider.'\n )}\n </p>\n )}\n </Card>\n </div>\n\n {/* Renewal pipeline */}\n <Section\n title={tr('enterprise.renewals.title', 'Renewal pipeline')}\n description={tr(\n 'enterprise.renewals.description',\n 'Renewal charges due by quarter, for planning renegotiations.'\n )}\n >\n <div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\"\n data-testid=\"enterprise-renewals\"\n >\n {data.renewalPipeline.map((q) => (\n <GlassCard key={q.label} treatment=\"plain\" className=\"p-5\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {q.label}\n </p>\n <p className=\"mt-1 text-xl font-bold tabular-nums text-text-primary\">\n {fmt(q.amount)}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'enterprise.renewals.counts',\n '{{renewals}} renewals · {{subs}} subscriptions',\n {\n renewals: q.renewalCount,\n subs: q.relationshipCount,\n }\n )}\n </p>\n {q.topRenewals.length > 0 && (\n <ul className=\"mt-3 space-y-1.5\">\n {q.topRenewals.map((r) => (\n <li\n key={r.relationshipId}\n className=\"flex items-center justify-between gap-2\"\n >\n <button\n type=\"button\"\n className={cn(linkCls, 'text-xs')}\n onClick={() => openRelationship(r.relationshipId)}\n >\n {r.label}\n </button>\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {formatDate(r.renewsAt)} · {fmt(r.amount)}\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n ))}\n </div>\n </Section>\n\n {/* Risk flags */}\n <Section\n title={tr('enterprise.risks.title', 'Compliance and risk flags ({{count}})', {\n count: data.riskFlagCount,\n })}\n >\n <GlassCard treatment=\"plain\" className=\"p-2\" data-testid=\"enterprise-risks\">\n {data.riskFlags.length ? (\n <ul className=\"divide-y divide-border-subtle\">\n {data.riskFlags.map((f) => (\n <li\n key={`${f.kind}-${f.relationshipId}`}\n className=\"flex flex-col gap-2 p-4 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex min-w-0 items-start gap-3\">\n <span\n className={cn(\n 'shrink-0 rounded-md px-2 py-0.5 text-xs font-medium',\n SEVERITY_STYLE[f.severity]\n )}\n >\n {tr(RISK_KIND[f.kind].key, RISK_KIND[f.kind].fallback)}\n </span>\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className={linkCls}\n onClick={() => openRelationship(f.relationshipId)}\n >\n {f.label}\n </button>\n <p className=\"text-xs text-text-secondary\">{f.detail}</p>\n </div>\n </div>\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {departmentName(tr, f.department)} · {fmt(f.monthlySpend)}\n {tr('enterprise.perMonth', '/mo')}\n </span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"flex items-center gap-2 p-4 text-sm text-text-secondary\">\n <ShieldAlert className=\"h-4 w-4\" aria-hidden />\n {tr('enterprise.risks.none', 'No compliance or risk issues found.')}\n </p>\n )}\n </GlassCard>\n </Section>\n\n {/* Licences */}\n <Section title={tr('enterprise.licenses.title', 'Licences')}>\n <GlassCard\n treatment=\"plain\"\n className=\"overflow-x-auto\"\n data-testid=\"enterprise-licenses\"\n >\n <table className=\"w-full min-w-[640px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-xs text-text-secondary\">\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.app', 'App')}\n </th>\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.department', 'Department')}\n </th>\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.seats', 'Seats used / assigned')}\n </th>\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.utilization', 'Utilisation')}\n </th>\n <th className=\"px-4 py-3 text-right font-medium\">\n {tr('enterprise.licenses.annualCost', 'Annual cost')}\n </th>\n <th className=\"px-4 py-3 font-medium\">\n {tr('enterprise.licenses.renews', 'Renews')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-subtle\">\n {data.licenses.map((l) => (\n <tr key={l.relationshipId}>\n <td className=\"max-w-[16rem] px-4 py-3\">\n <button\n type=\"button\"\n className={linkCls}\n onClick={() => openRelationship(l.relationshipId)}\n >\n {l.label}\n </button>\n {l.providerName && (\n <p className=\"truncate text-xs text-text-secondary\">{l.providerName}</p>\n )}\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {departmentName(tr, l.department)}\n </td>\n <td className=\"px-4 py-3 tabular-nums text-text-primary\">\n {l.seatsAssigned != null ? `${l.seatsUsed ?? 0} / ${l.seatsAssigned}` : '—'}\n </td>\n <td className=\"w-40 px-4 py-3\">\n {l.utilizationPct != null ? (\n <MiniProgress\n value={l.utilizationPct}\n tone={l.utilizationPct >= 70 ? 'positive' : 'warning'}\n />\n ) : (\n <span className=\"text-text-secondary\">—</span>\n )}\n </td>\n <td className=\"px-4 py-3 text-right font-semibold tabular-nums text-text-primary\">\n {fmt(l.annualCost)}\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {l.renewsAt ? formatDate(l.renewsAt) : '—'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n {data.licenses.length === 0 && (\n <p className=\"p-4 text-sm text-text-secondary\">\n {tr('enterprise.licenses.none', 'No subscription is billing right now.')}\n </p>\n )}\n </GlassCard>\n </Section>\n\n {data.otherCurrencies.length > 0 && (\n <p className=\"flex items-start gap-2 text-xs text-text-secondary\">\n <AlertTriangle className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" aria-hidden />\n {tr(\n 'enterprise.otherCurrencies',\n 'Not included above (amounts are never converted): {{list}}. Switch currency to see them.',\n {\n list: data.otherCurrencies\n .map((c) => `${formatCurrency(c.monthlySpend, c.currency)}/mo`)\n .join(', '),\n }\n )}\n </p>\n )}\n </div>\n )}\n\n {budgetsOpen && (\n <BudgetsModal\n currency={data.currency}\n departments={departmentNames}\n onClose={() => setBudgetsOpen(false)}\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bill negotiation request detail
|
|
3
|
+
* @module subscriberbot/pages/negotiations
|
|
4
|
+
*
|
|
5
|
+
* `/subscriberbot/negotiations/:id` — the playbook, submission (with the
|
|
6
|
+
* negotiation-desk authorisation for managed requests), messages with the
|
|
7
|
+
* desk, the self-serve outcome form, savings and the success fee, and the
|
|
8
|
+
* shared timeline. Every action is a real `Sub*Negotiation*` mutation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function NegotiationDetailPage(): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=NegotiationDetailPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NegotiationDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/negotiations/NegotiationDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8OH,wBAAgB,qBAAqB,gCAyKpC"}
|