refactor: replace manual currency formatting with global formatCurrency utility across order and financial modules

This commit is contained in:
Yoga Pangestu 2026-04-24 00:43:02 +07:00
parent 1a0c4a4601
commit 53cdb219d5
8 changed files with 45 additions and 56 deletions

View File

@ -2,6 +2,7 @@
import React, { useMemo } from "react"
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts"
import { formatCurrency } from "@/lib/formatters"
import {
Card,
@ -62,7 +63,7 @@ export const CustomBarChart = React.memo(function CustomBarChart({
return val;
}
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(val);
return formatCurrency(val);
};
return (

View File

@ -3,6 +3,7 @@ import { Pencil, Trash2, ImagePlus } from 'lucide-react';
import { DataTableColumnHeader } from '@/components/data-table-column-header';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { formatDate, formatCurrency } from '@/lib/formatters';
import type { Expense } from '@/types';
interface ColumnProps {
@ -11,13 +12,6 @@ interface ColumnProps {
onPreviewImage: (url: string) => void;
}
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(amount);
};
export const getColumns = ({ onEdit, onDelete, onPreviewImage }: ColumnProps): ColumnDef<Expense>[] => [
{
@ -66,11 +60,7 @@ export const getColumns = ({ onEdit, onDelete, onPreviewImage }: ColumnProps): C
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Tanggal" />
),
cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric'
}),
cell: ({ row }) => formatDate(row.original.created_at),
meta: { title: "Tanggal" },
},
{

View File

@ -31,6 +31,7 @@ import {
SheetTrigger
} from "@/components/ui/sheet";
import { cn } from '@/lib/utils';
import { formatCurrency, formatNumber } from '@/lib/formatters';
import * as orderRoutes from '@/routes/order';
import type { Product } from '@/types';
import type { OrderItem } from '@/types/order';
@ -238,7 +239,7 @@ return;
<PriceTypeLabel type={item.price_type} />
</Badge>
<p className="text-xs text-primary font-medium">
Rp {item.total.toLocaleString('id-ID')}
{formatCurrency(item.total)}
</p>
</div>
</div>
@ -260,7 +261,7 @@ return;
</div>
<span>×</span>
<span className="font-mono">
Rp {item.price.toLocaleString('id-ID')}
{formatCurrency(item.price)}
</span>
</div>
</div>
@ -327,7 +328,7 @@ return;
<div className="space-y-1.5 pt-2 border-t border-dashed">
<div className="flex justify-between text-xs">
<span className="text-muted-foreground">Subtotal</span>
<span className="font-medium">Rp {subtotal.toLocaleString('id-ID')}</span>
<span className="font-medium">{formatCurrency(subtotal)}</span>
</div>
<div className="flex justify-between items-center text-xs">
<span className="text-muted-foreground">Potongan / Diskon</span>
@ -351,7 +352,7 @@ return;
<div className="flex justify-between text-lg font-bold border-t pt-2">
<span className="">Total</span>
<span className="font-medium">Rp {total.toLocaleString('id-ID')}</span>
<span className="font-medium">{formatCurrency(total)}</span>
</div>
</div>
@ -375,7 +376,7 @@ return;
{change >= 0 && (Number(data.payment) || 0) > 0 && (
<div className="flex justify-between items-center p-2 rounded bg-green-500/10 border border-green-500/20 text-green-600 dark:text-green-400">
<span className="text-xs font-bold uppercase">Kembalian</span>
<span className="text-lg font-bold">Rp {change.toLocaleString('id-ID')}</span>
<span className="text-lg font-bold">{formatCurrency(change)}</span>
</div>
)}
</div>
@ -509,7 +510,7 @@ return;
<div className="flex flex-col flex-1 min-w-0">
<span className="text-[10px] uppercase font-bold text-muted-foreground">Harga</span>
<span className="text-sm font-bold text-primary tabular-nums truncate block">
Rp {currentPrice.toLocaleString('id-ID')}
{formatCurrency(currentPrice)}
</span>
</div>
<div className="flex items-center gap-1 bg-muted/50 rounded-xl sm:rounded-full p-1 sm:p-0.5 shrink-0 w-full sm:w-auto justify-between sm:justify-end border border-transparent hover:border-primary/20 transition-colors">
@ -597,7 +598,7 @@ return;
</div>
<div className="text-right">
<p className="text-[10px] opacity-80 uppercase font-bold tracking-wider">Total</p>
<p className="text-sm font-bold">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-sm font-bold">{formatCurrency(total)}</p>
</div>
</Button>
</SheetTrigger>

View File

@ -30,6 +30,7 @@ import {
SheetTrigger
} from "@/components/ui/sheet";
import { cn } from '@/lib/utils';
import { formatCurrency, formatNumber } from '@/lib/formatters';
import * as orderRoutes from '@/routes/order';
import type { Product } from '@/types';
import type { Order } from '@/types/order';
@ -248,7 +249,7 @@ return;
<PriceTypeLabel type={item.price_type} />
</Badge>
<p className="text-xs text-primary font-medium">
Rp {item.total.toLocaleString('id-ID')}
{formatCurrency(item.total)}
</p>
</div>
</div>
@ -270,7 +271,7 @@ return;
</div>
<span>×</span>
<span className="font-mono">
Rp {item.price.toLocaleString('id-ID')}
{formatCurrency(item.price)}
</span>
</div>
</div>
@ -331,7 +332,7 @@ return;
<div className="space-y-1.5 pt-2 border-t border-dashed text-xs">
<div className="flex justify-between">
<span className="text-muted-foreground">Subtotal</span>
<span className="font-medium">Rp {subtotal.toLocaleString('id-ID')}</span>
<span className="font-medium">{formatCurrency(subtotal)}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground">Potongan / Diskon</span>
@ -356,7 +357,7 @@ return;
<div className="flex justify-between text-lg font-bold border-t pt-2">
<span className="">Total</span>
<span className="font-medium">Rp {total.toLocaleString('id-ID')}</span>
<span className="font-medium">{formatCurrency(total)}</span>
</div>
<div className="space-y-2">
@ -379,7 +380,7 @@ return;
{change >= 0 && data.payment > 0 && (
<div className="flex justify-between items-center p-2 rounded bg-green-500/10 border border-green-500/20 text-green-600 dark:text-green-400">
<span className="text-xs font-bold uppercase">Kembalian</span>
<span className="text-lg font-bold">Rp {change.toLocaleString('id-ID')}</span>
<span className="text-lg font-bold">{formatCurrency(change)}</span>
</div>
)}
</div>
@ -513,7 +514,7 @@ return;
<div className="flex flex-col flex-1 min-w-0">
<span className="text-[10px] uppercase font-bold text-muted-foreground">Harga</span>
<span className="text-sm font-bold text-primary tabular-nums truncate block">
Rp {currentPrice.toLocaleString('id-ID')}
{formatCurrency(currentPrice)}
</span>
</div>
<div className="flex items-center gap-1 bg-muted/50 rounded-xl sm:rounded-full p-1 sm:p-0.5 shrink-0 w-full sm:w-auto justify-between sm:justify-end border border-transparent hover:border-primary/20 transition-colors">
@ -591,7 +592,7 @@ return;
</div>
<div className="text-right">
<p className="text-[10px] opacity-80 uppercase font-bold tracking-wider">Total</p>
<p className="text-sm font-bold">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-sm font-bold">{formatCurrency(total)}</p>
</div>
</Button>
</SheetTrigger>

View File

@ -4,6 +4,7 @@ import { useCallback } from 'react';
import { toast } from 'sonner';
import { usePrinter } from '@/hooks/use-printer';
import { EscPosEncoder } from '@/lib/esc-pos-encoder';
import { formatCurrency } from '@/lib/formatters';
import type { Order } from '@/types/order';
export function useOrderPrint() {
@ -62,7 +63,7 @@ export function useOrderPrint() {
if (discountAmount > 0) {
const discountLabel = 'Diskon:';
const discountVal = `- Rp ${discountAmount.toLocaleString('id-ID')}`;
const discountVal = `- ${formatCurrency(discountAmount)}`;
const dSpaces = width - discountLabel.length - discountVal.length;
result.line(discountLabel + ' '.repeat(Math.max(0, dSpaces)) + discountVal);
}

View File

@ -34,6 +34,7 @@ import {
SheetClose,
} from "@/components/ui/sheet";
import { cn } from '@/lib/utils';
import { formatCurrency, formatNumber } from '@/lib/formatters';
import purchaseRoutes from '@/routes/purchase';
import type { Product, ProductPrice } from '@/types';
@ -213,7 +214,7 @@ return;
<div className="flex-1 min-w-0">
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product.name}</h4>
<p className="text-xs text-primary font-medium mt-0.5">
Rp {(item.quantity * item.unit_price).toLocaleString('id-ID')}
{formatCurrency(item.quantity * item.unit_price)}
</p>
</div>
<Button
@ -233,7 +234,7 @@ return;
</div>
<span>×</span>
<span className="font-mono">
Rp {item.unit_price.toLocaleString('id-ID')}
{formatCurrency(item.unit_price)}
</span>
</div>
</div>
@ -297,7 +298,7 @@ return;
<div className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground">Total Belanja</p>
<p className="text-xl font-bold text-primary">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-xl font-bold text-primary">{formatCurrency(total)}</p>
</div>
<div className="text-right text-xs text-muted-foreground">
<p>{cartItems.length} produk</p>
@ -521,7 +522,7 @@ return;
<div className="flex-1 min-w-0">
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product.name}</h4>
<p className="text-xs text-primary font-medium mt-0.5">
Rp {(item.quantity * item.unit_price).toLocaleString('id-ID')}
{formatCurrency(item.quantity * item.unit_price)}
</p>
</div>
<Button
@ -540,7 +541,7 @@ return;
</div>
<span>×</span>
<span className="font-mono">
Rp {item.unit_price.toLocaleString('id-ID')}
{formatCurrency(item.unit_price)}
</span>
</div>
</div>
@ -602,7 +603,7 @@ return;
<div className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground">Total Belanja</p>
<p className="text-xl font-bold text-primary">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-xl font-bold text-primary">{formatCurrency(total)}</p>
</div>
<div className="text-right text-xs text-muted-foreground">
<p>{cartItems.length} produk</p>
@ -644,7 +645,7 @@ return;
</div>
<div className="text-right">
<p className="text-[10px] opacity-80 uppercase font-bold tracking-wider">Total</p>
<p className="text-sm font-bold">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-sm font-bold">{formatCurrency(total)}</p>
</div>
</Button>
</SheetTrigger>
@ -699,7 +700,7 @@ return;
<div className="flex-1 min-w-0">
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product.name}</h4>
<p className="text-xs text-primary font-medium mt-0.5">
Rp {(item.quantity * item.unit_price).toLocaleString('id-ID')}
{formatCurrency(item.quantity * item.unit_price)}
</p>
</div>
<Button
@ -718,7 +719,7 @@ return;
</div>
<span>×</span>
<span className="font-mono">
Rp {item.unit_price.toLocaleString('id-ID')}
{formatCurrency(item.unit_price)}
</span>
</div>
</div>
@ -778,7 +779,7 @@ return;
<div className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground">Total Belanja</p>
<p className="text-xl font-bold text-primary">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-xl font-bold text-primary">{formatCurrency(total)}</p>
</div>
<div className="text-right text-xs text-muted-foreground">
<p>{cartItems.length} produk</p>

View File

@ -34,6 +34,7 @@ import {
SheetClose,
} from "@/components/ui/sheet";
import { cn } from '@/lib/utils';
import { formatCurrency, formatNumber } from '@/lib/formatters';
import purchaseRoutes from '@/routes/purchase';
import type { Product, ProductPrice, Purchase } from '@/types';
@ -218,7 +219,7 @@ return;
<div className="flex-1 min-w-0">
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product?.name}</h4>
<p className="text-xs text-primary font-medium mt-0.5">
Rp {(item.quantity * item.unit_price).toLocaleString('id-ID')}
{formatCurrency(item.quantity * item.unit_price)}
</p>
</div>
<Button
@ -238,7 +239,7 @@ return;
</div>
<span>×</span>
<span className="font-mono">
Rp {item.unit_price.toLocaleString('id-ID')}
{formatCurrency(item.unit_price)}
</span>
</div>
</div>
@ -302,7 +303,7 @@ return;
<div className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground">Total Belanja</p>
<p className="text-xl font-bold text-primary">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-xl font-bold text-primary">{formatCurrency(total)}</p>
</div>
<div className="text-right text-xs text-muted-foreground">
<p>{data.items.length} produk</p>
@ -526,7 +527,7 @@ return;
<div className="flex-1 min-w-0">
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product?.name}</h4>
<p className="text-xs text-primary font-medium mt-0.5">
Rp {(item.quantity * item.unit_price).toLocaleString('id-ID')}
{formatCurrency(item.quantity * item.unit_price)}
</p>
</div>
<Button
@ -545,7 +546,7 @@ return;
</div>
<span>×</span>
<span className="font-mono">
Rp {item.unit_price.toLocaleString('id-ID')}
{formatCurrency(item.unit_price)}
</span>
</div>
</div>
@ -608,7 +609,7 @@ return;
<div className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground">Total Belanja</p>
<p className="text-xl font-bold text-primary">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-xl font-bold text-primary">{formatCurrency(total)}</p>
</div>
<div className="text-right text-xs text-muted-foreground">
<p>{data.items.length} produk</p>
@ -650,7 +651,7 @@ return;
</div>
<div className="text-right">
<p className="text-[10px] opacity-80 uppercase font-bold tracking-wider">Total</p>
<p className="text-sm font-bold">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-sm font-bold">{formatCurrency(total)}</p>
</div>
</Button>
</SheetTrigger>
@ -705,7 +706,7 @@ return;
<div className="flex-1 min-w-0">
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product?.name}</h4>
<p className="text-xs text-primary font-medium mt-0.5">
Rp {(item.quantity * item.unit_price).toLocaleString('id-ID')}
{formatCurrency(item.quantity * item.unit_price)}
</p>
</div>
<Button
@ -724,7 +725,7 @@ return;
</div>
<span>×</span>
<span className="font-mono">
Rp {item.unit_price.toLocaleString('id-ID')}
{formatCurrency(item.unit_price)}
</span>
</div>
</div>
@ -784,7 +785,7 @@ return;
<div className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground">Total Belanja</p>
<p className="text-xl font-bold text-primary">Rp {total.toLocaleString('id-ID')}</p>
<p className="text-xl font-bold text-primary">{formatCurrency(total)}</p>
</div>
<div className="text-right text-xs text-muted-foreground">
<p>{data.items.length} produk</p>

View File

@ -8,6 +8,7 @@ import type { AnalysisPageProps } from '@/types';
import { Head, router, usePage } from '@inertiajs/react';
import { CalendarIcon, Filter, FilterX } from 'lucide-react';
import { useState } from 'react';
import { formatDate } from '@/lib/formatters';
import { AovTrendChart } from '../components/charts/aov-trend-chart';
import { CustomBarChart } from '../components/charts/bar-chart';
import { CustomPieChart } from '../components/charts/pie-chart';
@ -26,14 +27,6 @@ export default function Analisa() {
const [isStartOpen, setIsStartOpen] = useState(false);
const [isEndOpen, setIsEndOpen] = useState(false);
const formatDate = (dateStr: string) => {
if (!dateStr) return null;
return new Intl.DateTimeFormat("id-ID", {
day: "numeric",
month: "long",
year: "numeric",
}).format(new Date(dateStr));
};
const formatToYmd = (date: Date) => {
return date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, '0') + "-" + String(date.getDate()).padStart(2, '0');