Merge branch 'dev' of github.com:dikstub-rssa/simrs-fe into feat/education-assessment-79

This commit is contained in:
Khafid Prayoga
2025-12-08 10:18:40 +07:00
835 changed files with 53072 additions and 3507 deletions
+138 -3
View File
@@ -8,7 +8,7 @@ export const dataStatusCodes: Record<string, string> = {
review: 'Review',
process: 'Proses',
done: 'Selesai',
canceled: 'Dibatalkan',
cancel: 'Dibatalkan',
rejected: 'Ditolak',
skiped: 'Dilewati',
}
@@ -66,9 +66,26 @@ export const timeUnitCodes: Record<string, string> = {
year: 'Tahun',
}
export const bigTimeUnitCodes: Record<string, string> = {
day: 'Hari',
week: 'Minggu',
month: 'Bulan',
year: 'Tahun',
}
export const dischargeMethodCodes: Record<string, string> = {
home: 'Home',
'home-request': 'Home Request',
home: "Pulang",
"home-request": "Pulang Atas Permintaan Sendiri",
"consul-back": "Konsultasi Balik / Lanjutan",
"consul-poly": "Konsultasi Poliklinik Lain",
"consul-executive": "Konsultasi Antar Dokter Eksekutif",
"consul-ch-day": "Konsultasi Hari Lain",
emergency: "Rujuk IGD",
"emergency-covid": "Rujuk IGD Covid",
inpatient: "Rujuk Rawat Inap",
external: "Rujuk Faskes Lain",
death: "Meninggal",
"death-on-arrival": "Meninggal Saat Tiba"
}
export const genderCodes: Record<string, string> = {
@@ -327,6 +344,8 @@ export const uploadCode: Record<string, string> = {
kk: 'person-family-card',
paspor: 'person-passport',
'mcu-report': 'mcu-item-result',
'vclaim-sep': 'vclaim-sep',
'vclaim-sipp': 'vclaim-sipp',
} as const
export type UploadCodeKey = keyof typeof uploadCode
@@ -366,3 +385,119 @@ export const medicalActionTypeCode: Record<string, string> = {
} as const
export type medicalActionTypeCodeKey = keyof typeof medicalActionTypeCode
export const encounterDocTypeCode: Record<string, string> = {
"person-resident-number": 'person-resident-number',
"person-driving-license": 'person-driving-license',
"person-passport": 'person-passport',
"person-family-card": 'person-family-card',
"mcu-item-result": 'mcu-item-result',
"vclaim-sep": 'vclaim-sep',
"vclaim-sipp": 'vclaim-sipp',
} as const
export type encounterDocTypeCodeKey = keyof typeof encounterDocTypeCode
export const encounterDocOpt: { label: string; value: encounterDocTypeCodeKey }[] = [
{ label: 'KTP', value: 'person-resident-number' },
{ label: 'SIM', value: 'person-driving-license' },
{ label: 'Passport', value: 'person-passport' },
{ label: 'Kartu Keluarga', value: 'person-family-card' },
{ label: 'Hasil MCU', value: 'mcu-item-result' },
{ label: 'Klaim SEP', value: 'vclaim-sep' },
{ label: 'Klaim SIPP', value: 'vclaim-sipp' },
]
export const docTypeCode = {
"encounter-patient": 'encounter-patient',
"encounter-support": 'encounter-support',
"encounter-other": 'encounter-other',
"vclaim-sep": 'vclaim-sep',
"vclaim-sipp": 'vclaim-sipp',
} as const
export const docTypeLabel = {
"encounter-patient": 'Data Pasien',
"encounter-support": 'Data Penunjang',
"encounter-other": 'Lain - Lain',
"vclaim-sep": 'SEP',
"vclaim-sipp": 'SIPP',
} as const
export type docTypeCodeKey = keyof typeof docTypeCode
export const supportingDocOpt = [
{ label: 'Data Pasien', value: 'encounter-patient' },
{ label: 'Data Penunjang', value: 'encounter-support' },
{ label: 'Lain - Lain', value: 'encounter-other' },
]
export type SurgeryType = "kecil" | "sedang" | "besar" | "khusus"
export const SurgeryTypeOptList: { label: string; value: SurgeryType }[] = [
{ label: 'Kecil', value: 'kecil' },
{ label: 'Sedang', value: 'sedang' },
{ label: 'Besar', value: 'besar' },
{ label: 'Khusus', value: 'khusus' },
]
export type BillingCodeType = "general" | "regional" | "local"
export const BillingCodeTypeOptList: { label: string; value: BillingCodeType }[] = [
{ label: 'General', value: 'general' },
{ label: 'Regional', value: 'regional' },
{ label: 'Local', value: 'local' },
]
export type SurgerySystemType = "cito" | "urgent" | "efektif" | "khusus"
export const SurgerySystemTypeOptList: { label: string; value: SurgerySystemType }[] = [
{ label: 'Cito', value: 'cito' },
{ label: 'Urgent', value: 'urgent' },
{ label: 'Efektif', value: 'efektif' },
{ label: 'Khusus', value: 'khusus' },
]
export type DissectionType = "bersih" | "bersih terkontaminasi" | "terkontaminasi kotor" | "kotor"
export const DissectionTypeOptList: { label: string; value: DissectionType }[] = [
{ label: 'Bersih', value: 'bersih' },
{ label: 'Bersih terkontaminasi', value: 'bersih terkontaminasi' },
{ label: 'Terkontaminasi kotor', value: 'terkontaminasi kotor' },
{ label: 'Kotor', value: 'kotor' },
]
export type SurgeryOrderType = "satu" | "ulangan"
export const SurgeryOrderTypeOptList: { label: string; value: SurgeryOrderType }[] = [
{ label: 'Satu', value: 'satu' },
{ label: 'Ulangan', value: 'ulangan' },
]
export type BirthDescriptionType = "lahir hidup" | "lahir mati"
export const BirthDescriptionTypeOptList: { label: string; value: BirthDescriptionType }[] = [
{ label: 'Lahir Hidup', value: 'lahir hidup' },
{ label: 'Lahir Mati', value: 'lahir mati' },
]
export type BirthPlaceDescriptionType = "rssa" | "bidan luar" | "dokter luar" | "dukun bayi" | "puskesmas" | "paramedis luar"
export const BirthPlaceDescriptionTypeOptList: { label: string; value: BirthPlaceDescriptionType }[] = [
{ label: 'RSSA', value: 'rssa' },
{ label: 'Bidan luar', value: 'bidan luar' },
{ label: 'Dokter luar', value: 'dokter luar' },
{ label: 'Dukun bayi', value: 'dukun bayi' },
{ label: 'Puskesmas', value: 'puskesmas' },
{ label: 'Paramedis luar', value: 'paramedis luar' },
]
export type SpecimenType = "pa" | "mikrobiologi" | "laborat" | "tidak perlu"
export const SpecimenTypeOptList: { label: string; value: SpecimenType }[] = [
{ label: 'PA', value: 'pa' },
{ label: 'Mikrobiologi', value: 'mikrobiologi' },
{ label: 'Laborat', value: 'laborat' },
{ label: 'Tidak perlu', value: 'tidak perlu' },
]
export type PrbProgramType = "ashma" | "diabetes mellitus" | "hipertensi" | "penyakit jantung" | "ppok" | "schizopherenia" | "stroke" | "systemic lupus erythematosus"
export const PrbProgramTypeOptList: { label: string; value: PrbProgramType }[] = [
{ label: 'ASHMA', value: 'ashma' },
{ label: 'Diabetes Mellitus', value: 'diabetes mellitus' },
{ label: 'Hipertensi', value: 'hipertensi' },
{ label: 'Penyakit Jantung', value: 'penyakit jantung' },
{ label: 'PPOK (Penyakit Paru Obstruktif Kronik)', value: 'ppok' },
{ label: 'Schizopherenia', value: 'schizopherenia' },
{ label: 'Stroke', value: 'stroke' },
{ label: 'Systemic Lupus Erythematosus', value: 'systemic lupus erythematosus' },
]
+94
View File
@@ -0,0 +1,94 @@
export const serviceTypes: Record<string, string> = {
'1': 'Rawat Inap',
'2': 'Rawat Jalan',
}
export const registerMethods: Record<string, string> = {
'1': 'Rujukan',
'2': 'IGD',
'3': 'Kontrol',
'4': 'Rujukan Internal',
}
export const classLevels: Record<string, string> = {
'1': 'Kelas 1',
'2': 'Kelas 2',
'3': 'Kelas 3',
}
export const classLevelUpgrades: Record<string, string> = {
'1': 'VVIP',
'2': 'VIP',
'3': 'Kelas 1',
'4': 'Kelas 2',
'5': 'Kelas 3',
'6': 'ICCU',
'7': 'ICU',
'8': 'Diatas Kelas 1',
}
export const classPaySources: Record<string, string> = {
'1': 'Pribadi',
'2': 'Pemberi Kerja',
'3': 'Asuransi Kesehatan Tambahan',
}
export const procedureTypes: Record<string, string> = {
'0': 'Prosedur tidak berkelanjutan',
'1': 'Prosedur dan terapi berkelanjutan',
}
export const purposeOfVisits: Record<string, string> = {
'0': 'Normal',
'1': 'Prosedur',
'2': 'Konsul Dokter',
}
export const trafficAccidents: Record<string, string> = {
'0': 'Bukan Kecelakaan lalu lintas [BKLL]',
'1': 'KLL dan Bukan Kecelakaan Kerja [BKK]',
'2': 'KLL dan KK',
'3': 'KK',
}
export const supportCodes: Record<string, string> = {
'1': 'Radioterapi',
'2': 'Kemoterapi',
'3': 'Rehabilitasi Medik',
'4': 'Rehabilitasi Psikososial',
'5': 'Transfusi Darah',
'6': 'Pelayanan Gigi',
'7': 'Laboratorium',
'8': 'USG',
'9': 'Farmasi',
'10': 'Lain-Lain',
'11': 'MRI',
'12': 'HEMODIALISA',
}
export const serviceAssessments: Record<string, string> = {
'1': 'Poli spesialis tidak tersedia pada hari sebelumnya',
'2': 'Jam Poli telah berakhir pada hari sebelumnya',
'3': 'Dokter Spesialis yang dimaksud tidak praktek pada hari sebelumnya',
'4': 'Atas Instruksi RS',
'5': 'Tujuan Kontrol',
}
export const paymentTypes: Record<string, string> = {
jkn: 'JKN (Jaminan Kesehatan Nasional)',
jkmm: 'JKMM (Jaminan Kesehatan Mandiri)',
spm: 'SPM (Sistem Pembayaran Mandiri)',
pks: 'PKS (Pembiayaan Kesehatan Sosial)',
}
export const sepRefTypeCodes: Record<string, string> = {
internal: 'Rujukan Internal',
external: 'Faskes Lain',
}
export const participantGroups: Record<string, string> = {
pbi: 'PBI (Penerima Bantuan Iuran)',
ppu: 'PPU (Pekerja Penerima Upah)',
pbu: 'PBU (Pekerja Bukan Penerima Upah)',
bp: 'BP (Bukan Pekerja)',
}
+80 -36
View File
@@ -1,44 +1,88 @@
const monthsInId = [
'Januari',
'Februari',
'Maret',
'April',
'Mei',
'Juni',
'Juli',
'Agustus',
'September',
'Oktober',
'November',
'Desember',
]
export function getAge(dateString: string, comparedDate?: string): { idFormat: string; extFormat: string } {
const birthDate = new Date(dateString);
const today = new Date();
const birthDate = new Date(dateString)
const today = new Date()
if (comparedDate) {
const comparedDateObj = new Date(comparedDate);
today.setFullYear(comparedDateObj.getFullYear());
today.setMonth(comparedDateObj.getMonth());
today.setDate(comparedDateObj.getDate());
}
if (comparedDate) {
const comparedDateObj = new Date(comparedDate)
today.setFullYear(comparedDateObj.getFullYear())
today.setMonth(comparedDateObj.getMonth())
today.setDate(comparedDateObj.getDate())
}
// Format the date part
const options: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' };
const idFormat = birthDate.toLocaleDateString('id-ID', options);
// Format the date part
const options: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' }
const idFormat = birthDate.toLocaleDateString('id-ID', options)
// Calculate age
let years = today.getFullYear() - birthDate.getFullYear();
let months = today.getMonth() - birthDate.getMonth();
let days = today.getDate() - birthDate.getDate();
// Calculate age
let years = today.getFullYear() - birthDate.getFullYear()
let months = today.getMonth() - birthDate.getMonth()
let days = today.getDate() - birthDate.getDate()
if (months < 0 || (months === 0 && days < 0)) {
years--;
months += 12;
}
if (months < 0 || (months === 0 && days < 0)) {
years--
months += 12
}
if (days < 0) {
const prevMonth = new Date(today.getFullYear(), today.getMonth() - 1, 0);
days += prevMonth.getDate();
months--;
}
if (days < 0) {
const prevMonth = new Date(today.getFullYear(), today.getMonth() - 1, 0)
days += prevMonth.getDate()
months--
}
// Format the age part
let extFormat = '';
if ([years, months, days].filter(Boolean).join(' ')) {
extFormat = `${years} Tahun ${months} Bulan ${days} Hari`;
} else {
extFormat = '0';
}
// Format the age part
let extFormat = ''
if ([years, months, days].filter(Boolean).join(' ')) {
extFormat = `${years} Tahun ${months} Bulan ${days} Hari`
} else {
extFormat = '0'
}
return {
idFormat,
extFormat
};
}
return {
idFormat,
extFormat,
}
}
// Date selection: default to today - today
export function getFormatDateId(date: Date) {
const dd = String(date.getDate()).padStart(2, '0')
const mm = monthsInId[date.getMonth()]
const yyyy = date.getFullYear()
return `${dd} ${mm} ${yyyy}`
}
export function formatDateYyyyMmDd(isoDateString: string): string {
const date = new Date(isoDateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Function to check if date is invalid (like "0001-01-01T00:00:00Z")
export function isValidDate(dateString: string | null | undefined): boolean {
if (!dateString) return false
// Check for invalid date patterns
if (dateString.startsWith('0001-01-01')) return false
try {
const date = new Date(dateString)
return !isNaN(date.getTime())
} catch {
return false
}
}
+154
View File
@@ -0,0 +1,154 @@
/**
* Download data as CSV file.
*
* @param headers - Array of header names. If omitted and data is array of objects, keys will be taken from first object.
* @param data - Array of rows. Each row can be either an object (key -> value) or an array of values.
* @param filename - optional file name to use for downloaded file
* @param delimiter - csv delimiter (default is comma)
* @param addBOM - add UTF-8 BOM to the file to make Excel detect UTF-8 correctly
* Usage examples:
* 1) With headers and array of objects
* downloadCsv(['name', 'age'], [{name: 'Alice', age: 25}, {name: 'Bob', age: 30}], 'people.csv');
* 2) Without headers (automatically uses object keys)
* downloadCsv(null, [{name: 'Alice', age: 25}, {name: 'Bob', age: 30}], 'people.csv');
* 3) With array-of-arrays
* downloadCsv(['col1', 'col2'], [['a', 'b'], ['c', 'd']], 'matrix.csv');
*/
export function downloadCsv(
headers: string[] | null,
headerLabels: string[],
data: Array<Record<string, any> | any[]>,
filename = 'data.csv',
delimiter = ',',
addBOM = true,
) {
if (!Array.isArray(data) || data.length === 0) {
// still create an empty CSV containing only headers
const csvHeader = headers ? headers.join(delimiter) : ''
const csvString = addBOM ? '\uFEFF' + csvHeader : csvHeader
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.setAttribute('download', filename)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
return
}
// if headers not provided and rows are objects, take keys from first object
let _headers: string[] | null = headers
if (!_headers) {
const firstRow = data[0]
if (typeof firstRow === 'object' && !Array.isArray(firstRow)) {
_headers = Object.keys(firstRow)
} else if (Array.isArray(firstRow)) {
// if rows are arrays and no headers provided, we won't add header row
_headers = null
}
}
const escape = (val: unknown) => {
if (val === null || typeof val === 'undefined') return ''
const str = String(val)
const needsQuoting = str.includes(delimiter) || str.includes('\n') || str.includes('\r') || str.includes('"')
if (!needsQuoting) return str
return '"' + str.replace(/"/g, '""') + '"'
}
const rows: string[] = data.map((row) => {
if (Array.isArray(row)) {
return row.map(escape).join(delimiter)
}
// object row - map using headers if available, otherwise use object values
if (_headers && Array.isArray(_headers)) {
return _headers.map((h) => escape((row as Record<string, any>)[h])).join(delimiter)
}
return Object.values(row).map(escape).join(delimiter)
})
const headerRow = headerLabels ? headerLabels.join(delimiter) : _headers ? _headers.join(delimiter) : null
const csvString = (addBOM ? '\uFEFF' : '') + [headerRow, ...rows].filter(Boolean).join('\r\n')
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.setAttribute('download', filename)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
/**
* Download data as XLS (Excel) file using xlsx library.
*
* @param headers - Array of header names. If omitted and data is array of objects, keys will be taken from first object.
* @param data - Array of rows. Each row can be either an object (key -> value) or an array of values.
* @param filename - optional file name to use for downloaded file (default: 'data.xlsx')
* @param sheetName - optional sheet name in workbook (default: 'Sheet1')
* Usage examples:
* 1) With headers and array of objects
* await downloadXls(['name', 'age'], [{name: 'Alice', age: 25}, {name: 'Bob', age: 30}], 'people.xlsx');
* 2) Without headers (automatically uses object keys)
* await downloadXls(null, [{name: 'Alice', age: 25}, {name: 'Bob', age: 30}], 'people.xlsx');
* 3) With custom sheet name
* await downloadXls(['col1', 'col2'], [['a', 'b'], ['c', 'd']], 'matrix.xlsx', 'MyData');
*/
export async function downloadXls(
headers: string[] | null,
headerLabels: string[],
data: Array<Record<string, any> | any[]>,
filename = 'data.xlsx',
sheetName = 'Sheet1',
) {
// Dynamically import xlsx to avoid server-side issues
const { utils, write } = await import('xlsx')
const { saveAs } = await import('file-saver')
if (!Array.isArray(data) || data.length === 0) {
// Create empty sheet with headers only
const ws = utils.aoa_to_sheet(headers ? [headers] : [[]])
const wb = utils.book_new()
utils.book_append_sheet(wb, ws, sheetName)
const wbout = write(wb, { bookType: 'xlsx', type: 'array' })
saveAs(new Blob([wbout], { type: 'application/octet-stream' }), filename)
return
}
// if headers not provided and rows are objects, take keys from first object
let _headers: string[] | null = headers
if (!_headers) {
const firstRow = data[0]
if (typeof firstRow === 'object' && !Array.isArray(firstRow)) {
_headers = Object.keys(firstRow)
} else if (Array.isArray(firstRow)) {
_headers = null
}
}
// Convert data rows to 2D array
const rows: any[][] = data.map((row) => {
if (Array.isArray(row)) {
return row
}
// object row - map using headers if available, otherwise use object values
if (_headers && Array.isArray(_headers)) {
return _headers.map((h) => (row as Record<string, any>)[h] ?? '')
}
return Object.values(row)
})
// Combine headers/labels and rows for sheet
// If caller provided headerLabels (as display labels), prefer them.
const sheetHeader = headerLabels ? headerLabels : _headers ? _headers : null
const sheetData = sheetHeader ? [sheetHeader, ...rows] : rows
// Create worksheet and workbook
const ws = utils.aoa_to_sheet(sheetData)
const wb = utils.book_new()
utils.book_append_sheet(wb, ws, sheetName)
// Write and save file
const wbout = write(wb, { bookType: 'xlsx', type: 'array' })
saveAs(new Blob([wbout], { type: 'application/octet-stream' }), filename)
}
+56
View File
@@ -0,0 +1,56 @@
import type { Permission } from '~/models/role'
export function usePageChecker() {
const route = useRoute()
const { checkRole, hasCreateAccess, hasReadAccess, hasUpdateAccess, hasDeleteAccess } = useRBAC()
function getRouteTitle() {
return route.meta.title as string
}
function getParamsId() {
const id = route.params.id
return typeof id === 'string' ? parseInt(id) : 0
}
function getPageAccess(
roleAccess: Record<string, Permission[]>,
type: 'create' | 'read' | 'update' | 'delete',
) {
// Check if user has access to this page, need to use try - catch for proper handling
const hasAccess = checkRole(roleAccess)
if (!hasAccess) {
navigateTo('/403')
}
// Define permission-based computed properties
const canCreate = hasCreateAccess(roleAccess)
const canRead = hasReadAccess(roleAccess)
const canUpdate = hasUpdateAccess(roleAccess)
const canDelete = hasDeleteAccess(roleAccess)
switch (type) {
case 'create':
return canCreate
case 'read':
return canRead
case 'update':
return canUpdate
case 'delete':
return canDelete
default:
return false
}
}
return {
checkRole,
hasCreateAccess,
hasReadAccess,
hasUpdateAccess,
hasDeleteAccess,
getRouteTitle,
getParamsId,
getPageAccess
}
}
+54 -32
View File
@@ -1,44 +1,66 @@
import type { RoleAccess } from '~/models/role'
export const PAGE_PERMISSIONS = {
'/patient': {
doctor: ['R'],
nurse: ['R'],
admisi: ['C', 'R', 'U', 'D'],
pharmacy: ['R'],
billing: ['R'],
management: ['R'],
'/client/patient': {
'emp|doc': ['R'],
'emp|nur': ['R'],
'emp|reg': ['C', 'R', 'U', 'D'],
'emp|pha': ['R'],
'emp|pay': ['R'],
'emp|mng': ['R'],
},
'/doctor': {
doctor: ['C', 'R', 'U', 'D'],
nurse: ['R'],
admisi: ['R'],
pharmacy: ['R'],
billing: ['R'],
management: ['R'],
'/human-src/employee': {
'div|hrd': ['C', 'R', 'U', 'D'],
},
'/human-src/intern': {
'div|hrd': ['C', 'R', 'U', 'D'],
},
'/satusehat': {
doctor: ['R'],
nurse: ['R'],
admisi: ['C', 'R', 'U', 'D'],
pharmacy: ['R'],
billing: ['R'],
management: ['R'],
'emp|doc': ['R'],
'emp|nur': ['R'],
'emp|reg': ['C', 'R', 'U', 'D'],
'emp|pha': ['R'],
'emp|pay': ['R'],
'emp|mng': ['R'],
},
'/outpatient/encounter': {
'emp|doc': ['C', 'R', 'U', 'D'],
'emp|nur': ['C', 'R', 'U', 'D'],
'emp|reg': ['R'],
'emp|pha': ['R'],
'emp|pay': ['R'],
'emp|mng': ['R'],
},
'/emergency/encounter': {
'emp|doc': ['C', 'R', 'U', 'D'],
'emp|nur': ['C', 'R', 'U', 'D'],
'emp|reg': ['R'],
'emp|pha': ['R'],
'emp|pay': ['R'],
'emp|mng': ['R'],
},
'/inpatient/encounter': {
'emp|doc': ['C', 'R', 'U', 'D'],
'emp|nur': ['C', 'R', 'U', 'D'],
'emp|reg': ['R'],
'emp|pha': ['R'],
'emp|pay': ['R'],
'emp|mng': ['R'],
},
'/rehab/encounter': {
doctor: ['C', 'R', 'U', 'D'],
nurse: ['R'],
admisi: ['R'],
pharmacy: ['R'],
billing: ['R'],
management: ['R'],
'emp|doc': ['C', 'R', 'U', 'D'],
'emp|nur': ['R'],
'emp|reg': ['R'],
'emp|pha': ['R'],
'emp|pay': ['R'],
'emp|mng': ['R'],
},
'/rehab/registration': {
doctor: ['C', 'R', 'U', 'D'],
nurse: ['R'],
admisi: ['R'],
pharmacy: ['R'],
billing: ['R'],
management: ['R'],
'emp|doc': ['C', 'R', 'U', 'D'],
'emp|nur': ['R'],
'emp|reg': ['R'],
'emp|pha': ['R'],
'emp|pay': ['R'],
'emp|mng': ['R'],
},
} as const satisfies Record<string, RoleAccess>
+16
View File
@@ -0,0 +1,16 @@
import { medicalRoles, respPosCode } from '~/const/common/role'
export function getServicePosition(role?: string): string {
if(!role) {
return 'none'
}
if (medicalRoles.includes(role)) {
return 'medical'
} else if (role === 'emp|reg') {
return 'registration'
} else if (role.includes('|resp')) {
return 'verificator'
} else {
return 'none'
}
}
+5
View File
@@ -0,0 +1,5 @@
export type CrudQueryParamsModeType = "list" | "entry"| "add"| "edit"
export const crudQueryParamsMode = {
list: 'list',
entry: 'entry',
}
+63
View File
@@ -1,6 +1,8 @@
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
@@ -116,3 +118,64 @@ export function calculateAge(birthDate: Date | string | null | undefined): strin
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',
})
}