i18n (Phase C batch 9): collectors, dashboard, monitoring, vouchers, cashier_reports, cashier_attendance ID/EN

This commit is contained in:
oxpoe committed 2026-10-09 07:54:34 +07:00
1 parent 69d6ebbd0e
commit d09ab217f9
8 files changed
+204 -72

No files matched your search

+67 -1
View File
@@ -1463,7 +1463,9 @@
"last_tr069": "Last TR-069:",
"test_conn": "Test Connection",
"threshold_hint": "Warning threshold for RX Power (-40 to -15 dBm)",
"timeout_hint": "Timeout for requests to GenieACS (5000-120000 ms)"
"timeout_hint": "Timeout for requests to GenieACS (5000-120000 ms)",
"genieacs_title": "GenieACS Settings",
"conn_live": "Connected Clients (Live)"
},
"cust": {
"ph_note": "Optional internal note...",
@@ -1986,6 +1988,70 @@
"night_profile": "Night Profile Name (MikroTik)",
"ph_night_profile": "-- Select Profile --",
"night_down": "Night Download (Mbps)"
},
"col": {
"title": "Collector Management",
"add_new": "Add New Collector",
"add": "Add Collector",
"list": "Collector List",
"all_paymode": "All Payment Modes",
"direct": "Direct Paid",
"approval": "Needs Approval",
"all_status": "All Account Statuses",
"phone": "Phone No.",
"area": "Assignment Area",
"paymode": "Payment Mode",
"del": "Delete Collector",
"full_name": "Full Name",
"area_region": "Area / Assignment Region",
"opt_all_area": "-- All Areas / Unrestricted --"
},
"mon": {
"title": "System Monitoring",
"updated": "Last update:",
"issues": "Issues",
"warnings": "Warnings",
"metrics": "System Metrics",
"perf": "Performance Summary (Last 10 Samples)",
"avg": "Average"
},
"vou": {
"no_profile": "No profile with price/duration (Mikhmon)",
"pick_profile": "-- Select Profile --"
},
"cr": {
"title": "Cashier Report",
"all_cashier": "All Cashiers",
"tx": "transactions",
"invoice": "Invoices:",
"topup": "Agent Deposit:",
"history": "Transaction History",
"th_type": "Type",
"th_detail": "Detail",
"th_note": "Note",
"badge_invoice": "INVOICE"
},
"ca": {
"today_status": "Today Status",
"checked_in": "Checked In",
"checked_out": "Checked Out",
"work_duration": "Work Duration",
"note_in": "Check-in Note",
"note_out": "Check-out Note",
"photo": "Attendance Photo",
"ph_note_out": "Check-out note (optional)",
"no_photo_out": "No photo taken yet. A photo is required for check-out.",
"not_checked_in": "Not Checked In",
"ph_note_in": "Check-in note (optional)",
"no_photo_in": "No photo taken yet. A photo is required for check-in.",
"total_days": "Total Days",
"this_month": "This Month",
"full_days": "Full Days",
"in_out": "Check-in & out",
"total_hours": "Total Hours",
"work_hours": "Work hours",
"avg": "Average",
"hours_per_day": "Hours/day"
}
},
"tech": {
+67 -1
View File
@@ -1464,7 +1464,9 @@
"last_tr069": "TR-069 terakhir:",
"test_conn": "Test Koneksi",
"threshold_hint": "Threshold untuk warning RX Power (-40 sampai -15 dBm)",
"timeout_hint": "Timeout untuk request ke GenieACS (5000-120000 ms)"
"timeout_hint": "Timeout untuk request ke GenieACS (5000-120000 ms)",
"genieacs_title": "Pengaturan GenieACS",
"conn_live": "Klien Terhubung (Live)"
},
"cust": {
"ph_note": "Catatan internal opsional...",
@@ -1987,6 +1989,70 @@
"night_profile": "Nama Profile Malam (MikroTik)",
"ph_night_profile": "-- Pilih Profile --",
"night_down": "Download Malam (Mbps)"
},
"col": {
"title": "Manajemen Kolektor",
"add_new": "Tambah Kolektor Baru",
"add": "Tambah Kolektor",
"list": "Daftar Kolektor",
"all_paymode": "Semua Mode Pembayaran",
"direct": "Langsung Lunas",
"approval": "Perlu Approval",
"all_status": "Semua Status Akun",
"phone": "No. HP",
"area": "Area Tugas",
"paymode": "Mode Pembayaran",
"del": "Hapus Kolektor",
"full_name": "Nama Lengkap",
"area_region": "Area / Wilayah Tugas",
"opt_all_area": "-- Semua Area / Bebas --"
},
"mon": {
"title": "Monitoring Sistem",
"updated": "Update terakhir:",
"issues": "Masalah",
"warnings": "Peringatan",
"metrics": "Metrik Sistem",
"perf": "Ringkasan Performa (10 Sample Terakhir)",
"avg": "Rata-rata"
},
"vou": {
"no_profile": "Tidak ada profile dengan harga/durasi (Mikhmon)",
"pick_profile": "-- Pilih Profile --"
},
"cr": {
"title": "Laporan Kasir",
"all_cashier": "Semua Kasir",
"tx": "transaksi",
"invoice": "Tagihan:",
"topup": "Deposit Agent:",
"history": "Riwayat Transaksi",
"th_type": "Jenis",
"th_detail": "Detail",
"th_note": "Catatan",
"badge_invoice": "TAGIHAN"
},
"ca": {
"today_status": "Status Hari Ini",
"checked_in": "Sudah Check-in",
"checked_out": "Sudah Check-out",
"work_duration": "Durasi Kerja",
"note_in": "Catatan Check-in",
"note_out": "Catatan Check-out",
"photo": "Foto Absensi",
"ph_note_out": "Catatan check-out (opsional)",
"no_photo_out": "Belum ada foto diambil. Foto wajib untuk check-out.",
"not_checked_in": "Belum Check-in",
"ph_note_in": "Catatan check-in (opsional)",
"no_photo_in": "Belum ada foto diambil. Foto wajib untuk check-in.",
"total_days": "Total Hari",
"this_month": "Bulan ini",
"full_days": "Hari Lengkap",
"in_out": "Check-in & out",
"total_hours": "Total Jam",
"work_hours": "Jam kerja",
"avg": "Rata-rata",
"hours_per_day": "Jam/hari"
}
},
"tech": {
+20 -20
View File
@@ -103,14 +103,14 @@
<!-- Today's Status -->
<div class="card">
<div class="card-hd">
<h6><i class="bi bi-calendar-day"></i> Status Hari Ini</h6>
<h6><i class="bi bi-calendar-day"></i> <%= t('admin.ca.today_status','Status Hari Ini') %></h6>
</div>
<div class="card-bd">
<% if (todayAttendance) { %>
<% if (todayAttendance.status === 'checked_in') { %>
<span class="status-badge status-checked-in"><i class="bi bi-check-lg"></i> Sudah Check-in</span>
<span class="status-badge status-checked-in"><i class="bi bi-check-lg"></i> <%= t('admin.ca.checked_in','Sudah Check-in') %></span>
<% } else { %>
<span class="status-badge status-checked-out"><i class="bi bi-check-lg"></i> Sudah Check-out</span>
<span class="status-badge status-checked-out"><i class="bi bi-check-lg"></i> <%= t('admin.ca.checked_out','Sudah Check-out') %></span>
<% } %>
<div class="info-row">
@@ -124,7 +124,7 @@
<span class="info-value"><%= formatDateLocal(todayAttendance.check_out_time) %></span>
</div>
<div class="info-row">
<span class="info-label">Durasi Kerja</span>
<span class="info-label"><%= t('admin.ca.work_duration','Durasi Kerja') %></span>
<span class="info-value">
<%= Math.floor(todayAttendance.work_duration_minutes / 60) %> jam
<%= todayAttendance.work_duration_minutes % 60 %> menit
@@ -134,21 +134,21 @@
<% if (todayAttendance.check_in_note) { %>
<div class="info-row">
<span class="info-label">Catatan Check-in</span>
<span class="info-label"><%= t('admin.ca.note_in','Catatan Check-in') %></span>
<span class="info-value"><%= todayAttendance.check_in_note %></span>
</div>
<% } %>
<% if (todayAttendance.check_out_note) { %>
<div class="info-row">
<span class="info-label">Catatan Check-out</span>
<span class="info-label"><%= t('admin.ca.note_out','Catatan Check-out') %></span>
<span class="info-value"><%= todayAttendance.check_out_note %></span>
</div>
<% } %>
<% if (todayAttendance.check_in_photo || todayAttendance.check_out_photo) { %>
<div class="info-row" style="align-items:flex-start">
<span class="info-label">Foto Absensi</span>
<span class="info-label"><%= t('admin.ca.photo','Foto Absensi') %></span>
<span class="info-value" style="display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end">
<% if (todayAttendance.check_in_photo) { %>
<a href="<%= todayAttendance.check_in_photo %>" target="_blank" style="text-decoration:none;color:var(--text);font-size:12px;text-align:center">
@@ -167,7 +167,7 @@
<% } %>
<% if (todayAttendance.status === 'checked_in') { %>
<textarea id="checkoutNote" class="note-input" placeholder="Catatan check-out (opsional)"></textarea>
<textarea id="checkoutNote" class="note-input" placeholder="<%= t('admin.ca.ph_note_out','Catatan check-out (opsional)') %>"></textarea>
<input id="checkoutPhoto" type="file" accept="image/*" capture="user" style="display: none;" onchange="updatePhotoInfo('checkoutPhoto', 'checkoutPhotoInfo', 'checkoutPhotoPreviewWrap', 'checkoutPhotoPreview')">
<div style="text-align: center; margin: 16px 0;">
@@ -177,7 +177,7 @@
<div style="font-size: 11px; color: var(--muted); margin-top: 6px;"><%= t('collector.tap_camera','Ketuk tombol kamera untuk mengambil foto selfie') %></div>
</div>
<div id="checkoutPhotoInfo" class="photo-help text-center">Belum ada foto diambil. Foto wajib untuk check-out.</div>
<div id="checkoutPhotoInfo" class="photo-help text-center"><%= t('admin.ca.no_photo_out','Belum ada foto diambil. Foto wajib untuk check-out.') %></div>
<div style="text-align: center; margin-top: 4px;">
<div id="checkoutPhotoStatus" class="photo-help" style="display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(239,68,68,0.18);color:#fecaca"><%= t('collector.selfie_required','Foto wajib') %></div>
</div>
@@ -195,10 +195,10 @@
<% } %>
<% } else { %>
<span class="status-badge status-not-checked-in"><i class="bi bi-x-lg"></i> Belum Check-in</span>
<span class="status-badge status-not-checked-in"><i class="bi bi-x-lg"></i> <%= t('admin.ca.not_checked_in','Belum Check-in') %></span>
<p style="color: var(--muted); margin: 16px 0;">Silakan lakukan check-in untuk memulai hari kerja Anda.</p>
<textarea id="checkinNote" class="note-input" placeholder="Catatan check-in (opsional)"></textarea>
<textarea id="checkinNote" class="note-input" placeholder="<%= t('admin.ca.ph_note_in','Catatan check-in (opsional)') %>"></textarea>
<input id="checkinPhoto" type="file" accept="image/*" capture="user" style="display: none;" onchange="updatePhotoInfo('checkinPhoto', 'checkinPhotoInfo', 'checkinPhotoPreviewWrap', 'checkinPhotoPreview')">
<div style="text-align: center; margin: 16px 0;">
@@ -208,7 +208,7 @@
<div style="font-size: 11px; color: var(--muted); margin-top: 6px;"><%= t('collector.tap_camera','Ketuk tombol kamera untuk mengambil foto selfie') %></div>
</div>
<div id="checkinPhotoInfo" class="photo-help text-center">Belum ada foto diambil. Foto wajib untuk check-in.</div>
<div id="checkinPhotoInfo" class="photo-help text-center"><%= t('admin.ca.no_photo_in','Belum ada foto diambil. Foto wajib untuk check-in.') %></div>
<div style="text-align: center; margin-top: 4px;">
<div id="checkinPhotoStatus" class="photo-help" style="display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(239,68,68,0.18);color:#fecaca"><%= t('collector.selfie_required','Foto wajib') %></div>
</div>
@@ -235,31 +235,31 @@
<div class="stat-grid" style="grid-template-columns:repeat(4,1fr)">
<div class="sc p">
<i class="bi bi-calendar-month sc-icon"></i>
<div class="sc-label">Total Hari</div>
<div class="sc-label"><%= t('admin.ca.total_days','Total Hari') %></div>
<div class="sc-val"><%= summary.total_days || 0 %></div>
<div class="sc-sub">Bulan ini</div>
<div class="sc-sub"><%= t('admin.ca.this_month','Bulan ini') %></div>
</div>
<div class="sc s">
<i class="bi bi-check-circle sc-icon"></i>
<div class="sc-label">Hari Lengkap</div>
<div class="sc-label"><%= t('admin.ca.full_days','Hari Lengkap') %></div>
<div class="sc-val"><%= summary.completed_days || 0 %></div>
<div class="sc-sub">Check-in & out</div>
<div class="sc-sub"><%= t('admin.ca.in_out','Check-in & out') %></div>
</div>
<div class="sc p">
<i class="bi bi-clock sc-icon"></i>
<div class="sc-label">Total Jam</div>
<div class="sc-label"><%= t('admin.ca.total_hours','Total Jam') %></div>
<div class="sc-val">
<%= summary.total_minutes ? Math.floor(summary.total_minutes / 60) : 0 %>
</div>
<div class="sc-sub">Jam kerja</div>
<div class="sc-sub"><%= t('admin.ca.work_hours','Jam kerja') %></div>
</div>
<div class="sc s">
<i class="bi bi-graph-up sc-icon"></i>
<div class="sc-label">Rata-rata</div>
<div class="sc-label"><%= t('admin.ca.avg','Rata-rata') %></div>
<div class="sc-val">
<%= summary.avg_minutes ? Math.floor(summary.avg_minutes / 60) : 0 %>
</div>
<div class="sc-sub">Jam/hari</div>
<div class="sc-sub"><%= t('admin.ca.hours_per_day','Jam/hari') %></div>
</div>
</div>
+12 -12
View File
@@ -2,7 +2,7 @@
<html lang="<%= lang || 'id' %>">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Laporan Kasir | <%= company %></title>
<title><%= t('admin.cr.title','Laporan Kasir') %> | <%= company %></title>
<link rel="stylesheet" href="/vendor/bootstrap-icons/bootstrap-icons.min.css">
<link rel="stylesheet" href="/css/admin.css">
</head>
@@ -14,7 +14,7 @@
<div class="tb-title">
<button class="hb-menu" onclick="toggleSidebar()"><i class="bi bi-list"></i></button>
<i class="bi bi-journal-text"></i>
<span>Laporan Kasir</span>
<span><%= t('admin.cr.title','Laporan Kasir') %></span>
</div>
<div class="tb-right">
<span class="adm-badge" style="font-size:12px;opacity:0.7;"><%= company %></span>
@@ -35,7 +35,7 @@
<div class="card-bd" style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
<% if (cashiers && cashiers.length > 1) { %>
<select class="fs" style="min-width:240px" onchange="location='/admin/cashiers/reports?cashierId='+encodeURIComponent(this.value)">
<option value="">Semua Kasir</option>
<option value=""><%= t('admin.cr.all_cashier','Semua Kasir') %></option>
<% (cashiers || []).forEach(c => { %>
<option value="<%= c.id %>" <%= cashierId && Number(cashierId)===Number(c.id) ? 'selected' : '' %>><%= c.name %> (@<%= c.username %>)</option>
<% }) %>
@@ -43,16 +43,16 @@
<% } else if (cashiers && cashiers.length === 1) { %>
<span class="badge bm"><%= cashiers[0].name %><%= cashiers[0].username ? (' (@' + cashiers[0].username + ')') : '' %></span>
<% } %>
<span class="badge bm"><%= Number(summary?.count || 0) %> transaksi</span>
<span class="badge bm"><%= Number(summary?.count || 0) %> <%= t('admin.cr.tx','transaksi') %></span>
<span class="badge bs">Rp <%= Number(summary?.total || 0).toLocaleString('id-ID') %></span>
<span class="badge bm">Tagihan: <%= Number(summary?.invoice_count || 0) %> (Rp <%= Number(summary?.invoice_total || 0).toLocaleString('id-ID') %>)</span>
<span class="badge bm">Deposit Agent: <%= Number(summary?.topup_count || 0) %> (Rp <%= Number(summary?.topup_total || 0).toLocaleString('id-ID') %>)</span>
<span class="badge bm"><%= t('admin.cr.invoice','Tagihan:') %> <%= Number(summary?.invoice_count || 0) %> (Rp <%= Number(summary?.invoice_total || 0).toLocaleString('id-ID') %>)</span>
<span class="badge bm"><%= t('admin.cr.topup','Deposit Agent:') %> <%= Number(summary?.topup_count || 0) %> (Rp <%= Number(summary?.topup_total || 0).toLocaleString('id-ID') %>)</span>
</div>
</div>
<div class="card" style="margin-top:16px">
<div class="card-hd">
<h6><i class="bi bi-list-ul"></i> Riwayat Transaksi</h6>
<h6><i class="bi bi-list-ul"></i> <%= t('admin.cr.history','Riwayat Transaksi') %></h6>
</div>
<div class="tbl-wrap">
<table class="dtbl">
@@ -60,16 +60,16 @@
<tr>
<th><%= t('collector.col_time','Waktu') %></th>
<th>Kasir</th>
<th>Jenis</th>
<th>Detail</th>
<th><%= t('admin.cr.th_type','Jenis') %></th>
<th><%= t('admin.cr.th_detail','Detail') %></th>
<th class="num"><%= t('collector.col_amount','Nominal') %></th>
<th>Catatan</th>
<th><%= t('admin.cr.th_note','Catatan') %></th>
<th class="num">Ref</th>
</tr>
</thead>
<tbody>
<% if (!rows || rows.length === 0) { %>
<tr><td colspan="7" class="text-center text-muted" style="padding:24px">Belum ada transaksi kasir.</td></tr>
<tr><td colspan="7" class="text-center text-muted" style="padding:24px">Belum ada <%= t('admin.cr.tx','transaksi') %> kasir.</td></tr>
<% } else { %>
<% rows.forEach(r => { %>
<tr>
@@ -79,7 +79,7 @@
<% if (r.kind === 'agent_topup') { %>
<span class="badge bs">DEPOSIT</span>
<% } else { %>
<span class="badge bm">TAGIHAN</span>
<span class="badge bm"><%= t('admin.cr.badge_invoice','TAGIHAN') %></span>
<% } %>
</td>
<td style="font-size:12px">
+25 -25
View File
@@ -14,11 +14,11 @@
<div class="tb-title">
<button class="hb-menu" onclick="toggleSidebar()"><i class="bi bi-list"></i></button>
<i class="bi bi-person-badge"></i>
<span>Manajemen Kolektor</span>
<span><%= t('admin.col.title','Manajemen Kolektor') %></span>
</div>
<div class="tb-right">
<span class="adm-badge"><i class="bi bi-person-fill"></i> <%= (typeof session !== 'undefined' && session.isCashier) ? t('admin.role.cashier', 'Kasir') : t('admin.role.admin', 'Admin') %></span>
<button class="btn btn-p btn-sm" onclick="openModal('addCollectorModal')"><i class="bi bi-plus-lg"></i> Tambah Kolektor</button>
<button class="btn btn-p btn-sm" onclick="openModal('addCollectorModal')"><i class="bi bi-plus-lg"></i> <%= t('admin.col.add','Tambah Kolektor') %></button>
</div>
</div>
@@ -29,19 +29,19 @@
<div class="card">
<div class="card-hd">
<h6><i class="bi bi-list-ul"></i> Daftar Kolektor</h6>
<h6><i class="bi bi-list-ul"></i> <%= t('admin.col.list','Daftar Kolektor') %></h6>
</div>
<div class="hint" style="padding:12px 16px 0 16px;font-size:12px;color:var(--muted)">
Pengaturan approval pembayaran berlaku per akun kolektor. `Langsung Lunas` berarti pembayaran dari kolektor langsung melunasi invoice, sedangkan `Perlu Approval` berarti harus disetujui admin atau kasir terlebih dahulu.
Pengaturan approval pembayaran berlaku per akun kolektor. `<%= t('admin.col.direct','Langsung Lunas') %>` berarti pembayaran dari kolektor langsung melunasi invoice, sedangkan `<%= t('admin.col.approval','Perlu Approval') %>` berarti harus disetujui admin atau kasir terlebih dahulu.
</div>
<div style="padding:12px 16px 0 16px;display:flex;gap:10px;flex-wrap:wrap">
<select id="collectorModeFilter" class="fs" style="max-width:220px">
<option value="all">Semua Mode Pembayaran</option>
<option value="direct">Langsung Lunas</option>
<option value="approval">Perlu Approval</option>
<option value="all"><%= t('admin.col.all_paymode','Semua Mode Pembayaran') %></option>
<option value="direct"><%= t('admin.col.direct','Langsung Lunas') %></option>
<option value="approval"><%= t('admin.col.approval','Perlu Approval') %></option>
</select>
<select id="collectorStatusFilter" class="fs" style="max-width:180px">
<option value="all">Semua Status Akun</option>
<option value="all"><%= t('admin.col.all_status','Semua Status Akun') %></option>
<option value="active"><%= t('common.active','Aktif') %></option>
<option value="inactive"><%= t('common.inactive','Nonaktif') %></option>
</select>
@@ -53,10 +53,10 @@
<th><%= t('pwa.field.name','Nama') %></th>
<th>Username</th>
<th>Password</th>
<th>No. HP</th>
<th>Area Tugas</th>
<th><%= t('admin.col.phone','No. HP') %></th>
<th><%= t('admin.col.area','Area Tugas') %></th>
<th><%= t('common.status','Status') %></th>
<th>Mode Pembayaran</th>
<th><%= t('admin.col.paymode','Mode Pembayaran') %></th>
<th><%= t('common.actions','Aksi') %></th>
</tr>
</thead>
@@ -86,7 +86,7 @@
<div class="act-menu">
<button type="button" class="act-item" onclick='editCollector(<%- JSON.stringify(c).split('<').join('\\u003c') %>)'><i class="bi bi-pencil"></i> Edit Kolektor</button>
<div class="act-sep"></div>
<button type="submit" form="cdel-<%= c.id %>" class="act-item danger"><i class="bi bi-trash"></i> Hapus Kolektor</button>
<button type="submit" form="cdel-<%= c.id %>" class="act-item danger"><i class="bi bi-trash"></i> <%= t('admin.col.del','Hapus Kolektor') %></button>
</div>
</div>
</td>
@@ -103,16 +103,16 @@
<div class="mo" id="addCollectorModal">
<div class="mb">
<form action="/admin/collectors" method="POST">
<div class="mh"><span class="mt">Tambah Kolektor Baru</span><button type="button" class="mc" onclick="closeModal('addCollectorModal')">&times;</button></div>
<div class="mh"><span class="mt"><%= t('admin.col.add_new','Tambah Kolektor Baru') %></span><button type="button" class="mc" onclick="closeModal('addCollectorModal')">&times;</button></div>
<div class="mbody">
<div class="fg"><label class="fl">Nama Lengkap</label><input type="text" name="name" class="fc" required></div>
<div class="fg"><label class="fl"><%= t('admin.col.full_name','Nama Lengkap') %></label><input type="text" name="name" class="fc" required></div>
<div class="fg"><label class="fl">Username Login</label><input type="text" name="username" class="fc" required></div>
<div class="fg"><label class="fl">Password</label><input type="text" name="password" class="fc" required></div>
<div class="fg"><label class="fl">No. HP</label><input type="text" name="phone" class="fc" placeholder="08123456789"></div>
<div class="fg"><label class="fl"><%= t('admin.col.phone','No. HP') %></label><input type="text" name="phone" class="fc" placeholder="08123456789"></div>
<div class="fg">
<label class="fl">Area / Wilayah Tugas</label>
<label class="fl"><%= t('admin.col.area_region','Area / Wilayah Tugas') %></label>
<select name="area" class="fs">
<option value="">-- Semua Area / Bebas --</option>
<option value=""><%= t('admin.col.opt_all_area','-- Semua Area / Bebas --') %></option>
<% if (typeof masterAreas !== 'undefined' && masterAreas && masterAreas.length > 0) { %>
<% masterAreas.forEach(a => { %>
<option value="<%= a.name %>"><%= a.name %></option>
@@ -125,8 +125,8 @@
<i class="bi bi-check-circle"></i> Auto-Approve Pembayaran
</label>
<select name="auto_approve" id="add-auto-approve" class="fs" onchange="updateCollectorModeBadge('add')">
<option value="0">Nonaktif (Perlu Approval Admin)</option>
<option value="1">Aktif (Langsung Lunas)</option>
<option value="0">Nonaktif (<%= t('admin.col.approval','Perlu Approval') %> Admin)</option>
<option value="1">Aktif (<%= t('admin.col.direct','Langsung Lunas') %>)</option>
</select>
<div id="add-mode-badge" class="hint" style="margin-top:8px;font-size:12px;color:#fbbf24">
Mode saat ini: Menunggu approval admin/kasir sebelum invoice menjadi lunas.
@@ -149,14 +149,14 @@
<form id="editForm" method="POST">
<div class="mh"><span class="mt">Edit Data Kolektor</span><button type="button" class="mc" onclick="closeModal('editCollectorModal')">&times;</button></div>
<div class="mbody">
<div class="fg"><label class="fl">Nama Lengkap</label><input type="text" name="name" id="e-name" class="fc" required></div>
<div class="fg"><label class="fl"><%= t('admin.col.full_name','Nama Lengkap') %></label><input type="text" name="name" id="e-name" class="fc" required></div>
<div class="fg"><label class="fl">Username Login</label><input type="text" name="username" id="e-username" class="fc" required></div>
<div class="fg"><label class="fl">Password</label><input type="text" name="password" id="e-password" class="fc" required></div>
<div class="fg"><label class="fl">No. HP</label><input type="text" name="phone" id="e-phone" class="fc"></div>
<div class="fg"><label class="fl"><%= t('admin.col.phone','No. HP') %></label><input type="text" name="phone" id="e-phone" class="fc"></div>
<div class="fg">
<label class="fl">Area / Wilayah Tugas</label>
<label class="fl"><%= t('admin.col.area_region','Area / Wilayah Tugas') %></label>
<select name="area" id="e-area" class="fs">
<option value="">-- Semua Area / Bebas --</option>
<option value=""><%= t('admin.col.opt_all_area','-- Semua Area / Bebas --') %></option>
<% if (typeof masterAreas !== 'undefined' && masterAreas && masterAreas.length > 0) { %>
<% masterAreas.forEach(a => { %>
<option value="<%= a.name %>"><%= a.name %></option>
@@ -176,8 +176,8 @@
<i class="bi bi-check-circle"></i> Auto-Approve Pembayaran
</label>
<select name="auto_approve" id="e-auto-approve" class="fs" onchange="updateCollectorModeBadge('edit')">
<option value="0">Nonaktif (Perlu Approval Admin)</option>
<option value="1">Aktif (Langsung Lunas)</option>
<option value="0">Nonaktif (<%= t('admin.col.approval','Perlu Approval') %> Admin)</option>
<option value="1">Aktif (<%= t('admin.col.direct','Langsung Lunas') %>)</option>
</select>
<div id="edit-mode-badge" class="hint" style="margin-top:8px;font-size:12px;color:#fbbf24">
Mode saat ini: Menunggu approval admin/kasir sebelum invoice menjadi lunas.
+3 -3
View File
@@ -248,7 +248,7 @@
<option value="online"><%= t('admin.dashboard.online', 'Online') %></option>
<option value="offline"><%= t('admin.dashboard.offline', 'Offline') %></option>
</select>
<button class="btn btn-g btn-sm" onclick="openGenieACSSettings()" title="Pengaturan GenieACS">
<button class="btn btn-g btn-sm" onclick="openGenieACSSettings()" title="<%= t('admin.dash.genieacs_title','Pengaturan GenieACS') %>">
<i class="bi bi-gear"></i> GenieACS
</button>
<button class="btn btn-p btn-sm" id="btn-refresh" onclick="refreshAll()">
@@ -802,7 +802,7 @@ async function showDetail(tag) {
<div style="margin-top:16px;background:var(--card-bg);border:1px solid var(--border);border-radius:12px;overflow:hidden">
<div style="padding:14px 18px;border-bottom:1px solid var(--border);font-weight:700;color:var(--muted);display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
<div><i class="bi bi-people"></i> Connected Clients (Live)</div>
<div><i class="bi bi-people"></i> <%= t('admin.dash.conn_live','Klien Terhubung (Live)') %></div>
<span class="badge bm">${connected.length}</span>
</div>
<div style="padding:16px 18px">
@@ -1029,7 +1029,7 @@ async function saveGenieACSSettings() {
showGenieACSMessage(result.message, 'success');
setTimeout(() => {
closeModal('genieacsModal');
showToast('Pengaturan GenieACS berhasil disimpan', 's');
showToast('<%= t('admin.dash.genieacs_title','Pengaturan GenieACS') %> berhasil disimpan', 's');
}, 1500);
} else {
showGenieACSMessage(result.message, 'danger');
+8 -8
View File
@@ -285,7 +285,7 @@
<div class="tb-title">
<button class="hb-menu" onclick="toggleSidebar()"><i class="bi bi-list"></i></button>
<i class="bi bi-activity"></i>
<span id="page-title">Monitoring Sistem</span>
<span id="page-title"><%= t('admin.mon.title','Monitoring Sistem') %></span>
</div>
<div class="tb-right">
<button onclick="location.reload()" class="btn btn-g btn-sm">
@@ -310,17 +310,17 @@
<i class="bi bi-<%= healthStatus.status === 'healthy' ? 'check-circle' : healthStatus.status === 'warning' ? 'exclamation-triangle' : 'x-circle' %>"></i>
<%= healthStatus.status.toUpperCase() %>
</div>
<p class="text-muted mt-2 fs12">Update terakhir: <%= new Date(healthStatus.timestamp).toLocaleString('id-ID') %></p>
<p class="text-muted mt-2 fs12"><%= t('admin.mon.updated','Update terakhir:') %> <%= new Date(healthStatus.timestamp).toLocaleString('id-ID') %></p>
</div>
</div>
<!-- Issues and Warnings -->
<!-- <%= t('admin.mon.issues','Masalah') %> and <%= t('admin.mon.warnings','Peringatan') %> -->
<% if (healthStatus.issues.length > 0 || healthStatus.warnings.length > 0) { %>
<div class="mon-grid">
<% if (healthStatus.issues.length > 0) { %>
<div class="card">
<div class="card-hd chd-d">
<h6><i class="bi bi-exclamation-triangle-fill"></i> Issues (<%= healthStatus.issues.length %>)</h6>
<h6><i class="bi bi-exclamation-triangle-fill"></i> <%= t('admin.mon.issues','Masalah') %> (<%= healthStatus.issues.length %>)</h6>
</div>
<div class="card-bd">
<% healthStatus.issues.forEach(issue => {
@@ -336,7 +336,7 @@
<% if (healthStatus.warnings.length > 0) { %>
<div class="card">
<div class="card-hd chd-w">
<h6><i class="bi bi-exclamation-triangle"></i> Warnings (<%= healthStatus.warnings.length %>)</h6>
<h6><i class="bi bi-exclamation-triangle"></i> <%= t('admin.mon.warnings','Peringatan') %> (<%= healthStatus.warnings.length %>)</h6>
</div>
<div class="card-bd">
<% healthStatus.warnings.forEach(warning => {
@@ -354,7 +354,7 @@
<!-- System Metrics -->
<div class="card mb16">
<div class="card-hd">
<h6><i class="bi bi-cpu"></i> Metrik Sistem</h6>
<h6><i class="bi bi-cpu"></i> <%= t('admin.mon.metrics','Metrik Sistem') %></h6>
</div>
<div class="card-bd">
<div class="stat-grid mb0">
@@ -491,12 +491,12 @@
<!-- Performance Summary -->
<div class="card mb16">
<div class="card-hd">
<h6><i class="bi bi-journal-text"></i> Ringkasan Performa (10 Sample Terakhir)</h6>
<h6><i class="bi bi-journal-text"></i> <%= t('admin.mon.perf','Ringkasan Performa (10 Sample Terakhir)') %></h6>
</div>
<div class="card-bd">
<div class="stat-grid stat-grid-3 mb0">
<div>
<p class="sc-label">Rata-rata</p>
<p class="sc-label"><%= t('admin.mon.avg','Rata-rata') %></p>
<table class="dtbl">
<tr>
<td class="text-muted">CPU</td>
+2 -2
View File
@@ -377,11 +377,11 @@
const sel = document.getElementById('profileName');
sel.innerHTML = '';
if (filtered.length === 0) {
sel.innerHTML = '<option value="">Tidak ada profile dengan harga/durasi (Mikhmon)</option>';
sel.innerHTML = '<option value=""><%= t('admin.vou.no_profile','Tidak ada profile dengan harga/durasi (Mikhmon)') %></option>';
document.getElementById('profileHint').innerText = '';
return;
}
sel.insertAdjacentHTML('beforeend', '<option value="">-- Pilih Profile --</option>');
sel.insertAdjacentHTML('beforeend', '<option value=""><%= t('admin.vou.pick_profile','-- Pilih Profile --') %></option>');
for (const x of filtered) {
const label = `${x.raw.name} (Rp ${Number(x.meta.price || 0).toLocaleString('id-ID')} • ${x.meta.validity})`;
sel.insertAdjacentHTML('beforeend', `<option value="${x.raw.name}">${label}</option>`);