Customer tax is exempt
@@ -50,24 +40,34 @@ const TaxRow: FC<{ value?: number | null }> = ({ value }) => {
<>
Tax
- {formatCurrency(value)}
+ {formatCurrency(value)}
>
);
};
+type BillingInvoiceFooterProps = {
+ subTotal?: number;
+ taxAmount?: number;
+ totalAmount: number;
+ currency?: string;
+};
+
export const BillingInvoiceFooter = ({
subTotal,
taxAmount,
totalAmount,
+ currency,
}: BillingInvoiceFooterProps) => {
return (
- {subTotal ? (
+ {subTotal || !taxAmount ? (
Sub total
- {formatCurrency(subTotal)}
+
+ {formatCurrency(subTotal || totalAmount, currency)}
+
) : null}
@@ -77,7 +77,9 @@ export const BillingInvoiceFooter = ({
Total
- {formatCurrency(totalAmount)}
+
+ {formatCurrency(totalAmount, currency)}
+
diff --git a/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/BillingInvoiceRow/BillingInvoiceRow.tsx b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/BillingInvoiceRow/BillingInvoiceRow.tsx
index e69881a4b3..ea02adfb11 100644
--- a/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/BillingInvoiceRow/BillingInvoiceRow.tsx
+++ b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/BillingInvoiceRow/BillingInvoiceRow.tsx
@@ -1,7 +1,9 @@
import { formatLargeNumbers } from 'component/impact-metrics/metricsFormatters.ts';
-import { formatCurrency } from '../types.ts';
+import { formatCurrency } from '../formatCurrency.ts';
import { ConsumptionIndicator } from '../ConsumptionIndicator/ConsumptionIndicator.tsx';
import { styled } from '@mui/material';
+import type { DetailedInvoicesLineSchema } from 'openapi';
+import { StyledAmountCell } from '../BillingInvoice.styles.tsx';
const StyledCellWithIndicator = styled('div')(({ theme }) => ({
display: 'flex',
@@ -14,34 +16,34 @@ type BillingInvoiceRowProps = {
quantity?: number;
amount?: number;
quota?: number;
+ usage?: number;
};
export const BillingInvoiceRow = ({
quantity,
- amount,
- quota,
+ consumption,
+ limit,
description,
-}: BillingInvoiceRowProps) => {
- const usage = quantity || 0;
+ currency,
+ totalAmount,
+}: DetailedInvoicesLineSchema) => {
const percentage =
- quota && quota > 0
- ? Math.min(100, Math.round((usage / quota) * 100))
+ limit && limit > 0
+ ? Math.min(100, Math.round(((consumption || 0) / limit) * 100))
: undefined;
return (
<>
{description}
- {percentage !== undefined && (
-
- )}
- {quota !== undefined ? formatLargeNumbers(quota) : '–'}
+
+ {limit !== undefined ? formatLargeNumbers(limit) : '–'}
{percentage !== undefined ? ` (${percentage}%)` : ''}
-
- {quantity !== undefined ? formatLargeNumbers(quantity) : '–'}
-
- {formatCurrency(amount || 0)}
+ {quantity ? formatLargeNumbers(quantity) : '–'}
+
+ {formatCurrency(totalAmount || 0, currency)}
+
>
);
};
diff --git a/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/formatCurrency.test.ts b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/formatCurrency.test.ts
new file mode 100644
index 0000000000..9677df8ccf
--- /dev/null
+++ b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/formatCurrency.test.ts
@@ -0,0 +1,57 @@
+import { describe, it, expect } from 'vitest';
+import { formatCurrency } from './formatCurrency.ts';
+
+describe('formatCurrency', () => {
+ it('formats USD currency', () => {
+ expect(formatCurrency(1000, 'USD')).toMatchInlineSnapshot(`"$1,000"`);
+ expect(formatCurrency(1234.56, 'USD')).toMatchInlineSnapshot(
+ `"$1,234.56"`,
+ );
+ expect(formatCurrency(1000000, 'USD')).toMatchInlineSnapshot(
+ `"$1,000,000"`,
+ );
+ expect(formatCurrency(0, 'USD')).toMatchInlineSnapshot(`"$0"`);
+ expect(formatCurrency(-500, 'USD')).toMatchInlineSnapshot(`"$-500"`);
+ });
+
+ it('formats EUR currency', () => {
+ expect(formatCurrency(1000, 'EUR')).toMatchInlineSnapshot(`"€ 1 000"`);
+ expect(formatCurrency(1234.56, 'EUR')).toMatchInlineSnapshot(
+ `"€ 1 234,56"`,
+ );
+ expect(formatCurrency(1000000, 'EUR')).toMatchInlineSnapshot(
+ `"€ 1 000 000"`,
+ );
+ expect(formatCurrency(0, 'EUR')).toMatchInlineSnapshot(`"€ 0"`);
+ expect(formatCurrency(-500, 'EUR')).toMatchInlineSnapshot(`"€ −500"`);
+ });
+
+ it('formats other currencies', () => {
+ expect(formatCurrency(1000, 'GBP')).toMatchInlineSnapshot(`"1000 GBP"`);
+ expect(formatCurrency(100000, 'JPY')).toMatchInlineSnapshot(
+ `"100000 JPY"`,
+ );
+ expect(formatCurrency(500, 'SEK')).toMatchInlineSnapshot(`"500 SEK"`);
+ expect(formatCurrency(1000, '')).toMatchInlineSnapshot(`"1000"`);
+ });
+
+ it('formats without currency', () => {
+ expect(formatCurrency(1000)).toMatchInlineSnapshot(`"1000"`);
+ expect(formatCurrency(1234.56)).toMatchInlineSnapshot(`"1234.56"`);
+ expect(formatCurrency(0)).toMatchInlineSnapshot(`"0"`);
+ expect(formatCurrency(-500)).toMatchInlineSnapshot(`"-500"`);
+ });
+
+ it('handles edge cases', () => {
+ expect(formatCurrency(0.01, 'USD')).toMatchInlineSnapshot(`"$0.01"`);
+ expect(formatCurrency(999999999, 'EUR')).toMatchInlineSnapshot(
+ `"€ 999 999 999"`,
+ );
+ expect(formatCurrency(10.999, 'USD')).toMatchInlineSnapshot(
+ `"$10.999"`,
+ );
+ expect(formatCurrency(10.999, 'EUR')).toMatchInlineSnapshot(
+ `"€ 10,999"`,
+ );
+ });
+});
diff --git a/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/formatCurrency.ts b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/formatCurrency.ts
new file mode 100644
index 0000000000..3253d932de
--- /dev/null
+++ b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/formatCurrency.ts
@@ -0,0 +1,10 @@
+export const formatCurrency = (value: number, currency?: string) => {
+ if (currency === 'USD') {
+ return `$${value.toLocaleString('en-US')}`;
+ }
+ if (currency === 'EUR') {
+ return `€\u2009${value.toLocaleString('no-NO')}`;
+ }
+
+ return `${value}${currency ? ' ' : ''}${currency || ''}`;
+};
diff --git a/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/types.ts b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/types.ts
deleted file mode 100644
index 6e75515113..0000000000
--- a/frontend/src/component/admin/billing/BillingInvoices/BillingInvoice/types.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-export interface UsageMetric {
- id: string;
- label: string;
- includedCurrent: number;
- includedMax: number;
- includedUnit: string;
- actual?: string;
- amount: number;
-}
-
-export const defaultMetrics: UsageMetric[] = [
- {
- id: 'frontend-traffic',
- label: 'Frontend traffic',
- includedCurrent: 10,
- includedMax: 10,
- includedUnit: 'M requests',
- actual: '1,085M requests',
- amount: 5425,
- },
- {
- id: 'service-connections',
- label: 'Service connections',
- includedCurrent: 7,
- includedMax: 7,
- includedUnit: 'connections',
- actual: '20 connections',
- amount: 0,
- },
- {
- id: 'release-templates',
- label: 'Release templates',
- includedCurrent: 3,
- includedMax: 5,
- includedUnit: 'templates',
- amount: 0,
- },
- {
- id: 'edge-frontend-traffic',
- label: 'Edge Frontend Traffic',
- includedCurrent: 2,
- includedMax: 10,
- includedUnit: 'M requests',
- amount: 0,
- },
- {
- id: 'edge-service-connections',
- label: 'Edge Service Connections',
- includedCurrent: 5,
- includedMax: 5,
- includedUnit: 'connections',
- amount: 0,
- },
-];
-
-export const formatCurrency = (value: number) =>
- `$${value.toLocaleString('en-US')}`;
diff --git a/frontend/src/hooks/api/getters/useDetailedInvoices/useDetailedInvoices.ts b/frontend/src/hooks/api/getters/useDetailedInvoices/useDetailedInvoices.ts
index e195792117..70f9c87acf 100644
--- a/frontend/src/hooks/api/getters/useDetailedInvoices/useDetailedInvoices.ts
+++ b/frontend/src/hooks/api/getters/useDetailedInvoices/useDetailedInvoices.ts
@@ -25,9 +25,9 @@ export const useDetailedInvoices = (options: SWRConfiguration = {}) => {
return {
invoices: [
- // TODO:MOCK
+ // FIXME: MOCK
{
- status: 'paid',
+ status: 'upcoming',
dueDate: '2023-09-01',
invoiceDate: '2023-08-01',
invoicePDF: 'https://example.com/invoice/1.pdf',
@@ -38,7 +38,9 @@ export const useDetailedInvoices = (options: SWRConfiguration = {}) => {
currency: 'USD',
description: 'Service C',
lookupKey: 'service-c',
- quantity: 1,
+ quantity: 0,
+ consumption: 100,
+ limit: 120,
totalAmount: 200,
},
],
@@ -48,20 +50,31 @@ export const useDetailedInvoices = (options: SWRConfiguration = {}) => {
description: 'Service A',
lookupKey: 'service-a',
quantity: 1,
+ consumption: 100,
totalAmount: 100,
},
{
currency: 'USD',
- description: 'Service B',
+ description: 'Backend streaming connections',
lookupKey: 'service-b',
- quantity: 100,
- limit: 120,
+ quantity: 324_000,
+ limit: 3_000_000,
+ consumption: 3_000_000,
totalAmount: 200,
},
+ {
+ currency: 'USD',
+ description: 'Frontend traffic bundle',
+ lookupKey: 'frontend-traffic-bundle',
+ quantity: 0,
+ consumption: 2_345_239,
+ limit: 5_000_000,
+ totalAmount: 0,
+ },
],
},
{
- status: 'unpaid',
+ status: 'invoiced',
dueDate: '2023-09-15',
invoiceDate: '2023-08-15',
invoicePDF: 'https://example.com/invoice/2.pdf',
@@ -69,7 +82,7 @@ export const useDetailedInvoices = (options: SWRConfiguration = {}) => {
totalAmount: 200,
mainLines: [
{
- currency: 'USD',
+ currency: 'EUR',
description: 'Service C',
lookupKey: 'service-c',
quantity: 1,