181 lines
5.0 KiB
TypeScript
181 lines
5.0 KiB
TypeScript
import type { ClassValue } from 'clsx'
|
|
import { clsx } from 'clsx'
|
|
import { format } from 'date-fns'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import { toast } from '~/components/pub/ui/toast'
|
|
|
|
export interface SelectOptionType<_T = string> {
|
|
value: string
|
|
label: string
|
|
code?: string
|
|
}
|
|
export interface CheckItem {
|
|
id: string
|
|
label: string
|
|
}
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs))
|
|
}
|
|
|
|
export function mapToComboboxOptList(items: Record<string, string>): SelectOptionType<string>[] {
|
|
if (!items) {
|
|
return []
|
|
}
|
|
const result: SelectOptionType<string>[] = []
|
|
Object.keys(items).forEach((item) => {
|
|
result.push({
|
|
label: items[item] as string,
|
|
value: item,
|
|
code: item,
|
|
})
|
|
})
|
|
return result
|
|
}
|
|
|
|
export function mapToCheckItems<T extends Record<string, string>, K extends keyof T & string>(
|
|
items: T,
|
|
): { id: K; label: T[K] }[] {
|
|
return Object.entries(items).map(([key, value]) => ({
|
|
id: key as K,
|
|
label: value as T[K],
|
|
}))
|
|
}
|
|
|
|
/**
|
|
* Mengkonversi string menjadi title case (huruf pertama setiap kata kapital)
|
|
* @param str - String yang akan dikonversi
|
|
* @returns String dalam format title case
|
|
*/
|
|
export function toTitleCase(str: string): string {
|
|
return str.toLowerCase().replace(/\b\w/g, (char) => char.toUpperCase())
|
|
}
|
|
|
|
/**
|
|
* Menghitung umur berdasarkan tanggal lahir
|
|
* @param birthDate - Tanggal lahir dalam format Date atau string
|
|
* @returns String umur dalam format "X tahun Y bulan" atau "X bulan" untuk bayi
|
|
*/
|
|
export function calculateAge(birthDate: Date | string | null | undefined): string {
|
|
if (!birthDate) {
|
|
return '-'
|
|
}
|
|
|
|
let birth: Date
|
|
|
|
// Konversi ke Date object
|
|
if (typeof birthDate === 'string') {
|
|
birth = new Date(birthDate)
|
|
} else {
|
|
birth = birthDate
|
|
}
|
|
|
|
// Validasi tanggal
|
|
if (isNaN(birth.getTime())) {
|
|
return '-'
|
|
}
|
|
|
|
const today = new Date()
|
|
const birthYear = birth.getFullYear()
|
|
const birthMonth = birth.getMonth()
|
|
const birthDay = birth.getDate()
|
|
|
|
const currentYear = today.getFullYear()
|
|
const currentMonth = today.getMonth()
|
|
const currentDay = today.getDate()
|
|
|
|
// Hitung tahun
|
|
let years = currentYear - birthYear
|
|
let months = currentMonth - birthMonth
|
|
|
|
// Adjust jika bulan atau hari belum lewat
|
|
if (months < 0 || (months === 0 && currentDay < birthDay)) {
|
|
years--
|
|
months += 12
|
|
}
|
|
|
|
if (currentDay < birthDay) {
|
|
months--
|
|
}
|
|
|
|
// Pastikan months tidak negatif
|
|
if (months < 0) {
|
|
months += 12
|
|
}
|
|
|
|
// Format output
|
|
if (years === 0) {
|
|
if (months === 0) {
|
|
// Hitung hari untuk bayi baru lahir
|
|
const diffTime = today.getTime() - birth.getTime()
|
|
const days = Math.floor(diffTime / (1000 * 60 * 60 * 24))
|
|
return days <= 30 ? `${days} hari` : '1 bulan'
|
|
}
|
|
return `${months} bulan`
|
|
} else if (months === 0) {
|
|
return `${years} tahun`
|
|
} else {
|
|
return `${years} tahun ${months} bulan`
|
|
}
|
|
}
|
|
|
|
export function formatDateToDatetimeLocal(inputDate: Date) {
|
|
const formattedString = format(inputDate, "yyyy-MM-dd'T'HH:mm")
|
|
return formattedString
|
|
}
|
|
|
|
|
|
/**
|
|
* Converts a plain JavaScript object (including File objects) into a FormData instance.
|
|
* @param {object} data - The object to convert (e.g., form values).
|
|
* @returns {FormData} The new FormData object suitable for API submission.
|
|
*/
|
|
export function toFormData(data: Record<string, any>): FormData {
|
|
const formData = new FormData();
|
|
|
|
for (const key in data) {
|
|
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
const value = data[key];
|
|
|
|
// Handle File objects, Blobs, or standard JSON values
|
|
if (value !== null && value !== undefined) {
|
|
// Check if the value is a File/Blob instance
|
|
if (value instanceof File || value instanceof Blob) {
|
|
// Append the file directly
|
|
formData.append(key, value);
|
|
} else if (typeof value === 'object') {
|
|
// Handle nested objects/arrays by stringifying them (optional, depends on API)
|
|
// Note: Most APIs expect nested data to be handled separately or passed as JSON string
|
|
// For simplicity, we stringify non-File objects.
|
|
formData.append(key, JSON.stringify(value));
|
|
} else {
|
|
// Append standard string, number, or boolean values
|
|
formData.append(key, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return formData;
|
|
}
|
|
|
|
export function printFormData(formData: FormData) {
|
|
console.log("--- FormData Contents ---");
|
|
// Use the entries() iterator to loop through key/value pairs
|
|
for (const [key, value] of formData.entries()) {
|
|
if (value instanceof File) {
|
|
console.log(`Key: ${key}, Value: [File: ${value.name}, Type: ${value.type}, Size: ${value.size} bytes]`);
|
|
} else {
|
|
console.log(`Key: ${key}, Value: "${value}"`);
|
|
}
|
|
}
|
|
console.log("-------------------------");
|
|
}
|
|
|
|
export function unauthorizedToast() {
|
|
toast({
|
|
title: 'Unauthorized',
|
|
description: 'You are not authorized to perform this action.',
|
|
variant: 'destructive',
|
|
})
|
|
} |