feat(WEB): 대시보드 금액 한국식 축약 포맷 적용 (만/억 단위)

- formatKoreanAmount 유틸 함수 추가 (1만 미만: 원, 1만~1억: 만, 1억 이상: 억+만)
- CEO 대시보드 일일일보, 당월 지출, 금액카드에 적용
- 기존 formatBillion 로컬 함수 제거 후 공통 유틸로 통합
This commit is contained in:
2026-02-02 00:58:18 +09:00
parent 9162f8fb6d
commit e684c495ee
3 changed files with 48 additions and 23 deletions

View File

@@ -12,6 +12,7 @@ import {
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { formatKoreanAmount } from '@/utils/formatAmount';
import type { CheckPoint, CheckPointType, AmountCard, HighlightColor } from './types';
// 섹션별 컬러 테마 타입
@@ -245,7 +246,7 @@ export const AmountCardItem = ({
if (card.currency === 'USD') {
return formatUSD(amount);
}
return formatBillion(amount);
return formatKoreanAmount(amount);
};
// 테마 적용 시 스타일
@@ -338,7 +339,7 @@ export const AmountCardItem = ({
{card.subItems.map((item, idx) => (
<div key={idx} className="flex justify-between gap-2">
<span className="shrink-0">{item.label}</span>
<span className="text-right">{typeof item.value === 'number' ? formatAmount(item.value, false) : item.value}</span>
<span className="text-right">{typeof item.value === 'number' ? formatKoreanAmount(item.value) : item.value}</span>
</div>
))}
</div>
@@ -348,7 +349,7 @@ export const AmountCardItem = ({
{!showTrend && !card.subItems && (card.subAmount !== undefined || card.previousAmount !== undefined || card.subLabel || card.previousLabel) && (
<div className="flex gap-4 mt-auto text-xs text-muted-foreground">
{card.subAmount !== undefined && card.subLabel && (
<span>{card.subLabel}: {card.unit === '건' ? `${card.subAmount}` : formatAmount(card.subAmount)}</span>
<span>{card.subLabel}: {card.unit === '건' ? `${card.subAmount}` : formatKoreanAmount(card.subAmount)}</span>
)}
{card.previousLabel && (
<span className="flex items-center gap-1">

View File

@@ -25,24 +25,13 @@ import {
import { useRouter } from 'next/navigation';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { formatKoreanAmount } from '@/utils/formatAmount';
import type { DailyReportData, MonthlyExpenseData, TodayIssueItem, TodayIssueSettings } from '../types';
// ============================================================
// 유틸리티 함수
// ============================================================
const formatBillion = (amount: number): string => {
const billion = amount / 100000000;
if (billion >= 1) {
return billion.toFixed(1) + '억원';
}
const man = amount / 10000;
if (man >= 1) {
return Math.floor(man).toLocaleString() + '만원';
}
return amount.toLocaleString() + '원';
};
const formatUSD = (amount: number): string => {
return '$ ' + new Intl.NumberFormat('en-US').format(amount);
};
@@ -106,7 +95,7 @@ export function EnhancedDailyReportSection({ data, onClick }: EnhancedDailyRepor
</div>
<div className="flex items-end gap-2">
<span style={{ color: '#0f172a' }} className="text-2xl font-bold">
{formatBillion(data.cards[0]?.amount || 0)}
{formatKoreanAmount(data.cards[0]?.amount || 0)}
</span>
{data.cards[0]?.changeRate && (
<span
@@ -140,7 +129,7 @@ export function EnhancedDailyReportSection({ data, onClick }: EnhancedDailyRepor
<span style={{ color: '#0f172a' }} className="text-2xl font-bold">
{data.cards[1]?.currency === 'USD'
? formatUSD(data.cards[1]?.amount || 0)
: formatBillion(data.cards[1]?.amount || 0)}
: formatKoreanAmount(data.cards[1]?.amount || 0)}
</span>
{data.cards[1]?.changeRate && (
<span
@@ -172,7 +161,7 @@ export function EnhancedDailyReportSection({ data, onClick }: EnhancedDailyRepor
</div>
<div className="flex items-end gap-2">
<span style={{ color: '#0f172a' }} className="text-2xl font-bold">
{formatBillion(data.cards[2]?.amount || 0)}
{formatKoreanAmount(data.cards[2]?.amount || 0)}
</span>
{data.cards[2]?.changeRate && (
<span
@@ -204,7 +193,7 @@ export function EnhancedDailyReportSection({ data, onClick }: EnhancedDailyRepor
</div>
<div className="flex items-end gap-2">
<span style={{ color: '#0f172a' }} className="text-2xl font-bold">
{formatBillion(data.cards[3]?.amount || 0)}
{formatKoreanAmount(data.cards[3]?.amount || 0)}
</span>
{data.cards[3]?.changeRate && (
<span
@@ -424,7 +413,7 @@ export function EnhancedMonthlyExpenseSection({ data, onCardClick }: EnhancedMon
</span>
</div>
<div style={{ color: '#0f172a' }} className="text-2xl font-bold">
{formatBillion(data.cards[0]?.amount || 0)}
{formatKoreanAmount(data.cards[0]?.amount || 0)}
</div>
{data.cards[0]?.previousLabel && (
<div style={{ backgroundColor: '#dcfce7', color: '#16a34a' }} className="flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold mt-auto w-fit">
@@ -449,7 +438,7 @@ export function EnhancedMonthlyExpenseSection({ data, onCardClick }: EnhancedMon
</span>
</div>
<div style={{ color: '#0f172a' }} className="text-2xl font-bold">
{formatBillion(data.cards[1]?.amount || 0)}
{formatKoreanAmount(data.cards[1]?.amount || 0)}
</div>
{data.cards[1]?.previousLabel && (
<div style={{ backgroundColor: '#dcfce7', color: '#16a34a' }} className="flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold mt-auto w-fit">
@@ -474,7 +463,7 @@ export function EnhancedMonthlyExpenseSection({ data, onCardClick }: EnhancedMon
</span>
</div>
<div style={{ color: '#0f172a' }} className="text-2xl font-bold">
{formatBillion(data.cards[2]?.amount || 0)}
{formatKoreanAmount(data.cards[2]?.amount || 0)}
</div>
{data.cards[2]?.previousLabel && (
<div style={{ backgroundColor: '#dcfce7', color: '#16a34a' }} className="flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold mt-auto w-fit">
@@ -499,7 +488,7 @@ export function EnhancedMonthlyExpenseSection({ data, onCardClick }: EnhancedMon
</span>
</div>
<div style={{ color: '#ffffff' }} className="text-2xl font-bold">
{formatBillion(totalAmount)}
{formatKoreanAmount(totalAmount)}
</div>
<div style={{ backgroundColor: 'rgba(255,255,255,0.2)', color: '#ffffff' }} className="flex items-center gap-1 px-2 py-1 rounded-full text-xs font-semibold mt-auto w-fit">
<TrendingUp className="h-3 w-3" />

View File

@@ -40,4 +40,39 @@ export function formatAmountManwon(amount: number): string {
}
const manwon = Math.round(amount / 10000);
return `${manwon.toLocaleString("ko-KR")}만원`;
}
/**
* 한국식 금액 축약 포맷
*
* - 1만 미만: 원 단위 (예: "5,000원")
* - 1만 ~ 1억 미만: 만 단위 (예: "5,320만")
* - 1억 이상: 억 + 만 단위 (예: "1억 5,000만", "12억 3,453만")
* - 만원 나머지가 0이면 "10억"
*/
export function formatKoreanAmount(amount: number): string {
if (amount == null || isNaN(amount)) return "0원";
const absAmount = Math.abs(amount);
const sign = amount < 0 ? "-" : "";
if (absAmount < 10000) {
// 만원 미만: 원 단위
return sign + absAmount.toLocaleString("ko-KR") + "원";
}
if (absAmount < 100000000) {
// 만원 ~ 억 미만: 만 단위
const manwon = Math.round(absAmount / 10000);
return sign + manwon.toLocaleString("ko-KR") + "만";
}
// 억 이상: 억 + 만 단위
const eok = Math.floor(absAmount / 100000000);
const remainder = Math.round((absAmount % 100000000) / 10000);
if (remainder === 0) {
return sign + eok.toLocaleString("ko-KR") + "억";
}
return sign + eok.toLocaleString("ko-KR") + "억 " + remainder.toLocaleString("ko-KR") + "만";
}