mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 03:05:42 +00:00
feat(ui-common): shared currency formatting + DJF in CurrencySelector
New lib/currency.ts is the single source of truth for currency display across both freight web apps: symbol, name and decimals per code (DJF is zero-decimal by convention, unlike ETB/USD). Portal's own currency lib and backoffice's ad-hoc formatters are rewired onto this in the next two commits instead of each guessing decimals. CurrencySelector gets an allowDjf prop mirroring the existing allowUsd gate, so import-shipment currency pickers can offer DJF the same way they offer USD. Claude-Session: https://claude.ai/code/session_01CZy77vCWhka3pnmVF9NDkL
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
import { Box, Text } from "@mantine/core";
|
import { Box, Text } from "@mantine/core";
|
||||||
import { Check } from "lucide-react";
|
import { Check } from "lucide-react";
|
||||||
|
|
||||||
|
import type { SupportedCurrency } from "../../lib/currency";
|
||||||
|
|
||||||
export interface CurrencySelectorProps {
|
export interface CurrencySelectorProps {
|
||||||
/** Selected currency code, or "" when none picked yet. */
|
/** Selected currency code, or "" when none picked yet. */
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (currency: "USD" | "ETB") => void;
|
onChange: (currency: SupportedCurrency) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
/** Validation error shown under the cards. */
|
/** Validation error shown under the cards. */
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -14,6 +16,12 @@ export interface CurrencySelectorProps {
|
|||||||
* USD is settled by bank transfer, never through the online gateway.
|
* USD is settled by bank transfer, never through the online gateway.
|
||||||
*/
|
*/
|
||||||
allowUsd?: boolean;
|
allowUsd?: boolean;
|
||||||
|
/**
|
||||||
|
* Offer DJF alongside ETB (and USD, if also allowed). Same import-only
|
||||||
|
* gating as `allowUsd` — DJF is settled both online (WAAFI / CAC Bank) and
|
||||||
|
* by bank transfer.
|
||||||
|
*/
|
||||||
|
allowDjf?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ETB_OPTION = {
|
const ETB_OPTION = {
|
||||||
@@ -30,6 +38,13 @@ const USD_OPTION = {
|
|||||||
hint: "Paid by bank transfer — send the slip to Finance",
|
hint: "Paid by bank transfer — send the slip to Finance",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
const DJF_OPTION = {
|
||||||
|
code: "DJF",
|
||||||
|
symbol: "Fdj",
|
||||||
|
name: "Djibouti Franc",
|
||||||
|
hint: "Pay online, or by bank transfer — send the slip to Finance",
|
||||||
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Card-style USD/ETB billing-currency picker. Renders unselected when `value`
|
* Card-style USD/ETB billing-currency picker. Renders unselected when `value`
|
||||||
* is "" so a required choice never looks pre-made.
|
* is "" so a required choice never looks pre-made.
|
||||||
@@ -40,8 +55,13 @@ export function CurrencySelector({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
error,
|
error,
|
||||||
allowUsd = false,
|
allowUsd = false,
|
||||||
|
allowDjf = false,
|
||||||
}: CurrencySelectorProps) {
|
}: CurrencySelectorProps) {
|
||||||
const options = allowUsd ? [ETB_OPTION, USD_OPTION] : [ETB_OPTION];
|
const options = [
|
||||||
|
ETB_OPTION,
|
||||||
|
...(allowUsd ? [USD_OPTION] : []),
|
||||||
|
...(allowDjf ? [DJF_OPTION] : []),
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -80,3 +80,12 @@ export type {
|
|||||||
BookingWindowStateInput,
|
BookingWindowStateInput,
|
||||||
BookingWindowUiState,
|
BookingWindowUiState,
|
||||||
} from "./lib/booking-window-display";
|
} from "./lib/booking-window-display";
|
||||||
|
|
||||||
|
export {
|
||||||
|
CURRENCY_META,
|
||||||
|
CURRENCY_CODES,
|
||||||
|
currencyDecimals,
|
||||||
|
currencySymbol,
|
||||||
|
formatCurrency,
|
||||||
|
} from "./lib/currency";
|
||||||
|
export type { SupportedCurrency } from "./lib/currency";
|
||||||
|
|||||||
43
packages/ui-common/src/lib/currency.ts
Normal file
43
packages/ui-common/src/lib/currency.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* The currency codes the freight apps support, and how each one displays —
|
||||||
|
* symbol, full name, decimal places. USD/ETB are conventional 2-decimal
|
||||||
|
* currencies; DJF (Djibouti Franc) is a zero-decimal currency by convention.
|
||||||
|
*
|
||||||
|
* Single source of truth for currency display across both freight web apps —
|
||||||
|
* import this instead of re-declaring a symbol map or a decimals rule.
|
||||||
|
*/
|
||||||
|
export const CURRENCY_META = {
|
||||||
|
ETB: { symbol: "Br", name: "Ethiopian Birr", decimals: 2 },
|
||||||
|
USD: { symbol: "$", name: "US Dollar", decimals: 2 },
|
||||||
|
DJF: { symbol: "Fdj", name: "Djibouti Franc", decimals: 0 },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type SupportedCurrency = keyof typeof CURRENCY_META;
|
||||||
|
|
||||||
|
export const CURRENCY_CODES = Object.keys(CURRENCY_META) as SupportedCurrency[];
|
||||||
|
|
||||||
|
/** Decimal places to render an amount in `code` with. Defaults to 2 for an unknown code. */
|
||||||
|
export function currencyDecimals(code?: string | null): number {
|
||||||
|
const meta = code ? CURRENCY_META[code as SupportedCurrency] : undefined;
|
||||||
|
return meta?.decimals ?? 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Display symbol for `code`. Falls back to printing the raw code. */
|
||||||
|
export function currencySymbol(code?: string | null): string {
|
||||||
|
const meta = code ? CURRENCY_META[code as SupportedCurrency] : undefined;
|
||||||
|
return meta?.symbol ?? code ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a money amount with its currency symbol, e.g. `Br 12,500.00` or
|
||||||
|
* `Fdj 2,194,318`. Unknown currency codes fall back to printing the raw code
|
||||||
|
* with 2 decimals.
|
||||||
|
*/
|
||||||
|
export function formatCurrency(amount: number, currency?: string | null): string {
|
||||||
|
const symbol = currencySymbol(currency);
|
||||||
|
const decimals = currencyDecimals(currency);
|
||||||
|
return `${symbol} ${Number(amount ?? 0).toLocaleString(undefined, {
|
||||||
|
minimumFractionDigits: decimals,
|
||||||
|
maximumFractionDigits: decimals,
|
||||||
|
})}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user