feat: change UI language into english from indonesia

This commit is contained in:
yosaphatprs 2025-12-04 11:10:10 +07:00
parent 7633bd25e3
commit 520099ca8b
41 changed files with 627 additions and 649 deletions

View File

@ -204,7 +204,7 @@ describe('AuditController', () => {
const result = controller.createAuditTrail();
expect(result).toEqual({
message: 'Proses audit trail dijalankan',
message: 'Audit trail process started',
status: 'STARTED',
});
expect(mockAuditService.storeAuditTrail).toHaveBeenCalled();

View File

@ -41,6 +41,6 @@ export class AuditController {
@UseGuards(AuthGuard)
createAuditTrail() {
this.auditService.storeAuditTrail();
return { message: 'Proses audit trail dijalankan', status: 'STARTED' };
return { message: 'Audit trail process started', status: 'STARTED' };
}
}

View File

@ -178,7 +178,7 @@ describe('AuthController', () => {
const result = controller.logout(mockResponse as any);
expect(result).toEqual({ message: 'Logout berhasil' });
expect(result).toEqual({ message: 'Logout successful' });
expect(mockResponse.clearCookie).toHaveBeenCalledWith('access_token', {
httpOnly: true,
secure: false,
@ -195,7 +195,7 @@ describe('AuthController', () => {
const result = controller.logout(mockResponse as any);
expect(result).toEqual({ message: 'Logout berhasil' });
expect(result).toEqual({ message: 'Logout successful' });
expect(mockResponse.clearCookie).toHaveBeenCalledWith('access_token', {
httpOnly: true,
secure: true,

View File

@ -63,6 +63,6 @@ export class AuthController {
sameSite: 'strict',
});
return { message: 'Logout berhasil' };
return { message: 'Logout successful' };
}
}

View File

@ -73,7 +73,7 @@ export class AuthService {
});
if (!user || !(await bcrypt.compare(password, user.password_hash))) {
throw new UnauthorizedException('Username atau password salah');
throw new UnauthorizedException('Wrong username or password');
}
const csrfToken = crypto.randomBytes(32).toString('hex');

View File

@ -7,13 +7,13 @@ export enum UserRole {
}
export class AuthDto {
@IsNotEmpty({ message: 'Username wajib diisi' })
@IsString({ message: 'Username harus berupa string' })
@Length(1, 100, { message: 'Username maksimal 100 karakter' })
@IsNotEmpty({ message: 'Username is required' })
@IsString({ message: 'Username must be a string' })
@Length(1, 100, { message: 'Username must be at most 100 characters' })
username: string;
@IsNotEmpty({ message: 'Password wajib diisi' })
@IsString({ message: 'Password harus berupa string' })
@Length(6, undefined, { message: 'Password minimal 6 karakter' })
@IsNotEmpty({ message: 'Password is required' })
@IsString({ message: 'Password must be a string' })
@Length(6, undefined, { message: 'Password must be at least 6 characters' })
password: string;
}

View File

@ -9,26 +9,26 @@ import { Expose, Transform } from 'class-transformer';
import { UserRole } from './auth.dto';
export class CreateUserDto {
@IsNotEmpty({ message: 'Nama lengkap wajib diisi' })
@IsString({ message: 'Nama lengkap harus berupa string' })
@Length(1, 255, { message: 'Nama lengkap maksimal 255 karakter' })
@IsNotEmpty({ message: 'Full name is required' })
@IsString({ message: 'Full name must be a string' })
@Length(1, 255, { message: 'Full name must be at most 255 characters' })
nama_lengkap: string;
@IsNotEmpty({ message: 'Username wajib diisi' })
@IsString({ message: 'Username harus berupa string' })
@Length(1, 100, { message: 'Username maksimal 100 karakter' })
@IsNotEmpty({ message: 'Username is required' })
@IsString({ message: 'Username must be a string' })
@Length(1, 100, { message: 'Username must be at most 100 characters' })
username: string;
@IsNotEmpty({ message: 'Password wajib diisi' })
@IsString({ message: 'Password harus berupa string' })
@IsNotEmpty({ message: 'Password is required' })
@IsString({ message: 'Password must be a string' })
@Length(6, 100, {
message: 'Password minimal 6 karakter dan maksimal 100 karakter',
message: 'Password must be between 6 and 100 characters',
})
password: string;
@IsOptional()
@IsString({ message: 'Role harus berupa string' })
@IsEnum(UserRole, { message: 'Role harus "admin" atau "user"' })
@IsString({ message: 'Role must be a string' })
@IsEnum(UserRole, { message: 'Role must be "admin" or "user"' })
role?: UserRole;
}

View File

@ -184,7 +184,7 @@ describe('FabricService', () => {
await expect(
service.storeLog('log-1', 'CREATE', 'user-1', '{}'),
).rejects.toThrow('Gagal menyimpan log ke blockchain');
).rejects.toThrow('Failed to store log to blockchain');
});
it('should not validate empty id (NO VALIDATION)', async () => {
@ -273,7 +273,7 @@ describe('FabricService', () => {
);
await expect(service.getLogById('non-existent')).rejects.toThrow(
'Gagal mengambil log dari blockchain',
'Failed to retrieve log from blockchain',
);
});
@ -325,7 +325,7 @@ describe('FabricService', () => {
);
await expect(service.getAllLogs()).rejects.toThrow(
'Gagal mengambil semua log dari blockchain',
'Failed to retrieve all logs from blockchain',
);
});
});
@ -370,7 +370,7 @@ describe('FabricService', () => {
);
await expect(service.getLogsWithPagination(10, '')).rejects.toThrow(
'Gagal mengambil log dengan paginasi dari blockchain',
'Failed to retrieve logs with pagination from blockchain',
);
});

View File

@ -65,7 +65,7 @@ export class FabricService implements OnModuleInit, OnApplicationShutdown {
const message = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Failed to store log: ${message}`);
throw new InternalServerErrorException(
'Gagal menyimpan log ke blockchain',
'Failed to store log to blockchain',
);
}
}
@ -78,7 +78,7 @@ export class FabricService implements OnModuleInit, OnApplicationShutdown {
const message = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Failed to get log by ID: ${message}`);
throw new InternalServerErrorException(
'Gagal mengambil log dari blockchain',
'Failed to retrieve log from blockchain',
);
}
}
@ -91,7 +91,7 @@ export class FabricService implements OnModuleInit, OnApplicationShutdown {
const message = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Failed to get all logs: ${message}`);
throw new InternalServerErrorException(
'Gagal mengambil semua log dari blockchain',
'Failed to retrieve all logs from blockchain',
);
}
}
@ -106,7 +106,7 @@ export class FabricService implements OnModuleInit, OnApplicationShutdown {
const message = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Failed to get logs with pagination: ${message}`);
throw new InternalServerErrorException(
'Gagal mengambil log dengan paginasi dari blockchain',
'Failed to retrieve logs with pagination from blockchain',
);
}
}

View File

