import moment from 'moment'; import 'moment/locale/id'; import { twMerge } from 'tailwind-merge'; import clsx, { ClassValue } from 'clsx'; // set locale globally moment.locale('id'); export const sleep = (ms: number = 1000) => new Promise((resolve) => setTimeout(resolve, ms)); export const formatDate = (date: moment.MomentInput, format?: string) => { if (!date) return '-'; return moment(date).format(format); }; export const cn = (...inputs: ClassValue[]) => { return twMerge(clsx(inputs)); }; export const formatNumber = ( value: number | bigint | Intl.StringNumericLiteral, locale = 'id-ID', minimumFractionDigits = 0, maximumFractionDigits = 2 ) => { return new Intl.NumberFormat(locale, { minimumFractionDigits, maximumFractionDigits, }).format(value); }; export function formatVechicleNumber(value: string): string { let result = ''; for (let i = 0; i < (value?.length ?? 0); i++) { const curr = value[i]; const prev = value[i - 1]; // Cek apakah terjadi perpindahan dari huruf ke angka atau angka ke huruf if (i > 0) { const isCurrDigit = /\d/.test(curr); const isPrevDigit = /\d/.test(prev); if (isCurrDigit !== isPrevDigit) { result += ' '; } } result += curr; } return result.trim().replace(/\s+/g, ' '); } export const formatCurrency = ( value: number | bigint | Intl.StringNumericLiteral, currency = 'IDR', locale = 'id-ID', minimumFractionDigits = 0, maximumFractionDigits = 2 ) => { return new Intl.NumberFormat(locale, { style: 'currency', currency, minimumFractionDigits, maximumFractionDigits, }).format(value); }; /** * Retrieves a nested value from an object using a dot-delimited key path. * Supports array indexes (e.g., "users.0.name") and returns a default value * if the path does not exist. * * @param obj - The source object to search. * @param path - Dot-delimited key string (e.g., "user.address.city"). * @param defaultValue - Optional value to return if the key path is not found. * @returns The value found at the specified path, or the default value. */ export function getByPath( obj: T, path: string, defaultValue?: D ): D { if (obj == null) return defaultValue as D; if (!path) return obj as D; const segments = path.split('.').filter(Boolean); let cur: { [key: string]: unknown } = obj; for (const seg of segments) { if (cur == null) return defaultValue as D; const key: string | number = Array.isArray(cur) && /^\d+$/.test(seg) ? Number(seg) : seg; if (Object(cur) !== cur || !(key in cur)) { return defaultValue as D; } cur = cur[key] as { [key: string]: unknown }; } return cur as D; } export const convertRowSelectionArrToObj = ( rowSelectionArr: string[] | number[] ) => { const result: Record = {}; rowSelectionArr.forEach((item) => { result[item] = true; }); return result; }; export const convertRowSelectionObjToArr = ( rowSelection: string[] | number[] ) => { const result = Object.keys(rowSelection).map(Number); return result; }; export const isPathActive = (pathname: string, link?: string) => { if (!link) return false; const splittedPathname = pathname.split('/'); const splittedLink = link.split('/'); const isActiveLinkValid = splittedLink.every((linkChunk, idx) => { return linkChunk === splittedPathname[idx]; }); return pathname.startsWith(link) && isActiveLinkValid; };