- {ceoData.dailySales.today.toLocaleString()}원
+ {formatNumber(ceoData.dailySales.today)}원
@@ -1428,7 +1429,7 @@ export function MainDashboard() {
{receivable.days}일 경과
-
{receivable.amount.toLocaleString()}원
+
{formatNumber(receivable.amount)}원
30 ? 'bg-red-500' : 'bg-yellow-500'} text-white text-xs`}>
{receivable.days > 30 ? '위험' : '주의'}
@@ -1693,11 +1694,11 @@ export function MainDashboard() {
이번달:
- {material.thisMonth.toLocaleString()}
+ {formatNumber(material.thisMonth)}
전월:
- {material.lastMonth.toLocaleString()}
+ {formatNumber(material.lastMonth)}
material.lastMonth ? 'text-red-600' : 'text-green-600'
@@ -1735,13 +1736,13 @@ export function MainDashboard() {
월 생산량
- {ceoData.materialEfficiency.productionOutput.toLocaleString()}ea
+ {formatNumber(ceoData.materialEfficiency.productionOutput)}ea
자재 소모량
- {ceoData.materialEfficiency.materialConsumption.toLocaleString()}kg
+ {formatNumber(ceoData.materialEfficiency.materialConsumption)}kg
@@ -2626,7 +2627,7 @@ export function MainDashboard() {
{customer.name}
-
{customer.amount.toLocaleString()}원
+
{formatNumber(customer.amount)}원
diff --git a/src/components/business/construction/estimates/actions.ts b/src/components/business/construction/estimates/actions.ts
index afcc149e..f0225a42 100644
--- a/src/components/business/construction/estimates/actions.ts
+++ b/src/components/business/construction/estimates/actions.ts
@@ -17,6 +17,7 @@ import type {
} from './types';
import { getEmptyPriceAdjustmentData } from './types';
import { apiClient } from '@/lib/api';
+import { formatDate } from '@/lib/utils/date';
/**
* 건설 프로젝트 - 견적관리 Server Actions
@@ -252,7 +253,7 @@ function transformQuoteToEstimate(apiData: ApiQuote): Estimate {
// 완료 상태인 경우 updated_at을 완료일로 사용
// (상태가 완료로 변경될 때 updated_at이 갱신되므로)
const completedDate = status === 'completed' && apiData.updated_at
- ? apiData.updated_at.split('T')[0] // ISO 형식에서 날짜만 추출
+ ? formatDate(apiData.updated_at) // ISO 형식에서 날짜만 추출
: null;
return {
diff --git a/src/components/business/construction/labor-management/LaborManagementClient.tsx b/src/components/business/construction/labor-management/LaborManagementClient.tsx
index 412aad8d..1141a6ae 100644
--- a/src/components/business/construction/labor-management/LaborManagementClient.tsx
+++ b/src/components/business/construction/labor-management/LaborManagementClient.tsx
@@ -30,6 +30,7 @@ import {
import type { Labor, LaborStats, LaborCategory, LaborStatus } from './types';
import { CATEGORY_OPTIONS, STATUS_OPTIONS, SORT_OPTIONS, DEFAULT_PAGE_SIZE } from './constants';
import { getLaborList, deleteLabor, deleteLaborBulk, getLaborStats } from './actions';
+import { formatNumber } from '@/lib/utils/amount';
// 테이블 컬럼 정의
const tableColumns = [
@@ -90,7 +91,7 @@ export default function LaborManagementClient({
// 가격 포맷
const formatPrice = useCallback((price: number | null) => {
if (price === null || price === 0) return '-';
- return price.toLocaleString();
+ return formatNumber(price);
}, []);
// M 값 포맷
diff --git a/src/components/business/construction/order-management/actions.ts b/src/components/business/construction/order-management/actions.ts
index d804de66..3cacedce 100644
--- a/src/components/business/construction/order-management/actions.ts
+++ b/src/components/business/construction/order-management/actions.ts
@@ -4,6 +4,7 @@ import type { Order, OrderStats, OrderDetail, OrderDetailFormData, OrderStatus,
import { apiClient } from '@/lib/api';
import type { CommonCode } from '@/lib/api/common-codes';
import { toCommonCodeOptions } from '@/lib/api/common-codes';
+import { formatDate } from '@/lib/utils/date';
// ========================================
// 타입 변환 함수
@@ -141,8 +142,8 @@ function transformOrder(apiOrder: ApiOrder): Order {
// 달력 표시용 기간: received_at ~ delivery_date
// received_at이 없으면 delivery_date를 시작일로 사용 (단일 날짜 이벤트)
// delivery_date도 없으면 created_at을 사용
- periodStart: apiOrder.received_at || apiOrder.delivery_date || apiOrder.created_at.split('T')[0],
- periodEnd: apiOrder.delivery_date || apiOrder.received_at || apiOrder.created_at.split('T')[0],
+ periodStart: apiOrder.received_at || apiOrder.delivery_date || formatDate(apiOrder.created_at),
+ periodEnd: apiOrder.delivery_date || apiOrder.received_at || formatDate(apiOrder.created_at),
createdAt: apiOrder.created_at,
updatedAt: apiOrder.updated_at,
};
diff --git a/src/components/business/construction/partners/PartnerForm.tsx b/src/components/business/construction/partners/PartnerForm.tsx
index d725003c..afac7c47 100644
--- a/src/components/business/construction/partners/PartnerForm.tsx
+++ b/src/components/business/construction/partners/PartnerForm.tsx
@@ -35,6 +35,8 @@ import {
partnerToFormData,
} from './types';
import { createPartner, updatePartner, deletePartner } from './actions';
+import { formatNumber } from '@/lib/utils/amount';
+import { getLocalDateString } from '@/lib/utils/date';
// 목업 문서 목록 (상세 모드에서 다운로드 버튼 테스트용)
const MOCK_DOCUMENTS: PartnerDocument[] = [
@@ -163,7 +165,7 @@ export default function PartnerForm({ mode, partnerId, initialData }: PartnerFor
const handleAddMemo = useCallback(() => {
if (!newMemo.trim()) return;
const now = new Date();
- const dateStr = now.toISOString().slice(0, 10);
+ const dateStr = getLocalDateString(now);
const timeStr = now.toTimeString().slice(0, 5);
const memo: PartnerMemo = {
id: String(Date.now()),
@@ -498,7 +500,7 @@ export default function PartnerForm({ mode, partnerId, initialData }: PartnerFor
미수금
diff --git a/src/components/business/construction/progress-billing/tables/ProgressBillingItemTable.tsx b/src/components/business/construction/progress-billing/tables/ProgressBillingItemTable.tsx
index 1b69a0df..25172ad5 100644
--- a/src/components/business/construction/progress-billing/tables/ProgressBillingItemTable.tsx
+++ b/src/components/business/construction/progress-billing/tables/ProgressBillingItemTable.tsx
@@ -23,6 +23,7 @@ import {
} from '@/components/ui/table';
import type { ProgressBillingItem } from '../types';
import { MOCK_BILLING_NAMES } from '../types';
+import { formatNumber } from '@/lib/utils/amount';
interface ProgressBillingItemTableProps {
items: ProgressBillingItem[];
@@ -142,7 +143,7 @@ export function ProgressBillingItemTable({
className="min-w-[50px]"
/>
) : (
- item.width.toLocaleString()
+ formatNumber(item.width)
)}
@@ -153,7 +154,7 @@ export function ProgressBillingItemTable({
className="min-w-[50px]"
/>
) : (
- item.height.toLocaleString()
+ formatNumber(item.height)
)}
{item.workTeamLeader}
@@ -169,10 +170,10 @@ export function ProgressBillingItemTable({
allowDecimal
/>
) : (
- item.quantity.toLocaleString()
+ formatNumber(item.quantity)
)}
-
{item.currentBilling.toLocaleString()}
+
{formatNumber(item.currentBilling)}
{item.status}
))}
diff --git a/src/components/clients/ClientDetail.tsx b/src/components/clients/ClientDetail.tsx
index 1302b7f6..3449cb76 100644
--- a/src/components/clients/ClientDetail.tsx
+++ b/src/components/clients/ClientDetail.tsx
@@ -28,6 +28,7 @@ import { Client } from "../../hooks/useClientList";
import { PageLayout } from "../organisms/PageLayout";
import { PageHeader } from "../organisms/PageHeader";
import { useMenuStore } from "@/stores/menuStore";
+import { formatNumber } from "@/lib/utils/amount";
interface ClientDetailProps {
client: Client;
@@ -70,8 +71,7 @@ export function ClientDetail({
// 금액 포맷
const formatCurrency = (amount: string) => {
if (!amount) return "-";
- const num = Number(amount);
- return `₩${num.toLocaleString()}`;
+ return `₩${formatNumber(Number(amount))}`;
};
return (
diff --git a/src/components/customer-center/EventManagement/types.ts b/src/components/customer-center/EventManagement/types.ts
index 3443a5c4..86621107 100644
--- a/src/components/customer-center/EventManagement/types.ts
+++ b/src/components/customer-center/EventManagement/types.ts
@@ -2,6 +2,8 @@
* 이벤트 타입 정의
*/
+import { formatDate } from '@/lib/utils/date';
+
// API 타입 re-export
export type { PostApiData, PostPaginationResponse } from '../shared/types';
@@ -14,7 +16,7 @@ export function transformPostToEvent(post: import('../shared/types').PostApiData
const endDateField = post.custom_field_values?.find(f => f.field_key === 'end_date');
// 기본값으로 created_at 사용
- const createdDate = post.created_at.split('T')[0];
+ const createdDate = formatDate(post.created_at);
return {
id: String(post.id),
diff --git a/src/components/customer-center/NoticeManagement/types.ts b/src/components/customer-center/NoticeManagement/types.ts
index f82a0d12..ac19f20d 100644
--- a/src/components/customer-center/NoticeManagement/types.ts
+++ b/src/components/customer-center/NoticeManagement/types.ts
@@ -2,6 +2,8 @@
* 공지사항 타입 정의
*/
+import { formatDate } from '@/lib/utils/date';
+
// API 타입 re-export
export type { PostApiData, PostPaginationResponse } from '../shared/types';
@@ -32,7 +34,7 @@ export function transformPostToNotice(post: import('../shared/types').PostApiDat
title: post.title,
content: post.content,
author: post.author?.name || '관리자',
- createdAt: post.created_at.split('T')[0],
+ createdAt: formatDate(post.created_at),
viewCount: post.views,
attachments: post.files?.map(file => ({
id: String(file.id),
diff --git a/src/components/document-system/viewer/DocumentViewer.tsx b/src/components/document-system/viewer/DocumentViewer.tsx
index e33e740f..1ebc5757 100644
--- a/src/components/document-system/viewer/DocumentViewer.tsx
+++ b/src/components/document-system/viewer/DocumentViewer.tsx
@@ -20,6 +20,7 @@ import {
DocumentFeatures,
PdfMeta,
} from '../types';
+import { getTodayString } from '@/lib/utils/date';
/**
* 문서 뷰어 (Shell)
@@ -263,7 +264,7 @@ export function DocumentViewer({
title,
orientation: 'portrait',
documentNumber: pdfMeta?.documentNumber || '',
- createdDate: pdfMeta?.createdDate || new Date().toISOString().slice(0, 10),
+ createdDate: pdfMeta?.createdDate || getTodayString(),
showHeaderFooter: pdfMeta?.showHeaderFooter !== false,
}),
});
diff --git a/src/components/hr/CardManagement/CardDetail.tsx b/src/components/hr/CardManagement/CardDetail.tsx
index bb515590..759de542 100644
--- a/src/components/hr/CardManagement/CardDetail.tsx
+++ b/src/components/hr/CardManagement/CardDetail.tsx
@@ -19,10 +19,12 @@ import {
SelectValue,
} from '@/components/ui/select';
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
+import { useDeleteDialog } from '@/hooks/useDeleteDialog';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { ContentSkeleton } from '@/components/ui/skeleton';
import { toast } from 'sonner';
+import { formatAmountWon as formatCurrency } from '@/lib/utils/amount';
import type { Card as CardType, CardFormData, CardStatus } from './types';
import {
CARD_COMPANIES,
@@ -40,10 +42,6 @@ import {
getApprovalFormUrl,
} from './actions';
-function formatCurrency(value: number): string {
- return value.toLocaleString('ko-KR') + '원';
-}
-
function formatExpiryDate(value: string): string {
if (value && value.length === 4) {
return `${value.slice(0, 2)}/${value.slice(2)}`;
@@ -71,7 +69,6 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
const router = useRouter();
const searchParams = useSearchParams();
const [mode, setMode] = useState(initialMode);
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [isLoadingApproval, setIsLoadingApproval] = useState(false);
const [employees, setEmployees] = useState
>([]);
@@ -154,16 +151,11 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
}
};
- const handleConfirmDelete = async () => {
- if (!card?.id) return;
- const result = await deleteCard(card.id);
- if (result.success) {
- toast.success('카드가 삭제되었습니다.');
- router.push('/ko/hr/card-management');
- } else {
- toast.error(result.error || '카드 삭제에 실패했습니다.');
- }
- };
+ const deleteDialog = useDeleteDialog({
+ onDelete: async (id) => deleteCard(id),
+ onSuccess: () => router.push('/ko/hr/card-management'),
+ entityName: '카드',
+ });
const handleCancel = () => {
if (isCreateMode) {
@@ -352,7 +344,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
{/* 하단 버튼 */}
- setShowDeleteDialog(true)} className="text-destructive hover:bg-destructive hover:text-destructive-foreground">
+ deleteDialog.single.open(card!.id)} className="text-destructive hover:bg-destructive hover:text-destructive-foreground">
삭제
@@ -364,8 +356,8 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
카드를 정말 삭제하시겠습니까?
@@ -375,7 +367,8 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
>
}
- onConfirm={handleConfirmDelete}
+ onConfirm={deleteDialog.single.confirm}
+ loading={deleteDialog.isPending}
/>
);
diff --git a/src/components/hr/CardManagement/index.tsx b/src/components/hr/CardManagement/index.tsx
index 9e662746..273e21a0 100644
--- a/src/components/hr/CardManagement/index.tsx
+++ b/src/components/hr/CardManagement/index.tsx
@@ -41,12 +41,9 @@ import {
CARD_STATUS_COLORS,
getCardCompanyLabel,
} from './types';
+import { formatAmountWon as formatCurrency } from '@/lib/utils/amount';
import { getCards, getCardStats } from './actions';
-function formatCurrency(value: number): string {
- return value.toLocaleString('ko-KR') + '원';
-}
-
export function CardManagement() {
const router = useRouter();
const itemsPerPage = 20;
diff --git a/src/components/hr/EmployeeManagement/EmployeeDetail.tsx b/src/components/hr/EmployeeManagement/EmployeeDetail.tsx
index 0837b61b..22d2da23 100644
--- a/src/components/hr/EmployeeManagement/EmployeeDetail.tsx
+++ b/src/components/hr/EmployeeManagement/EmployeeDetail.tsx
@@ -23,6 +23,7 @@ import {
USER_ROLE_LABELS,
USER_ACCOUNT_STATUS_LABELS,
} from './types';
+import { formatNumber } from '@/lib/utils/amount';
interface EmployeeDetailProps {
employee: Employee;
@@ -87,7 +88,7 @@ export function EmployeeDetail({ employee, onEdit, onDelete }: EmployeeDetailPro
{employee.salary && (
연봉
- {employee.salary.toLocaleString()}원
+ {formatNumber(employee.salary)}원
)}
{employee.bankAccount && (
diff --git a/src/components/hr/EmployeeManagement/index.tsx b/src/components/hr/EmployeeManagement/index.tsx
index fdce2064..bd621516 100644
--- a/src/components/hr/EmployeeManagement/index.tsx
+++ b/src/components/hr/EmployeeManagement/index.tsx
@@ -255,17 +255,17 @@ export function EmployeeManagement() {
// 테이블 컬럼 정의
const tableColumns = useMemo(() => [
{ key: 'rowNumber', label: '번호', className: 'w-[60px] text-center' },
- { key: 'employeeCode', label: '사원코드', className: 'min-w-[100px]' },
- { key: 'department', label: '부서', className: 'min-w-[100px]' },
- { key: 'position', label: '직책', className: 'min-w-[100px]' },
- { key: 'name', label: '이름', className: 'min-w-[80px]' },
- { key: 'rank', label: '직급', className: 'min-w-[80px]' },
- { key: 'phone', label: '휴대폰', className: 'min-w-[120px]' },
- { key: 'email', label: '이메일', className: 'min-w-[150px]' },
- { key: 'hireDate', label: '입사일', className: 'min-w-[100px]' },
- { key: 'status', label: '상태', className: 'min-w-[80px]' },
- { key: 'userId', label: '사용자아이디', className: 'min-w-[100px]' },
- { key: 'userRole', label: '권한', className: 'min-w-[80px]' },
+ { key: 'employeeCode', label: '사원코드', className: 'min-w-[100px]', sortable: true },
+ { key: 'department', label: '부서', className: 'min-w-[100px]', sortable: true },
+ { key: 'position', label: '직책', className: 'min-w-[100px]', sortable: true },
+ { key: 'name', label: '이름', className: 'min-w-[80px]', sortable: true },
+ { key: 'rank', label: '직급', className: 'min-w-[80px]', sortable: true },
+ { key: 'phone', label: '휴대폰', className: 'min-w-[120px]', sortable: true },
+ { key: 'email', label: '이메일', className: 'min-w-[150px]', sortable: true },
+ { key: 'hireDate', label: '입사일', className: 'min-w-[100px]', sortable: true },
+ { key: 'status', label: '상태', className: 'min-w-[80px]', sortable: true },
+ { key: 'userId', label: '사용자아이디', className: 'min-w-[100px]', sortable: true },
+ { key: 'userRole', label: '권한', className: 'min-w-[80px]', sortable: true },
{ key: 'actions', label: '작업', className: 'w-[100px] text-right' },
], []);
diff --git a/src/components/hr/VacationManagement/index.tsx b/src/components/hr/VacationManagement/index.tsx
index 4e569ea9..1cb27f2e 100644
--- a/src/components/hr/VacationManagement/index.tsx
+++ b/src/components/hr/VacationManagement/index.tsx
@@ -62,6 +62,7 @@ import {
} from './types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { toast } from 'sonner';
+import { formatDate } from '@/lib/utils/date';
// ===== Mock 데이터 생성 (request 탭용 - 신청 현황은 leaves API 사용 예정) =====
@@ -188,7 +189,7 @@ export function VacationManagement() {
position: item.jobTitle || '-', // job_title_label → 직책
rank: item.rank || '-', // json_extra.rank → 직급
vacationType: item.grantType as VacationType,
- grantDate: item.grantDate.split('T')[0],
+ grantDate: formatDate(item.grantDate),
grantDays: item.grantDays,
reason: item.reason || undefined,
createdAt: item.createdAt,
@@ -235,7 +236,7 @@ export function VacationManagement() {
endDate: item.endDate,
vacationDays: item.days,
status: item.status as RequestStatus,
- requestDate: item.createdAt.split('T')[0],
+ requestDate: formatDate(item.createdAt),
createdAt: item.createdAt,
updatedAt: item.updatedAt,
};
diff --git a/src/components/material/ReceivingManagement/actions.ts b/src/components/material/ReceivingManagement/actions.ts
index 5bd1b57e..a995e713 100644
--- a/src/components/material/ReceivingManagement/actions.ts
+++ b/src/components/material/ReceivingManagement/actions.ts
@@ -22,7 +22,7 @@ import { buildApiUrl } from '@/lib/api/query-params';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { executeServerAction } from '@/lib/api/execute-server-action';
import { executePaginatedAction } from '@/lib/api/execute-paginated-action';
-import { getTodayString } from '@/lib/utils/date';
+import { getTodayString, formatDate } from '@/lib/utils/date';
import type {
ReceivingItem,
@@ -414,7 +414,7 @@ function transformApiToListItem(data: ReceivingApiData): ReceivingItem {
// 수량 (입고수량)
receivingQty: data.receiving_qty ? parseFloat(String(data.receiving_qty)) : undefined,
// 입고변경일: updated_at 매핑
- receivingDate: data.updated_at ? data.updated_at.split('T')[0] : data.receiving_date,
+ receivingDate: data.updated_at ? formatDate(data.updated_at) : data.receiving_date,
// 작성자
createdBy: data.creator?.name,
// 상태
diff --git a/src/components/orders/OrderSalesDetailEdit.tsx b/src/components/orders/OrderSalesDetailEdit.tsx
index 2e55b7b7..13bc2100 100644
--- a/src/components/orders/OrderSalesDetailEdit.tsx
+++ b/src/components/orders/OrderSalesDetailEdit.tsx
@@ -39,7 +39,7 @@ import { toast } from "sonner";
import { IntegratedDetailTemplate } from "@/components/templates/IntegratedDetailTemplate";
import { orderSalesConfig } from "./orderSalesConfig";
import { BadgeSm } from "@/components/atoms/BadgeSm";
-import { formatAmount } from "@/lib/utils/amount";
+import { formatAmount, formatNumber } from "@/lib/utils/amount";
import {
OrderItem,
getOrderById,
@@ -196,7 +196,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
const upperUnit = (unit || "").toUpperCase();
if (countableUnits.includes(upperUnit)) {
- return Math.round(quantity).toLocaleString();
+ return formatNumber(Math.round(quantity));
}
const rounded = Math.round(quantity * 10000) / 10000;
diff --git a/src/components/orders/actions.ts b/src/components/orders/actions.ts
index 671e6259..1ac3d8a7 100644
--- a/src/components/orders/actions.ts
+++ b/src/components/orders/actions.ts
@@ -3,6 +3,7 @@
import { executeServerAction } from '@/lib/api/execute-server-action';
import { buildApiUrl } from '@/lib/api/query-params';
import type { PaginatedApiResponse } from '@/lib/api/types';
+import { formatDate } from '@/lib/utils/date';
// ============================================================================
// API 타입 정의
@@ -517,7 +518,7 @@ function transformApiToFrontend(apiData: ApiOrder): Order {
lotNumber: apiData.order_no,
quoteNumber: apiData.quote?.quote_number || '',
quoteId: apiData.quote_id ?? undefined,
- orderDate: apiData.received_at || apiData.created_at.split('T')[0],
+ orderDate: apiData.received_at || formatDate(apiData.created_at),
client: apiData.client_name || apiData.client?.name || '',
clientId: apiData.client_id ?? undefined,
siteName: apiData.site_name || '',
diff --git a/src/components/orders/documents/ContractDocument.tsx b/src/components/orders/documents/ContractDocument.tsx
index 4ac2670d..a2647b3d 100644
--- a/src/components/orders/documents/ContractDocument.tsx
+++ b/src/components/orders/documents/ContractDocument.tsx
@@ -7,7 +7,7 @@
* - DocumentHeader: simple 레이아웃 (결재란 없음)
*/
-import { formatAmount } from "@/lib/utils/amount";
+import { formatAmount, formatNumber } from "@/lib/utils/amount";
import { OrderItem } from "../actions";
import { DocumentHeader } from "@/components/document-system";
@@ -15,7 +15,7 @@ import { DocumentHeader } from "@/components/document-system";
* 수량 포맷 함수 (정수로 표시)
*/
function formatQuantity(quantity: number): string {
- return Math.round(quantity).toLocaleString();
+ return formatNumber(Math.round(quantity));
}
// 제품 정보 타입
diff --git a/src/components/orders/documents/PurchaseOrderDocument.tsx b/src/components/orders/documents/PurchaseOrderDocument.tsx
index 56b5288a..765c2cb8 100644
--- a/src/components/orders/documents/PurchaseOrderDocument.tsx
+++ b/src/components/orders/documents/PurchaseOrderDocument.tsx
@@ -7,6 +7,7 @@
import { getTodayString } from "@/lib/utils/date";
import { OrderItem } from "../actions";
+import { formatNumber } from '@/lib/utils/amount';
/**
* 수량 포맷 함수
@@ -18,7 +19,7 @@ function formatQuantity(quantity: number, unit?: string): string {
const upperUnit = (unit || "").toUpperCase();
if (countableUnits.includes(upperUnit)) {
- return Math.round(quantity).toLocaleString();
+ return formatNumber(Math.round(quantity));
}
const rounded = Math.round(quantity * 10000) / 10000;
diff --git a/src/components/orders/documents/SalesOrderDocument.tsx b/src/components/orders/documents/SalesOrderDocument.tsx
index 317ab590..b1d70b10 100644
--- a/src/components/orders/documents/SalesOrderDocument.tsx
+++ b/src/components/orders/documents/SalesOrderDocument.tsx
@@ -12,6 +12,7 @@ import { getTodayString } from "@/lib/utils/date";
import { OrderItem } from "../actions";
import { ProductInfo } from "./OrderDocumentModal";
import { ConstructionApprovalTable } from "@/components/document-system";
+import { formatNumber } from '@/lib/utils/amount';
interface SalesOrderDocumentProps {
documentNumber?: string;
@@ -270,10 +271,10 @@ export function SalesOrderDocument({
{row.no}
{row.type}
{row.code}
- {row.openW.toLocaleString()}
- {row.openH.toLocaleString()}
- {row.madeW.toLocaleString()}
- {row.madeH.toLocaleString()}
+ {formatNumber(row.openW)}
+ {formatNumber(row.openH)}
+ {formatNumber(row.madeW)}
+ {formatNumber(row.madeH)}
{row.guideRail}
{row.shaft}
{row.caseInch}
@@ -319,10 +320,10 @@ export function SalesOrderDocument({
{row.no}
{row.code}
- {row.openW.toLocaleString()}
- {row.openH.toLocaleString()}
- {row.madeW.toLocaleString()}
- {row.madeH.toLocaleString()}
+ {formatNumber(row.openW)}
+ {formatNumber(row.openH)}
+ {formatNumber(row.madeW)}
+ {formatNumber(row.madeH)}
{row.guideRail}
{row.shaft}
{row.jointBar}
diff --git a/src/components/orders/documents/TransactionDocument.tsx b/src/components/orders/documents/TransactionDocument.tsx
index e717b899..f10c9392 100644
--- a/src/components/orders/documents/TransactionDocument.tsx
+++ b/src/components/orders/documents/TransactionDocument.tsx
@@ -7,7 +7,7 @@
* - DocumentHeader: simple 레이아웃 (결재란 없음)
*/
-import { formatAmount } from "@/lib/utils/amount";
+import { formatAmount, formatNumber } from "@/lib/utils/amount";
import { OrderItem } from "../actions";
import { DocumentHeader } from "@/components/document-system";
@@ -21,7 +21,7 @@ function formatQuantity(quantity: number, unit?: string): string {
const upperUnit = (unit || "").toUpperCase();
if (countableUnits.includes(upperUnit)) {
- return Math.round(quantity).toLocaleString();
+ return formatNumber(Math.round(quantity));
}
const rounded = Math.round(quantity * 10000) / 10000;
diff --git a/src/components/organisms/DataTable.tsx b/src/components/organisms/DataTable.tsx
index 08bad834..30b7033b 100644
--- a/src/components/organisms/DataTable.tsx
+++ b/src/components/organisms/DataTable.tsx
@@ -10,6 +10,7 @@ import { IconWithBadge } from "@/components/molecules/IconWithBadge";
import { TableActions, TableAction } from "@/components/molecules/TableActions";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
+import { formatNumber } from '@/lib/utils/amount';
// 셀 타입 정의
export type CellType =
@@ -97,7 +98,7 @@ function renderCell(column: Column, value: any, row: T, index?: number): R
// 타입별 렌더링
switch (column.type) {
case "number":
- return {formattedValue?.toLocaleString()} ;
+ return {formatNumber(formattedValue)} ;
case "currency":
const locale = column.currencyConfig?.locale || "ko-KR";
diff --git a/src/components/outbound/ShipmentManagement/documents/ShipmentOrderDocument.tsx b/src/components/outbound/ShipmentManagement/documents/ShipmentOrderDocument.tsx
index 763c571b..8d9c9161 100644
--- a/src/components/outbound/ShipmentManagement/documents/ShipmentOrderDocument.tsx
+++ b/src/components/outbound/ShipmentManagement/documents/ShipmentOrderDocument.tsx
@@ -10,6 +10,7 @@ import { useState } from 'react';
import type { ShipmentDetail } from '../types';
import { DELIVERY_METHOD_LABELS } from '../types';
import { ConstructionApprovalTable } from '@/components/document-system';
+import { formatNumber } from '@/lib/utils/amount';
interface ShipmentOrderDocumentProps {
title: string;
@@ -271,10 +272,10 @@ export function ShipmentOrderDocument({ title, data, showDispatchInfo = false, s
{row.no}
{row.type}
{row.code}
- {row.openW.toLocaleString()}
- {row.openH.toLocaleString()}
- {row.madeW.toLocaleString()}
- {row.madeH.toLocaleString()}
+ {formatNumber(row.openW)}
+ {formatNumber(row.openH)}
+ {formatNumber(row.madeW)}
+ {formatNumber(row.madeH)}
{row.guideRail}
{row.shaft}
{row.caseInch}
@@ -320,10 +321,10 @@ export function ShipmentOrderDocument({ title, data, showDispatchInfo = false, s
{row.no}
{row.code}
- {row.openW.toLocaleString()}
- {row.openH.toLocaleString()}
- {row.madeW.toLocaleString()}
- {row.madeH.toLocaleString()}
+ {formatNumber(row.openW)}
+ {formatNumber(row.openH)}
+ {formatNumber(row.madeW)}
+ {formatNumber(row.madeH)}
{row.guideRail}
{row.shaft}
{row.jointBar}
diff --git a/src/components/pricing-distribution/PriceDistributionDetail.tsx b/src/components/pricing-distribution/PriceDistributionDetail.tsx
index 525ed13f..bed7ed4a 100644
--- a/src/components/pricing-distribution/PriceDistributionDetail.tsx
+++ b/src/components/pricing-distribution/PriceDistributionDetail.tsx
@@ -19,6 +19,7 @@ import { DatePicker } from '@/components/ui/date-picker';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { getPresetStyle } from '@/lib/utils/status-config';
+import { formatNumber } from '@/lib/utils/amount';
import { Checkbox } from '@/components/ui/checkbox';
import {
Select,
@@ -212,8 +213,7 @@ export function PriceDistributionDetail({ id, mode: propMode }: Props) {
// 금액 포맷
const formatPrice = (price?: number) => {
- if (price === undefined || price === null) return '-';
- return price.toLocaleString();
+ return formatNumber(price);
};
if (isLoading) {
diff --git a/src/components/pricing-distribution/PriceDistributionDocumentModal.tsx b/src/components/pricing-distribution/PriceDistributionDocumentModal.tsx
index bd2d0c2f..6d8a68bc 100644
--- a/src/components/pricing-distribution/PriceDistributionDocumentModal.tsx
+++ b/src/components/pricing-distribution/PriceDistributionDocumentModal.tsx
@@ -11,6 +11,7 @@
import { DocumentViewer } from '@/components/document-system/viewer/DocumentViewer';
import { DocumentHeader } from '@/components/document-system/components/DocumentHeader';
import { ConstructionApprovalTable } from '@/components/document-system/components/ConstructionApprovalTable';
+import { formatNumber } from '@/lib/utils/amount';
import type { PriceDistributionDetail } from './types';
interface Props {
@@ -132,7 +133,7 @@ export function PriceDistributionDocumentModal({ open, onOpenChange, detail }: P
-
{item.unit}
- {item.salesPrice.toLocaleString()}
+ {formatNumber(item.salesPrice)}
))}
diff --git a/src/components/pricing/PricingFinalizeDialog.tsx b/src/components/pricing/PricingFinalizeDialog.tsx
index dcbbd7df..967444ff 100644
--- a/src/components/pricing/PricingFinalizeDialog.tsx
+++ b/src/components/pricing/PricingFinalizeDialog.tsx
@@ -14,6 +14,7 @@ import {
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Lock, CheckCircle2 } from 'lucide-react';
+import { formatNumber } from '@/lib/utils/amount';
interface PricingFinalizeDialogProps {
open: boolean;
@@ -56,13 +57,13 @@ export function PricingFinalizeDialog({
매입단가:
- {purchasePrice?.toLocaleString() || '-'}원
+ {formatNumber(purchasePrice)}원
판매단가:
- {salesPrice?.toLocaleString() || '-'}원
+ {formatNumber(salesPrice)}원
diff --git a/src/components/pricing/PricingFormClient.tsx b/src/components/pricing/PricingFormClient.tsx
index 394da55c..3f420176 100644
--- a/src/components/pricing/PricingFormClient.tsx
+++ b/src/components/pricing/PricingFormClient.tsx
@@ -14,6 +14,7 @@
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { getTodayString } from '@/lib/utils/date';
+import { formatNumber } from '@/lib/utils/amount';
import {
DollarSign,
Package,
@@ -547,29 +548,29 @@ export function PricingFormClient({
입고가:
- {(purchasePrice || 0).toLocaleString()}원
+ {formatNumber(purchasePrice || 0)}원
가공비:
- {(processingCost || 0).toLocaleString()}원
+ {formatNumber(processingCost || 0)}원
소계:
- {((purchasePrice || 0) + (processingCost || 0)).toLocaleString()}원
+ {formatNumber((purchasePrice || 0) + (processingCost || 0))}원
{loss > 0 && (
LOSS ({loss}%):
- +{(((purchasePrice || 0) + (processingCost || 0)) * (loss / 100)).toLocaleString()}원
+ +{formatNumber(((purchasePrice || 0) + (processingCost || 0)) * (loss / 100))}원
)}
LOSS 적용 원가:
- {costWithLoss.toLocaleString()}원
+ {formatNumber(costWithLoss)}원
@@ -679,17 +680,17 @@ export function PricingFormClient({
LOSS 적용 원가:
- {costWithLoss.toLocaleString()}원
+ {formatNumber(costWithLoss)}원
판매단가:
- {salesPrice.toLocaleString()}원
+ {formatNumber(salesPrice)}원
마진:
- {marginAmount.toLocaleString()}원 ({marginRate.toFixed(1)}%)
+ {formatNumber(marginAmount)}원 ({marginRate.toFixed(1)}%)
diff --git a/src/components/pricing/PricingHistoryDialog.tsx b/src/components/pricing/PricingHistoryDialog.tsx
index a11ce919..56493bd7 100644
--- a/src/components/pricing/PricingHistoryDialog.tsx
+++ b/src/components/pricing/PricingHistoryDialog.tsx
@@ -15,6 +15,7 @@ import { Badge } from '@/components/ui/badge';
import { getPresetStyle } from '@/lib/utils/status-config';
import { Separator } from '@/components/ui/separator';
import { History } from 'lucide-react';
+import { formatNumber } from '@/lib/utils/amount';
import type { PricingData } from './types';
interface PricingHistoryDialogProps {
@@ -66,19 +67,19 @@ export function PricingHistoryDialog({
매입단가:
- {pricingData.purchasePrice?.toLocaleString() || '-'}원
+ {formatNumber(pricingData.purchasePrice)}원
가공비:
- {pricingData.processingCost?.toLocaleString() || '-'}원
+ {formatNumber(pricingData.processingCost)}원
판매단가:
- {pricingData.salesPrice?.toLocaleString() || '-'}원
+ {formatNumber(pricingData.salesPrice)}원
@@ -118,19 +119,19 @@ export function PricingHistoryDialog({
매입단가:
- {revision.previousData?.purchasePrice?.toLocaleString() || '-'}원
+ {formatNumber(revision.previousData?.purchasePrice)}원
가공비:
- {revision.previousData?.processingCost?.toLocaleString() || '-'}원
+ {formatNumber(revision.previousData?.processingCost)}원
판매단가:
- {revision.previousData?.salesPrice?.toLocaleString() || '-'}원
+ {formatNumber(revision.previousData?.salesPrice)}원
diff --git a/src/components/pricing/PricingListClient.tsx b/src/components/pricing/PricingListClient.tsx
index 13070d2e..cc1de3cf 100644
--- a/src/components/pricing/PricingListClient.tsx
+++ b/src/components/pricing/PricingListClient.tsx
@@ -31,6 +31,7 @@ import {
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
import type { PricingListItem, ItemType } from './types';
import { ITEM_TYPE_LABELS, ITEM_TYPE_COLORS } from './types';
+import { formatNumber } from '@/lib/utils/amount';
interface PricingListClientProps {
initialData: PricingListItem[];
@@ -100,8 +101,8 @@ export function PricingListClient({
// 금액 포맷팅
const formatPrice = (price?: number) => {
- if (price === undefined || price === null) return '-';
- return `${price.toLocaleString()}원`;
+ if (price == null) return '-';
+ return `${formatNumber(price)}원`;
};
// 품목 유형 Badge 렌더링
diff --git a/src/components/production/WorkOrders/documents/ScreenInspectionContent.tsx b/src/components/production/WorkOrders/documents/ScreenInspectionContent.tsx
index 1b2525f3..d3d08fce 100644
--- a/src/components/production/WorkOrders/documents/ScreenInspectionContent.tsx
+++ b/src/components/production/WorkOrders/documents/ScreenInspectionContent.tsx
@@ -31,6 +31,7 @@ import {
JudgmentCell,
calculateOverallResult,
} from './inspection-shared';
+import { formatNumber } from '@/lib/utils/amount';
export type { InspectionContentRef };
@@ -108,7 +109,7 @@ export const ScreenInspectionContent = forwardRef
{
const num = value.replace(/[^\d]/g, '');
if (!num) return '';
- return Number(num).toLocaleString();
+ return formatNumber(Number(num));
};
const handleInputChange = useCallback((rowId: number, field: 'lengthMeasured' | 'widthMeasured', value: string) => {
diff --git a/src/components/production/WorkOrders/documents/ScreenWorkLogContent.tsx b/src/components/production/WorkOrders/documents/ScreenWorkLogContent.tsx
index 7ec098ac..4dba5a84 100644
--- a/src/components/production/WorkOrders/documents/ScreenWorkLogContent.tsx
+++ b/src/components/production/WorkOrders/documents/ScreenWorkLogContent.tsx
@@ -23,6 +23,7 @@
import type { WorkOrder, WorkOrderItem } from '../types';
import { SectionHeader, ConstructionApprovalTable } from '@/components/document-system';
+import { formatNumber } from '@/lib/utils/amount';
// ===== 절단 계산 로직 (기존 시스템 calculateCutSize 이식) =====
@@ -122,7 +123,7 @@ export function ScreenWorkLogContent({ data: order, materialLots = [] }: ScreenW
const items = order.items || [];
// 숫자 천단위 콤마 포맷
- const fmt = (v?: number) => v != null ? v.toLocaleString() : '-';
+ const fmt = (v?: number) => v != null ? formatNumber(v) : '-';
// floorCode에서 부호 추출: "1층/FSS-01" → "FSS-01"
const getSymbolCode = (floorCode?: string) => {
diff --git a/src/components/production/WorkOrders/documents/SlatWorkLogContent.tsx b/src/components/production/WorkOrders/documents/SlatWorkLogContent.tsx
index 9b2833fd..b2ff7a5d 100644
--- a/src/components/production/WorkOrders/documents/SlatWorkLogContent.tsx
+++ b/src/components/production/WorkOrders/documents/SlatWorkLogContent.tsx
@@ -14,6 +14,7 @@
import type { WorkOrder } from '../types';
import { SectionHeader } from '@/components/document-system';
+import { formatNumber } from '@/lib/utils/amount';
interface MaterialInputLot {
lot_no: string;
@@ -47,7 +48,7 @@ export function SlatWorkLogContent({ data: order, materialLots = [] }: SlatWorkL
const items = order.items || [];
// 숫자 천단위 콤마 포맷
- const fmt = (v?: number) => v != null ? v.toLocaleString() : '-';
+ const fmt = (v?: number) => v != null ? formatNumber(v) : '-';
// floorCode에서 부호 추출: "1층/FSS-01" → "FSS-01"
const getSymbolCode = (floorCode?: string) => {
diff --git a/src/components/production/WorkOrders/documents/TemplateInspectionContent.tsx b/src/components/production/WorkOrders/documents/TemplateInspectionContent.tsx
index 26135c70..e3c98ae6 100644
--- a/src/components/production/WorkOrders/documents/TemplateInspectionContent.tsx
+++ b/src/components/production/WorkOrders/documents/TemplateInspectionContent.tsx
@@ -34,6 +34,7 @@ import {
getOrderInfo,
INPUT_CLASS,
} from './inspection-shared';
+import { formatNumber } from '@/lib/utils/amount';
export type { InspectionContentRef };
@@ -85,7 +86,7 @@ function resolveReferenceValue(
function formatStandard(item: InspectionTemplateSectionItem, workItem?: WorkItemData): string {
const refVal = resolveReferenceValue(item, workItem);
- if (refVal !== null) return refVal.toLocaleString();
+ if (refVal !== null) return formatNumber(refVal);
const sc = item.standard_criteria;
if (!sc) return item.standard || '-';
if (typeof sc === 'object') {
diff --git a/src/components/production/WorkOrders/types.ts b/src/components/production/WorkOrders/types.ts
index 270422a3..e5ac5ef5 100644
--- a/src/components/production/WorkOrders/types.ts
+++ b/src/components/production/WorkOrders/types.ts
@@ -2,6 +2,8 @@
* 작업지시 관리 타입 정의
*/
+import { formatDate } from '@/lib/utils/date';
+
// 공정 정보 (API 관계)
export interface ProcessInfo {
id: number;
@@ -439,14 +441,14 @@ export function transformApiToFrontend(api: WorkOrderApi): WorkOrder {
dueDate: api.scheduled_date || '-',
assignee: assigneeName,
assignees: assignees.length > 0 ? assignees : undefined,
- orderDate: api.created_at.split('T')[0],
+ orderDate: formatDate(api.created_at),
scheduledDate: api.scheduled_date || '',
shipmentDate: api.scheduled_date || '-',
isAssigned: api.assignee_id !== null || assignees.length > 0,
isStarted: ['in_progress', 'completed', 'shipped'].includes(api.status),
priority: priorityValue,
priorityLabel: getPriorityLabel(priorityValue),
- salesOrderDate: api.sales_order?.received_at?.split('T')[0] || api.sales_order?.created_at?.split('T')[0] || api.created_at.split('T')[0],
+ salesOrderDate: api.sales_order?.received_at?.split('T')[0] || api.sales_order?.created_at?.split('T')[0] || formatDate(api.created_at),
salesOrderWriter: api.sales_order?.writer?.name || '-',
clientContact: api.sales_order?.client_contact || '-',
shutterCount: api.sales_order?.root_nodes_count || null,
@@ -472,7 +474,7 @@ export function transformApiToFrontend(api: WorkOrderApi): WorkOrder {
status: issue.is_resolved ? 'resolved' : 'pending',
type: issue.priority === 'high' ? '긴급' : '일반',
description: issue.title + (issue.description ? ` - ${issue.description}` : ''),
- createdAt: issue.created_at.split('T')[0],
+ createdAt: formatDate(issue.created_at),
})),
note: api.memo || undefined,
};
diff --git a/src/components/production/WorkResults/WorkResultList.tsx b/src/components/production/WorkResults/WorkResultList.tsx
index 460620e5..7a743391 100644
--- a/src/components/production/WorkResults/WorkResultList.tsx
+++ b/src/components/production/WorkResults/WorkResultList.tsx
@@ -36,6 +36,7 @@ import { toast } from 'sonner';
import { getWorkResults, getWorkResultStats } from './actions';
import type { WorkResult, WorkResultStats } from './types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
+import { formatNumber } from '@/lib/utils/amount';
// 페이지당 항목 수
const ITEMS_PER_PAGE = 20;
@@ -129,19 +130,19 @@ export function WorkResultList() {
() => [
{
label: '총 생산수량',
- value: `${statsData.totalProduction.toLocaleString()}개`,
+ value: `${formatNumber(statsData.totalProduction)}개`,
icon: Package,
iconColor: 'text-gray-600',
},
{
label: '양품수량',
- value: `${statsData.totalGood.toLocaleString()}개`,
+ value: `${formatNumber(statsData.totalGood)}개`,
icon: CheckCircle2,
iconColor: 'text-green-600',
},
{
label: '불량수량',
- value: `${statsData.totalDefect.toLocaleString()}개`,
+ value: `${formatNumber(statsData.totalDefect)}개`,
icon: XCircle,
iconColor: 'text-red-600',
},
diff --git a/src/components/production/WorkerScreen/InspectionInputModal.tsx b/src/components/production/WorkerScreen/InspectionInputModal.tsx
index a9c93570..1e6ac898 100644
--- a/src/components/production/WorkerScreen/InspectionInputModal.tsx
+++ b/src/components/production/WorkerScreen/InspectionInputModal.tsx
@@ -23,6 +23,7 @@ import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { cn } from '@/lib/utils';
import type { InspectionTemplateData, InspectionTemplateSectionItem } from './types';
+import { formatNumber } from '@/lib/utils/amount';
// 중간검사 공정 타입
export type InspectionProcessType =
@@ -278,7 +279,7 @@ function resolveRefValue(
function formatDimension(val: number | undefined): string {
if (val === undefined || val === null) return '-';
- return val.toLocaleString();
+ return formatNumber(val);
}
// ===== 항목별 입력 유형 판별 =====
@@ -337,7 +338,7 @@ function DynamicInspectionForm({
if (isNumericItem(item)) {
const numValue = formValues[fieldKey] as number | null | undefined;
- const designLabel = designValue !== undefined ? designValue.toLocaleString() : '';
+ const designLabel = designValue !== undefined ? formatNumber(designValue) : '';
const toleranceLabel = item.tolerance
? ` (${designLabel ? designLabel + ' ' : ''}${formatToleranceLabel(item.tolerance)})`
: designLabel ? ` (${designLabel})` : '';
@@ -381,8 +382,8 @@ function DynamicInspectionForm({
const hasStandard = designValue !== undefined || item.standard;
const standardDisplay = designValue !== undefined
? (item.tolerance
- ? `${designValue.toLocaleString()} ${formatToleranceLabel(item.tolerance)}`
- : String(designValue.toLocaleString()))
+ ? `${formatNumber(designValue)} ${formatToleranceLabel(item.tolerance)}`
+ : String(formatNumber(designValue)))
: item.standard;
return (
diff --git a/src/components/production/WorkerScreen/MaterialInputModal.tsx b/src/components/production/WorkerScreen/MaterialInputModal.tsx
index 5163a080..7757e592 100644
--- a/src/components/production/WorkerScreen/MaterialInputModal.tsx
+++ b/src/components/production/WorkerScreen/MaterialInputModal.tsx
@@ -31,6 +31,7 @@ import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { getMaterialsForWorkOrder, registerMaterialInput, getMaterialsForItem, registerMaterialInputForItem, type MaterialForInput, type MaterialForItemInput } from './actions';
import type { WorkOrder } from '../ProductionDashboard/types';
import type { MaterialInput } from './types';
+import { formatNumber } from '@/lib/utils/amount';
interface MaterialInputModalProps {
open: boolean;
@@ -55,7 +56,7 @@ interface MaterialGroup {
lots: MaterialForInput[];
}
-const fmtQty = (v: number) => parseFloat(String(v)).toLocaleString();
+const fmtQty = (v: number) => formatNumber(parseFloat(String(v)));
export function MaterialInputModal({
open,
diff --git a/src/components/production/WorkerScreen/WorkItemCard.tsx b/src/components/production/WorkerScreen/WorkItemCard.tsx
index 7019af1d..02a3c8be 100644
--- a/src/components/production/WorkerScreen/WorkItemCard.tsx
+++ b/src/components/production/WorkerScreen/WorkItemCard.tsx
@@ -32,6 +32,7 @@ import type {
WorkStepData,
MaterialListItem,
} from './types';
+import { formatNumber } from '@/lib/utils/amount';
interface WorkItemCardProps {
item: WorkItemData;
@@ -91,7 +92,7 @@ export const WorkItemCard = memo(function WorkItemCard({
제작 사이즈
- {item.width.toLocaleString()} X {item.height.toLocaleString()} mm
+ {formatNumber(item.width)} X {formatNumber(item.height)} mm
{item.quantity}개
@@ -205,7 +206,7 @@ export const WorkItemCard = memo(function WorkItemCard({
{mat.lotNo}
{mat.itemName}
- {parseFloat(String(mat.quantity)).toLocaleString()}
+ {formatNumber(parseFloat(String(mat.quantity)))}
{mat.unit}
@@ -246,7 +247,7 @@ function ScreenCuttingInfo({ width, sheets }: { width: number; sheets: number })
절단정보
- 폭 {width.toLocaleString()}mm X {sheets}장
+ 폭 {formatNumber(width)}mm X {sheets}장
);
@@ -265,7 +266,7 @@ function SlatExtraInfo({
return (
- 길이 {length.toLocaleString()}mm
+ 길이 {formatNumber(length)}mm
슬랫 매수 {slatCount}장
@@ -317,7 +318,7 @@ function BendingExtraInfo({ info }: { info: BendingInfo }) {
{i === 0 ? '길이별 수량' : ''}
- {lq.length.toLocaleString()}mm X {lq.quantity}개
+ {formatNumber(lq.length)}mm X {lq.quantity}개
))}
diff --git a/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx b/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx
index b154290d..e7e90cb3 100644
--- a/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx
+++ b/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx
@@ -26,6 +26,7 @@ import type {
} from '../types';
import type { FqcDocument, FqcTemplate } from '../fqcActions';
import { buildReportDocumentDataForItem } from '../mockData';
+import { formatDate } from '@/lib/utils/date';
interface InspectionReportModalProps {
open: boolean;
@@ -215,7 +216,7 @@ export function InspectionReportModal({
// PDF 메타 정보
const pdfMeta = useFqcMode && fqcDocument
- ? { documentNumber: fqcDocument.documentNo, createdDate: fqcDocument.createdAt.split('T')[0] }
+ ? { documentNumber: fqcDocument.documentNo, createdDate: formatDate(fqcDocument.createdAt) }
: legacyCurrentData
? { documentNumber: legacyCurrentData.documentNumber, createdDate: legacyCurrentData.createdDate }
: undefined;
@@ -246,7 +247,7 @@ export function InspectionReportModal({
template={fqcTemplate}
documentData={fqcDocument.data}
documentNo={fqcDocument.documentNo}
- createdDate={fqcDocument.createdAt.split('T')[0]}
+ createdDate={formatDate(fqcDocument.createdAt)}
readonly={true}
/>
) : (
diff --git a/src/components/quotes/DiscountModal.tsx b/src/components/quotes/DiscountModal.tsx
index 4b618025..caae6405 100644
--- a/src/components/quotes/DiscountModal.tsx
+++ b/src/components/quotes/DiscountModal.tsx
@@ -12,6 +12,7 @@
import { useState, useEffect, useCallback } from "react";
import { Percent } from "lucide-react";
+import { formatNumber } from "@/lib/utils/amount";
import {
Dialog,
@@ -159,7 +160,7 @@ export function DiscountModal({
공급가액
@@ -189,7 +190,7 @@ export function DiscountModal({
handleDiscountAmountChange(e.target.value.replace(/,/g, ""))}
className="pr-8 text-right"
/>
@@ -203,7 +204,7 @@ export function DiscountModal({
할인 후 공급가액
diff --git a/src/components/quotes/FormulaViewModal.tsx b/src/components/quotes/FormulaViewModal.tsx
index f589cae2..3ed3feb4 100644
--- a/src/components/quotes/FormulaViewModal.tsx
+++ b/src/components/quotes/FormulaViewModal.tsx
@@ -8,6 +8,7 @@
import { Calculator, ChevronDown, ChevronRight } from "lucide-react";
import { useState } from "react";
+import { formatNumber } from "@/lib/utils/amount";
import {
Dialog,
DialogContent,
@@ -131,11 +132,11 @@ function LocationDetail({ location }: { location: LocationItem }) {
1개당
- {bom.grand_total.toLocaleString()}원
+ {formatNumber(bom.grand_total)}원
×
{location.quantity}개 =
- {(bom.grand_total * location.quantity).toLocaleString()}원
+ {formatNumber(bom.grand_total * location.quantity)}원
@@ -212,7 +213,7 @@ function FormulaTable({ formulas, stepName }: { formulas: FormulaItem[]; stepNam
{f.var}
{f.desc}
- {typeof f.value === 'number' ? f.value.toLocaleString() : f.value}
+ {typeof f.value === 'number' ? formatNumber(f.value) : f.value}
{f.unit}
))}
@@ -243,7 +244,7 @@ function FormulaTable({ formulas, stepName }: { formulas: FormulaItem[]; stepNam
{f.formula}
{f.calculation}
- {typeof f.result === 'number' ? f.result.toLocaleString() : f.result}
+ {typeof f.result === 'number' ? formatNumber(f.result) : f.result}
{f.unit}
@@ -273,9 +274,9 @@ function FormulaTable({ formulas, stepName }: { formulas: FormulaItem[]; stepNam
{f.item}
{f.qty_formula}
{f.qty_result}
- {f.unit_price?.toLocaleString()}
+ {formatNumber(f.unit_price)}
{f.price_calc}
- {f.total?.toLocaleString()}
+ {formatNumber(f.total)}
))}
@@ -299,7 +300,7 @@ function FormulaTable({ formulas, stepName }: { formulas: FormulaItem[]; stepNam
{f.category}
{f.formula}
- {f.result?.toLocaleString()}원
+ {typeof f.result === 'number' ? formatNumber(f.result) : f.result}원
))}
diff --git a/src/components/quotes/PurchaseOrderDocument.tsx b/src/components/quotes/PurchaseOrderDocument.tsx
index 30f63f7b..a670356d 100644
--- a/src/components/quotes/PurchaseOrderDocument.tsx
+++ b/src/components/quotes/PurchaseOrderDocument.tsx
@@ -8,6 +8,7 @@
import { QuoteFormData } from "./types";
import type { CompanyFormData } from "@/components/settings/CompanyInfoManagement/types";
import { DocumentHeader, LotApprovalTable } from "@/components/document-system";
+import { formatNumber } from '@/lib/utils/amount';
interface PurchaseOrderDocumentProps {
quote: QuoteFormData;
@@ -232,7 +233,7 @@ export function PurchaseOrderDocument({ quote, companyInfo }: PurchaseOrderDocum
{item.no}
{item.name}
{item.spec}
- {item.length > 0 ? item.length.toLocaleString() : ''}
+ {item.length > 0 ? formatNumber(item.length) : ''}
{item.quantity}
{item.note}
diff --git a/src/components/quotes/QuoteDocument.tsx b/src/components/quotes/QuoteDocument.tsx
index e46e01a8..312b24bb 100644
--- a/src/components/quotes/QuoteDocument.tsx
+++ b/src/components/quotes/QuoteDocument.tsx
@@ -9,6 +9,7 @@
import { QuoteFormData } from "./types";
import type { CompanyFormData } from "@/components/settings/CompanyInfoManagement/types";
import { DocumentHeader, SignatureSection } from "@/components/document-system";
+import { formatNumber } from "@/lib/utils/amount";
interface QuoteDocumentProps {
quote: QuoteFormData;
@@ -18,7 +19,7 @@ interface QuoteDocumentProps {
export function QuoteDocument({ quote, companyInfo }: QuoteDocumentProps) {
const formatAmount = (amount: number | undefined) => {
if (amount === undefined || amount === null) return '0';
- return amount.toLocaleString('ko-KR');
+ return formatNumber(amount);
};
const formatDate = (dateStr: string) => {
diff --git a/src/components/quotes/QuoteFooterBar.tsx b/src/components/quotes/QuoteFooterBar.tsx
index f7c1f1f9..1a97e410 100644
--- a/src/components/quotes/QuoteFooterBar.tsx
+++ b/src/components/quotes/QuoteFooterBar.tsx
@@ -11,6 +11,7 @@
import { Save, Check, ArrowLeft, Loader2, FileText, Pencil, ClipboardList, Percent, Calculator } from "lucide-react";
import { Button } from "../ui/button";
+import { formatNumber } from "@/lib/utils/amount";
// =============================================================================
// Props
@@ -92,7 +93,7 @@ export function QuoteFooterBar({
예상 전체 견적금액
- {totalAmount.toLocaleString()}
+ {formatNumber(totalAmount)}
원
diff --git a/src/components/quotes/QuoteManagementClient.tsx b/src/components/quotes/QuoteManagementClient.tsx
index e48ad60c..49ec1b4a 100644
--- a/src/components/quotes/QuoteManagementClient.tsx
+++ b/src/components/quotes/QuoteManagementClient.tsx
@@ -226,15 +226,15 @@ export function QuoteManagementClient({
// 테이블 컬럼
columns: [
{ key: 'rowNumber', label: '번호', className: 'w-[60px] text-center' },
- { key: 'quoteNumber', label: '견적번호', className: 'min-w-[120px]' },
- { key: 'registrationDate', label: '접수일', className: 'w-[100px]' },
- { key: 'status', label: '상태', className: 'w-[80px]' },
- { key: 'productCategory', label: '제품분류', className: 'w-[100px]' },
- { key: 'quantity', label: '수량', className: 'w-[60px] text-center' },
- { key: 'amount', label: '금액', className: 'w-[120px] text-right' },
- { key: 'client', label: '발주처', className: 'min-w-[100px]' },
- { key: 'site', label: '현장명', className: 'min-w-[120px]' },
- { key: 'manager', label: '담당자', className: 'w-[80px]' },
+ { key: 'quoteNumber', label: '견적번호', className: 'min-w-[120px]', sortable: true },
+ { key: 'registrationDate', label: '접수일', className: 'w-[100px]', sortable: true },
+ { key: 'status', label: '상태', className: 'w-[80px]', sortable: true },
+ { key: 'productCategory', label: '제품분류', className: 'w-[100px]', sortable: true },
+ { key: 'quantity', label: '수량', className: 'w-[60px] text-center', sortable: true },
+ { key: 'amount', label: '금액', className: 'w-[120px] text-right', sortable: true },
+ { key: 'client', label: '발주처', className: 'min-w-[100px]', sortable: true },
+ { key: 'site', label: '현장명', className: 'min-w-[120px]', sortable: true },
+ { key: 'manager', label: '담당자', className: 'w-[80px]', sortable: true },
{ key: 'remarks', label: '비고', className: 'min-w-[150px]' },
{ key: 'actions', label: '작업', className: 'w-[100px]' },
],
diff --git a/src/components/quotes/QuotePreviewContent.tsx b/src/components/quotes/QuotePreviewContent.tsx
index 3d399121..b9ae7c20 100644
--- a/src/components/quotes/QuotePreviewContent.tsx
+++ b/src/components/quotes/QuotePreviewContent.tsx
@@ -10,6 +10,7 @@
import React from 'react';
import type { QuoteFormDataV2 } from './QuoteRegistration';
+import { formatNumber } from '@/lib/utils/amount';
import type { BomCalculationResultItem } from './types';
// 양식 타입
@@ -212,10 +213,10 @@ export function QuotePreviewContent({
{loc.quantity}
SET
- {(loc.unitPrice || 0).toLocaleString()}
+ {formatNumber(loc.unitPrice || 0)}
- {(loc.totalPrice || 0).toLocaleString()}
+ {formatNumber(loc.totalPrice || 0)}
))}
@@ -231,7 +232,7 @@ export function QuotePreviewContent({
- {subtotal.toLocaleString()}
+ {formatNumber(subtotal)}
{/* 할인율 */}
@@ -250,7 +251,7 @@ export function QuotePreviewContent({
할인금액
- {hasDiscount ? `-${discountAmount.toLocaleString()}` : '0'}
+ {hasDiscount ? `-${formatNumber(discountAmount)}` : '0'}
@@ -259,7 +260,7 @@ export function QuotePreviewContent({
할인 후 금액
- {afterDiscount.toLocaleString()}
+ {formatNumber(afterDiscount)}
{/* 부가세 포함일 때 추가 행들 */}
@@ -269,13 +270,13 @@ export function QuotePreviewContent({
부가가치세 합계
- {vat.toLocaleString()}
+ {formatNumber(vat)}
총 견적금액
- {grandTotal.toLocaleString()}
+ {formatNumber(grandTotal)}
>
)}
@@ -289,7 +290,7 @@ export function QuotePreviewContent({
합계금액 ({vatIncluded ? '부가세 포함' : '부가세 별도'})
- ₩ {grandTotal.toLocaleString()}
+ ₩ {formatNumber(grandTotal)}
@@ -346,10 +347,10 @@ export function QuotePreviewContent({
{item.unit || 'EA'}
- {item.unit_price.toLocaleString()}
+ {formatNumber(item.unit_price)}
- {item.total_price.toLocaleString()}
+ {formatNumber(item.total_price)}
))
@@ -372,10 +373,10 @@ export function QuotePreviewContent({
- {(loc.bomResult?.grand_total || 0).toLocaleString()}
+ {formatNumber(loc.bomResult?.grand_total || 0)}
- {(locationSubtotal * loc.quantity).toLocaleString()}
+ {formatNumber(locationSubtotal * loc.quantity)}
@@ -388,7 +389,7 @@ export function QuotePreviewContent({
총 합계
- {subtotal.toLocaleString()}
+ {formatNumber(subtotal)}
diff --git a/src/components/quotes/QuoteRegistration.tsx b/src/components/quotes/QuoteRegistration.tsx
index 84d19d0b..701f3d53 100644
--- a/src/components/quotes/QuoteRegistration.tsx
+++ b/src/components/quotes/QuoteRegistration.tsx
@@ -53,6 +53,7 @@ import { useDevFill } from "@/components/dev/useDevFill";
import type { Vendor } from "../accounting/VendorManagement";
import type { BomMaterial, CalculationResults, BomCalculationResultItem } from "./types";
import { getLocalDateString, getDateAfterDays } from "@/lib/utils/date";
+import { formatNumber } from "@/lib/utils/amount";
// =============================================================================
// 타입 정의
@@ -322,7 +323,7 @@ export function QuoteRegistration({
// 할인 적용 핸들러
const handleApplyDiscount = useCallback((rate: number, amount: number) => {
setFormData(prev => ({ ...prev, discountRate: rate, discountAmount: amount }));
- toast.success(`할인이 적용되었습니다. (${rate.toFixed(1)}%, ${amount.toLocaleString()}원)`);
+ toast.success(`할인이 적용되었습니다. (${rate.toFixed(1)}%, ${formatNumber(amount)}원)`);
}, []);
// 개소별 합계
diff --git a/src/components/quotes/QuoteSummaryPanel.tsx b/src/components/quotes/QuoteSummaryPanel.tsx
index 22271695..9cb36844 100644
--- a/src/components/quotes/QuoteSummaryPanel.tsx
+++ b/src/components/quotes/QuoteSummaryPanel.tsx
@@ -12,6 +12,7 @@ import { useMemo } from "react";
import { Coins } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
+import { formatNumber } from "@/lib/utils/amount";
import type { LocationItem } from "./QuoteRegistration";
@@ -175,11 +176,11 @@ export function QuoteSummaryPanel({
상세소계
- {loc.totalPrice.toLocaleString()}
+ {formatNumber(loc.totalPrice)}
{loc.unitPrice > 0 && (
- 수량 적용: {(loc.unitPrice * loc.quantity).toLocaleString()}
+ 수량 적용: {formatNumber(loc.unitPrice * loc.quantity)}
)}
@@ -221,7 +222,7 @@ export function QuoteSummaryPanel({
({category.count}개)
- {category.amount.toLocaleString()}
+ {formatNumber(category.amount)}
@@ -232,11 +233,11 @@ export function QuoteSummaryPanel({