@ -1,12 +1,12 @@
import { IsString, IsNotEmpty, Length, IsEnum } from 'class-validator';
export class StoreLogDto {
@IsNotEmpty({ message: 'ID wajib diisi' })
@IsString({ message: 'ID harus berupa string' })
@IsNotEmpty({ message: 'ID is required' })
@IsString({ message: 'ID must be a string' })
id: string;
@IsNotEmpty({ message: 'Event wajib diisi' })
@IsString({ message: 'Event harus berupa string' })
@IsNotEmpty({ message: 'Event is required' })
@IsString({ message: 'Event must be a string' })
@IsEnum(
[
'tindakan_dokter_created',
@ -20,17 +20,17 @@ export class StoreLogDto {
'rekam_medis_deleted',
],
{
message: 'Event tidak valid',
message: 'Invalid event',
},
)
@Length(1, 100, { message: 'Event maksimal 100 karakter' })
@Length(1, 100, { message: 'Event must be at most 100 characters' })
event: string;
@IsNotEmpty({ message: 'User ID wajib diisi' })
@IsString({ message: 'User ID harus berupa string' })
@IsNotEmpty({ message: 'User ID is required' })
@IsString({ message: 'User ID must be a string' })
user_id: string;
@IsNotEmpty({ message: 'Payload wajib diisi' })
@IsString({ message: 'Payload harus berupa string' })
@IsNotEmpty({ message: 'Payload is required' })
@IsString({ message: 'Payload must be a string' })
payload: string;
}

View File

@ -134,7 +134,7 @@ export class ObatService {
async createObat(dto: CreateObatDto, user: ActiveUserPayload) {
if (!(await this.isIdVisitExists(dto.id_visit))) {
throw new BadRequestException(`ID Visit ${dto.id_visit} tidak ditemukan`);
throw new BadRequestException(`Visit ID ${dto.id_visit} not found`);
}
try {
@ -157,7 +157,7 @@ export class ObatService {
async createObatToDBAndBlockchain(dto: CreateObatDto, userId: number) {
if (!(await this.isIdVisitExists(dto.id_visit))) {
throw new BadRequestException(`Visit with id ${dto.id_visit} not found`);
throw new BadRequestException(`Visit id ${dto.id_visit} not found`);
}
try {
@ -200,11 +200,11 @@ export class ObatService {
const obatId = Number(id);
if (isNaN(obatId)) {
throw new BadRequestException('ID obat tidak valid');
throw new BadRequestException('ID medicine not valid');
}
if (!(await this.getObatById(obatId))) {
throw new BadRequestException(`Obat with id ${obatId} not found`);
throw new BadRequestException(`Medicine with id ${obatId} not found`);
}
try {
@ -244,15 +244,13 @@ export class ObatService {
const obatId = Number(id);
if (isNaN(obatId)) {
throw new BadRequestException('ID obat tidak valid');
throw new BadRequestException('Medicine ID not valid');
}
const existingObat = await this.getObatById(obatId);
if (!existingObat) {
throw new BadRequestException(
`Pemberian obat dengan ID ${obatId} tidak ditemukan`,
);
throw new BadRequestException(`Medicine with ID ${obatId} not found`);
}
const hasUpdates =
@ -261,7 +259,7 @@ export class ObatService {
dto.aturan_pakai !== existingObat.aturan_pakai;
if (!hasUpdates) {
throw new BadRequestException('Tidak ada perubahan data obat');
throw new BadRequestException('No changes in medicine data detected');
}
try {
@ -325,12 +323,12 @@ export class ObatService {
const obatId = Number(id);
if (isNaN(obatId)) {
throw new BadRequestException('ID obat tidak valid');
throw new BadRequestException('Medicine ID not valid');
}
const existingObat = await this.getObatById(obatId);
if (!existingObat) {
throw new BadRequestException(`Obat dengan ID ${obatId} tidak ditemukan`);
throw new BadRequestException(`Medicine with ID ${obatId} not found`);
}
try {

View File

@ -14,27 +14,29 @@ import {
import { Transform } from 'class-transformer';
export class CreateRekamMedisDto {
@IsNotEmpty({ message: 'Nomor rekam medis (no_rm) wajib diisi' })
@IsNotEmpty({ message: 'Medical record number (no_rm) is required' })
@IsString()
@Length(1, 20, { message: 'Nomor rekam medis maksimal 20 karakter' })
@Length(1, 20, {
message: 'Medical record number must be at most 20 characters',
})
no_rm: string;
@IsNotEmpty({ message: 'Nama pasien wajib diisi' })
@IsNotEmpty({ message: 'Patient name is required' })
@IsString()
@Length(1, 100, { message: 'Nama pasien maksimal 100 karakter' })
@Length(1, 100, { message: 'Patient name must be at most 100 characters' })
nama_pasien: string;
@IsOptional()
@IsInt({ message: 'Umur harus berupa angka bulat' })
@Min(0, { message: 'Umur tidak boleh negatif' })
@Max(150, { message: 'Umur tidak valid' })
@IsInt({ message: 'Age must be an integer' })
@Min(0, { message: 'Age cannot be negative' })
@Max(150, { message: 'Age is not valid' })
@Transform(({ value }) => (value ? parseInt(value) : null))
umur?: number;
@IsOptional()
@IsString()
@IsIn(['L', 'P', 'l', 'p'], {
message: 'Jenis kelamin harus "L" (Laki-laki) atau "P" (Perempuan)',
message: 'Gender must be "L" (Male) or "P" (Female)',
})
@Transform(({ value }) => value?.toUpperCase())
jenis_kelamin?: string;
@ -42,7 +44,7 @@ export class CreateRekamMedisDto {
@IsOptional()
@IsString()
@IsIn(['A', 'B', 'AB', 'O', '-'], {
message: 'Golongan darah harus A, B, AB, O, atau -',
message: 'Blood type must be A, B, AB, O, or -',
})
@Length(1, 2)
gol_darah?: string;
@ -70,37 +72,37 @@ export class CreateRekamMedisDto {
anamnese?: string;
@IsOptional()
@IsInt({ message: 'Tekanan darah sistolik harus berupa angka bulat' })
@IsInt({ message: 'Systolic blood pressure must be an integer' })
@Transform(({ value }) => (value ? parseInt(value) : null))
sistolik?: number;
@IsOptional()
@IsInt({ message: 'Tekanan darah diastolik harus berupa angka bulat' })
@IsInt({ message: 'Diastolic blood pressure must be an integer' })
@Transform(({ value }) => (value ? parseInt(value) : null))
diastolik?: number;
@IsOptional()
@IsInt({ message: 'Nadi harus berupa angka bulat' })
@IsInt({ message: 'Pulse must be an integer' })
@Transform(({ value }) => (value ? parseInt(value) : null))
nadi?: number;
@IsOptional()
@IsNumber({}, { message: 'Suhu harus berupa angka' })
@IsNumber({}, { message: 'Temperature must be a number' })
@Transform(({ value }) => (value ? parseFloat(value) : null))
suhu?: number;
@IsOptional()
@IsInt({ message: 'Pernapasan harus berupa angka bulat' })
@IsInt({ message: 'Respiration must be an integer' })
@Transform(({ value }) => (value ? parseInt(value) : null))
nafas?: number;
@IsOptional()
@IsNumber({}, { message: 'Tinggi badan harus berupa angka' })
@IsNumber({}, { message: 'Height must be a number' })
@Transform(({ value }) => (value ? parseFloat(value) : null))
tinggi_badan?: number;
@IsOptional()
@IsNumber({}, { message: 'Berat badan harus berupa angka' })
@IsNumber({}, { message: 'Weight must be a number' })
@Transform(({ value }) => (value ? parseFloat(value) : null))
berat_badan?: number;
@ -114,6 +116,6 @@ export class CreateRekamMedisDto {
tindak_lanjut?: string;
@IsOptional()
@IsDateString({}, { message: 'Waktu visit harus berupa tanggal yang valid' })
@IsDateString({}, { message: 'Visit time must be a valid date' })
waktu_visit?: string;
}

View File

@ -1,18 +1,18 @@
import { IsEnum, IsNumber, IsString } from 'class-validator';
export class PayloadRekamMedisDto {
@IsNumber({}, { message: 'ID dokter harus berupa angka' })
@IsNumber({}, { message: 'Doctor ID must be a number' })
dokter_id: number;
@IsString({ message: 'ID kunjungan harus berupa string' })
@IsString({ message: 'Visit ID must be a string' })
visit_id: string;
@IsEnum({}, { message: 'Anamnese harus berupa enum' })
@IsEnum({}, { message: 'Anamnese must be an enum' })
anamnese: string;
@IsEnum({}, { message: 'Jenis kasus harus berupa enum' })
@IsEnum({}, { message: 'Case type must be an enum' })
jenis_kasus: string;
@IsEnum({}, { message: 'Tindak lanjut harus berupa enum' })
@IsEnum({}, { message: 'Follow-up must be an enum' })
tindak_lanjut: string;
}

View File

@ -43,6 +43,7 @@ export class TindakanDokterController {
skip,
page,
orderBy: orderBy ? { [orderBy]: order || 'asc' } : undefined,
order,
});
}

View File

@ -125,7 +125,7 @@ export class TindakanDokterService {
});
if (!visitExists) {
throw new BadRequestException(`ID Visit ${dto.id_visit} tidak ditemukan`);
throw new BadRequestException(`Visit ID ${dto.id_visit} not found`);
}
const response = await this.prisma.validation_queue.create({
@ -172,7 +172,7 @@ export class TindakanDokterService {
});
return newTindakan;
} catch (error) {
console.error('Error creating Rekam Medis:', error);
console.error('Error creating Doctor Action:', error);
throw error;
}
}
@ -181,7 +181,7 @@ export class TindakanDokterService {
const tindakanId = Number(id);
if (Number.isNaN(tindakanId)) {
throw new BadRequestException('ID tindakan tidak valid');
throw new BadRequestException('Invalid action ID');
}
return this.prisma.pemberian_tindakan.findUnique({
@ -197,15 +197,13 @@ export class TindakanDokterService {
const tindakanId = Number(id);
if (Number.isNaN(tindakanId)) {
throw new BadRequestException('ID tindakan tidak valid');
throw new BadRequestException('Invalid doctor action ID');
}
const existing = await this.getTindakanDokterById(tindakanId);
if (!existing) {
throw new BadRequestException(
`Tindakan dokter dengan ID ${id} tidak ditemukan`,
);
throw new BadRequestException(`Doctor Action with ID ${id} not found`);
}
const hasUpdates =
@ -215,7 +213,7 @@ export class TindakanDokterService {
dto.kelompok_tindakan !== existing.kelompok_tindakan;
if (!hasUpdates) {
throw new BadRequestException('Tidak ada data tindakan yang diubah');
throw new BadRequestException("Doctor action data hasn't been changed");
}
if (dto.id_visit) {
@ -224,9 +222,7 @@ export class TindakanDokterService {
});
if (!visitExists) {
throw new BadRequestException(
`ID Visit ${dto.id_visit} tidak ditemukan`,
);
throw new BadRequestException(`Visit ID ${dto.id_visit} not found`);
}
}
@ -276,7 +272,7 @@ export class TindakanDokterService {
});
return updatedTindakan;
} catch (error) {
console.error('Error updating Tindakan Dokter:', error);
console.error('Error updating Doctor Action:', error);
throw error;
}
}
@ -285,7 +281,7 @@ export class TindakanDokterService {
const tindakanId = parseInt(id, 10);
if (Number.isNaN(tindakanId)) {
throw new BadRequestException('ID tindakan tidak valid');
throw new BadRequestException('Invalid action ID');
}
const currentData = await this.prisma.pemberian_tindakan.findUnique({
@ -293,9 +289,7 @@ export class TindakanDokterService {
});
if (!currentData) {
throw new BadRequestException(
`Tindakan dokter dengan ID ${id} tidak ditemukan`,
);
throw new BadRequestException(`Doctor action with ID ${id} not found`);
}
const idLog = `TINDAKAN_${id}`;
@ -330,15 +324,13 @@ export class TindakanDokterService {
const tindakanId = Number(id);
if (Number.isNaN(tindakanId)) {
throw new BadRequestException('ID tindakan tidak valid');
throw new BadRequestException('Invalid action ID');
}
const existingTindakan = await this.getTindakanDokterById(tindakanId);
if (!existingTindakan) {
throw new BadRequestException(
`Tindakan dokter dengan ID ${id} tidak ditemukan`,
);
throw new BadRequestException(`Doctor action with ID ${id} not found`);
}
try {
@ -368,7 +360,7 @@ export class TindakanDokterService {
return validationQueue;
} catch (error) {
console.error('Error deleting Tindakan Dokter:', error);
console.error('Error deleting Doctor Action:', error);
throw error;
}
}
@ -376,14 +368,14 @@ export class TindakanDokterService {
async deleteTindakanDokterFromDBAndBlockchain(id: number, userId: number) {
const tindakanId = Number(id);
if (Number.isNaN(tindakanId)) {
throw new BadRequestException('ID tindakan tidak valid');
throw new BadRequestException('Invalid action ID');
}
const existingTindakan = await this.getTindakanDokterById(tindakanId);
if (!existingTindakan) {
throw new BadRequestException(
`Tindakan dokter dengan ID ${tindakanId} tidak ditemukan`,
`Doctor action with ID ${tindakanId} not found`,
);
}
@ -410,7 +402,7 @@ export class TindakanDokterService {
});
return deletedTindakan;
} catch (error) {
console.error('Error deleting Tindakan Dokter:', error);
console.error('Error deleting Doctor Action:', error);
throw error;
}
}

View File

@ -81,7 +81,7 @@ const handleDeleteCancel = () => {
>
{{ column.label }}
</th>
<th v-if="isAksi" class="text-dark">Aksi</th>
<th v-if="isAksi" class="text-dark">Action</th>
</tr>
</thead>
<tbody>
@ -112,7 +112,7 @@ const handleDeleteCancel = () => {
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
<p>{{ emptyMessage || "Tidak ada data" }}</p>
<p>{{ emptyMessage || "No data available" }}</p>
</div>
</td>
</tr>
@ -196,7 +196,7 @@ const handleDeleteCancel = () => {
? 'tooltip tooltip-right flex items-center justify-center'
: '',
]"
data-tip="Data ini sedang dalam proses validasi untuk dihapus"
data-tip="This data is currently undergoing validation for deletion"
>
<span
:class="[
@ -235,7 +235,7 @@ const handleDeleteCancel = () => {
? 'tooltip tooltip-right flex items-center'
: '',
]"
data-tip="Data ini sedang dalam proses validasi untuk dihapus"
data-tip="This data is currently undergoing validation for deletion"
>
<span
:class="[
@ -346,16 +346,16 @@ const handleDeleteCancel = () => {
<DialogConfirm
ref="deleteDialogRef"
title="Hapus Data"
title="Delete Data"
:message="
pendingDeleteItem?.id
? `Apakah Anda yakin ingin menghapus item dengan ID ${pendingDeleteItem.id}?`
? `Are you sure you want to delete item with ID ${pendingDeleteItem.id}?`
: pendingDeleteItem?.id_visit
? `Apakah Anda yakin ingin menghapus item dengan ID Visit ${pendingDeleteItem?.id_visit}?`
: 'Apakah Anda yakin ingin menghapus item ini?'
? `Are you sure you want to delete item with Visit ID ${pendingDeleteItem?.id_visit}?`
: 'Are you sure you want to delete this item?'
"
confirm-text="Hapus"
cancel-text="Batal"
confirm-text="Delete"
cancel-text="Cancel"
@confirm="handleDeleteConfirm"
@cancel="handleDeleteCancel"
/>

View File

@ -12,7 +12,7 @@ const dateTime = ref(new Date());
let clockInterval: number;
const formattedDate = computed(() =>
dateTime.value.toLocaleDateString("id-ID", {
dateTime.value.toLocaleDateString("en-US", {
day: "2-digit",
month: "long",
year: "numeric",

View File

@ -39,15 +39,14 @@ const handlePageChange = (page: number) => {
<div>
<!-- Pagination Info -->
<div class="text-sm text-gray-600 px-4 py-4">
Menampilkan data {{ startIndex }} - {{ endIndex }} dari
{{ totalCount }} data
Showing data {{ startIndex }} - {{ endIndex }} from {{ totalCount }} items
</div>
<!-- Pagination Controls -->
<div class="flex justify-between items-center px-4 pb-4">
<!-- Items per page selector -->
<div class="flex items-center gap-2">
<span class="text-xs text-gray-600">Data per halaman:</span>
<span class="text-xs text-gray-600">Items per page:</span>
<div class="dropdown dropdown-top">
<div
tabindex="0"

View File

@ -144,7 +144,7 @@ const isActive = (routeName: string) => {
? 'bg-dark text-white hover:bg-dark'
: '',
]"
data-tip="Rekam Medis"
data-tip="Medical Records"
@click="navigateTo('rekam-medis')"
>
<svg
@ -167,7 +167,7 @@ const isActive = (routeName: string) => {
</svg>
<span
class="is-drawer-close:hidden is-drawer-open:opacity-100 transition-opacity is-drawer-open:duration-300 is-drawer-open:delay-300"
>Rekam<span class="opacity-0">_</span>Medis</span
>Medical<span class="opacity-0">_</span>Records</span
>
</button>
</li>
@ -181,7 +181,7 @@ const isActive = (routeName: string) => {
? 'bg-dark text-white hover:bg-dark'
: '',
]"
data-tip="Pemberian Tindakan"
data-tip="Doctor Actions"
@click="navigateTo('pemberian-tindakan')"
>
<svg
@ -204,7 +204,7 @@ const isActive = (routeName: string) => {
</svg>
<span
class="is-drawer-close:hidden is-drawer-open:opacity-100 transition-opacity is-drawer-open:duration-300 is-drawer-open:delay-300"
>Tindakan</span
>Actions</span
>
</button>
</li>
@ -216,7 +216,7 @@ const isActive = (routeName: string) => {
'mb-1 is-drawer-close:tooltip is-drawer-close:tooltip-right active:bg-dark',
isActive('obat') ? 'bg-dark text-white hover:bg-dark' : '',
]"
data-tip="Obat"
data-tip="Medicine Administration"
@click="navigateTo('obat')"
>
<svg
@ -236,7 +236,7 @@ const isActive = (routeName: string) => {
</svg>
<span
class="is-drawer-close:hidden is-drawer-open:opacity-100 transition-opacity is-drawer-open:duration-300 is-drawer-open:delay-300"
>Obat</span
>Medicine</span
>
</button>
</li>
@ -248,7 +248,7 @@ const isActive = (routeName: string) => {
'mb-1 is-drawer-close:tooltip is-drawer-close:tooltip-right active:bg-dark',
isActive('validasi') ? 'bg-dark text-white hover:bg-dark' : '',
]"
data-tip="Validasi"
data-tip="Validation"
@click="navigateTo('validasi')"
>
<svg
@ -268,7 +268,7 @@ const isActive = (routeName: string) => {
</svg>
<span
class="is-drawer-close:hidden is-drawer-open:opacity-100 transition-opacity is-drawer-open:duration-300 is-drawer-open:delay-300"
>Validasi</span
>Validation</span
>
</button>
</li>
@ -404,10 +404,10 @@ const isActive = (routeName: string) => {
<!-- Logout Confirmation Dialog -->
<DialogConfirm
ref="logoutDialog"
title="Keluar"
message="Apakah Anda yakin ingin keluar dari aplikasi?"
confirm-text="Ya, Keluar"
cancel-text="Batal"
title="Logout"
message="Are you sure you want to logout from the application?"
confirm-text="Yes, Logout"
cancel-text="Cancel"
@confirm="handleLogoutConfirm"
/>
</div>

View File

@ -37,7 +37,7 @@ export const FILTER = {
B: "B",
AB: "AB",
O: "O",
"Tidak Tahu": "Tidak Tahu",
Unknown: "Unknown",
},
KATEGORI_TINDAKAN: {
radiologi: "Radiologi",
@ -63,34 +63,34 @@ export const FILTER = {
laboratorium: "LABORATORIUM",
},
AUDIT_TYPE: {
all: "Semua Tipe",
rekam_medis: "Rekam Medis",
tindakan: "Tindakan",
obat: "Obat",
all: "All Types",
rekam_medis: "Medical Records",
tindakan: "Actions",
obat: "Medicine",
proof: "Proof",
},
AUDIT_TAMPERED: {
all: "Semua Data",
tampered: "Termanipulasi",
clean: "Tidak Termanipulasi",
all: "All Data",
tampered: "Tampered",
clean: "Not Tampered",
},
};
export const SORT_OPTIONS = {
REKAM_MEDIS: {
waktu_visit: "ID Visit",
no_rm: "Nomor Rekam Medis",
umur: "Umur",
waktu_visit: "Visit ID",
no_rm: "Medical Record Number",
umur: "Age",
},
TINDAKAN: {
id: "ID",
id_visit: "ID Visit",
id_visit: "Visit ID",
},
OBAT: {
id: "ID",
id_visit: "ID Visit",
jumlah_obat: "Jumlah Obat",
obat: "Obat",
id_visit: "Visit ID",
jumlah_obat: "Medicine Amount",
obat: "Medicine Name",
},
USERS: {
id: "ID",
@ -98,9 +98,9 @@ export const SORT_OPTIONS = {
role: "Role",
},
VALIDATION: {
id: "ID Validasi",
created_at: "Waktu Dibuat",
processed_at: "Waktu Diproses",
id: "Validation ID",
created_at: "Created At",
processed_at: "Processed At",
},
AUDIT_TRAIL: {
last_sync: "Last Sync",
@ -111,47 +111,47 @@ export const SORT_OPTIONS = {
export const REKAM_MEDIS_TABLE_COLUMNS = [
{
key: "id_visit" as keyof RekamMedis,
label: "ID Visit",
label: "Visit ID",
class: "text-dark",
},
{
key: "no_rm" as keyof RekamMedis,
label: "No RM",
label: "Medical Record Number",
class: "text-dark",
},
{
key: "nama_pasien" as keyof RekamMedis,
label: "Nama Pasien",
label: "Patient Name",
class: "text-dark",
},
{
key: "waktu_visit" as keyof RekamMedis,
label: "Waktu Visit",
label: "Visit Time",
class: "text-dark",
},
{
key: "umur" as keyof RekamMedis,
label: "Umur",
label: "Age",
class: "text-dark",
},
{
key: "jenis_kelamin" as keyof RekamMedis,
label: "Jenis Kelamin",
label: "Gender",
class: "text-dark",
},
{
key: "gol_darah" as keyof RekamMedis,
label: "Golongan Darah",
label: "Blood Type",
class: "text-dark",
},
{
key: "kode_diagnosa" as keyof RekamMedis,
label: "Kode Diagnosa",
label: "Diagnosis Code",
class: "text-dark",
},
{
key: "tindak_lanjut" as keyof RekamMedis,
label: "Tindak Lanjut",
label: "Follow-up Action",
class: "text-dark",
},
];
@ -164,22 +164,22 @@ export const TINDAKAN_TABLE_COLUMNS = [
},
{
key: "id_visit" as keyof TindakanDokter,
label: "ID Visit",
label: "Visit ID",
class: "text-dark",
},
{
key: "tindakan" as keyof TindakanDokter,
label: "Tindakan",
label: "Action",
class: "text-dark",
},
{
key: "kategori_tindakan" as keyof TindakanDokter,
label: "Kategori Tindakan",
label: "Action Category",
class: "text-dark",
},
{
key: "kelompok_tindakan" as keyof TindakanDokter,
label: "Kelompok Tindakan",
label: "Action Group",
class: "text-dark",
},
];
@ -192,7 +192,7 @@ export const USERS_TABLE_COLUMNS = [
},
{
key: "name" as keyof Users,
label: "Nama Lengkap",
label: "Full Name",
class: "text-dark",
},
{
@ -221,7 +221,7 @@ export const USERS_TABLE_COLUMNS = [
export const LOG_TABLE_COLUMNS = [
{
key: "txId" as keyof BlockchainLog,
label: "ID Transaksi",
label: "Transaction ID",
class: "text-dark",
},
{
@ -259,27 +259,27 @@ export const VALIDATION_TABLE_COLUMNS = [
},
{
key: "table_name" as keyof ValidationLog,
label: "Kelompok Data",
label: "Data Group",
class: "text-dark",
},
{
key: "record_id" as keyof ValidationLog,
label: "ID Record",
label: "Record ID",
class: "text-dark",
},
{
key: "action" as keyof ValidationLog,
label: "Aksi",
label: "Action",
class: "text-dark",
},
{
key: "user_id_request" as keyof ValidationLog,
label: "User ID Request",
label: "Requesting User ID",
class: "text-dark",
},
{
key: "user_id_process" as keyof ValidationLog,
label: "User ID Proses",
label: "Processing User ID",
class: "text-dark",
},
{
@ -289,12 +289,12 @@ export const VALIDATION_TABLE_COLUMNS = [
},
{
key: "created_at" as keyof ValidationLog,
label: "Waktu Dibuat",
label: "Created At",
class: "text-dark",
},
{
key: "processed_at" as keyof ValidationLog,
label: "Waktu Diproses",
label: "Processed At",
class: "text-dark",
},
];

View File

@ -1,7 +1,7 @@
import { z } from "zod";
const JENIS_KELAMIN_OPTIONS = ["laki-laki", "perempuan"] as const;
const GOLONGAN_DARAH_OPTIONS = ["A", "B", "AB", "O", "Tidak Tahu"] as const;
const GOLONGAN_DARAH_OPTIONS = ["A", "B", "AB", "O", "Unknown"] as const;
const TINDAK_LANJUT_OPTIONS = [
"Dipulangkan untuk Kontrol",
"Dirawat",
@ -82,66 +82,69 @@ const numericString = (message: string, options?: NumericFieldOptions) =>
export const rekamMedisFormSchema = z
.object({
no_rm: trimmedString("No Rekam Medis wajib diisi", {
no_rm: trimmedString("Medical record number is required", {
max: {
value: 20,
message: "Nomor rekam medis maksimal 20 karakter",
message: "Medical record number must be at most 20 characters",
},
}),
nama_pasien: trimmedString("Nama pasien wajib diisi", {
nama_pasien: trimmedString("Patient name is required", {
max: {
value: 100,
message: "Nama pasien maksimal 100 karakter",
message: "Patient name must be at most 100 characters",
},
}),
umur: numericString("Umur wajib diisi dan harus valid", {
umur: numericString("Age is required and must be valid", {
min: 0,
max: 150,
}),
jenis_kelamin: z
.string()
.trim()
.refine((value) => isJenisKelaminOption(value), "Pilih jenis kelamin"),
.refine((value) => isJenisKelaminOption(value), "Please select gender"),
gol_darah: z
.array(z.string().trim())
.min(1, "Pilih golongan darah")
.min(1, "Please select blood type")
.refine(
(values) => values.every((value) => isGolonganDarahOption(value)),
"Golongan darah tidak valid"
"Invalid blood type"
),
pekerjaan: trimmedString("Pekerjaan wajib diisi"),
anamnese: trimmedString("Anamnese wajib diisi"),
kode_diagnosa: trimmedString("Kode diagnosa wajib diisi"),
diagnosa: trimmedString("Diagnosa wajib diisi"),
diastolik: numericString("Tekanan diastolik harus diisi dan valid", {
pekerjaan: trimmedString("Occupation is required"),
anamnese: trimmedString("Anamnese is required"),
kode_diagnosa: trimmedString("Diagnosis code is required"),
diagnosa: trimmedString("Diagnosis is required"),
diastolik: numericString(
"Diastolic pressure is required and must be valid",
{
min: 0,
}
),
sistolik: numericString("Systolic pressure is required and must be valid", {
min: 0,
}),
sistolik: numericString("Tekanan sistolik harus diisi dan valid", {
nadi: numericString("Pulse is required and must be valid", {
min: 0,
}),
nadi: numericString("Nadi harus diisi dan valid", {
nafas: numericString("Respiration is required and must be valid", {
min: 0,
}),
nafas: numericString("Nafas harus diisi dan valid", {
min: 0,
}),
suhu: numericString("Suhu harus diisi dan valid", {
suhu: numericString("Temperature is required and must be valid", {
min: 25,
max: 45,
}),
tinggi_badan: numericString("Tinggi badan harus diisi dan valid", {
tinggi_badan: numericString("Height is required and must be valid", {
min: 0,
}),
berat_badan: numericString("Berat badan harus diisi dan valid", {
berat_badan: numericString("Weight is required and must be valid", {
min: 0,
}),
jenis_kasus: trimmedString("Jenis kasus wajib diisi"),
jenis_kasus: trimmedString("Case type is required"),
tindak_lanjut: z
.string()
.trim()
.refine(
(value) => isTindakLanjutOption(value),
"Pastikan Tindak Lanjut Valid"
"Please select a valid follow-up option"
),
})
.passthrough();

View File

@ -54,29 +54,29 @@ const isKelompokTindakanOption = (value: string): value is KelompokTindakan =>
const kategoriTindakanSchema = z
.string()
.trim()
.min(1, "Kategori tindakan wajib diisi")
.refine(isKategoriTindakanOption, "Pastikan kategori tindakan valid")
.min(1, "Action category is required")
.refine(isKategoriTindakanOption, "Please select a valid action category")
.transform((value) => value as KategoriTindakan);
const kelompokTindakanSchema = z
.string()
.trim()
.min(1, "Kelompok tindakan wajib diisi")
.refine(isKelompokTindakanOption, "Pastikan kelompok tindakan valid")
.min(1, "Action group is required")
.refine(isKelompokTindakanOption, "Please select a valid action group")
.transform((value) => value as KelompokTindakan);
export const tindakanFormSchema = z
.object({
id_visit: trimmedString("ID Visit wajib diisi", {
id_visit: trimmedString("Visit ID is required", {
max: {
value: 50,
message: "ID Visit maksimal 50 karakter",
message: "Visit ID must be at most 50 characters",
},
}),
tindakan: trimmedString("Nama tindakan wajib diisi", {
tindakan: trimmedString("Action name is required", {
max: {
value: 150,
message: "Nama tindakan maksimal 150 karakter",
message: "Action name must be at most 150 characters",
},
}),
kategori_tindakan: kategoriTindakanSchema,

View File

@ -3,9 +3,9 @@
class="h-screen w-screen flex justify-center flex-col items-center bg-white text-dark text-lg"
>
<h1 class="font-bold">404 - Page Not Found</h1>
<p>Halaman tidak ditemukan.</p>
<p>Page not found.</p>
<router-link to="/dashboard"
>Kembali ke <span class="font-bold">Dashboard</span></router-link
>Back to <span class="font-bold">Dashboard</span></router-link
>
</div>
</template>

View File

@ -8,12 +8,12 @@ import { useRouter } from "vue-router";
const validationSchema = toTypedSchema(
zod.object({
username: zod
.string({ message: "Input tidak valid" })
.min(1, { message: "Masukkan username" }),
.string({ message: "Invalid input" })
.min(1, { message: "Please enter username" }),
password: zod
.string({ message: "Input tidak valid" })
.min(1, { message: "Masukkan password" })
.min(6, { message: "Password minimal 6 karakter" }),
.string({ message: "Invalid input" })
.min(1, { message: "Please enter password" })
.min(6, { message: "Password must be at least 6 characters" }),
})
);
@ -55,7 +55,7 @@ const onSubmit = handleSubmit(async (values: any) => {
if (error && Array.isArray(error.message)) {
loginError.value = error.message[0];
} else {
loginError.value = error.message || "Terjadi kesalahan saat login.";
loginError.value = error.message || "An error occurred during login.";
}
} finally {
isLoading.value = false;
@ -63,7 +63,7 @@ const onSubmit = handleSubmit(async (values: any) => {
});
const baseButtonClass =
"btn btn-primary hover:btn-primary-content text-white font-bold border-primary py-4 px-4 rounded-md focus:outline-none focus:shadow-outline w-full";
"btn bg-dark text-white font-bold py-4 px-4 rounded-md focus:outline-none focus:shadow-outline w-full";
const buttonClass = computed(() => {
return [
@ -96,7 +96,7 @@ const buttonClass = computed(() => {
class="shadow appearance-none border rounded w-full py-2 px-3 mb-1 text-dark leading-tight focus:outline-none focus:shadow-outline"
id="username"
type="text"
placeholder="Masukkan username"
placeholder="Enter username"
/>
<span class="text-red-800">{{ usernameError }}</span>
</div>
@ -109,7 +109,7 @@ const buttonClass = computed(() => {
class="shadow appearance-none border rounded w-full py-2 px-3 text-dark mb-1 leading-tight focus:outline-none focus:shadow-outline"
id="password"
type="password"
placeholder="Masukkan password"
placeholder="Enter password"
/>
<span class="text-red-800">{{ passwordError }}</span>
</div>
@ -119,7 +119,7 @@ const buttonClass = computed(() => {
v-if="isLoading"
class="loading loading-dots loading-sm"
></span>
<span v-else>Masuk</span>
<span v-else>Sign In</span>
</button>
</div>
</form>

View File

@ -86,7 +86,7 @@ const rekamMedisCountLineChartData = computed(() => {
labels: labels,
datasets: [
{
label: "Jumlah Rekam Medis Baru",
label: "New Medical Records Count",
backgroundColor: "#1a2a4f",
borderColor: "#1a2a4f",
borderWidth: 2,
@ -123,7 +123,7 @@ const rekamMedisLineChartOptions = ref({
callbacks: {
label: function (context: any) {
return (
context.dataset.label + ": " + context.parsed.y + " rekam medis"
context.dataset.label + ": " + context.parsed.y + " medical records"
);
},
},
@ -159,10 +159,10 @@ const rekamMedisLineChartOptions = ref({
});
const auditDataPerKelompokData = computed(() => ({
labels: ["Rekam Medis", "Pemberian Tindakan", "Obat"],
labels: ["Medical Records", "Doctor Actions", "Medicine"],
datasets: [
{
label: "Jumlah Data",
label: "Data Count",
backgroundColor: "#1a2a4f",
data: stats.tamperedDataPerKelompok.value,
},
@ -200,11 +200,11 @@ const auditBarOption = {
const normalizeTableName = (tableName: string) => {
switch (tableName) {
case "rekam_medis":
return "Rekam Medis";
return "Medical Records";
case "pemberian_tindakan":
return "Pemberian Tindakan";
case "obat":
return "Obat";
return "Doctor Actions";
case "pemberian_obat":
return "Medicine Administration";
default:
return tableName;
}
@ -262,31 +262,33 @@ onMounted(() => {
<div class="bg-light w-full text-dark">
<div class="flex h-full p-2">
<Sidebar>
<PageHeader title="Dashboard" subtitle="Detail Dashboard" />
<PageHeader title="Dashboard" subtitle="Dashboard Details" />
<div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mt-4">
<div
class="bg-white p-4 rounded-lg shadow-md flex flex-col items-center"
>
<h2 class="text-lg font-semibold mb-2">Total Rekam Medis</h2>
<h2 class="text-lg font-semibold mb-2">Total Medical Records</h2>
<p class="text-3xl font-bold">{{ stats.countRekamMedis }}</p>
</div>
<div
class="bg-white p-4 rounded-lg shadow-md flex flex-col items-center"
>
<h2 class="text-lg font-semibold mb-2">Total Tindakan Dokter</h2>
<h2 class="text-lg font-semibold mb-2">Total Doctor Actions</h2>
<p class="text-3xl font-bold">{{ stats.countTindakanDokter }}</p>
</div>
<div
class="bg-white p-4 rounded-lg shadow-md flex flex-col items-center"
>
<h2 class="text-lg font-semibold mb-2">Total Obat</h2>
<h2 class="text-lg font-semibold mb-2">
Total Medicine Administration
</h2>
<p class="text-3xl font-bold">{{ stats.countObat }}</p>
</div>
<div
class="bg-white p-4 rounded-lg shadow-md flex flex-col items-center"
>
<h2 class="text-lg font-semibold mb-2">Total Data Audit</h2>
<h2 class="text-lg font-semibold mb-2">Total Audit Data</h2>
<p class="text-3xl font-bold">
{{ stats.auditNonTampered.value + stats.auditTampered.value }}
</p>
@ -297,7 +299,7 @@ onMounted(() => {
class="flex flex-col flex-2 mt-4 bg-white p-4 rounded-lg shadow-md min-w-0"
>
<h5 class="flex-1 text-lg font-semibold text-center">
Jumlah Rekam Medis Baru 7 Hari Terakhir
New Medical Records in Last 7 Days
</h5>
<div class="flex-5 w-full">
<Line
@ -310,13 +312,13 @@ onMounted(() => {
to="/rekam-medis"
class="text-sm hover:opacity-75 transition-all"
>
Lihat Data Rekam Medis >>
View Medical Records >>
</RouterLink>
</div>
</div>
<div class="flex-1 mt-4 bg-white p-4 rounded-lg shadow-md min-w-0">
<h5 class="text-lg font-semibold mb-2 text-center">
Data Audit Trail
Audit Trail Summary
</h5>
<div class="w-full">
<Doughnut
@ -339,7 +341,7 @@ onMounted(() => {
to="/audit-trail"
class="text-sm hover:opacity-75 transition-all"
>
Lihat Detail Audit Trail >>
View Audit Trail Details >>
</RouterLink>
</div>
</div>
@ -347,17 +349,17 @@ onMounted(() => {
<div class="flex gap-x-4">
<div class="mt-4 flex-2 bg-white p-4 rounded-lg shadow-md h-fit">
<h5 class="text-lg font-semibold text-center">
Data yang perlu divalidasi (<span
class="font-bold text-red-400"
>{{ validasiData.totalCount }}</span
Data pending validation (<span class="font-bold text-red-400">{{
validasiData.totalCount
}}</span
>)
</h5>
<table class="w-full mt-4">
<thead>
<tr class="text-left">
<th class="pb-2">Kelompok Data</th>
<th class="pb-2">Tipe Aksi</th>
<th class="pb-2">ID User</th>
<th class="pb-2">Data Group</th>
<th class="pb-2">Action Type</th>
<th class="pb-2">User ID</th>
</tr>
</thead>
<tbody class="">
@ -383,13 +385,13 @@ onMounted(() => {
to="/validasi"
class="text-sm hover:opacity-75 transition-all"
>
Lihat Detail Validasi Data >>
View Validation Details >>
</RouterLink>
</div>
</div>
<div class="mt-4 flex-1 bg-white p-4 rounded-lg shadow-md">
<h5 class="text-lg font-semibold text-center">
Tampered data per kelompok data
Tampered data per data group
</h5>
<div class="h-64 w-full">
<Bar

View File

@ -139,11 +139,11 @@ const deriveType = (entry: Partial<AuditLogEntry>): AuditLogType => {
};
const typeLabelMap: Record<AuditLogType, string> = {
rekam_medis: "Rekam Medis",
tindakan: "Tindakan",
obat: "Obat",
rekam_medis: "Medical Records",
tindakan: "Actions",
obat: "Medicine",
proof: "Proof",
unknown: "Tidak Diketahui",
unknown: "Unknown",
};
const normalizeEntry = (entry: any): AuditLogEntry => {
@ -343,17 +343,17 @@ onMounted(async () => {
});
socket.on("connect", () => {
console.log("Berhasil terhubung ke WebSocket:", socket.id);
console.log("Successfully connected to WebSocket:", socket.id);
});
socket.on("audit.progress", (data) => {
console.log("Menerima progres:", data);
console.log("Receiving progress:", data);
status.value = "RUNNING";
progres.value = data.progress_count;
});
socket.on("audit.complete", (data) => {
console.log("Menerima selesai:", data);
console.log("Receiving completion:", data);
fetchData();
status.value = "COMPLETED";
});
@ -379,7 +379,7 @@ onBeforeUnmount(() => {
<div class="bg-light w-full text-dark">
<div class="flex h-full p-2">
<Sidebar>
<PageHeader title="Audit Trail" subtitle="Riwayat Log Blockchain" />
<PageHeader title="Audit Trail" subtitle="Blockchain Log History" />
<div
class="collapse collapse-arrow bg-white border-white border shadow-sm mb-2"
@ -394,37 +394,37 @@ onBeforeUnmount(() => {
<div class="flex gap-x-4">
<div class="flex gap-x-4 items-end">
<div class="h-full">
<label for="jenis_kelamin" class="font-bold">Tipe Data</label>
<label for="jenis_kelamin" class="font-bold">Data Type</label>
<select
v-model="filters.type"
class="select bg-white border border-gray-300 mt-1"
>
<option disabled selected value="initial">
Pilih Tipe Data
Select Data Type
</option>
<option value="rekam_medis">Rekam Medis</option>
<option value="tindakan">Tindakan</option>
<option value="obat">Obat</option>
<option value="rekam_medis">Medical Records</option>
<option value="tindakan">Doctors Actions</option>
<option value="obat">Medicine Administration</option>
<option value="proof">Proof</option>
<option value="all">Semua Tipe</option>
<option value="all">All Types</option>
</select>
</div>
</div>
<div class="flex gap-x-4 items-end">
<div class="h-full">
<label for="jenis_kelamin" class="font-bold"
>Status Manipulasi</label
>Tampering Status</label
>
<select
v-model="filters.tampered"
class="select bg-white border border-gray-300 mt-1"
>
<option disabled selected value="initial">
Pilih Status Manipulasi
Select Tampering Status
</option>
<option value="tampered">Tampered</option>
<option value="non_tampered">Non-tampered</option>
<option value="all">Semua</option>
<option value="all">All</option>
</select>
</div>
</div>
@ -448,14 +448,14 @@ onBeforeUnmount(() => {
<div class="flex flex-col w-full gap-2 md:flex-row md:items-center">
<SearchInput
v-model="searchId"
placeholder="Cari berdasarkan ID Log"
placeholder="Search by Log ID (e.g., REKAM_12345)"
@search="handleSearch"
/>
<div class="flex items-center gap-2 md:ml-4">
<SortDropdown
v-model="sortBy"
:options="SORT_OPTIONS.AUDIT_TRAIL"
label="Urut berdasarkan:"
label="Sort by:"
@change="handleSortChange"
/>
<button
@ -481,11 +481,11 @@ onBeforeUnmount(() => {
</div>
<DialogConfirm
ref="auditDialog"
title="Konfirmasi"
message="Apakah anda yakin ingin menjalankan audit? (Proses ini mungkin
memakan waktu lama)"
confirm-text="Iya, Jalankan"
cancel-text="Batal"
title="Confirmation"
message="Are you sure you want to run the audit? (This process may
take a long time)"
confirm-text="Yes, Run"
cancel-text="Cancel"
@confirm="runAuditTrail"
/>
<ButtonDark
@ -494,7 +494,7 @@ onBeforeUnmount(() => {
status === 'STARTING' ||
status === 'RUNNING'
"
text="Lakukan Audit"
text="Run Audit"
@click="showAuditModal"
/>
</div>
@ -509,11 +509,9 @@ onBeforeUnmount(() => {
<div class="h-48 flex justify-center items-center flex-col gap-4">
<span class="loading loading-ring loading-xl"></span>
<div class="text-center">
<p class="font-semibold mb-2">
Proses audit sedang berjalan...
</p>
<p class="font-semibold mb-2">Audit process is running...</p>
<p class="text-sm text-gray-600">
{{ progres }} data telah diperiksa
{{ progres }} data has been checked
</p>
</div>
</div>
@ -523,7 +521,7 @@ onBeforeUnmount(() => {
:data="logs"
:columns="AUDIT_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Tidak ada log audit"
empty-message="No audit logs available"
:is-aksi="false"
/>
<PaginationControls

View File

@ -103,23 +103,23 @@ const validate = (): CreateObatFormErrors => {
const validationErrors: CreateObatFormErrors = {};
if (!data.value.id_visit.trim()) {
validationErrors.id_visit = "ID Visit wajib diisi";
validationErrors.id_visit = "ID Visit is required";
}
if (!data.value.obat.trim()) {
validationErrors.obat = "Nama obat wajib diisi";
validationErrors.obat = "Medicine name is required";
}
const jumlahRaw = data.value.jumlah_obat.trim();
if (!jumlahRaw) {
validationErrors.jumlah_obat = "Jumlah obat wajib diisi";
validationErrors.jumlah_obat = "Medicine quantity is required";
} else {
const jumlahNumber = Number(jumlahRaw);
if (!Number.isInteger(jumlahNumber) || jumlahNumber <= 0) {
validationErrors.jumlah_obat =
"Jumlah obat harus berupa bilangan bulat positif";
"Medicine quantity must be a positive integer";
}
}
@ -164,7 +164,7 @@ const handleSubmit = async () => {
} catch (error) {
console.error("Failed to create obat:", error);
submitError.value =
(error as ApiError)?.message || "Gagal menyimpan data obat.";
(error as ApiError)?.message || "Failed to save medicine data.";
} finally {
isLoading.value = false;
}
@ -194,7 +194,7 @@ watch(
);
onMounted(() => {
document.title = "Tambah Pemberian Obat - Hospital Log";
document.title = "Add Medicine Administration - Hospital Log";
fetchVisitOptions("");
});
</script>
@ -203,19 +203,22 @@ onMounted(() => {
<div class="bg-light w-full text-dark">
<div class="flex h-full p-2">
<Sidebar>
<PageHeader title="Pemberian Obat" subtitle="Tambah Pemberian Obat" />
<PageHeader
title="Medicine Administration"
subtitle="Add Medicine Administration"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
<div class="flex flex-col px-4 py-4 justify-between gap-4">
<div class="breadcrumbs text-sm">
<ul>
<li class="font-bold">
<RouterLink to="/obat">Pemberian Obat</RouterLink>
<RouterLink to="/obat">Medicine Administration</RouterLink>
</li>
<li>Tambah Pemberian Obat</li>
<li>Add Medicine Administration</li>
</ul>
</div>
<h5 class="font-bold">Tambah Pemberian Obat</h5>
<h5 class="font-bold">Add Medicine Administration</h5>
<div v-if="isSuccess" role="alert" class="alert alert-success">
<svg
@ -231,7 +234,7 @@ onMounted(() => {
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Berhasil menambahkan data obat!</span>
<span>Successfully added medicine data!</span>
</div>
<div v-if="submitError" role="alert" class="alert alert-error">
@ -256,7 +259,7 @@ onMounted(() => {
v-if="isLoading"
>
<span class="loading loading-ring loading-xl"></span>
<h5>Mohon tunggu, sedang menyimpan data obat...</h5>
<h5>Please wait, saving medicine data...</h5>
</div>
<form v-else @submit.prevent="handleSubmit" class="text-sm">
@ -264,7 +267,7 @@ onMounted(() => {
class="mt-2"
label="ID Visit"
type="text"
placeholder="Masukkan ID visit"
placeholder="Enter visit ID"
v-model="data.id_visit"
:error="errors.id_visit || null"
list="visit-options"
@ -280,25 +283,25 @@ onMounted(() => {
</datalist>
<FieldInput
class="mt-4"
label="Obat"
label="Medicine"
type="text"
placeholder="Masukkan nama obat"
placeholder="Enter medicine name"
v-model="data.obat"
:error="errors.obat || null"
/>
<FieldInput
class="mt-4"
label="Jumlah Obat"
label="Medicine Quantity"
type="text"
placeholder="Masukkan jumlah obat"
placeholder="Enter medicine quantity"
v-model="data.jumlah_obat"
:error="errors.jumlah_obat || null"
/>
<FieldInput
class="mt-4"
label="Aturan Pakai"
label="Usage Instructions"
type="text"
placeholder="Masukkan aturan pakai"
placeholder="Enter usage instructions"
v-model="data.aturan_pakai"
:error="errors.aturan_pakai || null"
/>
@ -308,7 +311,7 @@ onMounted(() => {
>
<ButtonDark
type="submit"
:text="isLoading ? 'Menyimpan...' : 'Simpan Obat'"
:text="isLoading ? 'Saving...' : 'Save Medicine'"
:isLoading="isLoading"
/>
</div>

View File

@ -127,7 +127,7 @@ const fetchLogData = async () => {
onMounted(async () => {
await fetchData();
await fetchLogData();
document.title = "Rekam Medis Detail - Hospital Log";
document.title = "Medicine Administration Detail - Hospital Log";
});
</script>
@ -136,8 +136,8 @@ onMounted(async () => {
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Pemberian Obat"
:subtitle="`Detail Pemberian Obat. ID ${route.params.id}`"
title="Medicine Administration"
:subtitle="`Medicine Administration Detail. ID ${route.params.id}`"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
@ -145,21 +145,21 @@ onMounted(async () => {
<div class="breadcrumbs text-sm">
<ul>
<li class="font-bold">
<RouterLink to="/obat">Pemberian Obat</RouterLink>
<RouterLink to="/obat">Medicine Administration</RouterLink>
</li>
<li>Detail Pemberian Obat {{ route.params.id }}</li>
<li>Medicine Administration Detail {{ route.params.id }}</li>
</ul>
</div>
<h5 class="font-bold">Detail Pemberian Obat</h5>
<h5 class="font-bold">Medicine Administration Detail</h5>
<div class="text-sm">
<p>ID: {{ data?.id }}</p>
<p>ID Visit: {{ data?.id_visit }}</p>
<p>Obat: {{ data?.obat }}</p>
<p>Jumlah Obat: {{ data?.jumlah_obat }}</p>
<p>Aturan Pakai: {{ data?.aturan_pakai }}</p>
<p>Medicine: {{ data?.obat }}</p>
<p>Medicine Quantity: {{ data?.jumlah_obat }}</p>
<p>Usage Instructions: {{ data?.aturan_pakai }}</p>
</div>
<hr />
<h5 class="font-bold">Log Perubahan</h5>
<h5 class="font-bold">Change Log</h5>
<div role="alert" class="alert alert-error" v-if="isTampered">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -174,13 +174,13 @@ onMounted(async () => {
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Peringatan! Manipulasi Data Terdeteksi.</span>
<span>Warning! Data Manipulation Detected.</span>
</div>
<DataTable
:data="dataLog"
:columns="LOG_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Belum terdapat log perubahan"
empty-message="No change logs available yet"
:is-aksi="false"
/>
</div>

View File

@ -51,15 +51,15 @@ const sortOrder = ref<"asc" | "desc">(
const tableColumns = [
{ key: "id" as keyof ObatData, label: "#", class: "text-dark" },
{ key: "id_visit" as keyof ObatData, label: "ID Visit", class: "text-dark" },
{ key: "obat" as keyof ObatData, label: "Obat", class: "text-dark" },
{ key: "obat" as keyof ObatData, label: "Medicine", class: "text-dark" },
{
key: "jumlah_obat" as keyof ObatData,
label: "Jumlah Obat",
label: "Medicine Quantity",
class: "text-dark",
},
{
key: "aturan_pakai" as keyof ObatData,
label: "Aturan Pakai",
label: "Usage Instructions",
class: "text-dark",
},
];
@ -207,7 +207,10 @@ onMounted(async () => {
<div class="bg-light w-full text-dark">
<div class="flex h-full p-2">
<Sidebar>
<PageHeader title="Obat" subtitle="Manajemen Obat" />
<PageHeader
title="Medicine Administration"
subtitle="Medicine Management"
/>
<div class="bg-white rounded-xl shadow-md">
<div
class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 px-4 pt-4 pb-2"
@ -215,14 +218,14 @@ onMounted(async () => {
<div class="flex flex-col w-full gap-2 md:flex-row md:items-center">
<SearchInput
v-model="searchObat"
placeholder="Cari berdasarkan Obat"
placeholder="Search by Medicine Administration ID"
@search="handleSearch"
/>
<div class="flex items-center gap-2 md:ml-4">
<SortDropdown
v-model="sortBy"
:options="SORT_OPTIONS.OBAT"
label="Urut berdasarkan:"
label="Sort by:"
@change="handleSortChange"
/>
<button
@ -250,7 +253,7 @@ onMounted(async () => {
to="/pemberian-obat-add"
class="btn bg-dark btn-sm self-start md:self-auto"
>
Tambah Obat
Add Medicine
</RouterLink>
</div>
@ -272,7 +275,7 @@ onMounted(async () => {
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Data obat berhasil dikirim untuk validasi penghapusan</span>
<span>Medicine data successfully sent for deletion validation</span>
</div>
<!-- Data Table -->
@ -280,7 +283,7 @@ onMounted(async () => {
:data="data"
:columns="tableColumns"
:is-loading="api.isLoading.value"
empty-message="Tidak ada data obat"
empty-message="No medicine data available"
@details="handleDetails"
@update="handleUpdate"
@delete="handleDelete"

View File

@ -110,7 +110,7 @@ const handleSubmit = async () => {
isLoading.value = true;
if (id.value === data.value.id) {
if (id.value !== parseInt(route.params.id as string)) {
alert("ID tidak valid. Data akan dimuat ulang.");
alert("Invalid ID. Data will be reloaded.");
await fetchData();
return;
}
@ -181,7 +181,7 @@ const fetchLogData = async () => {
onMounted(async () => {
await fetchData();
await fetchLogData();
document.title = "Rekam Medis Detail - Hospital Log";
document.title = "Medicine Administration Detail - Hospital Log";
});
</script>
@ -190,8 +190,8 @@ onMounted(async () => {
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Pemberian Obat"
:subtitle="`Update Pemberian Obat. ID ${route.params.id}`"
title="Medicine Administration"
:subtitle="`Update Medicine Administration. ID ${route.params.id}`"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
@ -199,12 +199,12 @@ onMounted(async () => {
<div class="breadcrumbs text-sm">
<ul>
<li class="font-bold">
<RouterLink to="/obat">Pemberian Obat</RouterLink>
<RouterLink to="/obat">Medicine Administration</RouterLink>
</li>
<li>Update Pemberian Obat {{ route.params.id }}</li>
<li>Update Medicine Administration {{ route.params.id }}</li>
</ul>
</div>
<h5 class="font-bold">Update Pemberian Obat</h5>
<h5 class="font-bold">Update Medicine Administration</h5>
<div v-if="isSuccess" role="alert" class="alert alert-success">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -219,7 +219,7 @@ onMounted(async () => {
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Berhasil memperbarui data!</span>
<span>Successfully updated data!</span>
</div>
<div>
<form @submit.prevent="handleSubmit">
@ -239,40 +239,40 @@ onMounted(async () => {
/>
<FieldInput
class="mt-2"
label="Obat"
label="Medicine"
type="text"
v-model="data.obat"
:is-disabled="false"
/>
<FieldInput
class="mt-2"
label="Jumlah Obat"
label="Medicine Quantity"
type="text"
v-model="data.jumlah_obat"
:is-disabled="false"
/>
<FieldInput
class="mt-2"
label="Aturan Pakai"
label="Usage Instructions"
type="text"
placeholder="Belum ada aturan pakai."
placeholder="No usage instructions yet."
v-model="data.aturan_pakai"
:is-disabled="false"
/>
<div class="flex justify-end">
<ButtonDark
type="submit"
:text="isLoading ? 'Menyimpan...' : 'Simpan Perubahan'"
:text="isLoading ? 'Saving...' : 'Save Changes'"
class="mt-4"
:isLoading="isLoading"
>
Simpan Perubahan
Save Changes
</ButtonDark>
</div>
</form>
</div>
<hr />
<h5 class="font-bold">Log Perubahan</h5>
<h5 class="font-bold">Change Log</h5>
<div role="alert" class="alert alert-error" v-if="isTampered">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -287,13 +287,13 @@ onMounted(async () => {
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Peringatan! Manipulasi Data Terdeteksi.</span>
<span>Warning! Data tampering detected.</span>
</div>
<DataTable
:data="dataLog"
:columns="LOG_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Belum terdapat log perubahan"
empty-message="No change logs yet"
:is-aksi="false"
/>
</div>

View File

@ -128,7 +128,7 @@ const handleSubmit = async () => {
} catch (error) {
console.error("Failed to create tindakan:", error);
submitError.value =
(error as ApiError)?.message || "Gagal menyimpan data tindakan.";
(error as ApiError)?.message || "Failed to save action data.";
} finally {
isLoading.value = false;
}
@ -158,7 +158,7 @@ watch(
);
onMounted(() => {
document.title = "Tambah Pemberian Tindakan - Hospital Log";
document.title = "Add Doctor Action - Hospital Log";
fetchVisitOptions("");
});
</script>
@ -167,10 +167,7 @@ onMounted(() => {
<div class="bg-light w-full text-dark">
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Pemberian Tindakan"
subtitle="Tambah Pemberian Tindakan"
/>
<PageHeader title="Doctor Action" subtitle="Add Doctor Action" />
<div class="bg-white rounded-xl shadow-md text-dark">
<div class="flex flex-col px-4 py-4 justify-between gap-4">
@ -178,13 +175,13 @@ onMounted(() => {
<ul>
<li class="font-bold">
<RouterLink to="/pemberian-tindakan"
>Pemberian Tindakan</RouterLink
>Doctor Action</RouterLink
>
</li>
<li>Tambah Pemberian Tindakan</li>
<li>Add Doctor Action</li>
</ul>
</div>
<h5 class="font-bold">Tambah Pemberian Tindakan</h5>
<h5 class="font-bold">Add Doctor Action</h5>
<div v-if="isSuccess" role="alert" class="alert alert-success">
<svg
@ -200,7 +197,7 @@ onMounted(() => {
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Berhasil menambahkan data tindakan!</span>
<span>Successfully added action data!</span>
</div>
<div v-if="submitError" role="alert" class="alert alert-error">
@ -225,15 +222,15 @@ onMounted(() => {
v-if="isLoading"
>
<span class="loading loading-ring loading-xl"></span>
<h5>Mohon tunggu, sedang menyimpan data tindakan...</h5>
<h5>Please wait, saving action data...</h5>
</div>
<form v-else @submit.prevent="handleSubmit" class="text-sm">
<FieldInput
class="mt-2"
label="ID Visit"
label="Visit ID"
type="text"
placeholder="Masukkan ID visit"
placeholder="Enter visit ID"
v-model="data.id_visit"
:error="errors.id_visit || null"
list="visit-options"
@ -249,16 +246,16 @@ onMounted(() => {
</datalist>
<FieldInput
class="mt-4"
label="Tindakan"
label="Doctor Action"
type="text"
placeholder="Masukkan nama tindakan"
placeholder="Enter action name"
v-model="data.tindakan"
:error="errors.tindakan || null"
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Kategori Tindakan"
label="Kategori Tindakan"
placeholder="Enter doctor action category"
label="Doctor Action Category"
list="kategori_tindakan"
v-model="data.kategori_tindakan"
:error="errors.kategori_tindakan || null"
@ -274,8 +271,8 @@ onMounted(() => {
</datalist>
<FieldInput
class="mt-2"
placeholder="Masukkan Kelompok Tindakan"
label="Kelompok Tindakan"
placeholder="Enter doctor action group"
label="Doctor Action Group"
list="kelompok_tindakan"
v-model="data.kelompok_tindakan"
:error="errors.kelompok_tindakan || null"
@ -294,7 +291,7 @@ onMounted(() => {
>
<ButtonDark
type="submit"
:text="isLoading ? 'Menyimpan...' : 'Simpan Tindakan'"
:text="isLoading ? 'Saving...' : 'Save Action'"
:isLoading="isLoading"
/>
</div>

View File

@ -128,7 +128,7 @@ const fetchLogData = async () => {
onMounted(async () => {
await fetchData();
await fetchLogData();
document.title = `Detail Pemberian Tindakan - ID ${route.params.id}`;
document.title = `Doctor Action Details - ID ${route.params.id}`;
});
</script>
@ -137,8 +137,8 @@ onMounted(async () => {
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Pemberian Tindakan"
:subtitle="`Detail Pemberian Tindakan ${route.params.id}`"
title="Doctor Actions"
:subtitle="`Doctor Action Details ${route.params.id}`"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
@ -147,28 +147,28 @@ onMounted(async () => {
<ul>
<li class="font-bold">
<RouterLink to="/pemberian-tindakan">
Pemberian Tindakan
Doctor Actions
</RouterLink>
</li>
<li>Detail Pemberian Tindakan {{ route.params.id }}</li>
<li>Doctor Action Details {{ route.params.id }}</li>
</ul>
</div>
<h5 class="font-bold">Detail Pemberian Tindakan</h5>
<h5 class="font-bold">Doctor Action Details</h5>
<div class="text-sm">
<p>ID: {{ tindakan?.id }}</p>
<p>ID Visit: {{ tindakan?.id_visit }}</p>
<p>Tindakan: {{ tindakan?.tindakan }}</p>
<p>Visit ID: {{ tindakan?.id_visit }}</p>
<p>Doctor Action: {{ tindakan?.tindakan }}</p>
<p>
Kategori Tindakan:
Doctor Action Category:
{{ tindakan?.kategori_tindakan || "-" }}
</p>
<p>
Kelompok Tindakan:
Doctor Action Group:
{{ tindakan?.kelompok_tindakan || "-" }}
</p>
</div>
<hr />
<h5 class="font-bold">Log Perubahan</h5>
<h5 class="font-bold">Change Log</h5>
<div role="alert" class="alert alert-error" v-if="isTampered">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -183,14 +183,14 @@ onMounted(async () => {
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Peringatan! Manipulasi Data Terdeteksi.</span>
<span>Warning! Data Manipulation Detected.</span>
</div>
<DataTable
:data="dataLog"
:columns="LOG_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Belum terdapat log perubahan"
empty-message="No change logs available yet"
:is-aksi="false"
/>
</div>

View File

@ -275,7 +275,7 @@ const handleSubmit = async () => {
} catch (error) {
console.error("Failed to update tindakan:", error);
submitError.value =
(error as ApiError)?.message || "Gagal memperbarui data tindakan.";
(error as ApiError)?.message || "Failed to update action data.";
} finally {
isSubmitting.value = false;
}
@ -312,7 +312,7 @@ onMounted(async () => {
fetchLogData(),
fetchVisitOptions(""),
]);
document.title = `Edit Pemberian Tindakan - ID ${routeId.value}`;
document.title = `Edit Doctor Action - ID ${routeId.value}`;
});
watch(
@ -342,8 +342,8 @@ watch(
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Pemberian Tindakan"
:subtitle="`Update Pemberian Tindakan ${routeId}`"
title="Doctor Actions"
:subtitle="`Update Doctor Action ${routeId}`"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
@ -352,13 +352,13 @@ watch(
<ul>
<li class="font-bold">
<RouterLink to="/pemberian-tindakan">
Pemberian Tindakan
Doctor Actions
</RouterLink>
</li>
<li>Update Pemberian Tindakan {{ routeId }}</li>
<li>Update Doctor Action {{ routeId }}</li>
</ul>
</div>
<h5 class="font-bold">Update Pemberian Tindakan</h5>
<h5 class="font-bold">Update Doctor Action</h5>
<div v-if="isSuccess" role="alert" class="alert alert-success">
<svg
@ -374,7 +374,7 @@ watch(
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Berhasil memperbarui data!</span>
<span>Successfully updated data!</span>
</div>
<div v-if="submitError" role="alert" class="alert alert-error">
@ -399,7 +399,7 @@ watch(
v-if="isInitialLoading"
>
<span class="loading loading-ring loading-xl"></span>
<h5>Memuat data tindakan...</h5>
<h5>Loading action data...</h5>
</div>
<form v-else @submit.prevent="handleSubmit" class="text-sm">
@ -412,9 +412,9 @@ watch(
/>
<FieldInput
class="mt-4"
label="ID Visit"
label="Visit ID"
type="text"
placeholder="Masukkan ID visit"
placeholder="Enter visit ID"
v-model="data.id_visit"
:error="errors.id_visit || null"
list="visit-options"
@ -430,16 +430,16 @@ watch(
</datalist>
<FieldInput
class="mt-4"
label="Tindakan"
label="Doctor Action"
type="text"
placeholder="Masukkan nama tindakan"
placeholder="Enter doctor action name"
v-model="data.tindakan"
:error="errors.tindakan || null"
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Kategori Tindakan"
label="Kategori Tindakan"
placeholder="Enter doctor action category"
label="Doctor Action Category"
list="kategori_tindakan"
v-model="data.kategori_tindakan"
:error="errors.kategori_tindakan || null"
@ -455,8 +455,8 @@ watch(
</datalist>
<FieldInput
class="mt-2"
placeholder="Masukkan Kelompok Tindakan"
label="Kelompok Tindakan"
placeholder="Enter doctor action group"
label="Doctor Action Group"
list="kelompok_tindakan"
v-model="data.kelompok_tindakan"
:error="errors.kelompok_tindakan || null"
@ -475,14 +475,14 @@ watch(
>
<ButtonDark
type="submit"
:text="isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'"
:text="isSubmitting ? 'Saving...' : 'Save Changes'"
:isLoading="isSubmitting"
/>
</div>
</form>
<hr />
<h5 class="font-bold">Log Perubahan</h5>
<h5 class="font-bold">Change Log</h5>
<div role="alert" class="alert alert-error" v-if="isTampered">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -497,14 +497,14 @@ watch(
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Peringatan! Manipulasi Data Terdeteksi.</span>
<span>Warning! Data Manipulation Detected.</span>
</div>
<DataTable
:data="dataLog"
:columns="LOG_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Belum terdapat log perubahan"
empty-message="No change logs available yet"
:is-aksi="false"
/>
</div>

View File

@ -72,25 +72,12 @@ const updateQueryParams = () => {
};
const fetchData = async () => {
console.log("Fetch Data Params :", {
take: pagination.pageSize.value.toString(),
page: pagination.page.value.toString(),
orderBy: sortBy.value,
order: sortOrder.value,
...(searchIdVisit.value && { id_visit: searchIdVisit.value }),
...(filter.value.tindakan && { tindakan: filter.value.tindakan }),
...(filter.value.kategori.length > 0
? { kategori: filter.value.kategori.join(",") }
: {}),
...(filter.value.kelompok.length > 0
? { kelompok: filter.value.kelompok.join(",") }
: {}),
});
try {
const queryParams = new URLSearchParams({
take: pagination.pageSize.value.toString(),
page: pagination.page.value.toString(),
orderBy: sortBy.value,
order: sortOrder.value,
...(searchIdVisit.value && { id_visit: searchIdVisit.value }),
...(filter.value.tindakan && { tindakan: filter.value.tindakan }),
...(filter.value.kategori.length > 0
@ -166,7 +153,10 @@ const handleSortChange = (newSortBy: string) => {
};
const toggleSortOrder = () => {
console.log(sortOrder.value);
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
pagination.reset();
fetchData();
};
const handlePageSizeChange = (newSize: number) => {
@ -191,7 +181,7 @@ const handleDelete = async (item: TindakanDokter) => {
await fetchData();
} catch (error) {
console.error("Error deleting tindakan:", error);
alert("Gagal menghapus data tindakan");
alert("Failed to delete action data");
}
};
@ -226,7 +216,7 @@ onMounted(async () => {
}
await fetchData();
document.title = "Tindakan Dokter - Hospital Log";
document.title = "Doctors Actions - Hospital Log";
});
</script>
@ -235,8 +225,8 @@ onMounted(async () => {
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Pemberian Tindakan"
subtitle="Manajemen Pemberian Tindakan"
title="Doctors Actions"
subtitle="Doctors Actions Management"
/>
<div
class="collapse collapse-arrow bg-white border-white border shadow-sm mb-2"
@ -250,19 +240,19 @@ onMounted(async () => {
<div class="collapse-content text-sm flex flex-col">
<div class="flex justify-between gap-4">
<div class="flex flex-col flex-1">
<label for="tindakan" class="font-bold">Tindakan</label>
<label for="tindakan" class="font-bold">Action</label>
<input
class="mt-1 border border-gray-200 bg-gray-50 px-3 py-2 rounded-md shadow-sm focus:border-gray-300 focus:bg-gray-100 focus:outline-0 inset-shadow-gray-300 focus:inset-shadow-xs/80"
type="text"
name="tindakan"
id="tindakan"
placeholder="Masukkan Tindakan"
placeholder="Enter Action"
v-model="filter.tindakan"
/>
</div>
<div class="h-full flex-1">
<label for="kelompok_tindakan" class="font-bold"
>Kelompok tindakan</label
>Action Group</label
>
<form class="mt-2 flex flex-wrap gap-1 items-center">
<input
@ -287,7 +277,7 @@ onMounted(async () => {
</div>
<div class="h-full mt-4">
<label for="kategori_tindakan" class="font-bold"
>Kategori Tindakan</label
>Action Category</label
>
<form class="mt-1 flex flex-wrap gap-1">
<input
@ -322,7 +312,7 @@ onMounted(async () => {
@click="handleApplyFilter"
class="btn btn-sm bg-dark hover:bg-light hover:text-dark active:inset-shadow-sm active:inset-shadow-black/50"
>
Terapkan
Apply
</button>
</div>
</div>
@ -334,14 +324,14 @@ onMounted(async () => {
<div class="flex flex-col w-full gap-2 md:flex-row md:items-center">
<SearchInput
v-model="searchIdVisit"
placeholder="Cari berdasarkan ID Visit"
placeholder="Search by Visit ID"
@search="handleSearch"
/>
<div class="flex items-center gap-2 md:ml-4">
<SortDropdown
v-model="sortBy"
:options="SORT_OPTIONS.TINDAKAN"
label="Urut berdasarkan:"
label="Sort by:"
@change="handleSortChange"
/>
<button
@ -369,7 +359,7 @@ onMounted(async () => {
to="/pemberian-tindakan/add"
class="btn btn-sm bg-dark text-light hover:bg-light hover:text-dark active:inset-shadow-sm active:inset-shadow-black/50 self-start md:self-auto"
>
Tambah Pemberian Tindakan
Add Doctors Action
</RouterLink>
</div>
@ -391,9 +381,7 @@ onMounted(async () => {
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span
>Data tindakan berhasil dikirim untuk validasi penghapusan</span
>
<span>Action data successfully sent for deletion validation</span>
</div>
<!-- Data Table -->
@ -401,7 +389,7 @@ onMounted(async () => {
:data="data"
:columns="TINDAKAN_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Tidak ada data tindakan"
empty-message="No action data available"
@details="handleDetails"
@update="handleUpdate"
@delete="handleDelete"

View File

@ -3,7 +3,6 @@ import Sidebar from "../../../components/dashboard/Sidebar.vue";
import Footer from "../../../components/dashboard/Footer.vue";
import PageHeader from "../../../components/dashboard/PageHeader.vue";
import { onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import FieldInput from "../../../components/dashboard/FieldInput.vue";
import { FILTER } from "../../../constants/pagination";
import {
@ -12,7 +11,6 @@ import {
} from "../../../validation/rekamMedis";
import { useApi } from "../../../composables/useApi";
const route = useRoute();
const api = useApi();
const data = ref<{
@ -233,7 +231,7 @@ watch(
);
onMounted(() => {
document.title = "Tambah Rekam Medis - Hospital Log";
document.title = "Add Medical Record - Hospital Log";
});
</script>
@ -242,8 +240,8 @@ onMounted(() => {
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Rekam Medis"
:subtitle="`Detail Rekam Medis ${route.params.id}`"
title="Medical Record"
:subtitle="`Create New Medical Record`"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
@ -251,12 +249,12 @@ onMounted(() => {
<div class="breadcrumbs text-sm">
<ul>
<li class="font-bold">
<RouterLink to="/rekam-medis">Rekam Medis</RouterLink>
<RouterLink to="/rekam-medis">Medical Record</RouterLink>
</li>
<li>Tambah Rekam Medis</li>
<li>Add Medical Record</li>
</ul>
</div>
<h5 class="font-bold">Tambah Rekam Medis</h5>
<h5 class="font-bold">Add Medical Record</h5>
<div v-if="isSuccess" role="alert" class="alert alert-success">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -271,14 +269,14 @@ onMounted(() => {
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Berhasil menambahkan rekam medis!</span>
<span>Successfully added medical record!</span>
</div>
<div
class="flex h-screen flex-col justify-center gap-y-2 items-center"
v-if="isLoading"
>
<span class="loading loading-ring loading-xl"></span>
<h5>Mohon tunggu, sedang menambahkan rekam medis...</h5>
<h5>Please wait, adding medical record...</h5>
</div>
<form
@submit.prevent="handleSubmit"
@ -294,28 +292,28 @@ onMounted(() => {
<div
class="collapse-title font-semibold text-base text-dark"
>
Identitas Pasien
Patient Identity
</div>
<div class="collapse-content">
<FieldInput
placeholder="Masukkan No. Rekam Medis"
label="No Rekam Medis"
placeholder="Enter Medical Record Number"
label="Medical Record Number"
v-model="data.no_rm"
:error="errors.no_rm"
:readonly="false"
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Nama Pasien"
label="Nama Pasien"
placeholder="Enter Patient Name"
label="Patient Name"
v-model="data.nama_pasien"
:error="errors.nama_pasien"
:readonly="false"
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Umur"
label="Umur"
placeholder="Enter Age"
label="Age"
type="text"
v-model="data.umur"
:error="errors.umur"
@ -323,7 +321,7 @@ onMounted(() => {
/>
<div class="flex flex-col mt-2">
<label for="jenis_kelamin" class="font-bold"
>Jenis Kelamin</label
>Gender</label
>
<select
v-model="data.jenis_kelamin"
@ -336,10 +334,10 @@ onMounted(() => {
]"
>
<option disabled selected value="initial">
Pilih Jenis Kelamin
Select Gender
</option>
<option value="laki-laki">Laki-laki</option>
<option value="perempuan">Perempuan</option>
<option value="laki-laki">Male</option>
<option value="perempuan">Female</option>
</select>
<p
v-if="errors.jenis_kelamin"
@ -350,7 +348,7 @@ onMounted(() => {
</div>
<div class="flex flex-col mt-2">
<label for="golongan_darah" class="font-bold"
>Golongan Darah</label
>Blood Type</label
>
<div
class="filter mt-1 flex flex-wrap justify-start items-center"
@ -382,16 +380,16 @@ onMounted(() => {
</div>
<FieldInput
class="mt-2"
placeholder="Masukkan Pekerjaan"
label="Pekerjaan"
placeholder="Enter Occupation (e.g., Teacher, Engineer, Student)"
label="Occupation"
v-model="data.pekerjaan"
:error="errors.pekerjaan"
:readonly="false"
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Suku"
label="Suku"
placeholder="Enter Ethnicity"
label="Ethnicity"
v-model="data.suku"
:error="errors.suku"
:readonly="false"
@ -409,21 +407,21 @@ onMounted(() => {
<div
class="collapse-title font-semibold text-base text-dark"
>
Tanda-Tanda Vital
Vital Signs
</div>
<div class="collapse-content">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4">
<FieldInput
placeholder="Masukkan Nadi (bpm)"
label="Nadi (bpm)"
placeholder="Enter Pulse (bpm)"
label="Pulse (bpm)"
type="text"
v-model="data.nadi"
:error="errors.nadi"
:readonly="false"
/>
<FieldInput
placeholder="Masukkan Nafas (per menit)"
label="Nafas (per menit)"
placeholder="Enter Respiration (per minute)"
label="Respiration (per minute)"
type="text"
v-model="data.nafas"
:error="errors.nafas"
@ -431,8 +429,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Sistolik (mmHg)"
label="Tekanan Darah Sistolik (mmHg)"
placeholder="Enter Systolic Blood Pressure (mmHg)"
label="Systolic Blood Pressure (mmHg)"
type="text"
v-model="data.sistolik"
:error="errors.sistolik"
@ -440,8 +438,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Diastolik (mmHg)"
label="Tekanan Darah Diastolik (mmHg)"
placeholder="Enter Diastolic Blood Pressure (mmHg)"
label="Diastolic Blood Pressure (mmHg)"
type="text"
v-model="data.diastolik"
:error="errors.diastolik"
@ -449,8 +447,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Suhu (°C)"
label="Suhu Tubuh (°C)"
placeholder="Enter Body Temperature (°C)"
label="Body Temperature (°C)"
type="text"
v-model="data.suhu"
:error="errors.suhu"
@ -458,8 +456,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Tinggi Badan (cm)"
label="Tinggi Badan (cm)"
placeholder="Enter Height (cm)"
label="Height (cm)"
type="text"
v-model="data.tinggi_badan"
:error="errors.tinggi_badan"
@ -467,8 +465,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Berat Badan (kg)"
label="Berat Badan (kg)"
placeholder="Enter Weight (kg)"
label="Weight (kg)"
type="text"
v-model="data.berat_badan"
:error="errors.berat_badan"
@ -486,7 +484,7 @@ onMounted(() => {
>
<input type="checkbox" checked />
<div class="collapse-title font-semibold text-base text-dark">
Informasi Medis
Medical Information
</div>
<div class="collapse-content">
<div class="flex flex-col">
@ -494,7 +492,7 @@ onMounted(() => {
<textarea
id="anamnese"
v-model="data.anamnese"
placeholder="Masukkan Anamnese"
placeholder="Enter Anamnese"
:class="[
'textarea bg-white mt-1 min-h-[100px] border',
errors.anamnese
@ -509,18 +507,18 @@ onMounted(() => {
</div>
<FieldInput
class="mt-2"
placeholder="Masukkan Kode Diagnosa"
label="Kode Diagnosa"
placeholder="Enter Diagnosis Code"
label="Diagnosis Code"
v-model="data.kode_diagnosa"
:error="errors.kode_diagnosa"
:readonly="false"
/>
<div class="flex flex-col mt-2">
<label for="diagnosa" class="font-bold">Diagnosa</label>
<label for="diagnosa" class="font-bold">Diagnosis</label>
<textarea
id="diagnosa"
v-model="data.diagnosa"
placeholder="Masukkan Diagnosa"
placeholder="Enter Diagnosis"
:class="[
'textarea bg-white mt-1 min-h-[100px] border',
errors.diagnosa
@ -535,8 +533,8 @@ onMounted(() => {
</div>
<FieldInput
class="mt-2"
placeholder="Masukkan Jenis Kasus"
label="Jenis Kasus"
placeholder="Enter Case Type"
label="Case Type"
v-model="data.jenis_kasus"
:error="errors.jenis_kasus"
:readonly="false"
@ -551,12 +549,12 @@ onMounted(() => {
>
<input type="checkbox" checked />
<div class="collapse-title font-semibold text-base text-dark">
Tindak Lanjut
Follow-up
</div>
<div class="collapse-content">
<FieldInput
placeholder="Masukkan Tindak Lanjut"
label="Tindak Lanjut"
placeholder="Enter Follow-up"
label="Follow-up"
list="tindak_lanjut"
v-model="data.tindak_lanjut"
:error="errors.tindak_lanjut"
@ -588,7 +586,7 @@ onMounted(() => {
isLoading ? 'loading loading-spinner loading-xs' : ''
"
></span>
{{ isLoading ? "Menyimpan..." : "Simpan Rekam Medis" }}
{{ isLoading ? "Saving..." : "Save Medical Record" }}
</button>
</div>
</form>

View File

@ -98,7 +98,7 @@ const fetchLogData = async () => {
onMounted(async () => {
await fetchData();
await fetchLogData();
document.title = "Rekam Medis Detail - Hospital Log";
document.title = "Medical Record Detail - Hospital Log";
});
</script>
@ -107,8 +107,8 @@ onMounted(async () => {
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Rekam Medis"
:subtitle="`Detail Rekam Medis ${route.params.id}`"
title="Medical Records"
:subtitle="`Medical Record Detail ${route.params.id}`"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
@ -116,64 +116,60 @@ onMounted(async () => {
<div class="breadcrumbs text-sm">
<ul>
<li class="font-bold">
<RouterLink to="/rekam-medis">Rekam Medis</RouterLink>
<RouterLink to="/rekam-medis">Medical Records</RouterLink>
</li>
<li>Detail Rekam Medis {{ route.params.id }}</li>
<li>Medical Record {{ route.params.id }} Detail</li>
</ul>
</div>
<h5 class="font-bold">Detail Rekam Medis</h5>
<h5 class="font-bold">Medical Record Detail</h5>
<div class="text-sm">
<p>ID Visit: {{ data?.id_visit }}</p>
<p>Waktu Visit: {{ data?.waktu_visit }}</p>
<p>Nomor Rekam Medis: {{ data?.no_rm }}</p>
<p>Nama Pasien: {{ data?.nama_pasien }}</p>
<p>Umur: {{ data?.umur }} tahun</p>
<p>Visit ID: {{ data?.id_visit }}</p>
<p>Visit Time: {{ data?.waktu_visit }}</p>
<p>Medical Record Number: {{ data?.no_rm }}</p>
<p>Patient Name: {{ data?.nama_pasien }}</p>
<p>Age: {{ data?.umur }} years</p>
<p>
Jenis Kelamin:
{{ data?.jenis_kelamin == "L" ? "Laki-laki" : "Perempuan" }}
Gender:
{{ data?.jenis_kelamin == "L" ? "Male" : "Female" }}
</p>
<p>
Golongan Darah:
{{
data?.gol_darah !== "-" ? data?.gol_darah : "Belum Diketahui"
}}
Blood Type:
{{ data?.gol_darah !== "-" ? data?.gol_darah : "Unknown" }}
</p>
<p>
Pekerjaan:
Occupation:
{{ data?.pekerjaan ? data?.pekerjaan : "-" }}
</p>
<p>
Suku:
Ethnicity:
{{ data?.suku ? data?.suku : "-" }}
</p>
<p>Anamnese: {{ data?.anamnese ? data?.anamnese : "-" }}</p>
<p>Anamnesis: {{ data?.anamnese ? data?.anamnese : "-" }}</p>
<p>
Kode Diagnosa:
Diagnosis Code:
{{ data?.kode_diagnosa ? data?.kode_diagnosa : "-" }}
</p>
<p>Diagnosa: {{ data?.diagnosa ? data?.diagnosa : "-" }}</p>
<p>Diastolik: {{ data?.diastolik ? data?.diastolik : "-" }}</p>
<p>Sistolik: {{ data?.sistolik ? data?.sistolik : "-" }}</p>
<p>Nadi: {{ data?.nadi ? data?.nadi : "-" }}</p>
<p>Nafas: {{ data?.nafas ? data?.nafas : "-" }}</p>
<p>Suhu: {{ data?.suhu ? data?.suhu : "-" }}</p>
<p>Diagnosis: {{ data?.diagnosa ? data?.diagnosa : "-" }}</p>
<p>Diastolic: {{ data?.diastolik ? data?.diastolik : "-" }}</p>
<p>Systolic: {{ data?.sistolik ? data?.sistolik : "-" }}</p>
<p>Pulse: {{ data?.nadi ? data?.nadi : "-" }}</p>
<p>Respiration: {{ data?.nafas ? data?.nafas : "-" }}</p>
<p>Temperature: {{ data?.suhu ? data?.suhu : "-" }}</p>
<p>
Tinggi Badan:
Height:
{{ data?.tinggi_badan ? data?.tinggi_badan : "-" }}
</p>
<p>Weight: {{ data?.berat_badan ? data?.berat_badan : "-" }}</p>
<p>
Berat Badan: {{ data?.berat_badan ? data?.berat_badan : "-" }}
Case Type: {{ data?.jenis_kasus ? data?.jenis_kasus : "-" }}
</p>
<p>
Jenis Kasus: {{ data?.jenis_kasus ? data?.jenis_kasus : "-" }}
</p>
<p>
Tindak Lanjut:
Follow-up Action:
{{ data?.tindak_lanjut ? data?.tindak_lanjut : "-" }}
</p>
</div>
<hr />
<h5 class="font-bold">Log Perubahan</h5>
<h5 class="font-bold">Change Log</h5>
<div role="alert" class="alert alert-error" v-if="isTampered">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -188,14 +184,14 @@ onMounted(async () => {
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Peringatan! Manipulasi Data Terdeteksi.</span>
<span>Warning! Data tampering detected.</span>
</div>
<DataTable
:data="dataLog"
:columns="LOG_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Belum terdapat log perubahan"
empty-message="No change logs yet"
:is-aksi="false"
/>
</div>

View File

@ -283,7 +283,7 @@ const handleDelete = async (item: RekamMedis) => {
await fetchData();
} catch (error) {
console.error("Error deleting rekam medis:", error);
alert("Gagal menghapus data rekam medis");
alert("Failed to delete medical record");
}
};
@ -359,7 +359,10 @@ onBeforeUnmount(() => {
<div class="bg-light w-full text-dark">
<div class="flex h-full p-2">
<Sidebar>
<PageHeader title="Rekam Medis" subtitle="Manajemen Rekam Medis" />
<PageHeader
title="Medical Records"
subtitle="Medical Records Management"
/>
<div
class="collapse collapse-arrow bg-white border-white border shadow-sm mb-2"
>
@ -372,7 +375,7 @@ onBeforeUnmount(() => {
<div class="collapse-content text-sm flex flex-col">
<div class="flex gap-x-4 items-end">
<div>
<label for="id_visit" class="font-bold">ID Visit</label>
<label for="id_visit" class="font-bold">Visit ID</label>
<input
class="mt-1 border border-gray-200 bg-gray-50 px-3 py-2 rounded-md shadow-sm focus:border-gray-300 focus:bg-gray-100 focus:outline-0 inset-shadow-gray-300 focus:inset-shadow-xs/80"
type="text"
@ -383,21 +386,19 @@ onBeforeUnmount(() => {
/>
</div>
<div>
<label for="nama_pasien" class="font-bold">Nama Pasien</label>
<label for="nama_pasien" class="font-bold">Patient Name</label>
<input
class="mt-1 border border-gray-200 bg-gray-50 px-3 py-2 rounded-md shadow-sm focus:border-gray-300 focus:bg-gray-100 focus:outline-0 inset-shadow-gray-300 focus:inset-shadow-xs/80"
type="text"
name="nama_pasien"
id="nama_pasien"
placeholder="Masukkan Nama Pasien"
placeholder="Enter Patient Name"
v-model="filter.nama_pasien"
/>
</div>
<div class="w-6/12">
<label for="range_tanggal" class="font-bold"
>Rentang Tanggal</label
>
<label for="range_tanggal" class="font-bold">Date Range</label>
<button
popoverTarget="cally-popover1"
:class="[
@ -413,10 +414,10 @@ onBeforeUnmount(() => {
filter.rentang_tanggal.start && filter.rentang_tanggal.end
? `${new Date(
filter.rentang_tanggal.start
).toLocaleDateString("id-ID")} - ${new Date(
).toLocaleDateString("en-US")} - ${new Date(
filter.rentang_tanggal.end
).toLocaleDateString("id-ID")}`
: "Pilih rentang tanggal"
).toLocaleDateString("en-US")}`
: "Select date range"
}}
</button>
</div>
@ -455,33 +456,29 @@ onBeforeUnmount(() => {
</div>
<div class="w-full px-2 h-full">
<label class="font-bold block mb-4"
>Rentang Umur: {{ filter.rentang_umur[0] }} -
{{ filter.rentang_umur[1] }} tahun</label
>Age Range: {{ filter.rentang_umur[0] }} -
{{ filter.rentang_umur[1] }} years</label
>
<div ref="ageSliderRef" class="mb-4"></div>
</div>
</div>
<div class="flex gap-x-4 items-end mt-4">
<div class="h-full">
<label for="jenis_kelamin" class="font-bold"
>Jenis Kelamin</label
>
<label for="jenis_kelamin" class="font-bold">Gender</label>
<select
v-model="filter.jenis_kelamin"
class="select bg-white border border-gray-300 mt-1"
>
<option disabled selected value="initial">
Pilih Jenis Kelamin
Select Gender
</option>
<option value="laki-laki">Laki-laki</option>
<option value="perempuan">Perempuan</option>
<option value="semua">Semua</option>
<option value="laki-laki">Male</option>
<option value="perempuan">Female</option>
<option value="semua">All</option>
</select>
</div>
<div class="h-full">
<label for="golongan_darah" class="font-bold"
>Golongan Darah</label
>
<label for="golongan_darah" class="font-bold">Blood Type</label>
<form
class="mt-1 flex flex-wrap gap-1 justify-center items-center"
>
@ -507,21 +504,21 @@ onBeforeUnmount(() => {
<div class="flex flex-col">
<label for="kode_diagnosa" class="font-bold"
>Kode Diagnosa</label
>Diagnosis Code</label
>
<input
class="mt-1 border border-gray-200 bg-gray-50 px-3 py-2 rounded-md shadow-sm focus:border-gray-300 focus:bg-gray-100 focus:outline-0 inset-shadow-gray-300 focus:inset-shadow-xs/80"
type="text"
name="kode_diagnosa"
id="kode_diagnosa"
placeholder="Masukkan Kode Diagnosa"
placeholder="Enter Diagnosis Code"
v-model="filter.kode_diagnosa"
/>
</div>
</div>
<fieldset class="fieldset mt-4">
<label for="tindak_lanjut" class="font-bold text-sm"
>Tindak Lanjut</label
>Follow-up Action</label
>
<form class="flex flex-wrap gap-2 items-center">
<input
@ -555,7 +552,7 @@ onBeforeUnmount(() => {
@click="handleApplyFilter"
class="btn btn-sm bg-dark hover:bg-light hover:text-dark active:inset-shadow-sm active:inset-shadow-black/50"
>
Terapkan
Apply Filter
</button>
</div>
</div>
@ -567,14 +564,14 @@ onBeforeUnmount(() => {
<div class="flex flex-col w-full gap-2 md:flex-row md:items-center">
<SearchInput
v-model="searchRekamMedis"
placeholder="Cari berdasarkan Nomor Rekam Medis"
placeholder="Search by Medical Record Number"
@search="handleSearch"
/>
<div class="flex items-center gap-2 md:ml-4">
<SortDropdown
v-model="sortBy"
:options="SORT_OPTIONS.REKAM_MEDIS"
label="Urut berdasarkan:"
label="Sort by:"
@change="handleSortChange"
/>
<button
@ -602,7 +599,7 @@ onBeforeUnmount(() => {
to="/rekam-medis/tambah"
class="btn btn-sm bg-dark text-light hover:bg-light hover:text-dark active:inset-shadow-sm active:inset-shadow-black/50 self-start md:self-auto"
>
Tambah Rekam Medis
Add Medical Record
</RouterLink>
</div>
@ -625,8 +622,7 @@ onBeforeUnmount(() => {
/>
</svg>
<span
>Data rekam medis berhasil dikirim untuk validasi
penghapusan</span
>Medical record successfully sent for deletion validation</span
>
</div>
@ -635,7 +631,7 @@ onBeforeUnmount(() => {
:data="data"
:columns="REKAM_MEDIS_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Tidak ada data rekam medis"
empty-message="No medical records available"
@details="handleDetails"
@update="handleUpdate"
@delete="handleDelete"

View File

@ -206,7 +206,7 @@ const loadRekamMedis = async () => {
errors.value = {};
} catch (error) {
console.error("Failed to fetch rekam medis:", error);
fetchError.value = "Gagal memuat data rekam medis.";
fetchError.value = "Failed to load medical record.";
} finally {
isFetching.value = false;
}
@ -347,7 +347,7 @@ watch(
);
onMounted(() => {
document.title = "Edit Rekam Medis - Hospital Log";
document.title = "Edit Medical Record - Hospital Log";
loadRekamMedis();
});
</script>
@ -357,8 +357,8 @@ onMounted(() => {
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Rekam Medis"
:subtitle="`Edit Rekam Medis ${route.params.id}`"
title="Medical Record"
:subtitle="`Edit Medical Record ${route.params.id}`"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
@ -366,19 +366,19 @@ onMounted(() => {
<div class="breadcrumbs text-sm">
<ul>
<li class="font-bold">
<RouterLink to="/rekam-medis">Rekam Medis</RouterLink>
<RouterLink to="/rekam-medis">Medical Records</RouterLink>
</li>
<li>Edit Rekam Medis</li>
<li>Edit Medical Record</li>
</ul>
</div>
<h5 class="font-bold">Edit Rekam Medis</h5>
<h5 class="font-bold">Edit Medical Record</h5>
<div
class="flex h-screen flex-col justify-center gap-y-2 items-center"
v-if="isFetching"
>
<span class="loading loading-ring loading-xl"></span>
<h5>Mohon tunggu, sedang memuat rekam medis...</h5>
<h5>Please wait, loading medical record...</h5>
</div>
<div v-else>
@ -400,7 +400,7 @@ onMounted(() => {
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Berhasil memperbarui rekam medis!</span>
<span>Successfully updated medical record!</span>
</div>
<div
v-if="fetchError"
@ -410,7 +410,7 @@ onMounted(() => {
<RouterLink
class="btn btn-sm bg-dark text-light"
to="/rekam-medis"
>Kembali ke Rekam Medis</RouterLink
>Back to Medical Records</RouterLink
>
</div>
@ -419,7 +419,7 @@ onMounted(() => {
v-else-if="isSubmitting"
>
<span class="loading loading-ring loading-xl"></span>
<h5>Mohon tunggu, sedang memperbarui rekam medis...</h5>
<h5>Please wait, updating medical record...</h5>
</div>
<form v-else @submit.prevent="handleSubmit" class="text-sm">
@ -432,28 +432,28 @@ onMounted(() => {
<div
class="collapse-title font-semibold text-base text-dark"
>
Identitas Pasien
Patient Identity
</div>
<div class="collapse-content">
<FieldInput
placeholder="Masukkan No. Rekam Medis"
label="No Rekam Medis"
placeholder="Enter Medical Record Number"
label="Medical Record Number"
v-model="data.no_rm"
:error="errors.no_rm"
:readonly="false"
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Nama Pasien"
label="Nama Pasien"
placeholder="Enter Patient Name"
label="Patient Name"
v-model="data.nama_pasien"
:error="errors.nama_pasien"
:readonly="false"
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Umur"
label="Umur"
placeholder="Enter Age"
label="Age"
type="text"
v-model="data.umur"
:error="errors.umur"
@ -461,7 +461,7 @@ onMounted(() => {
/>
<div class="flex flex-col mt-2">
<label for="jenis_kelamin" class="font-bold"
>Jenis Kelamin</label
>Gender</label
>
<select
v-model="data.jenis_kelamin"
@ -474,10 +474,10 @@ onMounted(() => {
]"
>
<option disabled value="initial">
Pilih Jenis Kelamin
Select Gender
</option>
<option value="laki-laki">Laki-laki</option>
<option value="perempuan">Perempuan</option>
<option value="laki-laki">Male</option>
<option value="perempuan">Female</option>
</select>
<p
v-if="errors.jenis_kelamin"
@ -488,7 +488,7 @@ onMounted(() => {
</div>
<div class="flex flex-col mt-2">
<label for="golongan_darah" class="font-bold"
>Golongan Darah</label
>Blood Type</label
>
<div
class="filter mt-1 flex flex-wrap justify-start items-center"
@ -520,16 +520,16 @@ onMounted(() => {
</div>
<FieldInput
class="mt-2"
placeholder="Masukkan Pekerjaan"
label="Pekerjaan"
placeholder="Enter Occupation"
label="Occupation"
v-model="data.pekerjaan"
:error="errors.pekerjaan"
:readonly="false"
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Suku"
label="Suku"
placeholder="Enter Ethnicity"
label="Ethnicity"
v-model="data.suku"
:error="errors.suku"
:readonly="false"
@ -546,21 +546,21 @@ onMounted(() => {
<div
class="collapse-title font-semibold text-base text-dark"
>
Tanda-Tanda Vital
Vital Signs
</div>
<div class="collapse-content">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4">
<FieldInput
placeholder="Masukkan Nadi (bpm)"
label="Nadi (bpm)"
placeholder="Enter Pulse (bpm)"
label="Pulse (bpm)"
type="text"
v-model="data.nadi"
:error="errors.nadi"
:readonly="false"
/>
<FieldInput
placeholder="Masukkan Nafas (per menit)"
label="Nafas (per menit)"
placeholder="Enter Respiration (per minute)"
label="Respiration (per minute)"
type="text"
v-model="data.nafas"
:error="errors.nafas"
@ -568,8 +568,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Sistolik (mmHg)"
label="Tekanan Darah Sistolik (mmHg)"
placeholder="Enter Systolic (mmHg)"
label="Systolic Blood Pressure (mmHg)"
type="text"
v-model="data.sistolik"
:error="errors.sistolik"
@ -577,8 +577,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Diastolik (mmHg)"
label="Tekanan Darah Diastolik (mmHg)"
placeholder="Enter Diastolic (mmHg)"
label="Diastolic Blood Pressure (mmHg)"
type="text"
v-model="data.diastolik"
:error="errors.diastolik"
@ -586,8 +586,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Suhu (°C)"
label="Suhu Tubuh (°C)"
placeholder="Enter Temperature (°C)"
label="Body Temperature (°C)"
type="text"
v-model="data.suhu"
:error="errors.suhu"
@ -595,8 +595,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Tinggi Badan (cm)"
label="Tinggi Badan (cm)"
placeholder="Enter Height (cm)"
label="Height (cm)"
type="text"
v-model="data.tinggi_badan"
:error="errors.tinggi_badan"
@ -604,8 +604,8 @@ onMounted(() => {
/>
<FieldInput
class="mt-2"
placeholder="Masukkan Berat Badan (kg)"
label="Berat Badan (kg)"
placeholder="Enter Body Weight (kg)"
label="Body Weight (kg)"
type="text"
v-model="data.berat_badan"
:error="errors.berat_badan"
@ -622,7 +622,7 @@ onMounted(() => {
>
<input type="checkbox" checked />
<div class="collapse-title font-semibold text-base text-dark">
Informasi Medis
Medical Information
</div>
<div class="collapse-content">
<div class="flex flex-col">
@ -630,7 +630,7 @@ onMounted(() => {
<textarea
id="anamnese"
v-model="data.anamnese"
placeholder="Masukkan Anamnese"
placeholder="Enter Anamnese"
:class="[
'textarea bg-white mt-1 min-h-[100px] border',
errors.anamnese
@ -645,18 +645,18 @@ onMounted(() => {
</div>
<FieldInput
class="mt-2"
placeholder="Masukkan Kode Diagnosa"
label="Kode Diagnosa"
placeholder="Enter Diagnosis Code"
label="Diagnosis Code"
v-model="data.kode_diagnosa"
:error="errors.kode_diagnosa"
:readonly="false"
/>
<div class="flex flex-col mt-2">
<label for="diagnosa" class="font-bold">Diagnosa</label>
<label for="diagnosa" class="font-bold">Diagnosis</label>
<textarea
id="diagnosa"
v-model="data.diagnosa"
placeholder="Masukkan Diagnosa"
placeholder="Enter Diagnosis"
:class="[
'textarea bg-white mt-1 min-h-[100px] border',
errors.diagnosa
@ -671,8 +671,8 @@ onMounted(() => {
</div>
<FieldInput
class="mt-2"
placeholder="Masukkan Jenis Kasus"
label="Jenis Kasus"
placeholder="Enter Case Type"
label="Case Type"
v-model="data.jenis_kasus"
:error="errors.jenis_kasus"
:readonly="false"
@ -685,12 +685,12 @@ onMounted(() => {
>
<input type="checkbox" checked />
<div class="collapse-title font-semibold text-base text-dark">
Tindak Lanjut
Follow Up
</div>
<div class="collapse-content">
<FieldInput
placeholder="Masukkan Tindak Lanjut"
label="Tindak Lanjut"
placeholder="Enter Follow Up"
label="Follow Up"
list="tindak_lanjut"
v-model="data.tindak_lanjut"
:error="errors.tindak_lanjut"
@ -714,7 +714,7 @@ onMounted(() => {
class="btn btn-sm bg-dark text-light"
:disabled="isSubmitting"
>
Simpan Perubahan
Save Changes
</button>
</div>
</form>

View File

@ -130,15 +130,13 @@ const handleUpdate = (item: Users) => {
};
const handleDelete = async (item: Users) => {
if (
confirm(`Apakah Anda yakin ingin menghapus pengguna "${item.username}"?`)
) {
if (confirm(`Are you sure you want to delete user "${item.username}"?`)) {
try {
await api.delete(`/obat/${item.id}`);
await fetchData();
} catch (error) {
console.error("Error deleting obat:", error);
alert("Gagal menghapus data obat");
alert("Failed to delete user data");
}
}
};
@ -182,7 +180,7 @@ onMounted(async () => {
<div class="bg-light w-full text-dark">
<div class="flex h-full p-2">
<Sidebar>
<PageHeader title="Users" subtitle="Manajemen Pengguna" />
<PageHeader title="Users" subtitle="User Management" />
<div class="bg-white rounded-xl shadow-md">
<div
@ -191,14 +189,14 @@ onMounted(async () => {
<div class="flex flex-col w-full gap-2 md:flex-row md:items-center">
<SearchInput
v-model="searchUsername"
placeholder="Cari berdasarkan username"
placeholder="Search by username"
@search="handleSearch"
/>
<div class="flex items-center gap-2 md:ml-4">
<SortDropdown
v-model="sortBy"
:options="SORT_OPTIONS.USERS"
label="Urut berdasarkan:"
label="Sort by:"
@change="handleSortChange"
/>
<button
@ -226,7 +224,7 @@ onMounted(async () => {
to="/users/add"
class="btn btn-sm bg-dark text-light hover:bg-light hover:text-dark active:inset-shadow-sm active:inset-shadow-black/50 self-start md:self-auto"
>
Tambah Pengguna
Add User
</RouterLink>
</div>
@ -235,7 +233,7 @@ onMounted(async () => {
:data="data"
:columns="USERS_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Tidak ada data pengguna"
empty-message="No user data"
@details="handleDetails"
@update="handleUpdate"
@delete="handleDelete"

View File

@ -182,15 +182,15 @@ const showRejectDialog = () => {
};
const tableLabelMap: Record<string, string> = {
pemberian_obat: "Pemberian Obat",
pemberian_tindakan: "Pemberian Tindakan",
rekam_medis: "Rekam Medis",
pemberian_obat: "Medicine Administration",
pemberian_tindakan: "Doctor Actions",
rekam_medis: "Medical Records",
};
const actionLabelMap: Record<string, string> = {
CREATE: "Tambah Data",
UPDATE: "Ubah Data",
DELETE: "Hapus Data",
CREATE: "Add Data",
UPDATE: "Edit Data",
DELETE: "Delete Data",
};
const statusBadgeClass = computed(() => {
@ -235,22 +235,22 @@ const handleApprove = async () => {
{}
);
console.log("Approve response:", response);
alert("Permintaan berhasil disetujui");
alert("Request successfully approved");
router.push({ name: "validasi" });
} catch (error) {
console.error("Error approving validation:", error);
alert("Gagal menyetujui permintaan");
alert("Failed to approve request");
}
};
const handleReject = async () => {
try {
await api.post(`/validation/${route.params.id}/reject`, {});
alert("Permintaan berhasil ditolak");
alert("Request successfully rejected");
router.push({ name: "validasi" });
} catch (error) {
console.error("Error rejecting validation:", error);
alert("Gagal menolak permintaan");
alert("Failed to reject request");
}
};
@ -276,7 +276,7 @@ const formatTimestamp = (rawValue?: string) => {
onMounted(async () => {
await fetchData();
document.title = `Review Validasi - ID ${route.params.id}`;
document.title = `Review Validation - ID ${route.params.id}`;
});
</script>
@ -285,8 +285,8 @@ onMounted(async () => {
<div class="flex h-full p-2">
<Sidebar>
<PageHeader
title="Validasi"
:subtitle="`Review Permintaan Validasi ${route.params.id}`"
title="Validation"
:subtitle="`Review Validation Request ${route.params.id}`"
/>
<div class="bg-white rounded-xl shadow-md text-dark">
@ -294,49 +294,51 @@ onMounted(async () => {
<div class="breadcrumbs text-sm">
<ul>
<li class="font-bold">
<RouterLink to="/validasi"> Validasi </RouterLink>
<RouterLink to="/validasi"> Validation </RouterLink>
</li>
<li>Review Permintaan {{ route.params.id }}</li>
<li>Review Request {{ route.params.id }}</li>
</ul>
</div>
<div class="flex justify-between items-center">
<h5 class="font-bold">Detail Permintaan Validasi</h5>
<h5 class="font-bold">Validation Request Details</h5>
<span class="badge" :class="statusBadgeClass">
{{ validation?.status }}
</span>
</div>
<div class="text-sm">
<p><strong>ID Permintaan:</strong> {{ validation?.id }}</p>
<p>
<strong>Tabel:</strong>
<strong>Validation Request ID:</strong> {{ validation?.id }}
</p>
<p>
<strong>Table:</strong>
{{
tableLabelMap[validation?.table_name || ""] ||
validation?.table_name
}}
</p>
<p><strong>ID Record:</strong> {{ validation?.record_id }}</p>
<p><strong>Record ID:</strong> {{ validation?.record_id }}</p>
<p>
<strong>Aksi:</strong>
<strong>Action:</strong>
{{
actionLabelMap[validation?.action || ""] || validation?.action
}}
</p>
<p>
<strong>Pemohon:</strong> User ID
<strong>Requester:</strong> User ID
{{ validation?.user_id_request }}
</p>
<p>
<strong>Diajukan:</strong>
<strong>Submitted:</strong>
{{ formatTimestamp(validation?.created_at) }}
</p>
<p v-if="validation?.processed_at">
<strong>Diproses:</strong>
<strong>Processed:</strong>
{{ formatTimestamp(validation?.processed_at) }}
</p>
<p v-if="validation?.user_id_process">
<strong>Diproses oleh:</strong> User ID
<strong>Processed by:</strong> User ID
{{ validation?.user_id_process }}
</p>
</div>
@ -364,7 +366,7 @@ onMounted(async () => {
<div
class="bg-white border border-dark rounded-lg p-4 text-sm overflow-x-auto flex-1"
>
<h1 class="font-bold mb-2">Data Sebelum</h1>
<h1 class="font-bold mb-2">Before Data</h1>
<pre
class="whitespace-pre-wrap"
v-html="formatJsonWithHighlight(previousLog, 'old')"
@ -373,7 +375,7 @@ onMounted(async () => {
<div
class="bg-white border border-dark rounded-lg p-4 text-sm overflow-x-auto flex-1"
>
<h1 class="font-bold mb-2">Data Sesudah</h1>
<h1 class="font-bold mb-2">After Data</h1>
<pre
class="whitespace-pre-wrap"
v-html="formatJsonWithHighlight(normalizedNewPayload, 'new')"
@ -389,13 +391,13 @@ onMounted(async () => {
type="button"
@click="showApproveDialog"
:disabled="api.isLoading.value"
text="Setujui"
text="Approve"
/>
<button
@click="showRejectDialog"
class="bg-white btn btn-sm text-dark"
>
Tolak
Reject
</button>
</div>
</div>
@ -403,19 +405,19 @@ onMounted(async () => {
<DialogConfirm
ref="approveDialog"
title="Setujui Permintaan"
message="Apakah Anda yakin ingin menyetujui permintaan validasi ini?"
confirm-text="Ya, Setujui"
cancel-text="Batal"
title="Approve Request"
message="Are you sure you want to approve this validation request?"
confirm-text="Yes, Approve"
cancel-text="Cancel"
@confirm="handleApprove"
/>
<DialogConfirm
ref="rejectDialog"
title="Tolak Permintaan"
message="Apakah Anda yakin ingin menolak permintaan validasi ini?"
confirm-text="Ya, Tolak"
cancel-text="Batal"
title="Reject Request"
message="Are you sure you want to reject this validation request?"
confirm-text="Yes, Reject"
cancel-text="Cancel"
@confirm="handleReject"
/>
</Sidebar>

View File

@ -83,11 +83,11 @@ const normalizedData = (rawData: any[]): ValidationLog[] => {
const formattedTableName = (tableName: string): string => {
switch (tableName) {
case "rekam_medis":
return "Rekam Medis";
return "Medical Records";
case "pemberian_tindakan":
return "Tindakan Dokter";
return "Doctor Actions";
case "pemberian_obat":
return "Obat";
return "Medicine";
default:
return tableName;
}
@ -115,7 +115,7 @@ const normalizedData = (rawData: any[]): ValidationLog[] => {
if (isNaN(date.getTime())) {
return dateString;
}
return date.toLocaleString("id-ID", {
return date.toLocaleString("en-US", {
timeZone: "Asia/Jakarta",
day: "2-digit",
month: "2-digit",
@ -132,7 +132,7 @@ const normalizedData = (rawData: any[]): ValidationLog[] => {
if (isNaN(date.getTime())) {
return dateString;
}
return date.toLocaleString("id-ID", {
return date.toLocaleString("en-US", {
timeZone: "Asia/Jakarta",
day: "2-digit",
month: "2-digit",
@ -228,13 +228,13 @@ const handleUpdate = (item: ValidationLog) => {
};
const handleDelete = async (item: ValidationLog) => {
if (confirm(`Apakah Anda yakin ingin menghapus item "${item.id}"?`)) {
if (confirm(`Are you sure you want to delete item "${item.id}"?`)) {
try {
await api.delete(`/validation/${item.id}`);
await fetchData();
} catch (error) {
console.error("Error deleting validation:", error);
alert("Gagal menghapus data validasi");
alert("Failed to delete validation data");
}
}
};
@ -302,7 +302,7 @@ onMounted(async () => {
<div class="bg-light w-full text-dark">
<div class="flex h-full p-2">
<Sidebar>
<PageHeader title="Validasi" subtitle="Manajemen Validasi" />
<PageHeader title="Validation" subtitle="Validation Management" />
<div
class="collapse collapse-arrow bg-white border-white border shadow-sm mb-2"
@ -318,57 +318,59 @@ onMounted(async () => {
<div class="flex gap-x-4 items-end">
<div class="h-full">
<label for="jenis_kelamin" class="font-bold"
>Kelompok Data</label
>Data Group</label
>
<select
v-model="filters.kelompok_data"
class="select bg-white border border-gray-300 mt-1"
>
<option disabled selected value="initial">
Pilih Kelompok Data
Select Data Group
</option>
<option value="rekam_medis">Rekam Medis</option>
<option value="pemberian_tindakan">Tindakan</option>
<option value="pemberian_obat">Obat</option>
<option value="all">Semua Tipe</option>
<option value="rekam_medis">Medical Records</option>
<option value="pemberian_tindakan">Doctor Actions</option>
<option value="pemberian_obat">
Medicine Administration
</option>
<option value="all">All Types</option>
</select>
</div>
</div>
<div class="flex gap-x-4 items-end">
<div class="h-full">
<label for="jenis_kelamin" class="font-bold"
>Jenis Aksi</label
>Action Type</label
>
<select
v-model="filters.aksi"
class="select bg-white border border-gray-300 mt-1"
>
<option disabled selected value="initial">
Pilih Jenis Aksi
Select Action Type
</option>
<option value="CREATE">Create</option>
<option value="UPDATE">Update</option>
<option value="DELETE">Delete</option>
<option value="all">Semua</option>
<option value="all">All</option>
</select>
</div>
</div>
<div class="flex gap-x-4 items-end">
<div class="h-full">
<label for="jenis_kelamin" class="font-bold"
>Status Validasi</label
>Validation Status</label
>
<select
v-model="filters.status"
class="select bg-white border border-gray-300 mt-1"
>
<option disabled selected value="initial">
Pilih Status Validasi
Select Validation Status
</option>
<option value="PENDING">Pending</option>
<option value="APPROVED">Approved</option>
<option value="REJECTED">Rejected</option>
<option value="all">Semua</option>
<option value="all">All</option>
</select>
</div>
</div>
@ -391,14 +393,14 @@ onMounted(async () => {
<div class="flex flex-col w-full gap-2 md:flex-row md:items-center">
<SearchInput
v-model="searchId"
placeholder="Cari berdasarkan ID Record"
placeholder="Search by Record ID"
@search="handleSearch"
/>
<div class="flex items-center gap-2 md:ml-4">
<SortDropdown
v-model="sortBy"
:options="SORT_OPTIONS.VALIDATION"
label="Urut berdasarkan:"
label="Sort by:"
@change="handleSortChange"
/>
<button
@ -429,7 +431,7 @@ onMounted(async () => {
:data="data"
:columns="VALIDATION_TABLE_COLUMNS"
:is-loading="api.isLoading.value"
empty-message="Tidak ada data validasi"
empty-message="No validation data available"
@details="handleDetails"
@update="handleUpdate"
@delete="handleDelete"