i18n (Phase C batch 12): residu empty-state/hint di 24 file admin ID/EN

This commit is contained in:
oxpoe committed 2026-10-09 08:07:59 +07:00
1 parent e70bc23651
commit 60ef8bafab
26 files changed
+209 -97

No files matched your search

+2 -2
View File
@@ -396,7 +396,7 @@
<div style="background: var(--sub-bg); border: 1px solid var(--border); padding: 18px; border-radius: 16px; margin-bottom: 5px;">
<h4 style="margin: 0 0 12px 0; font-size: 13px; color: var(--text); font-weight: 700;"><i class="bi bi-bezier2 text-primary"></i> LAN / WLAN Port Binding</h4>
<label class="fl" style="margin-bottom: 8px;">Pilih LAN Port yang akan di-bind:</label>
<label class="fl" style="margin-bottom: 8px;"><%= t('admin.acs.bind_lan','Pilih LAN Port yang akan di-bind:') %></label>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 15px;">
<label class="bind-tag active" id="lblLan1">
<input type="checkbox" name="lanPorts" value="LAN1" checked style="display: none;" onchange="syncTagClass(this, 'lblLan1')"> LAN 1
@@ -412,7 +412,7 @@
</label>
</div>
<label class="fl" style="margin-bottom: 8px;">Pilih WLAN (SSID) yang akan di-bind:</label>
<label class="fl" style="margin-bottom: 8px;"><%= t('admin.acs.bind_ssid','Pilih WLAN (SSID) yang akan di-bind:') %></label>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;">
<label class="bind-tag active" id="lblWlan1">
<input type="checkbox" name="wlanSsids" value="SSID1" checked style="display: none;" onchange="syncTagClass(this, 'lblWlan1')"> SSID 1
+2 -2
View File
@@ -372,8 +372,8 @@
<% } else { %>
<div class="card-bd" style="text-align:center;padding:60px 20px">
<div style="font-size:64px;margin-bottom:16px;opacity:0.3"><i class="bi bi-inbox"></i></div>
<h3 style="color:var(--muted);font-weight:500">Tidak ada data absensi</h3>
<p style="color:var(--muted);font-size:14px">Belum ada karyawan yang melakukan absensi pada tanggal ini.</p>
<h3 style="color:var(--muted);font-weight:500"><%= t('admin.att.empty','Tidak ada data absensi') %></h3>
<p style="color:var(--muted);font-size:14px"><%= t('admin.att.empty_sub','Belum ada karyawan yang melakukan absensi pada tanggal ini.') %></p>
</div>
<% } %>
</div>
+2 -2
View File
@@ -99,7 +99,7 @@
<div class="fg">
<label class="fl"><%= t('admin.bk.pick_file','Pilih File Backup (.db / .json)') %></label>
<input type="file" class="fc" name="backupFile" accept=".db,.sqlite,.json" required style="padding: 6px 10px;">
<small class="text-muted" style="font-size: 10px; display: block; margin-top: 4px;">File .db (database) atau .json (settings)</small>
<small class="text-muted" style="font-size: 10px; display: block; margin-top: 4px;"><%= t('admin.bk.file_hint','File .db (database) atau .json (settings)') %></small>
</div>
<button type="submit" class="btn btn-s w-100">
<i class="bi bi-cloud-arrow-up"></i> Upload & Restore
@@ -116,7 +116,7 @@
<div class="fg">
<label class="fl"><%= t('admin.bk.retention','Retensi (Hari)') %></label>
<input type="number" class="fc" name="retentionDays" value="30" min="1" max="365">
<small class="text-muted" style="font-size: 10px; display: block; margin-top: 4px;">Backup lebih lama dari periode ini akan dihapus</small>
<small class="text-muted" style="font-size: 10px; display: block; margin-top: 4px;"><%= t('admin.bk.retention_hint','Backup lebih lama dari periode ini akan dihapus') %></small>
</div>
<button type="submit" class="btn btn-w w-100">
<i class="bi bi-stars"></i> Bersihkan Sekarang
+3 -3
View File
@@ -186,7 +186,7 @@
<button type="button" onclick="clearSelectedPhoto('checkoutPhoto')" class="btn btn-d btn-sm" id="checkoutPhotoClearBtn" style="display: none; border-radius: 8px; font-size: 11px;"><%= t('collector.delete_photo','Hapus Foto') %></button>
</div>
<div id="checkoutPhotoPreviewWrap" style="display:none;margin-top:8px">
<div class="photo-help" style="margin-bottom:6px">Preview foto final yang akan dikirim:</div>
<div class="photo-help" style="margin-bottom:6px"><%= t('admin.ca.preview','Preview foto final yang akan dikirim:') %></div>
<img id="checkoutPhotoPreview" alt="Preview foto check-out" style="width:100%;max-width:240px;border-radius:12px;border:1px solid var(--border);object-fit:cover">
</div>
<button id="checkoutSubmitBtn" class="btn btn-d btn-sm" style="width:100%;margin-top:12px;opacity:0.55;cursor:not-allowed" onclick="checkout(event)" disabled>
@@ -196,7 +196,7 @@
<% } else { %>
<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>
<p style="color: var(--muted); margin: 16px 0;"><%= t('admin.ca.checkin_hint','Silakan lakukan check-in untuk memulai hari kerja Anda.') %></p>
<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')">
@@ -217,7 +217,7 @@
<button type="button" onclick="clearSelectedPhoto('checkinPhoto')" class="btn btn-d btn-sm" id="checkinPhotoClearBtn" style="display: none; border-radius: 8px; font-size: 11px;"><%= t('collector.delete_photo','Hapus Foto') %></button>
</div>
<div id="checkinPhotoPreviewWrap" style="display:none;margin-top:8px">
<div class="photo-help" style="margin-bottom:6px">Preview foto final yang akan dikirim:</div>
<div class="photo-help" style="margin-bottom:6px"><%= t('admin.ca.preview','Preview foto final yang akan dikirim:') %></div>
<img id="checkinPhotoPreview" alt="Preview foto check-in" style="width:100%;max-width:240px;border-radius:12px;border:1px solid var(--border);object-fit:cover">
</div>
<button id="checkinSubmitBtn" class="btn btn-s btn-sm" style="width:100%;margin-top:12px;opacity:0.55;cursor:not-allowed" onclick="checkin(event)" disabled>
+1 -1
View File
@@ -101,7 +101,7 @@
<% if (decidedRole === 'system') { %>
<div style="font-size:10px;color:var(--muted)">Kolektor auto-approve aktif</div>
<% } else if (r.status === 'pending') { %>
<div style="font-size:10px;color:var(--muted)">Menunggu admin atau kasir</div>
<div style="font-size:10px;color:var(--muted)"><%= t('admin.cpay.wait_approval','Menunggu admin atau kasir') %></div>
<% } else if (r.decided_by_name) { %>
<div style="font-size:10px;color:var(--muted)">Oleh: <%= r.decided_by_name %></div>
<% } %>
+1 -1
View File
@@ -62,7 +62,7 @@
</thead>
<tbody>
<% if (!collectors || collectors.length === 0) { %>
<tr><td colspan="8" class="text-center text-muted" style="padding:20px">Belum ada data kolektor.</td></tr>
<tr><td colspan="8" class="text-center text-muted" style="padding:20px"><%= t('admin.col.empty','Belum ada data kolektor.') %></td></tr>
<% } else { %>
<% collectors.forEach(c => { %>
<tr data-mode="<%= c.auto_approve ? 'direct' : 'approval' %>" data-status="<%= c.is_active ? 'active' : 'inactive' %>">
+2 -2
View File
@@ -819,7 +819,7 @@
<div class="fg">
<label class="fl" style="font-weight:700; color:var(--warning);"><i class="bi bi-key-fill"></i> Password PPPoE / RADIUS *</label>
<input class="fc" name="pppoe_password" id="e_pppoe_password" placeholder="<%= t('admin.cust.ph_pppoe_pass','Masukkan password PPPoE') %>" required>
<small class="text-muted" style="font-size:10px;display:block;margin-top:4px">Kredensial login modem pelanggan untuk RADIUS & PPPoE.</small>
<small class="text-muted" style="font-size:10px;display:block;margin-top:4px"><%= t('admin.cust.modem_hint','Kredensial login modem pelanggan untuk RADIUS & PPPoE.') %></small>
</div>
</div>
</div>
@@ -886,7 +886,7 @@
<div class="fg">
<label class="fl"><i class="bi bi-hourglass-split text-warning me-1"></i> <%= t('admin.cust.validity_prepaid','Masa Aktif / Expired Date (Khusus Prabayar)') %></label>
<input class="fc" type="date" name="expired_at" id="e_expired_at">
<small class="text-muted" style="font-size:10px;display:block;margin-top:2px;">Otomatis dihitung saat pasang/bayar. Bisa disesuaikan manual jika perlu.</small>
<small class="text-muted" style="font-size:10px;display:block;margin-top:2px;"><%= t('admin.cust.reg_hint','Otomatis dihitung saat pasang/bayar. Bisa disesuaikan manual jika perlu.') %></small>
</div>
</div>
<div class="form-row">
+1 -1
View File
@@ -125,7 +125,7 @@
</thead>
<tbody>
<% if (customers.length===0) { %>
<tr><td colspan="7" class="text-center text-muted" style="padding:24px">Tidak ada pelanggan.</td></tr>
<tr><td colspan="7" class="text-center text-muted" style="padding:24px"><%= t('admin.disc.empty_cust','Tidak ada pelanggan.') %></td></tr>
<% } %>
<% customers.forEach(function(c){ %>
<tr class="cust-row <%= c.status==='suspended'?'suspended':'' %>">
+1 -1
View File
@@ -73,7 +73,7 @@
</thead>
<tbody>
<% if (cashIn.length===0) { %>
<tr><td colspan="7" class="text-center text-muted" style="padding:32px">Belum ada data kas masuk.</td></tr>
<tr><td colspan="7" class="text-center text-muted" style="padding:32px"><%= t('admin.cashin.empty','Belum ada data kas masuk.') %></td></tr>
<% } %>
<% cashIn.forEach((item)=>{ %>
<tr>
+1 -1
View File
@@ -71,7 +71,7 @@
</thead>
<tbody>
<% if (expenses.length===0) { %>
<tr><td colspan="8" class="text-center text-muted" style="padding:32px">Belum ada data pengeluaran.</td></tr>
<tr><td colspan="8" class="text-center text-muted" style="padding:32px"><%= t('admin.exp.empty','Belum ada data pengeluaran.') %></td></tr>
<% } %>
<% expenses.forEach((item)=>{ %>
<tr>
+6 -6
View File
@@ -847,7 +847,7 @@
<div class="form-row">
<div class="fg"><label class="fl">Jumlah Port (Kapasitas)</label><input type="number" name="port_capacity" class="fc" min="1" step="1" value="16" required></div>
<div class="fg"><label class="fl">PON Port (opsional)</label><input type="text" name="pon_port" class="fc" placeholder="Contoh: 1/2/7"></div>
<div class="fg"><label class="fl"><%= t('admin.map.pon_port','PON Port (opsional)') %></label><input type="text" name="pon_port" class="fc" placeholder="Contoh: 1/2/7"></div>
</div>
<label class="fl"><%= t('admin.map.form.pick_location', 'Pilih Lokasi di Peta') %></label>
@@ -878,7 +878,7 @@
<div class="form-row">
<div class="fg"><label class="fl">Jumlah Port (Kapasitas)</label><input type="number" name="port_capacity" id="e_odp_capacity" class="fc" min="1" step="1" required></div>
<div class="fg"><label class="fl">PON Port (opsional)</label><input type="text" name="pon_port" id="e_odp_pon" class="fc" placeholder="Contoh: 1/2/7"></div>
<div class="fg"><label class="fl"><%= t('admin.map.pon_port','PON Port (opsional)') %></label><input type="text" name="pon_port" id="e_odp_pon" class="fc" placeholder="Contoh: 1/2/7"></div>
</div>
<label class="fl"><%= t('admin.map.form.adjust_location', 'Sesuaikan Lokasi di Peta') %></label>
@@ -1121,7 +1121,7 @@
function renderCustomerTable() {
var tb = document.getElementById('customersTableBody');
if (!tb) return;
if (!customers.length) { tb.innerHTML = '<tr><td colspan="6" style="padding:16px;text-align:center;color:#64748b;">Belum ada data pelanggan.</td></tr>'; return; }
if (!customers.length) { tb.innerHTML = '<tr><td colspan="6" style="padding:16px;text-align:center;color:#64748b;"><%= t('admin.map.empty_cust','Belum ada data pelanggan.') %></td></tr>'; return; }
tb.innerHTML = customers.map(function (c) {
var hasGps = Boolean(c.lat && c.lng);
var isActive = c.status === 'active';
@@ -1149,7 +1149,7 @@
function renderOdpTable() {
var tb = document.getElementById('odpsTbody');
if (!tb) return;
if (!odps.length) { tb.innerHTML = '<tr><td colspan="5" style="padding:16px;text-align:center;color:#64748b;">Belum ada data ODP.</td></tr>'; return; }
if (!odps.length) { tb.innerHTML = '<tr><td colspan="5" style="padding:16px;text-align:center;color:#64748b;"><%= t('admin.map.empty_odp','Belum ada data ODP.') %></td></tr>'; return; }
tb.innerHTML = odps.map(function (o) {
var pu = o.port_usage || { capacity: Number(o.port_capacity || 16) || 16, usedCount: 0, remaining: Number(o.port_capacity || 16) || 16 };
return '<tr data-name="' + escapeHtml(String(o.name || '').toLowerCase()) + '" data-desc="' + escapeHtml(String(o.description || '').toLowerCase()) + '">' +
@@ -1158,7 +1158,7 @@
'<td><div style="font-weight:800;font-size:12px;">' +
'<button type="button" class="btn btn-sm btn-g" style="font-weight:800;padding:2px 8px" onclick="showOdpPorts(' + Number(o.id) + ')" title="Lihat visual port terpakai"><i class="bi bi-diagram-2"></i> ' + pu.usedCount + '/' + pu.capacity + '</button>' +
(pu.remaining === 0 ? ' <span class="cp-chip" style="background:#fee2e2;color:#991b1b;border-color:#fecaca"><i class="bi bi-exclamation-triangle-fill"></i> PENUH</span>' : ' <span style="font-size:11px;color:var(--muted)">sisa ' + pu.remaining + '</span>') +
'</div><div style="font-size:11px;color:var(--muted);margin-top:2px">Klik untuk lihat visual port</div></td>' +
'</div><div style="font-size:11px;color:var(--muted);margin-top:2px"><%= t('admin.map.view_port','Klik untuk lihat visual port') %></div></td>' +
'<td><div style="font-size:11.5px;font-family:monospace;color:#334155;font-weight:700;">' + (Number(o.lat) || 0).toFixed(6) + ', ' + (Number(o.lng) || 0).toFixed(6) + '</div></td>' +
'<td style="white-space:nowrap"><div class="act">' +
'<button type="button" class="act-btn" title="Aksi" onclick="actToggle(event, this)"><i class="bi bi-three-dots-vertical"></i></button>' +
@@ -2268,7 +2268,7 @@
if (legend) legend.textContent = 'Hijau = terhubung (online), Merah = offline, tanpa warna = belum terhubung. Terpakai ' + (u.usedCount || 0) + '/' + cap + '.';
if (title) title.textContent = 'Port ODP (' + (u.usedCount || 0) + '/' + cap + ')';
})
.catch(function () { grid.innerHTML = '<span class="text-muted">Gagal memuat data port.</span>'; });
.catch(function () { grid.innerHTML = '<span class="text-muted"><%= t('admin.map.port_err','Gagal memuat data port.') %></span>'; });
}
function buildWaLink(phone, name) {
+2 -2
View File
@@ -850,7 +850,7 @@
}
pppoeProfilesData = await res.json();
if (!Array.isArray(pppoeProfilesData) || pppoeProfilesData.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="text-center text-muted">Tidak ada data profile PPPoE.</td></tr>`;
tbody.innerHTML = `<tr><td colspan="5" class="text-center text-muted"><%= t('admin.mt.empty_pppoe','Tidak ada data profile PPPoE.') %></td></tr>`;
return;
}
tbody.innerHTML = pppoeProfilesData.map((p, i) => `
@@ -886,7 +886,7 @@
}
hotspotProfilesData = await res.json();
if (!Array.isArray(hotspotProfilesData) || hotspotProfilesData.length === 0) {
tbody.innerHTML = `<tr><td colspan="7" class="text-center text-muted">Tidak ada data profile Hotspot.</td></tr>`;
tbody.innerHTML = `<tr><td colspan="7" class="text-center text-muted"><%= t('admin.mt.empty_hs','Tidak ada data profile Hotspot.') %></td></tr>`;
return;
}
tbody.innerHTML = hotspotProfilesData.map((p, i) => {
+1 -1
View File
@@ -1386,7 +1386,7 @@
overlay.style.pointerEvents = 'auto';
const overlayText = overlay.querySelector('.overlay-text');
if (overlayText) {
overlayText.innerHTML = `<span style="color:var(--danger);font-weight:700">Koneksi MikroTik Gagal</span><br><span style="font-size:11px;text-transform:none;margin-top:4px;display:block;color:var(--text-muted)">Pastikan IP/port benar dan API service aktif. Mencoba kembali...</span>`;
overlayText.innerHTML = `<span style="color:var(--danger);font-weight:700">Koneksi MikroTik Gagal</span><br><span style="font-size:11px;text-transform:none;margin-top:4px;display:block;color:var(--text-muted)"><%= t('admin.md.reconnect_hint','Pastikan IP/port benar dan API service aktif. Mencoba kembali...') %></span>`;
}
}
}
+4 -4
View File
@@ -358,7 +358,7 @@
<i class="bi bi-wifi" style="font-size:20px;color:#3b82f6"></i>
<div>
<div style="font-weight:700;color:#e2e8f0">Konfigurasi WiFi</div>
<div class="hint" style="font-size:12px;margin-top:2px">Set SSID dan password WiFi ONU</div>
<div class="hint" style="font-size:12px;margin-top:2px"><%= t('admin.onu.ssid_hint','Set SSID dan password WiFi ONU') %></div>
</div>
</div>
<input type="checkbox" id="enableWifi" onchange="toggleWifiFields()" style="width:20px;height:20px;cursor:pointer">
@@ -384,7 +384,7 @@
<i class="bi bi-ethernet" style="font-size:20px;color:#10b981"></i>
<div>
<div style="font-weight:700;color:#e2e8f0">Mode LAN</div>
<div class="hint" style="font-size:12px;margin-top:2px">Bridge (MikroTik dial) atau Router (ONU dial)</div>
<div class="hint" style="font-size:12px;margin-top:2px"><%= t('admin.onu.wan_mode_hint','Bridge (MikroTik dial) atau Router (ONU dial)') %></div>
</div>
</div>
<input type="checkbox" id="enableLanMode" onchange="toggleLanModeFields()" style="width:20px;height:20px;cursor:pointer">
@@ -460,7 +460,7 @@
<div class="fg" style="margin:0">
<label class="fl">ACS URL <span style="color:#ef4444">*</span></label>
<input type="text" class="fc" name="tr069AcsUrl" placeholder="http://acs.example.com:7547">
<div class="hint" style="font-size:11px;margin-top:4px">Format: http://host:port atau https://host:port</div>
<div class="hint" style="font-size:11px;margin-top:4px"><%= t('admin.onu.ph_url','Format: http://host:port atau https://host:port') %></div>
</div>
<div class="form-row2">
<div class="fg" style="margin:0">
@@ -585,7 +585,7 @@ async function scanUnconfiguredONUs() {
});
listDiv.innerHTML = html;
} else {
listDiv.innerHTML = '<div class="hint" style="text-align:center;padding:40px"><i class="bi bi-check-circle" style="font-size:32px;color:#22c55e"></i><p>Tidak ada ONU yang belum terkonfigurasi</p></div>';
listDiv.innerHTML = '<div class="hint" style="text-align:center;padding:40px"><i class="bi bi-check-circle" style="font-size:32px;color:#22c55e"></i><p><%= t('admin.onu.empty','Tidak ada ONU yang belum terkonfigurasi') %></p></div>';
}
} catch (error) {
listDiv.innerHTML = '<div class="alert alert-d"><i class="bi bi-exclamation-circle"></i> Error: ' + error.message + '</div>';
+6 -6
View File
@@ -253,7 +253,7 @@
</small>
</div>
<div style="background: rgba(34,197,94,0.06); padding: 12px 14px; border-radius: 10px; margin-top: 10px;">
<div class="fw6" style="font-size:12px;margin-bottom:8px;color:var(--success)"><i class="bi bi-percent"></i> Promo & prorata tagihan</div>
<div class="fw6" style="font-size:12px;margin-bottom:8px;color:var(--success)"><i class="bi bi-percent"></i> <%= t('admin.pkg.promo_prorata','Promo & prorata tagihan') %></div>
<div class="form-row">
<div class="fg"><label class="fl"><%= t('admin.pkg.promo_price','Harga promo (Rp / siklus)') %></label><input class="fc" name="promo_price" type="number" min="0" placeholder="<%= t('admin.pkg.ph_promo_price','Opsional, mis. 99000') %>"></div>
<div class="fg"><label class="fl"><%= t('admin.pkg.promo_cycles','Jumlah bulan promo') %></label><input class="fc" name="promo_cycles" type="number" min="0" value="0" placeholder="<%= t('admin.pkg.ph_promo_cycles','0 = tanpa promo') %>"></div>
@@ -349,7 +349,7 @@
<div class="fg"><label class="fl"><%= t('admin.pkg.night_down','Download Malam (Mbps)') %></label><input class="fc" name="night_speed_down" type="number" step="any" min="0" placeholder="20"></div>
<div class="fg"><label class="fl">Upload Malam (Mbps)</label><input class="fc" name="night_speed_up" type="number" step="any" min="0" placeholder="10"></div>
</div>
<div style="font-size: 10px; color: var(--muted); margin-top: 4px;">Sistem akan ganti profile ppp pelanggan ke profile ini pada jam 00:00 - 06:00.</div>
<div style="font-size: 10px; color: var(--muted); margin-top: 4px;"><%= t('admin.pkg.night_hint','Sistem akan ganti profile ppp pelanggan ke profile ini pada jam 00:00 - 06:00.') %></div>
</div>
<hr style="opacity:0.1; margin: 15px 0;">
@@ -454,14 +454,14 @@
</div>
<div style="background: rgba(34,197,94,0.06); padding: 12px 14px; border-radius: 10px; margin-top: 10px;">
<div class="fw6" style="font-size:12px;margin-bottom:8px;color:var(--success)"><i class="bi bi-percent"></i> Promo & prorata tagihan</div>
<div class="fw6" style="font-size:12px;margin-bottom:8px;color:var(--success)"><i class="bi bi-percent"></i> <%= t('admin.pkg.promo_prorata','Promo & prorata tagihan') %></div>
<div class="form-row">
<div class="fg"><label class="fl"><%= t('admin.pkg.promo_price','Harga promo (Rp / siklus)') %></label><input class="fc" name="promo_price" id="e_promo_price" type="number" min="0" placeholder="Kosong = tanpa harga promo"></div>
<div class="fg"><label class="fl"><%= t('admin.pkg.promo_cycles','Jumlah bulan promo') %></label><input class="fc" name="promo_cycles" id="e_promo_cycles" type="number" min="0" value="0"></div>
</div>
<div class="flex ai-c" style="gap:8px;margin-top:4px">
<input type="checkbox" name="prorate_first_invoice" id="e_prorate" value="1">
<label for="e_prorate" style="margin:0;font-size:12px">Prorata tagihan pertama (tanggal pasang = bulan tagihan)</label>
<label for="e_prorate" style="margin:0;font-size:12px"><%= t('admin.pkg.prorata_first','Prorata tagihan pertama (tanggal pasang = bulan tagihan)') %></label>
</div>
</div>
@@ -536,7 +536,7 @@
<input type="checkbox" name="use_night_speed" id="e_use_night" value="1" onchange="toggleEditNight()">
<h6 style="font-size:12px; margin: 0; color: var(--primary); text-transform: uppercase; letter-spacing: 0.5px;"><i class="bi bi-clock"></i> Aktifkan Jam Kalong</h6>
</div>
<small class="text-muted" style="font-size:11px;display:block;margin:-6px 0 8px 26px">Centang untuk mengatur (kolom terbuka otomatis bila paket ini memakai Jam Kalong).</small>
<small class="text-muted" style="font-size:11px;display:block;margin:-6px 0 8px 26px"><%= t('admin.pkg.kalong_hint','Centang untuk mengatur (kolom terbuka otomatis bila paket ini memakai Jam Kalong).') %></small>
<div id="e_night_fields" style="display:none">
<div class="fg">
<label class="fl"><%= t('admin.pkg.prof_name','Nama Profil') %>e Malam (MikroTik)</label>
@@ -559,7 +559,7 @@
<input type="checkbox" name="use_fup" id="e_use_fup" value="1" onchange="toggleEditFup()">
<h6 style="font-size:12px; margin: 0; color: var(--danger); text-transform: uppercase; letter-spacing: 0.5px;"><i class="bi bi-speedometer2"></i> Aktifkan FUP</h6>
</div>
<small class="text-muted" style="font-size:11px;display:block;margin:-6px 0 8px 26px">Centang untuk mengatur (kolom terbuka otomatis bila paket ini memakai FUP).</small>
<small class="text-muted" style="font-size:11px;display:block;margin:-6px 0 8px 26px"><%= t('admin.pkg.fup_hint','Centang untuk mengatur (kolom terbuka otomatis bila paket ini memakai FUP).') %></small>
<div id="e_fup_fields" style="display:none">
<div class="fg">
<label class="fl"><%= t('admin.pkg.prof_name','Nama Profil') %>e FUP (MikroTik)</label>
+2 -2
View File
@@ -87,7 +87,7 @@
</div>
<div class="card-bd">
<% if (employees.length === 0) { %>
<div style="text-align:center;padding:40px"><div style="font-size:48px;opacity:.3"><i class="bi bi-person-fill"></i></div><p style="color:var(--muted)">Belum ada karyawan aktif</p></div>
<div style="text-align:center;padding:40px"><div style="font-size:48px;opacity:.3"><i class="bi bi-person-fill"></i></div><p style="color:var(--muted)"><%= t('admin.payroll.empty_staff','Belum ada karyawan aktif') %></p></div>
<% } else { %>
<% employees.forEach(emp => { %>
<div class="emp-card type-<%= emp.employee_type %>">
@@ -137,7 +137,7 @@
</div>
<div class="card-bd">
<% if (slips.length === 0) { %>
<div style="text-align:center;padding:40px"><div style="font-size:48px;opacity:.3"><i class="bi bi-inbox"></i></div><p style="color:var(--muted)">Belum ada slip gaji untuk periode ini</p><p style="color:var(--muted);font-size:12px">Klik "Generate Semua" untuk membuat slip gaji otomatis</p></div>
<div style="text-align:center;padding:40px"><div style="font-size:48px;opacity:.3"><i class="bi bi-inbox"></i></div><p style="color:var(--muted)"><%= t('admin.payroll.empty_slip','Belum ada slip gaji untuk periode ini') %></p><p style="color:var(--muted);font-size:12px"><%= t('admin.payroll.gen_hint','Klik "Generate Semua" untuk membuat slip gaji otomatis') %></p></div>
<% } else { %>
<!-- Bulk Actions -->
+2 -2
View File
@@ -226,12 +226,12 @@
<div class="fg"><label class="fl">PPN di dalam harga?</label>
<select class="fs" name="finance_revenue_includes_ppn">
<option value="0" <%= finance.inclPpn ? '' : 'selected' %>>Tidak — PPN ditambahkan</option>
<option value="1" <%= finance.inclPpn ? 'selected' : '' %>>Ya — PPN sudah termasuk</option>
<option value="1" <%= finance.inclPpn ? 'selected' : '' %>><%= t('admin.rep.ppn_incl','Ya — PPN sudah termasuk') %></option>
</select>
</div>
</div>
<button type="submit" class="btn btn-p btn-sm"><i class="bi bi-save"></i> Simpan Pengaturan</button>
<span class="hint" style="margin-left:10px">PPh Badan, PPN, USO &amp; BHP dihitung otomatis dari nilai ini.</span>
<span class="hint" style="margin-left:10px"><%- t('admin.rep.tax_hint','PPh Badan, PPN, USO &amp; BHP dihitung otomatis dari nilai ini.') %></span>
</form>
</div>
</details>
+1 -1
View File
@@ -556,7 +556,7 @@
<!-- TAB 2: RouterOS Manual Script (.rsc) -->
<div id="isolir-tab-script" style="display:none;">
<p class="small text-muted mb-2">Script ini di-generate otomatis berdasarkan IP server & URL billing (app_url). Anda dapat menyalin dan menempelkannya di Winbox Terminal MikroTik.</p>
<p class="small text-muted mb-2"><%= t('admin.rt.script_hint','Script ini di-generate otomatis berdasarkan IP server & URL billing (app_url). Anda dapat menyalin dan menempelkannya di Winbox Terminal MikroTik.') %></p>
<div id="isolir-script-meta" class="small text-muted mb-2 fw-bold" style="color:#a5b4fc !important;"></div>
<textarea id="isolir-script-body" class="fc" readonly rows="14" style="width:100%;font-family:ui-monospace,monospace;font-size:12px;background:rgba(15,23,42,0.9);color:#818cf8;border-radius:8px;"></textarea>
</div>
+4 -4
View File
@@ -543,7 +543,7 @@
<option value="googlefiber"><%= t('admin.set.sp_googlefiber','Google Fiber Speed Test (eksternal, buka tab)') %></option>
<option value="speedtestnet"><%= t('admin.set.sp_speedtestnet','Speedtest.net / Ookla (eksternal, buka tab)') %></option>
</select>
<div style="font-size:10px; color:var(--muted); margin-top:4px">Preset hanya mengisi otomatis — sesuaikan URL bila perlu.</div>
<div style="font-size:10px; color:var(--muted); margin-top:4px"><%= t('admin.set.speedtest_hint','Preset hanya mengisi otomatis — sesuaikan URL bila perlu.') %></div>
</div>
<div class="fg">
<label class="fl"><%= t('admin.set.speed_link','Link Speed Test') %></label>
@@ -615,14 +615,14 @@
<div class="fg" style="margin-top:10px">
<label class="fl"><%= t('admin.set.qris_payload','QRIS Static Payload (String)') %></label>
<textarea class="fc" name="qris_static_payload" rows="3" placeholder="000201010211...6304XXXX"><%= settings.qris_static_payload || '' %></textarea>
<div style="font-size:10px; color:var(--muted); margin-top:4px">Jika diisi, sistem akan membuat QRIS dinamis per transaksi (nominal langsung terisi saat scan). Jika kosong, sistem pakai URL gambar seperti biasa.</div>
<div style="font-size:10px; color:var(--muted); margin-top:4px"><%= t('admin.set.qris_dyn_hint','Jika diisi, sistem akan membuat QRIS dinamis per transaksi (nominal langsung terisi saat scan). Jika kosong, sistem pakai URL gambar seperti biasa.') %></div>
</div>
<div style="margin-top:10px">
<div style="display:flex; gap:10px; align-items:flex-end; flex-wrap:wrap">
<div class="fg" style="min-width:260px; flex: 1 1 260px">
<label class="fl"><%= t('admin.set.qris_upload','Upload Gambar QRIS (opsional)') %></label>
<input class="fc" type="file" id="qris_upload_file" name="qris_file" accept="image/png,image/jpeg,image/jpg,image/webp">
<div style="font-size:10px; color:var(--muted); margin-top:4px">Jika di-upload, sistem akan menyimpan file dan otomatis mengisi URL QRIS.</div>
<div style="font-size:10px; color:var(--muted); margin-top:4px"><%= t('admin.set.qris_upload_hint','Jika di-upload, sistem akan menyimpan file dan otomatis mengisi URL QRIS.') %></div>
</div>
<button class="btn btn-s" type="button" id="qris_upload_btn" style="height:38px"><i class="bi bi-upload"></i> Upload</button>
</div>
@@ -727,7 +727,7 @@
<label class="fl"><%= t('admin.set.ewallet_list','E-Wallet (1 per baris: Nama|Nomor|Atas Nama)') %></label>
<textarea class="fc" name="payment_ewallet_accounts" rows="3" placeholder="DANA|08123456789|Admin OPEN-ISP"><%= settings.payment_ewallet_accounts || '' %></textarea>
</div>
<div style="font-size:10px; color:var(--muted); margin-top:4px">Nominal &amp; kode unik sama seperti QRIS; verifikasi otomatis (webhook forwarder) + manual (upload bukti).</div>
<div style="font-size:10px; color:var(--muted); margin-top:4px"><%- t('admin.set.bank_hint','Nominal &amp; kode unik sama seperti QRIS; verifikasi otomatis (webhook forwarder) + manual (upload bukti).') %></div>
</div>
<!-- Hubungkan Webhook Notifikasi E-Wallet (Mode 2) -->
+1 -1
View File
@@ -314,7 +314,7 @@ document.addEventListener('click', function(e) {
const allPhotos = [...customerPhotos, ...techPhotos];
if (allPhotos.length === 0) {
gallery.innerHTML = '<div class="col-12 text-center text-muted">Tidak ada foto</div>';
gallery.innerHTML = '<div class="col-12 text-center text-muted"><%= t('admin.tk.no_photo','Tidak ada foto') %></div>';
} else {
// Customer photos first
if (customerPhotos.length > 0) {
+3 -3
View File
@@ -146,7 +146,7 @@
<div class="fg">
<label class="fl">Status Paket *</label>
<select class="fc" id="form_is_active" required>
<option value="1">Aktif (Tampil di portal pelanggan)</option>
<option value="1"><%= t('admin.vp.opt_active','Aktif (Tampil di portal pelanggan)') %></option>
<option value="0">Nonaktif (Sembunyikan)</option>
</select>
</div>
@@ -177,7 +177,7 @@
const list = document.getElementById('packages-list');
if (currentPackages.length === 0) {
list.innerHTML = '<tr><td colspan="9" class="text-center text-muted" style="padding: 24px;">Belum ada paket voucher hotspot real-time terdaftar. Klik tombol Tambah diatas untuk memulai!</td></tr>';
list.innerHTML = '<tr><td colspan="9" class="text-center text-muted" style="padding: 24px;"><%= t('admin.vp.empty','Belum ada paket voucher hotspot real-time terdaftar. Klik tombol Tambah diatas untuk memulai!') %></td></tr>';
return;
}
@@ -272,7 +272,7 @@
document.getElementById('form_validity').value = meta.validity;
hintEl.innerHTML = `<span style="color:var(--success)"><b>Terdeteksi:</b> Harga Rp ${meta.price.toLocaleString('id-ID')} • Durasi ${meta.validity}</span>`;
} else {
hintEl.innerHTML = '<span class="text-muted">Profil tidak memiliki metadata Mikhmon. Isi harga dan masa aktif manual.</span>';
hintEl.innerHTML = '<span class="text-muted"><%= t('admin.vp.no_meta','Profil tidak memiliki metadata Mikhmon. Isi harga dan masa aktif manual.') %></span>';
}
}
+1 -1
View File
@@ -161,7 +161,7 @@
<div style="font-size:11px;color:var(--muted);margin-top:4px">Default REST GOWA port <code>3000</code>. Jalankan: <code>whatsapp rest</code>.</div>
</div>
<div class="fg" style="flex:1;">
<label class="fl">Device ID (opsional)</label>
<label class="fl"><%= t('admin.wa.ph_deviceid','Device ID (opsional)') %></label>
<input class="fc" name="gowa_device_id" value="<%= gowaSettings.gowa_device_id %>" placeholder="multi-device (mis. device1)">
</div>
</div>
+1 -1
View File
@@ -278,7 +278,7 @@ async function loadChatMessages(phone, showSpinner = false) {
if (data.ok) {
const messages = data.messages || [];
if (messages.length === 0) {
container.innerHTML = `<div class="text-center text-muted" style="margin:auto;">Belum ada pesan dengan pelanggan ini. Ketik pesan di bawah dan tekan Enter untuk memulai.</div>`;
container.innerHTML = `<div class="text-center text-muted" style="margin:auto;"><%= t('admin.wlc.empty','Belum ada pesan dengan pelanggan ini. Ketik pesan di bawah dan tekan Enter untuk memulai.') %></div>`;
return;
}
+5 -5
View File
@@ -54,7 +54,7 @@
<label class="fl"><%= t('admin.wt.lbl_reminder','Template Pengingat Tagihan (Bulanan / Manual)') %></label>
<textarea class="fc" id="tmpl_auto_billing" name="whatsapp_auto_billing_message" rows="8" required style="width: 100%;"><%= templates.whatsapp_auto_billing_message %></textarea>
<div style="font-size: 11px; color: var(--muted); margin-top: 6px; display:flex; justify-between; align-items:center;">
<span><i class="bi bi-info-circle"></i> Variabel: <b style="color:var(--primary)">{{id_pelanggan}}</b> <span style="color:var(--primary)"><%= t('common.required','(wajib)') %></span>, <b style="color:var(--primary)">{{pppoe}}</b> <span style="color:var(--muted)">(Username PPPoE, opsional)</span>, <b>{{nama}}</b>, <b>{{tagihan}}</b>, <b>{{rincian}}</b>, <b>{{paket}}</b>, <b>{{link}}</b>.</span>
<span><i class="bi bi-info-circle"></i> Variabel: <b style="color:var(--primary)">{{id_pelanggan}}</b> <span style="color:var(--primary)"><%= t('common.required','(wajib)') %></span>, <b style="color:var(--primary)">{{pppoe}}</b> <span style="color:var(--muted)"><%= t('admin.wt.ph_user','(Username PPPoE, opsional)') %></span>, <b>{{nama}}</b>, <b>{{tagihan}}</b>, <b>{{rincian}}</b>, <b>{{paket}}</b>, <b>{{link}}</b>.</span>
</div>
</div>
@@ -62,7 +62,7 @@
<label class="fl"><%= t('admin.wt.lbl_qris','Template Tagihan QRIS (Manual)') %></label>
<textarea class="fc" id="tmpl_billing_qris" name="whatsapp_billing_qris_message" rows="8" required style="width: 100%;"><%= templates.whatsapp_billing_qris_message %></textarea>
<div style="font-size: 11px; color: var(--muted); margin-top: 6px;">
<i class="bi bi-info-circle"></i> Variabel: <b style="color:var(--primary)">{{id_pelanggan}}</b> <span style="color:var(--primary)"><%= t('common.required','(wajib)') %></span>, <b style="color:var(--primary)">{{pppoe}}</b> <span style="color:var(--muted)">(Username PPPoE, opsional)</span>, <b>{{nama}}</b>, <b>{{periode}}</b>, <b>{{paket}}</b>, <b>{{qris_nominal}}</b>, <b>{{qris_kode}}</b>, <b>{{qris_qr}}</b>.
<i class="bi bi-info-circle"></i> Variabel: <b style="color:var(--primary)">{{id_pelanggan}}</b> <span style="color:var(--primary)"><%= t('common.required','(wajib)') %></span>, <b style="color:var(--primary)">{{pppoe}}</b> <span style="color:var(--muted)"><%= t('admin.wt.ph_user','(Username PPPoE, opsional)') %></span>, <b>{{nama}}</b>, <b>{{periode}}</b>, <b>{{paket}}</b>, <b>{{qris_nominal}}</b>, <b>{{qris_kode}}</b>, <b>{{qris_qr}}</b>.
</div>
</div>
</div>
@@ -72,7 +72,7 @@
<label class="fl"><%= t('admin.wt.lbl_success','Template Pembayaran Sukses (Lunas)') %></label>
<textarea class="fc" id="tmpl_payment_success" name="whatsapp_payment_success_message" rows="8" required style="width: 100%;"><%= templates.whatsapp_payment_success_message %></textarea>
<div style="font-size: 11px; color: var(--muted); margin-top: 6px;">
<i class="bi bi-info-circle"></i> Variabel: <b style="color:var(--primary)">{{id_pelanggan}}</b> <span style="color:var(--primary)"><%= t('common.required','(wajib)') %></span>, <b style="color:var(--primary)">{{pppoe}}</b> <span style="color:var(--muted)">(Username PPPoE, opsional)</span>, <b>{{nama}}</b>, <b>{{no_invoice}}</b>, <b>{{paket}}</b>, <b>{{periode}}</b>, <b>{{waktu}}</b>, <b>{{metode}}</b>, <b>{{total}}</b>, <b>{{company}}</b>, <b>{{company_phone}}</b>, <b>{{link_portal}}</b>, <b>{{link_pdf}}</b> (link PDF invoice).
<i class="bi bi-info-circle"></i> Variabel: <b style="color:var(--primary)">{{id_pelanggan}}</b> <span style="color:var(--primary)"><%= t('common.required','(wajib)') %></span>, <b style="color:var(--primary)">{{pppoe}}</b> <span style="color:var(--muted)"><%= t('admin.wt.ph_user','(Username PPPoE, opsional)') %></span>, <b>{{nama}}</b>, <b>{{no_invoice}}</b>, <b>{{paket}}</b>, <b>{{periode}}</b>, <b>{{waktu}}</b>, <b>{{metode}}</b>, <b>{{total}}</b>, <b>{{company}}</b>, <b>{{company_phone}}</b>, <b>{{link_portal}}</b>, <b>{{link_pdf}}</b> (link PDF invoice).
</div>
</div>
@@ -80,7 +80,7 @@
<label class="fl"><%= t('admin.wt.lbl_isolir','Template Pemberitahuan Isolir (Otomatis / Manual)') %></label>
<textarea class="fc" id="tmpl_isolir" name="whatsapp_isolir_message" rows="8" required style="width: 100%;"><%= templates.whatsapp_isolir_message %></textarea>
<div style="font-size: 11px; color: var(--muted); margin-top: 6px;">
<i class="bi bi-info-circle"></i> Variabel: <b style="color:var(--primary)">{{id_pelanggan}}</b> <span style="color:var(--primary)"><%= t('common.required','(wajib)') %></span>, <b style="color:var(--primary)">{{pppoe}}</b> <span style="color:var(--muted)">(Username PPPoE, opsional)</span>, <b>{{nama}}</b>, <b>{{paket}}</b>, <b>{{tagihan}}</b>, <b>{{link}}</b>.
<i class="bi bi-info-circle"></i> Variabel: <b style="color:var(--primary)">{{id_pelanggan}}</b> <span style="color:var(--primary)"><%= t('common.required','(wajib)') %></span>, <b style="color:var(--primary)">{{pppoe}}</b> <span style="color:var(--muted)"><%= t('admin.wt.ph_user','(Username PPPoE, opsional)') %></span>, <b>{{nama}}</b>, <b>{{paket}}</b>, <b>{{tagihan}}</b>, <b>{{link}}</b>.
</div>
</div>
@@ -113,7 +113,7 @@
<button type="button" class="btn btn-primary btn-sm" onclick="fillOfficialReceiptPreset()"><i class="bi bi-receipt-cutoff"></i> Muat Template Struk Resmi Lengkap</button>
<button type="button" class="btn btn-g btn-sm" onclick="fillSpintaxPreset()"><i class="bi bi-magic"></i> Muat Template Ringkas (Spintax Anti-Ban)</button>
<button type="button" class="btn btn-w btn-sm" onclick="showMetaConverter()"><i class="bi bi-meta"></i> <i class="bi bi-magic"></i> <%= t('admin.wt.convert','Konversi Format Meta Cloud API') %></button>
<div style="flex-basis:100%; font-size:11.5px; color:var(--muted); margin-top:4px"><i class="bi bi-exclamation-circle" style="color:var(--primary)"></i> Setiap template <b>wajib menyertakan variabel ID Pelanggan</b> <b style="color:var(--primary)">{{id_pelanggan}}</b>.</div>
<div style="flex-basis:100%; font-size:11.5px; color:var(--muted); margin-top:4px"><i class="bi bi-exclamation-circle" style="color:var(--primary)"></i> Setiap template <b><%= t('admin.wt.warn_custid','wajib menyertakan variabel ID Pelanggan') %></b> <b style="color:var(--primary)">{{id_pelanggan}}</b>.</div>
<div id="presetStatus" style="flex-basis:100%; font-size:12px; display:none"></div>
</div>
<button type="submit" class="btn btn-p"><i class="bi bi-save"></i> <%= t('admin.wt.save','Simpan Template') %></button>