commit 8e3ea9e8d1d7e3b06bc6e53e0b97f62222276171 Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Thu Nov 20 16:14:03 2025 +0700 Feat: UI control letter history commit f11f97f936447bdb225918abb43313f8db540d67 Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Thu Nov 20 15:18:25 2025 +0700 Squashed commit of the following: commitdab6adc4a9Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Tue Nov 18 11:19:48 2025 +0700 Fix: add role authorization in Resume commitc28fc8f7aaMerge:7ed1cc8bcfb4c1Author: Muhammad Hasyim Chaidir Ali <68959522+Hasyim-Kai@users.noreply.github.com> Date: Tue Nov 18 09:02:16 2025 +0700 Merge branch 'dev' into feat/resume-81 commit7ed1cc83bfAuthor: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Mon Nov 17 15:04:01 2025 +0700 Feat: add doc preview in Resume List commitbcfb4c1456Merge:1cbde57975c87dAuthor: Munawwirul Jamal <57973347+munaja@users.noreply.github.com> Date: Mon Nov 17 11:15:14 2025 +0700 Merge pull request #147 from dikstub-rssa/feat/surat-kontrol-135 Feat: Integration Rehab Medik - Surat Kontrol commit15ab43c1b1Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Mon Nov 17 10:38:21 2025 +0700 Feat: add verification capthca and form adjustment commit53bd8e7f6eAuthor: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Fri Nov 7 08:55:23 2025 +0700 Fix: refactor rehab medik - Resume UI commitfc308809b8Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Wed Oct 29 14:57:19 2025 +0700 Feat: add UI Rehab Medik > Proses > Resume commit9b383a5437Merge:a4dc7d7831749aAuthor: Muhammad Hasyim Chaidir Ali <68959522+Hasyim-Kai@users.noreply.github.com> Date: Wed Oct 29 13:32:47 2025 +0700 Merge pull request #139 from dikstub-rssa/dev Update branch feat/resume-81 commit 2b7bea70d66e8472220a2a2406889fc489cc1ebd Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Tue Nov 18 11:20:51 2025 +0700 Fix: Typo in Control Letter commit 808e91527cf95de2a47387bb792a3af2e16d907b Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com> Date: Tue Nov 18 10:59:50 2025 +0700 Fix: add role authorization in Control Letter
163 lines
4.5 KiB
TypeScript
163 lines
4.5 KiB
TypeScript
import type { ClassValue } from 'clsx'
|
|
import { clsx } from 'clsx'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import { toast } from '~/components/pub/ui/toast'
|
|
|
|
export interface SelectOptionType<_T = string> {
|
|
value: string
|
|
label: string
|
|
code?: 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
|
|
}
|
|
|
|
/**
|
|
* 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`
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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',
|
|
})
|
|
} |