checkpoint: goals feature, wallet balance, and goals/wallet detail UI

- Add goals feature (models, migrations, API, web pages)
- Add reserved/centralized wallet balance service
- Add wallet detail page and overview components
- Add new UI components (progress, multi-select, FAB)
- Remove stray empty -H/-d files from working tree
This commit is contained in:
Dwindi Ramadhana
2026-06-17 20:40:00 +07:00
parent 35e93b826a
commit 6a6e74562c
401 changed files with 9517 additions and 397 deletions

23
apps/web/src/constants/currencies.ts Normal file → Executable file
View File

@@ -43,13 +43,30 @@ export const getCurrencyByCode = (code: string) => {
export const formatCurrency = (amount: number, currencyCode: string) => {
const useLanguage = localStorage.getItem('language') || 'en';
const currency = getCurrencyByCode(currencyCode);
if (!currency) return `${amount} ${(amount === 1) ? currencyCode : currencyCode + (useLanguage == 'en' ? 's' : '')}`;
// For non-currency codes (units like "gram", "shares", etc.)
if (!currency) {
const formatter = new Intl.NumberFormat('id-ID', {
minimumFractionDigits: 0,
maximumFractionDigits: 2
});
const formattedAmount = formatter.format(amount);
return `${formattedAmount} ${(amount === 1) ? currencyCode : currencyCode + (useLanguage == 'en' ? 's' : '')}`;
}
// For IDR, format without decimals
if (currencyCode === 'IDR') {
return `${currency.symbol} ${amount.toLocaleString('id-ID', { maximumFractionDigits: 0 })}`;
const formatter = new Intl.NumberFormat('id-ID', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
});
return `${currency.symbol} ${formatter.format(amount)}`;
}
// For other currencies, use 2 decimal places
return `${currency.symbol} ${amount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
const formatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
return `${currency.symbol} ${formatter.format(amount)}`;
};