refactor: replace manual currency formatting with global formatCurrency utility across order and financial modules
This commit is contained in:
parent
1a0c4a4601
commit
53cdb219d5
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import React, { useMemo } from "react"
|
import React, { useMemo } from "react"
|
||||||
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts"
|
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts"
|
||||||
|
import { formatCurrency } from "@/lib/formatters"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -62,7 +63,7 @@ export const CustomBarChart = React.memo(function CustomBarChart({
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(val);
|
return formatCurrency(val);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Pencil, Trash2, ImagePlus } from 'lucide-react';
|
|||||||
import { DataTableColumnHeader } from '@/components/data-table-column-header';
|
import { DataTableColumnHeader } from '@/components/data-table-column-header';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
|
import { formatDate, formatCurrency } from '@/lib/formatters';
|
||||||
import type { Expense } from '@/types';
|
import type { Expense } from '@/types';
|
||||||
|
|
||||||
interface ColumnProps {
|
interface ColumnProps {
|
||||||
@ -11,13 +12,6 @@ interface ColumnProps {
|
|||||||
onPreviewImage: (url: string) => void;
|
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>[] => [
|
export const getColumns = ({ onEdit, onDelete, onPreviewImage }: ColumnProps): ColumnDef<Expense>[] => [
|
||||||
{
|
{
|
||||||
@ -66,11 +60,7 @@ export const getColumns = ({ onEdit, onDelete, onPreviewImage }: ColumnProps): C
|
|||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader column={column} title="Tanggal" />
|
<DataTableColumnHeader column={column} title="Tanggal" />
|
||||||
),
|
),
|
||||||
cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString('id-ID', {
|
cell: ({ row }) => formatDate(row.original.created_at),
|
||||||
day: '2-digit',
|
|
||||||
month: 'long',
|
|
||||||
year: 'numeric'
|
|
||||||
}),
|
|
||||||
meta: { title: "Tanggal" },
|
meta: { title: "Tanggal" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import {
|
|||||||
SheetTrigger
|
SheetTrigger
|
||||||
} from "@/components/ui/sheet";
|
} from "@/components/ui/sheet";
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
||||||
import * as orderRoutes from '@/routes/order';
|
import * as orderRoutes from '@/routes/order';
|
||||||
import type { Product } from '@/types';
|
import type { Product } from '@/types';
|
||||||
import type { OrderItem } from '@/types/order';
|
import type { OrderItem } from '@/types/order';
|
||||||
@ -238,7 +239,7 @@ return;
|
|||||||
<PriceTypeLabel type={item.price_type} />
|
<PriceTypeLabel type={item.price_type} />
|
||||||
</Badge>
|
</Badge>
|
||||||
<p className="text-xs text-primary font-medium">
|
<p className="text-xs text-primary font-medium">
|
||||||
Rp {item.total.toLocaleString('id-ID')}
|
{formatCurrency(item.total)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -260,7 +261,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
Rp {item.price.toLocaleString('id-ID')}
|
{formatCurrency(item.price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -327,7 +328,7 @@ return;
|
|||||||
<div className="space-y-1.5 pt-2 border-t border-dashed">
|
<div className="space-y-1.5 pt-2 border-t border-dashed">
|
||||||
<div className="flex justify-between text-xs">
|
<div className="flex justify-between text-xs">
|
||||||
<span className="text-muted-foreground">Subtotal</span>
|
<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>
|
||||||
<div className="flex justify-between items-center text-xs">
|
<div className="flex justify-between items-center text-xs">
|
||||||
<span className="text-muted-foreground">Potongan / Diskon</span>
|
<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">
|
<div className="flex justify-between text-lg font-bold border-t pt-2">
|
||||||
<span className="">Total</span>
|
<span className="">Total</span>
|
||||||
<span className="font-medium">Rp {total.toLocaleString('id-ID')}</span>
|
<span className="font-medium">{formatCurrency(total)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -375,7 +376,7 @@ return;
|
|||||||
{change >= 0 && (Number(data.payment) || 0) > 0 && (
|
{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">
|
<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-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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -509,7 +510,7 @@ return;
|
|||||||
<div className="flex flex-col flex-1 min-w-0">
|
<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-[10px] uppercase font-bold text-muted-foreground">Harga</span>
|
||||||
<span className="text-sm font-bold text-primary tabular-nums truncate block">
|
<span className="text-sm font-bold text-primary tabular-nums truncate block">
|
||||||
Rp {currentPrice.toLocaleString('id-ID')}
|
{formatCurrency(currentPrice)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<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>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-[10px] opacity-80 uppercase font-bold tracking-wider">Total</p>
|
<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>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import {
|
|||||||
SheetTrigger
|
SheetTrigger
|
||||||
} from "@/components/ui/sheet";
|
} from "@/components/ui/sheet";
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
||||||
import * as orderRoutes from '@/routes/order';
|
import * as orderRoutes from '@/routes/order';
|
||||||
import type { Product } from '@/types';
|
import type { Product } from '@/types';
|
||||||
import type { Order } from '@/types/order';
|
import type { Order } from '@/types/order';
|
||||||
@ -248,7 +249,7 @@ return;
|
|||||||
<PriceTypeLabel type={item.price_type} />
|
<PriceTypeLabel type={item.price_type} />
|
||||||
</Badge>
|
</Badge>
|
||||||
<p className="text-xs text-primary font-medium">
|
<p className="text-xs text-primary font-medium">
|
||||||
Rp {item.total.toLocaleString('id-ID')}
|
{formatCurrency(item.total)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -270,7 +271,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
Rp {item.price.toLocaleString('id-ID')}
|
{formatCurrency(item.price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -331,7 +332,7 @@ return;
|
|||||||
<div className="space-y-1.5 pt-2 border-t border-dashed text-xs">
|
<div className="space-y-1.5 pt-2 border-t border-dashed text-xs">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-muted-foreground">Subtotal</span>
|
<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>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-muted-foreground">Potongan / Diskon</span>
|
<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">
|
<div className="flex justify-between text-lg font-bold border-t pt-2">
|
||||||
<span className="">Total</span>
|
<span className="">Total</span>
|
||||||
<span className="font-medium">Rp {total.toLocaleString('id-ID')}</span>
|
<span className="font-medium">{formatCurrency(total)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@ -379,7 +380,7 @@ return;
|
|||||||
{change >= 0 && data.payment > 0 && (
|
{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">
|
<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-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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -513,7 +514,7 @@ return;
|
|||||||
<div className="flex flex-col flex-1 min-w-0">
|
<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-[10px] uppercase font-bold text-muted-foreground">Harga</span>
|
||||||
<span className="text-sm font-bold text-primary tabular-nums truncate block">
|
<span className="text-sm font-bold text-primary tabular-nums truncate block">
|
||||||
Rp {currentPrice.toLocaleString('id-ID')}
|
{formatCurrency(currentPrice)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<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>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-[10px] opacity-80 uppercase font-bold tracking-wider">Total</p>
|
<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>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useCallback } from 'react';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { usePrinter } from '@/hooks/use-printer';
|
import { usePrinter } from '@/hooks/use-printer';
|
||||||
import { EscPosEncoder } from '@/lib/esc-pos-encoder';
|
import { EscPosEncoder } from '@/lib/esc-pos-encoder';
|
||||||
|
import { formatCurrency } from '@/lib/formatters';
|
||||||
import type { Order } from '@/types/order';
|
import type { Order } from '@/types/order';
|
||||||
|
|
||||||
export function useOrderPrint() {
|
export function useOrderPrint() {
|
||||||
@ -62,7 +63,7 @@ export function useOrderPrint() {
|
|||||||
|
|
||||||
if (discountAmount > 0) {
|
if (discountAmount > 0) {
|
||||||
const discountLabel = 'Diskon:';
|
const discountLabel = 'Diskon:';
|
||||||
const discountVal = `- Rp ${discountAmount.toLocaleString('id-ID')}`;
|
const discountVal = `- ${formatCurrency(discountAmount)}`;
|
||||||
const dSpaces = width - discountLabel.length - discountVal.length;
|
const dSpaces = width - discountLabel.length - discountVal.length;
|
||||||
result.line(discountLabel + ' '.repeat(Math.max(0, dSpaces)) + discountVal);
|
result.line(discountLabel + ' '.repeat(Math.max(0, dSpaces)) + discountVal);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
SheetClose,
|
SheetClose,
|
||||||
} from "@/components/ui/sheet";
|
} from "@/components/ui/sheet";
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
||||||
import purchaseRoutes from '@/routes/purchase';
|
import purchaseRoutes from '@/routes/purchase';
|
||||||
import type { Product, ProductPrice } from '@/types';
|
import type { Product, ProductPrice } from '@/types';
|
||||||
|
|
||||||
@ -213,7 +214,7 @@ return;
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product.name}</h4>
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -233,7 +234,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
Rp {item.unit_price.toLocaleString('id-ID')}
|
{formatCurrency(item.unit_price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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 className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Belanja</p>
|
<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>
|
||||||
<div className="text-right text-xs text-muted-foreground">
|
<div className="text-right text-xs text-muted-foreground">
|
||||||
<p>{cartItems.length} produk</p>
|
<p>{cartItems.length} produk</p>
|
||||||
@ -521,7 +522,7 @@ return;
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product.name}</h4>
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -540,7 +541,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
Rp {item.unit_price.toLocaleString('id-ID')}
|
{formatCurrency(item.unit_price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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 className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Belanja</p>
|
<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>
|
||||||
<div className="text-right text-xs text-muted-foreground">
|
<div className="text-right text-xs text-muted-foreground">
|
||||||
<p>{cartItems.length} produk</p>
|
<p>{cartItems.length} produk</p>
|
||||||
@ -644,7 +645,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-[10px] opacity-80 uppercase font-bold tracking-wider">Total</p>
|
<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>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
@ -699,7 +700,7 @@ return;
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product.name}</h4>
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -718,7 +719,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
Rp {item.unit_price.toLocaleString('id-ID')}
|
{formatCurrency(item.unit_price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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 className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Belanja</p>
|
<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>
|
||||||
<div className="text-right text-xs text-muted-foreground">
|
<div className="text-right text-xs text-muted-foreground">
|
||||||
<p>{cartItems.length} produk</p>
|
<p>{cartItems.length} produk</p>
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
SheetClose,
|
SheetClose,
|
||||||
} from "@/components/ui/sheet";
|
} from "@/components/ui/sheet";
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
||||||
import purchaseRoutes from '@/routes/purchase';
|
import purchaseRoutes from '@/routes/purchase';
|
||||||
import type { Product, ProductPrice, Purchase } from '@/types';
|
import type { Product, ProductPrice, Purchase } from '@/types';
|
||||||
|
|
||||||
@ -218,7 +219,7 @@ return;
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product?.name}</h4>
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -238,7 +239,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
Rp {item.unit_price.toLocaleString('id-ID')}
|
{formatCurrency(item.unit_price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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 className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Belanja</p>
|
<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>
|
||||||
<div className="text-right text-xs text-muted-foreground">
|
<div className="text-right text-xs text-muted-foreground">
|
||||||
<p>{data.items.length} produk</p>
|
<p>{data.items.length} produk</p>
|
||||||
@ -526,7 +527,7 @@ return;
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product?.name}</h4>
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -545,7 +546,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
Rp {item.unit_price.toLocaleString('id-ID')}
|
{formatCurrency(item.unit_price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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 className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Belanja</p>
|
<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>
|
||||||
<div className="text-right text-xs text-muted-foreground">
|
<div className="text-right text-xs text-muted-foreground">
|
||||||
<p>{data.items.length} produk</p>
|
<p>{data.items.length} produk</p>
|
||||||
@ -650,7 +651,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-[10px] opacity-80 uppercase font-bold tracking-wider">Total</p>
|
<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>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
@ -705,7 +706,7 @@ return;
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-sm font-semibold leading-tight line-clamp-1">{item.product?.name}</h4>
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -724,7 +725,7 @@ return;
|
|||||||
</div>
|
</div>
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
Rp {item.unit_price.toLocaleString('id-ID')}
|
{formatCurrency(item.unit_price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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 className="rounded-xl bg-primary/10 border border-primary/20 p-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Belanja</p>
|
<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>
|
||||||
<div className="text-right text-xs text-muted-foreground">
|
<div className="text-right text-xs text-muted-foreground">
|
||||||
<p>{data.items.length} produk</p>
|
<p>{data.items.length} produk</p>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type { AnalysisPageProps } from '@/types';
|
|||||||
import { Head, router, usePage } from '@inertiajs/react';
|
import { Head, router, usePage } from '@inertiajs/react';
|
||||||
import { CalendarIcon, Filter, FilterX } from 'lucide-react';
|
import { CalendarIcon, Filter, FilterX } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { formatDate } from '@/lib/formatters';
|
||||||
import { AovTrendChart } from '../components/charts/aov-trend-chart';
|
import { AovTrendChart } from '../components/charts/aov-trend-chart';
|
||||||
import { CustomBarChart } from '../components/charts/bar-chart';
|
import { CustomBarChart } from '../components/charts/bar-chart';
|
||||||
import { CustomPieChart } from '../components/charts/pie-chart';
|
import { CustomPieChart } from '../components/charts/pie-chart';
|
||||||
@ -26,14 +27,6 @@ export default function Analisa() {
|
|||||||
const [isStartOpen, setIsStartOpen] = useState(false);
|
const [isStartOpen, setIsStartOpen] = useState(false);
|
||||||
const [isEndOpen, setIsEndOpen] = 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) => {
|
const formatToYmd = (date: Date) => {
|
||||||
return date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, '0') + "-" + String(date.getDate()).padStart(2, '0');
|
return date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, '0') + "-" + String(date.getDate()).padStart(2, '0');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user