diff --git a/backend/api/src/modules/rekammedis/rekammedis.controller.ts b/backend/api/src/modules/rekammedis/rekammedis.controller.ts index a29b509..6be0d57 100644 --- a/backend/api/src/modules/rekammedis/rekammedis.controller.ts +++ b/backend/api/src/modules/rekammedis/rekammedis.controller.ts @@ -27,6 +27,16 @@ export class RekamMedisController { @Query('orderBy') orderBy: string, @Query('no_rm') no_rm: string, @Query('order') order: 'asc' | 'desc', + @Query('id_visit') id_visit: string, + @Query('nama_pasien') nama_pasien: string, + @Query('tanggal_start') tanggal_start: string, + @Query('tanggal_end') tanggal_end: string, + @Query('umur_min') umur_min: string, + @Query('umur_max') umur_max: string, + @Query('jenis_kelamin') jenis_kelamin: string, + @Query('gol_darah') gol_darah: string, + @Query('kode_diagnosa') kode_diagnosa: string, + @Query('tindak_lanjut') tindak_lanjut: string, ) { return this.rekammedisService.getAllRekamMedis({ take, @@ -35,6 +45,16 @@ export class RekamMedisController { orderBy, no_rm, order, + id_visit, + nama_pasien, + tanggal_start, + tanggal_end, + umur_min, + umur_max, + jenis_kelamin, + gol_darah, + kode_diagnosa, + tindak_lanjut, }); } diff --git a/backend/api/src/modules/rekammedis/rekammedis.service.ts b/backend/api/src/modules/rekammedis/rekammedis.service.ts index f2e8e58..b2abaf5 100644 --- a/backend/api/src/modules/rekammedis/rekammedis.service.ts +++ b/backend/api/src/modules/rekammedis/rekammedis.service.ts @@ -6,6 +6,26 @@ import { CreateLogDto } from '../log/dto/create-log.dto'; @Injectable() export class RekammedisService { + // Define known values as constants to avoid hardcoding everywhere + private readonly KNOWN_BLOOD_TYPES = ['A', 'B', 'AB', 'O']; + private readonly KNOWN_TINDAK_LANJUT = [ + 'Dipulangkan untuk Kontrol', + 'Dirawat', + 'Dirujuk ke RS', + 'Konsul Ke Poli Lain', + 'Konsultasi Dokter Spesialis', + 'Kontrol', + 'Kontrol Ulang', + 'Masuk Rawat Inap', + 'Meninggal Dunia Sebelum Dirawat', + 'Meninggal Dunia Setelah Dirawat', + 'Pulang', + 'Rencana Operasi', + 'Rujuk Balik', + 'Selesai Pelayanan IGD', + 'Selesai Pelayanan Rawat Jalan', + ]; + constructor(private prisma: PrismaService) {} async getAllRekamMedis(params: { @@ -15,8 +35,36 @@ export class RekammedisService { orderBy?: any; no_rm?: string; order?: 'asc' | 'desc'; + id_visit?: string; + nama_pasien?: string; + tanggal_start?: string; + tanggal_end?: string; + umur_min?: string; + umur_max?: string; + jenis_kelamin?: string; + gol_darah?: string; + kode_diagnosa?: string; + tindak_lanjut?: string; }) { - const { skip, page, orderBy, order, no_rm } = params; + const { + skip, + page, + orderBy, + order, + no_rm, + id_visit, + nama_pasien, + tanggal_start, + tanggal_end, + umur_min, + umur_max, + jenis_kelamin, + kode_diagnosa, + } = params; + + const golDarahArray = params.gol_darah?.split(',') || []; + const tindakLanjutArray = params.tindak_lanjut?.split(',') || []; + console.log('Params Received:', params); const take = params.take ? parseInt(params.take.toString()) : 10; const skipValue = skip ? parseInt(skip.toString()) @@ -24,27 +72,140 @@ export class RekammedisService { ? (parseInt(page.toString()) - 1) * take : 0; + const buildMultiSelectFilter = ( + fieldName: string, + selectedValues: string[], + knownValues: string[], + unknownLabel: string, + includesDash: boolean = false, + ) => { + if (selectedValues.length === 0) { + return undefined; + } + + const hasKnownValues = selectedValues.some((val) => + knownValues.includes(val), + ); + const hasUnknown = selectedValues.includes(unknownLabel); + const totalOptions = knownValues.length + 1; + + if (selectedValues.length === totalOptions) { + return undefined; + } + + if (hasUnknown && !hasKnownValues) { + const conditions: any[] = [{ [fieldName]: { equals: null } }]; + + if (includesDash) { + conditions.push({ [fieldName]: { equals: '-' } }); + conditions.push({ [fieldName]: { notIn: knownValues } }); + } + + return conditions.length > 1 ? { OR: conditions } : conditions[0]; + } + + if (hasKnownValues && hasUnknown) { + const knownSelected = selectedValues.filter( + (val) => val !== unknownLabel, + ); + const conditions: any[] = [ + { [fieldName]: { in: knownSelected } }, + { [fieldName]: { equals: null } }, + ]; + + if (includesDash) { + conditions.push({ [fieldName]: { equals: '-' } }); + conditions.push({ [fieldName]: { notIn: knownValues } }); + } + + return { OR: conditions }; + } + + return { [fieldName]: { in: selectedValues } }; + }; + + const golDarahFilter = buildMultiSelectFilter( + 'gol_darah', + golDarahArray, + this.KNOWN_BLOOD_TYPES, + 'Tidak Tahu', + true, + ); + + const tindakLanjutFilter = buildMultiSelectFilter( + 'tindak_lanjut', + tindakLanjutArray, + this.KNOWN_TINDAK_LANJUT, + 'Belum Ada Keterangan', + false, + ); + + const whereClause = { + no_rm: no_rm ? { startsWith: no_rm } : undefined, + id_visit: id_visit ? { contains: id_visit } : undefined, + nama_pasien: nama_pasien ? { contains: nama_pasien } : undefined, + waktu_visit: + tanggal_start && tanggal_end + ? { + gte: new Date(tanggal_start), + lte: new Date(tanggal_end), + } + : undefined, + umur: + umur_min && umur_max + ? { + gte: parseInt(umur_min, 10), + lte: parseInt(umur_max, 10), + } + : undefined, + jenis_kelamin: jenis_kelamin ? { equals: jenis_kelamin } : undefined, + kode_diagnosa: kode_diagnosa ? { contains: kode_diagnosa } : undefined, + ...golDarahFilter, + ...tindakLanjutFilter, + }; + const results = await this.prisma.rekam_medis.findMany({ skip: skipValue, take: take, - where: { - no_rm: no_rm ? no_rm : undefined, - }, + where: whereClause, orderBy: orderBy ? { [orderBy]: order || 'asc' } : { waktu_visit: order ? order : 'asc' }, }); const count = await this.prisma.rekam_medis.count({ - where: { - no_rm: no_rm ? { contains: no_rm } : undefined, + where: whereClause, + }); + + const umurMin = await this.prisma.rekam_medis.findMany({ + distinct: ['umur'], + orderBy: { + umur: 'asc', + }, + select: { + umur: true, }, }); - // console.log('Fetched Rekam Medis:', count); + const umurMax = await this.prisma.rekam_medis.findMany({ + distinct: ['umur'], + orderBy: { + umur: 'desc', + }, + select: { + umur: true, + }, + }); + + const rangeUmur = { + min: umurMin.length > 0 ? umurMin[0].umur : null, + max: umurMax.length > 0 ? umurMax[0].umur : null, + }; + return { ...results, totalCount: count, + rangeUmur: rangeUmur, }; } diff --git a/frontend/hospital-log/package-lock.json b/frontend/hospital-log/package-lock.json index 1bbc9b1..50b2bd2 100644 --- a/frontend/hospital-log/package-lock.json +++ b/frontend/hospital-log/package-lock.json @@ -10,7 +10,9 @@ "dependencies": { "@tailwindcss/vite": "^4.1.16", "@vee-validate/zod": "^4.15.1", + "cally": "^0.8.0", "daisyui": "^5.3.10", + "nouislider": "^15.8.1", "vee-validate": "^4.15.1", "vue": "^3.5.22", "vue-router": "4", @@ -638,6 +640,12 @@ "node": ">=14" } }, + "node_modules/atomico": { + "version": "1.79.2", + "resolved": "https://registry.npmjs.org/atomico/-/atomico-1.79.2.tgz", + "integrity": "sha512-mshhLRMeIltNYbnQnqgnrvJ/uDa8XDfTQcjw3ymOygQqwHIQ4Sp0LcNYMCbACkV3DtV+eDXb9szwU4qMUuGwYQ==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -729,6 +737,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/cally": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cally/-/cally-0.8.0.tgz", + "integrity": "sha512-jvQ2QMrsZM/ZPG/LWTkJEUPrp/ew1uS2KjKA/E6ru7mVvTMY2JgSagci9IghLmuamFh1pDajrxXAX4Qgo4FHbw==", + "license": "MIT", + "dependencies": { + "atomico": "^1.76.1" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001751", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", @@ -1005,6 +1022,12 @@ "node": ">=0.10.0" } }, + "node_modules/nouislider": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.8.1.tgz", + "integrity": "sha512-93TweAi8kqntHJSPiSWQ1o/uZ29VWOmal9YKb6KKGGlCkugaNfAupT7o1qTHqdJvNQ7S0su5rO6qRFCjP8fxtw==", + "license": "MIT" + }, "node_modules/path-browserify": { "version": "1.0.1", "dev": true, diff --git a/frontend/hospital-log/package.json b/frontend/hospital-log/package.json index 68643fe..e7aaf86 100644 --- a/frontend/hospital-log/package.json +++ b/frontend/hospital-log/package.json @@ -11,7 +11,9 @@ "dependencies": { "@tailwindcss/vite": "^4.1.16", "@vee-validate/zod": "^4.15.1", + "cally": "^0.8.0", "daisyui": "^5.3.10", + "nouislider": "^15.8.1", "vee-validate": "^4.15.1", "vue": "^3.5.22", "vue-router": "4", diff --git a/frontend/hospital-log/src/components/dashboard/filter/CheckboxGroup.vue b/frontend/hospital-log/src/components/dashboard/filter/CheckboxGroup.vue new file mode 100644 index 0000000..19fcd5f --- /dev/null +++ b/frontend/hospital-log/src/components/dashboard/filter/CheckboxGroup.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/frontend/hospital-log/src/components/dashboard/filter/DateRangePicker.vue b/frontend/hospital-log/src/components/dashboard/filter/DateRangePicker.vue new file mode 100644 index 0000000..f2aa9fa --- /dev/null +++ b/frontend/hospital-log/src/components/dashboard/filter/DateRangePicker.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/frontend/hospital-log/src/components/dashboard/filter/RangeSlider.vue b/frontend/hospital-log/src/components/dashboard/filter/RangeSlider.vue new file mode 100644 index 0000000..6b0c736 --- /dev/null +++ b/frontend/hospital-log/src/components/dashboard/filter/RangeSlider.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/frontend/hospital-log/src/components/dashboard/filter/SelectInput.vue b/frontend/hospital-log/src/components/dashboard/filter/SelectInput.vue new file mode 100644 index 0000000..6adf24b --- /dev/null +++ b/frontend/hospital-log/src/components/dashboard/filter/SelectInput.vue @@ -0,0 +1,44 @@ + + + diff --git a/frontend/hospital-log/src/components/dashboard/filter/TextInput.vue b/frontend/hospital-log/src/components/dashboard/filter/TextInput.vue new file mode 100644 index 0000000..a6e01fa --- /dev/null +++ b/frontend/hospital-log/src/components/dashboard/filter/TextInput.vue @@ -0,0 +1,36 @@ + + + diff --git a/frontend/hospital-log/src/components/dashboard/filter/index.ts b/frontend/hospital-log/src/components/dashboard/filter/index.ts new file mode 100644 index 0000000..3286abc --- /dev/null +++ b/frontend/hospital-log/src/components/dashboard/filter/index.ts @@ -0,0 +1,5 @@ +export { default as TextInput } from "./TextInput.vue"; +export { default as DateRangePicker } from "./DateRangePicker.vue"; +export { default as RangeSlider } from "./RangeSlider.vue"; +export { default as SelectInput } from "./SelectInput.vue"; +export { default as CheckboxGroup } from "./CheckboxGroup.vue"; diff --git a/frontend/hospital-log/src/constants/interfaces.ts b/frontend/hospital-log/src/constants/interfaces.ts new file mode 100644 index 0000000..052694b --- /dev/null +++ b/frontend/hospital-log/src/constants/interfaces.ts @@ -0,0 +1,11 @@ +interface RekamMedis { + id_visit: string; + no_rm: string; + nama_pasien: string; + waktu_visit: Date; + umur: number; + jenis_kelamin: string; + gol_darah: string; + kode_diagnosa: string; + tindak_lanjut: string; +} diff --git a/frontend/hospital-log/src/constants/pagination.ts b/frontend/hospital-log/src/constants/pagination.ts index b01e505..6a03733 100644 --- a/frontend/hospital-log/src/constants/pagination.ts +++ b/frontend/hospital-log/src/constants/pagination.ts @@ -1,9 +1,39 @@ +import type { RekamMedis } from "./interfaces"; + export const ITEMS_PER_PAGE_OPTIONS = [5, 10, 25, 50, 100] as const; export const DEFAULT_PAGE_SIZE = 10; export const DEBOUNCE_DELAY = 500; // milliseconds +export const FILTER = { + TINDAK_LANJUT: { + dipulangkan: "Dipulangkan untuk Kontrol", + dirawat: "Dirawat", + dirujuk: "Dirujuk ke RS", + konsul_poli_lain: "Konsul Ke Poli Lain", + konsul_spesialis: "Konsultasi Dokter Spesialis", + kontrol: "Kontrol", + kontrol_ulang: "Kontrol Ulang", + rawat_inap: "Masuk Rawat Inap", + meninggal_sebelum_dirawat: "Meninggal Dunia Sebelum Dirawat", + meninggal_setelah_dirawat: "Meninggal Dunia Setelah Dirawat", + pulang: "Pulang", + rencana_operasi: "Rencana Operasi", + rujuk_balik: "Rujuk Balik", + selesai_igd: "Selesai Pelayanan IGD", + selesai_rawat_jalan: "Selesai Pelayanan Rawat Jalan", + belum_ada_keterangan: "Belum Ada Keterangan", + }, + GOLONGAN_DARAH: { + A: "A", + B: "B", + AB: "AB", + O: "O", + "Tidak Tahu": "Tidak Tahu", + }, +}; + export const SORT_OPTIONS = { OBAT: { id: "ID", @@ -17,3 +47,51 @@ export const SORT_OPTIONS = { umur: "Umur", }, } as const; + +export const REKAM_MEDIS_TABLE_COLUMNS = [ + { + key: "id_visit" as keyof RekamMedis, + label: "ID Visit", + class: "text-dark", + }, + { + key: "no_rm" as keyof RekamMedis, + label: "No RM", + class: "text-dark", + }, + { + key: "nama_pasien" as keyof RekamMedis, + label: "Nama Pasien", + class: "text-dark", + }, + { + key: "waktu_visit" as keyof RekamMedis, + label: "Waktu Visit", + class: "text-dark", + }, + { + key: "umur" as keyof RekamMedis, + label: "Umur", + class: "text-dark", + }, + { + key: "jenis_kelamin" as keyof RekamMedis, + label: "Jenis Kelamin", + class: "text-dark", + }, + { + key: "gol_darah" as keyof RekamMedis, + label: "Golongan Darah", + class: "text-dark", + }, + { + key: "kode_diagnosa" as keyof RekamMedis, + label: "Kode Diagnosa", + class: "text-dark", + }, + { + key: "tindak_lanjut" as keyof RekamMedis, + label: "Tindak Lanjut", + class: "text-dark", + }, +]; diff --git a/frontend/hospital-log/src/views/dashboard/RekamMedisView.vue b/frontend/hospital-log/src/views/dashboard/RekamMedisView.vue index ea2cbd0..576bdcc 100644 --- a/frontend/hospital-log/src/views/dashboard/RekamMedisView.vue +++ b/frontend/hospital-log/src/views/dashboard/RekamMedisView.vue @@ -16,23 +16,21 @@ import { DEBOUNCE_DELAY, ITEMS_PER_PAGE_OPTIONS, SORT_OPTIONS, + FILTER, + REKAM_MEDIS_TABLE_COLUMNS, } from "../../constants/pagination"; - -interface RekamMedis { - id_visit: string; - no_rm: string; - nama_pasien: string; - waktu_visit: Date; - umur: number; - jenis_kelamin: string; - gol_darah: string; - kode_diagnosa: string; - tindak_lanjut: string; -} +import "cally"; +import noUiSlider from "nouislider"; +import "nouislider/dist/nouislider.css"; +import type { RekamMedis } from "../../constants/interfaces"; interface ApiResponse { data: RekamMedis[]; totalCount: number; + rangeUmur: { + min: number; + max: number; + }; } const data = ref([]); @@ -47,54 +45,29 @@ const pagination = usePagination({ initialPage: Number(route.query.page) || 1, initialPageSize: Number(route.query.pageSize) || DEFAULT_PAGE_SIZE, }); +const today: string = new Date().toISOString().split("T")[0] || ""; +const ageSliderRef = ref(null); +const ageRange = ref<[number, number]>([0, 100]); -const tableColumns = [ - { - key: "id_visit" as keyof RekamMedis, - label: "ID Visit", - class: "text-dark", - }, - { - key: "no_rm" as keyof RekamMedis, - label: "No RM", - class: "text-dark", - }, - { - key: "nama_pasien" as keyof RekamMedis, - label: "Nama Pasien", - class: "text-dark", - }, - { - key: "waktu_visit" as keyof RekamMedis, - label: "Waktu Visit", - class: "text-dark", - }, - { - key: "umur" as keyof RekamMedis, - label: "Umur", - class: "text-dark", - }, - { - key: "jenis_kelamin" as keyof RekamMedis, - label: "Jenis Kelamin", - class: "text-dark", - }, - { - key: "gol_darah" as keyof RekamMedis, - label: "Golongan Darah", - class: "text-dark", - }, - { - key: "kode_diagnosa" as keyof RekamMedis, - label: "Kode Diagnosa", - class: "text-dark", - }, - { - key: "tindak_lanjut" as keyof RekamMedis, - label: "Tindak Lanjut", - class: "text-dark", - }, -]; +const filter = ref<{ + id_visit: string | null; + nama_pasien: string | null; + rentang_tanggal: { start: Date | null; end: Date | null }; + rentang_umur: [number, number]; + jenis_kelamin: string | null; + kode_diagnosa: string; + gol_darah: string[]; + tindak_lanjut: string[]; +}>({ + id_visit: null, + nama_pasien: null, + rentang_tanggal: { start: null, end: null }, + rentang_umur: [0, 100], + jenis_kelamin: "initial", + kode_diagnosa: "", + gol_darah: [], + tindak_lanjut: [], +}); const updateQueryParams = () => { const query: Record = { @@ -110,18 +83,126 @@ const updateQueryParams = () => { query.sortBy = sortBy.value; } + if (filter.value.id_visit) { + query.id_visit = filter.value.id_visit; + } + + if (filter.value.nama_pasien) { + query.nama_pasien = filter.value.nama_pasien; + } + + if (filter.value.rentang_tanggal.end && filter.value.rentang_tanggal.start) { + query.tanggal_start = filter.value.rentang_tanggal.start.toString(); + query.tanggal_end = filter.value.rentang_tanggal.end.toString(); + } + + console.log(filter.value.rentang_umur[0], filter.value.rentang_umur[1]); + console.log(ageRange.value[0], ageRange.value[1]); + if ( + filter.value.rentang_umur[0] !== ageRange.value[0] && + filter.value.rentang_umur[1] !== ageRange.value[1] + ) { + query.umur_min = filter.value.rentang_umur[0].toString(); + query.umur_max = filter.value.rentang_umur[1].toString(); + } + + if (filter.value.jenis_kelamin && filter.value.jenis_kelamin !== "initial") { + query.jenis_kelamin = filter.value.jenis_kelamin; + } + + if (filter.value.gol_darah.length > 0) { + query.gol_darah = filter.value.gol_darah.join(","); + } + + if (filter.value.kode_diagnosa) { + query.kode_diagnosa = filter.value.kode_diagnosa; + } + + if (filter.value.tindak_lanjut.length > 0) { + query.tindak_lanjut = filter.value.tindak_lanjut.join(","); + } + router.replace({ query }); }; -const fetchData = async () => { +const handleCalendarChange = (date: any) => { + filter.value.rentang_tanggal.start = date.target.value.split("/")[0]; + filter.value.rentang_tanggal.end = date.target.value.split("/")[1]; + + console.log("Range Date:", filter.value.rentang_tanggal); +}; + +const handleResetGolonganDarah = () => { + filter.value.gol_darah = []; +}; + +const handleResetTindakLanjut = () => { + filter.value.tindak_lanjut = []; +}; + +const handleResetFilter = () => { + if (ageSliderRef.value && (ageSliderRef.value as any).noUiSlider) { + (ageSliderRef.value as any).noUiSlider.set([ + ageRange.value[0], + ageRange.value[1], + ]); + } + + filter.value = { + id_visit: null, + nama_pasien: null, + rentang_tanggal: { start: null, end: null }, + rentang_umur: [ageRange.value[0], ageRange.value[1]], + jenis_kelamin: "initial", + kode_diagnosa: "", + gol_darah: [], + tindak_lanjut: [], + }; +}; + +const handleApplyFilter = () => { + pagination.reset(); + fetchData(); +}; + +const fetchData = async (isFirst?: boolean) => { try { const queryParams = new URLSearchParams({ take: pagination.pageSize.value.toString(), page: pagination.page.value.toString(), orderBy: sortBy.value, - ...(searchRekamMedis.value && { obat: searchRekamMedis.value }), + ...(searchRekamMedis.value && { no_rm: searchRekamMedis.value }), + ...(filter.value.id_visit && { id_visit: filter.value.id_visit }), + ...(filter.value.nama_pasien && { + nama_pasien: filter.value.nama_pasien, + }), + ...(filter.value.rentang_tanggal.start && + filter.value.rentang_tanggal.end && { + tanggal_start: filter.value.rentang_tanggal.start.toString(), + tanggal_end: filter.value.rentang_tanggal.end.toString(), + }), + ...(filter.value.rentang_umur && + !isFirst && { + umur_min: filter.value.rentang_umur[0].toString(), + umur_max: filter.value.rentang_umur[1].toString(), + }), + ...(filter.value.jenis_kelamin && + filter.value.jenis_kelamin !== "initial" && { + jenis_kelamin: filter.value.jenis_kelamin, + }), + ...(filter.value.kode_diagnosa && { + kode_diagnosa: filter.value.kode_diagnosa, + }), + ...(filter.value.gol_darah.length > 0 && { + gol_darah: filter.value.gol_darah.join(","), + }), + ...(filter.value.tindak_lanjut.length > 0 && { + tindak_lanjut: filter.value.tindak_lanjut.join(","), + }), }); + console.log("Fetching with params:", queryParams.toString()); + const result = await api.get( `/rekammedis?${queryParams.toString()}` ); @@ -129,9 +210,12 @@ const fetchData = async () => { if ("data" in result && Array.isArray(result.data)) { data.value = result.data; pagination.totalCount.value = result.totalCount; + ageRange.value = [result.rangeUmur.min, result.rangeUmur.max]; } else { const apiResponse = result as any; pagination.totalCount.value = apiResponse.totalCount; + ageRange.value = [apiResponse.rangeUmur.min, apiResponse.rangeUmur.max]; + console.log("API Response:", ageRange.value); const dataArray: RekamMedis[] = []; Object.keys(apiResponse).forEach((key) => { @@ -143,12 +227,14 @@ const fetchData = async () => { } }); // console.log("Fetched data array:", dataArray); - data.value = dataArray; - } - updateQueryParams(); + data.value = dataArray; + if (!isFirst) { + updateQueryParams(); + } + } } catch (error) { - console.error("Error fetching obat data:", error); + console.error("Error fetching rekam medis data:", error); data.value = []; } }; @@ -162,6 +248,8 @@ const handleSearch = () => { const handleSortChange = (newSortBy: string) => { sortBy.value = newSortBy; + const element = document.activeElement; + if (element && element instanceof HTMLElement) element.blur(); pagination.reset(); fetchData(); }; @@ -212,8 +300,35 @@ onMounted(async () => { sortBy.value = route.query.sortBy as string; } - await fetchData(); + await fetchData(true); + filter.value.rentang_umur = [ageRange.value[0], ageRange.value[1]]; document.title = "RekamMedis - Hospital Log"; + + // Initialize noUiSlider + if (ageSliderRef.value) { + const slider = noUiSlider.create(ageSliderRef.value, { + start: [filter.value.rentang_umur[0], filter.value.rentang_umur[1]], + connect: true, + step: 1, + range: { + min: filter.value.rentang_umur[0], + max: filter.value.rentang_umur[1], + }, + animate: true, + animationDuration: 300, + tooltips: [ + { to: (value: number) => Math.round(value).toString() }, + { to: (value: number) => Math.round(value).toString() }, + ], + }); + + slider.on("update", (values: (string | number)[]) => { + filter.value.rentang_umur = [ + Math.round(Number(values[0])), + Math.round(Number(values[1])), + ]; + }); + } }); @@ -222,8 +337,207 @@ onMounted(async () => {
+
+ +
+ Filter +
+
+
+
+ + +
+
+ + +
-
+
+ + +
+ +
+ +
+
+
+
+
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + +
+
+
+
{ {
- + diff --git a/frontend/hospital-log/vite.config.ts b/frontend/hospital-log/vite.config.ts index a08417b..956fb85 100644 --- a/frontend/hospital-log/vite.config.ts +++ b/frontend/hospital-log/vite.config.ts @@ -5,7 +5,16 @@ import { fileURLToPath, URL } from "node:url"; // https://vite.dev/config/ export default defineConfig({ - plugins: [vue(), tailwindcss()], + plugins: [ + vue({ + template: { + compilerOptions: { + isCustomElement: (tag) => tag.startsWith("calendar-"), + }, + }, + }), + tailwindcss(), + ], resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)),