Merge branch 'dev' of github.com:dikstub-rssa/simrs-fe into feat/education-assessment-79
This commit is contained in:
+138
-3
@@ -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' },
|
||||
]
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export type CrudQueryParamsModeType = "list" | "entry"| "add"| "edit"
|
||||
export const crudQueryParamsMode = {
|
||||
list: 'list',
|
||||
entry: 'entry',
|
||||
}
|
||||
@@ -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',
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user