i18n (Phase C batch 11): areas, routers, broadcast, backup, whatsapp_templates, mikrotik_display ID/EN

This commit is contained in:
oxpoe committed 2026-10-09 07:59:09 +07:00
1 parent fd074f8ff7
commit e70bc23651
8 files changed
+264 -88

No files matched your search

+88
View File
@@ -2183,6 +2183,94 @@
"save": "Save Expense", "save": "Save Expense",
"title": "Expenses", "title": "Expenses",
"th_method": "Method" "th_method": "Method"
},
"ar": {
"heading": "Area / Region Management",
"add": "Add New Area",
"list": "Area / Region Master List",
"th_desc": "Note / Description",
"th_cust_count": "Customer Count",
"th_col_count": "Collector Count",
"del": "Delete Area",
"name_req": "Area / Region Name *",
"ph_name": "Example: Sukamaju Village, Block C1-C10",
"ph_desc": "Example: Coverage area ODP-01 to ODP-05",
"save": "Save Area",
"edit": "Edit Area Data",
"title": "Area Management",
"th_name": "Area / Region Name"
},
"rt": {
"list": "MikroTik Router List",
"view_grid": "Card Grid",
"script_isolir": "Isolir Script",
"add_now": "Add Router Now",
"add": "Add Router",
"pppoe_active": "PPPoE Active",
"live_detail": "Live Status Detail",
"setup_fw": "Isolir Firewall Setup",
"edit": "Edit Router",
"del": "Delete Router",
"live_title": "Router Live Detail"
},
"bc": {
"send_title": "Send Invoices Now (Manual — if Auto is OFF)",
"send": "Send Invoices Now",
"spintax": "Spintax Word Variation:",
"spintax_desc": "Use <code>{Hi|Good Morning|Dear}</code> so the message is auto-randomized & not treated as spam.",
"human_delay": "Human-like Typing Delay:",
"human_delay_desc": "Before sending, the system waits a moment (scaled to message length) to mimic human typing tempo.",
"random_batch": "Random Delay & Batching:",
"random_batch_desc": "Sent with dynamic random delays and automatic rest between batches.",
"isolir_date": "Isolir Date",
"opt_static": "Static",
"opt_random": "Random (anti-ban)",
"delay_sec": "Delay (seconds)",
"stop": "Stop"
},
"bk": {
"title": "Backup & Recovery",
"total": "Total Backups",
"files_saved": "Files stored",
"db_backup": "Database backup",
"set_backup": "Settings backup",
"size_used": "Capacity used",
"create": "Create New Backup",
"type": "Backup Type",
"opt_all": "All (Database + Settings)",
"opt_db": "Database Only",
"opt_set": "Settings Only",
"upload": "Upload & Restore Backup",
"pick_file": "Select Backup File (.db / .json)",
"auto_clean": "Automatic Cleanup",
"retention": "Retention (Days)",
"history": "Backup History",
"empty": "No backup files available"
},
"wt": {
"title": "WhatsApp Notification Template Settings",
"lbl_reminder": "Invoice Reminder Template (Monthly / Manual)",
"lbl_qris": "QRIS Invoice Template (Manual)",
"lbl_success": "Payment Success Template (Paid)",
"lbl_isolir": "Isolir Notice Template (Auto / Manual)",
"lbl_arrears": "Arrears Warning Template (approaching deactivation)",
"lbl_autopaid": "Auto-Paid Template (Rp 0 / auto_isolate=0)",
"lbl_autoinactive": "Auto-Deactivate Template (arrears)",
"lbl_autodelete": "Auto-Delete Template (4th month)",
"convert": "Convert to Meta Cloud API Format",
"save": "Save Template",
"tool_title": "Meta WhatsApp Cloud API Template Converter",
"pick_tmpl": "Select the Template to Convert to Meta Format:",
"opt_reminder": "Invoice Reminder Template",
"opt_qris": "QRIS Invoice Template",
"opt_success": "Payment Success Template",
"opt_isolir": "Isolir Notice Template",
"vars": "Parameter Order Variables (Components Payload):"
},
"md": {
"connecting": "Connecting to MikroTik...",
"fullscreen": "Fullscreen",
"exit": "Exit"
} }
}, },
"tech": { "tech": {
+88
View File
@@ -2184,6 +2184,94 @@
"save": "Simpan Pengeluaran", "save": "Simpan Pengeluaran",
"title": "Pengeluaran", "title": "Pengeluaran",
"th_method": "Metode" "th_method": "Metode"
},
"ar": {
"heading": "Manajemen Area / Wilayah",
"add": "Tambah Area Baru",
"list": "Daftar Master Area / Wilayah",
"th_desc": "Keterangan / Deskripsi",
"th_cust_count": "Jumlah Pelanggan",
"th_col_count": "Jumlah Kolektor",
"del": "Hapus Area",
"name_req": "Nama Area / Wilayah *",
"ph_name": "Contoh: Kelurahan Sukamaju, Blok C1-C10",
"ph_desc": "Contoh: Wilayah jangkauan ODP-01 s/d ODP-05",
"save": "Simpan Area",
"edit": "Edit Data Area",
"title": "Manajemen Area",
"th_name": "Nama Area / Wilayah"
},
"rt": {
"list": "Daftar Router MikroTik",
"view_grid": "Grid Kartu",
"script_isolir": "Script Isolir",
"add_now": "Tambah Router Sekarang",
"add": "Tambah Router",
"pppoe_active": "PPPoE Aktif",
"live_detail": "Detail Live Status",
"setup_fw": "Setup Firewall Isolir",
"edit": "Edit Router",
"del": "Hapus Router",
"live_title": "Detail Live Router"
},
"bc": {
"send_title": "Kirim Tagihan Sekarang (Manual — bila Auto OFF)",
"send": "Kirim Tagihan Sekarang",
"spintax": "Variasi Kata Spintax:",
"spintax_desc": "Gunakan <code>{Halo|Selamat Pagi|Yth.}</code> agar isi pesan otomatis diacak & tidak dianggap spam.",
"human_delay": "Jeda Mengetik Manusiawi:",
"human_delay_desc": "Sebelum dikirim, sistem menunggu sejenak (menyesuaikan panjang pesan) meniru tempo menulis manusia.",
"random_batch": "Jeda Acak & Batching:",
"random_batch_desc": "Dikirim dengan jeda acak dinamis dan jeda istirahat otomatis antar batch.",
"isolir_date": "Tanggal Isolir",
"opt_static": "Statis",
"opt_random": "Random (anti-ban)",
"delay_sec": "Jeda (detik)",
"stop": "Hentikan"
},
"bk": {
"title": "Backup & Recovery",
"total": "Total Backup",
"files_saved": "File tersimpan",
"db_backup": "Backup database",
"set_backup": "Backup pengaturan",
"size_used": "Kapasitas digunakan",
"create": "Buat Backup Baru",
"type": "Tipe Backup",
"opt_all": "Semua (Database + Settings)",
"opt_db": "Database Saja",
"opt_set": "Settings Saja",
"upload": "Upload & Restore Backup",
"pick_file": "Pilih File Backup (.db / .json)",
"auto_clean": "Pembersihan Otomatis",
"retention": "Retensi (Hari)",
"history": "Riwayat Backup",
"empty": "Belum ada file backup tersedia"
},
"wt": {
"title": "Pengaturan Template Notifikasi WhatsApp",
"lbl_reminder": "Template Pengingat Tagihan (Bulanan / Manual)",
"lbl_qris": "Template Tagihan QRIS (Manual)",
"lbl_success": "Template Pembayaran Sukses (Lunas)",
"lbl_isolir": "Template Pemberitahuan Isolir (Otomatis / Manual)",
"lbl_arrears": "Template Peringatan Tunggakan (menuju nonaktif)",
"lbl_autopaid": "Template Auto-Lunas (Rp 0 / auto_isolate=0)",
"lbl_autoinactive": "Template Auto-Nonaktif (menunggak)",
"lbl_autodelete": "Template Auto-Hapus (bulan ke-4)",
"convert": "Konversi Format Meta Cloud API",
"save": "Simpan Template",
"tool_title": "Tool Konversi Template Meta WhatsApp Cloud API",
"pick_tmpl": "Pilih Template yang Akan Dikonversi ke Format Meta:",
"opt_reminder": "Template Pengingat Tagihan",
"opt_qris": "Template Tagihan QRIS",
"opt_success": "Template Pembayaran Sukses",
"opt_isolir": "Template Pemberitahuan Isolir",
"vars": "Daftar Variabel Urutan Parameter (Components Payload):"
},
"md": {
"connecting": "Menghubungkan ke MikroTik...",
"fullscreen": "Layar Penuh",
"exit": "Keluar"
} }
}, },
"tech": { "tech": {
+17 -17
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>Manajemen Area | <%= company %></title> <title><%= t('admin.ar.title','Manajemen Area') %> | <%= company %></title>
<link rel="stylesheet" href="/vendor/bootstrap-icons/bootstrap-icons.min.css"> <link rel="stylesheet" href="/vendor/bootstrap-icons/bootstrap-icons.min.css">
<link rel="stylesheet" href="/css/admin.css"> <link rel="stylesheet" href="/css/admin.css">
</head> </head>
@@ -15,11 +15,11 @@
<div class="tb-title"> <div class="tb-title">
<button class="hb-menu" onclick="toggleSidebar()"><i class="bi bi-list"></i></button> <button class="hb-menu" onclick="toggleSidebar()"><i class="bi bi-list"></i></button>
<i class="bi bi-geo-alt"></i> <i class="bi bi-geo-alt"></i>
<span>Manajemen Area / Wilayah</span> <span><%= t('admin.ar.heading','Manajemen Area / Wilayah') %></span>
</div> </div>
<div class="tb-right"> <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> <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('addAreaModal')"><i class="bi bi-plus-lg"></i> Tambah Area Baru</button> <button class="btn btn-p btn-sm" onclick="openModal('addAreaModal')"><i class="bi bi-plus-lg"></i> <%= t('admin.ar.add','Tambah Area Baru') %></button>
</div> </div>
</div> </div>
@@ -30,7 +30,7 @@
<div class="card"> <div class="card">
<div class="card-hd"> <div class="card-hd">
<h6><i class="bi bi-geo-alt-fill"></i> Daftar Master Area / Wilayah (<%= areas.length %>)</h6> <h6><i class="bi bi-geo-alt-fill"></i> <%= t('admin.ar.list','Daftar Master Area / Wilayah') %> (<%= areas.length %>)</h6>
</div> </div>
<div class="hint" style="padding:12px 16px 0 16px;font-size:12px;color:var(--muted)"> <div class="hint" style="padding:12px 16px 0 16px;font-size:12px;color:var(--muted)">
Area digunakan untuk mengelompokkan lokasi pelanggan dan wilayah tugas kolektor. Kolektor yang memiliki area tugas tertentu hanya akan mengelola pelanggan di area tersebut. Area digunakan untuk mengelompokkan lokasi pelanggan dan wilayah tugas kolektor. Kolektor yang memiliki area tugas tertentu hanya akan mengelola pelanggan di area tersebut.
@@ -40,16 +40,16 @@
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>Nama Area / Wilayah</th> <th><%= t('admin.ar.th_name','Nama Area / Wilayah') %></th>
<th>Keterangan / Deskripsi</th> <th><%= t('admin.ar.th_desc','Keterangan / Deskripsi') %></th>
<th>Jumlah Pelanggan</th> <th><%= t('admin.ar.th_cust_count','Jumlah Pelanggan') %></th>
<th>Jumlah Kolektor</th> <th><%= t('admin.ar.th_col_count','Jumlah Kolektor') %></th>
<th><%= t('common.actions','Aksi') %></th> <th><%= t('common.actions','Aksi') %></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% if (!areas || areas.length === 0) { %> <% if (!areas || areas.length === 0) { %>
<tr><td colspan="6" class="text-center text-muted" style="padding:24px">Belum ada data area. Klik "Tambah Area Baru" untuk membuat area.</td></tr> <tr><td colspan="6" class="text-center text-muted" style="padding:24px">Belum ada data area. Klik "<%= t('admin.ar.add','Tambah Area Baru') %>" untuk membuat area.</td></tr>
<% } else { %> <% } else { %>
<% areas.forEach((a, idx) => { %> <% areas.forEach((a, idx) => { %>
<tr> <tr>
@@ -71,7 +71,7 @@
<div class="act-menu"> <div class="act-menu">
<button type="button" class="act-item" onclick='editArea(<%- JSON.stringify(a).split('<').join('\\u003c') %>)'><i class="bi bi-pencil"></i> Edit Area</button> <button type="button" class="act-item" onclick='editArea(<%- JSON.stringify(a).split('<').join('\\u003c') %>)'><i class="bi bi-pencil"></i> Edit Area</button>
<div class="act-sep"></div> <div class="act-sep"></div>
<button type="submit" form="adel-<%= a.id %>" class="act-item danger"><i class="bi bi-trash"></i> Hapus Area</button> <button type="submit" form="adel-<%= a.id %>" class="act-item danger"><i class="bi bi-trash"></i> <%= t('admin.ar.del','Hapus Area') %></button>
</div> </div>
</div> </div>
</td> </td>
@@ -89,20 +89,20 @@
<div class="mo" id="addAreaModal"> <div class="mo" id="addAreaModal">
<div class="mb"> <div class="mb">
<form action="/admin/areas" method="POST"> <form action="/admin/areas" method="POST">
<div class="mh"><span class="mt"><i class="bi bi-geo-alt-fill"></i> Tambah Area Baru</span><button type="button" class="mc" onclick="closeModal('addAreaModal')">&times;</button></div> <div class="mh"><span class="mt"><i class="bi bi-geo-alt-fill"></i> <%= t('admin.ar.add','Tambah Area Baru') %></span><button type="button" class="mc" onclick="closeModal('addAreaModal')">&times;</button></div>
<div class="mbody"> <div class="mbody">
<div class="fg"> <div class="fg">
<label class="fl">Nama Area / Wilayah *</label> <label class="fl"><%= t('admin.ar.name_req','Nama Area / Wilayah *') %></label>
<input type="text" name="name" class="fc" placeholder="Contoh: Kelurahan Sukamaju, Blok C1-C10" required> <input type="text" name="name" class="fc" placeholder="<%= t('admin.ar.ph_name','Contoh: Kelurahan Sukamaju, Blok C1-C10') %>" required>
</div> </div>
<div class="fg"> <div class="fg">
<label class="fl"><%= t('common.note_optional2','Keterangan / Catatan (Opsional)') %></label> <label class="fl"><%= t('common.note_optional2','Keterangan / Catatan (Opsional)') %></label>
<textarea name="description" class="fc" rows="3" placeholder="Contoh: Wilayah jangkauan ODP-01 s/d ODP-05"></textarea> <textarea name="description" class="fc" rows="3" placeholder="<%= t('admin.ar.ph_desc','Contoh: Wilayah jangkauan ODP-01 s/d ODP-05') %>"></textarea>
</div> </div>
</div> </div>
<div class="mf"> <div class="mf">
<button type="button" class="btn btn-g" onclick="closeModal('addAreaModal')"><%= t('common.cancel','Batal') %></button> <button type="button" class="btn btn-g" onclick="closeModal('addAreaModal')"><%= t('common.cancel','Batal') %></button>
<button type="submit" class="btn btn-p"><i class="bi bi-check-lg"></i> Simpan Area</button> <button type="submit" class="btn btn-p"><i class="bi bi-check-lg"></i> <%= t('admin.ar.save','Simpan Area') %></button>
</div> </div>
</form> </form>
</div> </div>
@@ -112,10 +112,10 @@
<div class="mo" id="editAreaModal"> <div class="mo" id="editAreaModal">
<div class="mb"> <div class="mb">
<form id="editAreaForm" method="POST"> <form id="editAreaForm" method="POST">
<div class="mh"><span class="mt"><i class="bi bi-pencil-square"></i> Edit Data Area</span><button type="button" class="mc" onclick="closeModal('editAreaModal')">&times;</button></div> <div class="mh"><span class="mt"><i class="bi bi-pencil-square"></i> <%= t('admin.ar.edit','Edit Data Area') %></span><button type="button" class="mc" onclick="closeModal('editAreaModal')">&times;</button></div>
<div class="mbody"> <div class="mbody">
<div class="fg"> <div class="fg">
<label class="fl">Nama Area / Wilayah *</label> <label class="fl"><%= t('admin.ar.name_req','Nama Area / Wilayah *') %></label>
<input type="text" name="name" id="e-area-name" class="fc" required> <input type="text" name="name" id="e-area-name" class="fc" required>
</div> </div>
<div class="fg"> <div class="fg">
+17 -17
View File
@@ -20,7 +20,7 @@
<div class="tb-title"> <div class="tb-title">
<button class="hb-menu" onclick="toggleSidebar()"><i class="bi bi-list"></i></button> <button class="hb-menu" onclick="toggleSidebar()"><i class="bi bi-list"></i></button>
<i class="bi bi-hdd-stack"></i> <i class="bi bi-hdd-stack"></i>
<span id="page-title">Backup & Recovery</span> <span id="page-title"><%= t('admin.bk.title','Backup & Recovery') %></span>
</div> </div>
<div class="tb-right"> <div class="tb-right">
<span class="adm-badge"> <span class="adm-badge">
@@ -46,42 +46,42 @@
<div class="stat-grid"> <div class="stat-grid">
<div class="sc p"> <div class="sc p">
<i class="bi bi-files sc-icon"></i> <i class="bi bi-files sc-icon"></i>
<div class="sc-label">Total Backup</div> <div class="sc-label"><%= t('admin.bk.total','Total Backup') %></div>
<div class="sc-val"><%= total %></div> <div class="sc-val"><%= total %></div>
<div class="sc-sub">File tersimpan</div> <div class="sc-sub"><%= t('admin.bk.files_saved','File tersimpan') %></div>
</div> </div>
<div class="sc s"> <div class="sc s">
<i class="bi bi-database sc-icon"></i> <i class="bi bi-database sc-icon"></i>
<div class="sc-label">Database</div> <div class="sc-label">Database</div>
<div class="sc-val"><%= backups.filter(b => b.type === 'database').length %></div> <div class="sc-val"><%= backups.filter(b => b.type === 'database').length %></div>
<div class="sc-sub">Backup database</div> <div class="sc-sub"><%= t('admin.bk.db_backup','Backup database') %></div>
</div> </div>
<div class="sc w"> <div class="sc w">
<i class="bi bi-gear sc-icon"></i> <i class="bi bi-gear sc-icon"></i>
<div class="sc-label">Settings</div> <div class="sc-label">Settings</div>
<div class="sc-val"><%= backups.filter(b => b.type === 'settings').length %></div> <div class="sc-val"><%= backups.filter(b => b.type === 'settings').length %></div>
<div class="sc-sub">Backup pengaturan</div> <div class="sc-sub"><%= t('admin.bk.set_backup','Backup pengaturan') %></div>
</div> </div>
<div class="sc i"> <div class="sc i">
<i class="bi bi-hdd sc-icon"></i> <i class="bi bi-hdd sc-icon"></i>
<div class="sc-label">Total Size</div> <div class="sc-label">Total Size</div>
<div class="sc-val"><%= (backups.reduce((sum, b) => sum + b.sizeKB, 0) / 1024).toFixed(1) %><span style="font-size:14px"> MB</span></div> <div class="sc-val"><%= (backups.reduce((sum, b) => sum + b.sizeKB, 0) / 1024).toFixed(1) %><span style="font-size:14px"> MB</span></div>
<div class="sc-sub">Kapasitas digunakan</div> <div class="sc-sub"><%= t('admin.bk.size_used','Kapasitas digunakan') %></div>
</div> </div>
</div> </div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-bottom: 16px;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-bottom: 16px;">
<!-- Create Backup --> <!-- Create Backup -->
<div class="card"> <div class="card">
<div class="card-hd"><h6><i class="bi bi-plus-circle"></i> Buat Backup Baru</h6></div> <div class="card-hd"><h6><i class="bi bi-plus-circle"></i> <%= t('admin.bk.create','Buat Backup Baru') %></h6></div>
<div class="card-bd"> <div class="card-bd">
<form action="/admin/backup/create" method="POST"> <form action="/admin/backup/create" method="POST">
<div class="fg"> <div class="fg">
<label class="fl">Tipe Backup</label> <label class="fl"><%= t('admin.bk.type','Tipe Backup') %></label>
<select class="fs" name="type"> <select class="fs" name="type">
<option value="all">Semua (Database + Settings)</option> <option value="all"><%= t('admin.bk.opt_all','Semua (Database + Settings)') %></option>
<option value="database">Database Saja</option> <option value="database"><%= t('admin.bk.opt_db','Database Saja') %></option>
<option value="settings">Settings Saja</option> <option value="settings"><%= t('admin.bk.opt_set','Settings Saja') %></option>
</select> </select>
</div> </div>
<button type="submit" class="btn btn-p w-100"> <button type="submit" class="btn btn-p w-100">
@@ -93,11 +93,11 @@
<!-- Upload & Restore --> <!-- Upload & Restore -->
<div class="card"> <div class="card">
<div class="card-hd"><h6><i class="bi bi-upload"></i> Upload & Restore Backup</h6></div> <div class="card-hd"><h6><i class="bi bi-upload"></i> <%= t('admin.bk.upload','Upload & Restore Backup') %></h6></div>
<div class="card-bd"> <div class="card-bd">
<form action="/admin/backup/upload-restore" method="POST" enctype="multipart/form-data" onsubmit="return confirm('PERINGATAN: Memulihkan file backup akan menimpa data/pengaturan aplikasi saat ini. Sistem akan membuat backup otomatis sebelum restore. Lanjutkan?')"> <form action="/admin/backup/upload-restore" method="POST" enctype="multipart/form-data" onsubmit="return confirm('PERINGATAN: Memulihkan file backup akan menimpa data/pengaturan aplikasi saat ini. Sistem akan membuat backup otomatis sebelum restore. Lanjutkan?')">
<div class="fg"> <div class="fg">
<label class="fl">Pilih File Backup (.db / .json)</label> <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;"> <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;">File .db (database) atau .json (settings)</small>
</div> </div>
@@ -110,11 +110,11 @@
<!-- Cleanup --> <!-- Cleanup -->
<div class="card"> <div class="card">
<div class="card-hd"><h6><i class="bi bi-trash"></i> Pembersihan Otomatis</h6></div> <div class="card-hd"><h6><i class="bi bi-trash"></i> <%= t('admin.bk.auto_clean','Pembersihan Otomatis') %></h6></div>
<div class="card-bd"> <div class="card-bd">
<form action="/admin/backup/cleanup" method="POST"> <form action="/admin/backup/cleanup" method="POST">
<div class="fg"> <div class="fg">
<label class="fl">Retensi (Hari)</label> <label class="fl"><%= t('admin.bk.retention','Retensi (Hari)') %></label>
<input type="number" class="fc" name="retentionDays" value="30" min="1" max="365"> <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;">Backup lebih lama dari periode ini akan dihapus</small>
</div> </div>
@@ -129,13 +129,13 @@
<!-- Backup List --> <!-- Backup List -->
<div class="card"> <div class="card">
<div class="card-hd"> <div class="card-hd">
<h6><i class="bi bi-list-ul"></i> Riwayat Backup</h6> <h6><i class="bi bi-list-ul"></i> <%= t('admin.bk.history','Riwayat Backup') %></h6>
</div> </div>
<div class="card-bd"> <div class="card-bd">
<% if (backups.length === 0) { %> <% if (backups.length === 0) { %>
<div class="text-center py-5"> <div class="text-center py-5">
<i class="bi bi-inbox text-muted" style="font-size: 40px; opacity: 0.3;"></i> <i class="bi bi-inbox text-muted" style="font-size: 40px; opacity: 0.3;"></i>
<p class="text-muted mt-2">Belum ada file backup tersedia</p> <p class="text-muted mt-2"><%= t('admin.bk.empty','Belum ada file backup tersedia') %></p>
</div> </div>
<% } else { %> <% } else { %>
<div class="tbl-wrap"> <div class="tbl-wrap">
+10 -10
View File
@@ -59,9 +59,9 @@
<i class="bi bi-shield-check" style="font-size: 16px;"></i> Anti-Banned Aktif (Spintax + Jeda Mengetik) <i class="bi bi-shield-check" style="font-size: 16px;"></i> Anti-Banned Aktif (Spintax + Jeda Mengetik)
</div> </div>
<div style="font-size: 12px; line-height: 1.5; color: var(--text);"> <div style="font-size: 12px; line-height: 1.5; color: var(--text);">
• <strong>Variasi Kata Spintax:</strong> Gunakan <code>{Halo|Selamat Pagi|Yth.}</code> agar isi pesan otomatis diacak & tidak dianggap spam.<br> • <strong><%= t('admin.bc.spintax','Variasi Kata Spintax:') %></strong> <%- t('admin.bc.spintax_desc','Gunakan <code>{Halo|Selamat Pagi|Yth.}</code> agar isi pesan otomatis diacak & tidak dianggap spam.') %><br>
• <strong>Jeda Mengetik Manusiawi:</strong> Sebelum dikirim, sistem menunggu sejenak (menyesuaikan panjang pesan) meniru tempo menulis manusia.<br> • <strong><%= t('admin.bc.human_delay','Jeda Mengetik Manusiawi:') %></strong> <%= t('admin.bc.human_delay_desc','Sebelum dikirim, sistem menunggu sejenak (menyesuaikan panjang pesan) meniru tempo menulis manusia.') %><br>
• <strong>Jeda Acak & Batching:</strong> Dikirim dengan jeda acak dinamis dan jeda istirahat otomatis antar batch. • <strong><%= t('admin.bc.random_batch','Jeda Acak & Batching:') %></strong> <%= t('admin.bc.random_batch_desc','Dikirim dengan jeda acak dinamis dan jeda istirahat otomatis antar batch.') %>
</div> </div>
</div> </div>
@@ -114,11 +114,11 @@
</div> </div>
<div class="card mb16"> <div class="card mb16">
<div class="card-hd"><h6><i class="bi bi-send-check"></i> Kirim Tagihan Sekarang (Manual — bila Auto OFF)</h6></div> <div class="card-hd"><h6><i class="bi bi-send-check"></i> <%= t('admin.bc.send_title','Kirim Tagihan Sekarang (Manual — bila Auto OFF)') %></h6></div>
<form method="POST" action="/admin/whatsapp/send-billing-now" onsubmit="return confirm('Kirim tagihan sekarang ke pelanggan belum bayar?');"> <form method="POST" action="/admin/whatsapp/send-billing-now" onsubmit="return confirm('Kirim tagihan sekarang ke pelanggan belum bayar?');">
<div class="card-bd"> <div class="card-bd">
<div style="display:flex;gap:14px;flex-wrap:wrap"> <div style="display:flex;gap:14px;flex-wrap:wrap">
<div class="fg"><label class="fl"><i class="bi bi-calendar-event"></i> Tanggal Isolir</label> <div class="fg"><label class="fl"><i class="bi bi-calendar-event"></i> <%= t('admin.bc.isolir_date','Tanggal Isolir') %></label>
<select class="fs" name="isolate_day"> <select class="fs" name="isolate_day">
<option value="0"><%= t('collector.filter_all','Semua') %></option> <option value="0"><%= t('collector.filter_all','Semua') %></option>
<% (isolateDays||[]).forEach(function(d){ %><option value="<%= d %>">Tgl <%= d %></option><% }); %> <% (isolateDays||[]).forEach(function(d){ %><option value="<%= d %>">Tgl <%= d %></option><% }); %>
@@ -131,11 +131,11 @@
</div> </div>
<div class="fg"><label class="fl"><i class="bi bi-clock-history"></i> Interval</label> <div class="fg"><label class="fl"><i class="bi bi-clock-history"></i> Interval</label>
<select class="fs" name="interval_mode" id="bsMode" onchange="bsToggle()"> <select class="fs" name="interval_mode" id="bsMode" onchange="bsToggle()">
<option value="static">Statis</option> <option value="static"><%= t('admin.bc.opt_static','Statis') %></option>
<option value="random">Random (anti-ban)</option> <option value="random"><%= t('admin.bc.opt_random','Random (anti-ban)') %></option>
</select> </select>
</div> </div>
<div class="fg" id="bsStatic"><label class="fl">Jeda (detik)</label> <div class="fg" id="bsStatic"><label class="fl"><%= t('admin.bc.delay_sec','Jeda (detik)') %></label>
<input class="fc" type="number" name="interval_value" value="15" min="3" max="600"> <input class="fc" type="number" name="interval_value" value="15" min="3" max="600">
</div> </div>
<div class="fg" id="bsRandom" style="display:none"><label class="fl">Jeda acak (min–max detik)</label> <div class="fg" id="bsRandom" style="display:none"><label class="fl">Jeda acak (min–max detik)</label>
@@ -150,8 +150,8 @@
<div class="card-bd" style="border-top:1px solid var(--border);background:var(--bg2);display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap"> <div class="card-bd" style="border-top:1px solid var(--border);background:var(--bg2);display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap">
<div id="bsResult" style="font-size:12px" class="text-muted"></div> <div id="bsResult" style="font-size:12px" class="text-muted"></div>
<div style="display:flex;gap:8px"> <div style="display:flex;gap:8px">
<button type="button" class="btn btn-g" onclick="bsStop()"><i class="bi bi-stop-circle"></i> Hentikan</button> <button type="button" class="btn btn-g" onclick="bsStop()"><i class="bi bi-stop-circle"></i> <%= t('admin.bc.stop','Hentikan') %></button>
<button type="submit" class="btn btn-p"><i class="bi bi-send"></i> Kirim Tagihan Sekarang</button> <button type="submit" class="btn btn-p"><i class="bi bi-send"></i> <%= t('admin.bc.send','Kirim Tagihan Sekarang') %></button>
</div> </div>
</div> </div>
</form> </form>
+10 -10
View File
@@ -827,7 +827,7 @@
<!-- Loading Overlay --> <!-- Loading Overlay -->
<div id="loading-overlay" class="noc-overlay"> <div id="loading-overlay" class="noc-overlay">
<div class="spinner"></div> <div class="spinner"></div>
<div class="overlay-text">Menghubungkan ke MikroTik...</div> <div class="overlay-text"><%= t('admin.md.connecting','Menghubungkan ke MikroTik...') %></div>
</div> </div>
<div class="noc-container"> <div class="noc-container">
@@ -857,16 +857,16 @@
<i class="bi bi-chevron-down router-select-icon"></i> <i class="bi bi-chevron-down router-select-icon"></i>
</div> </div>
<!-- Fullscreen Button --> <!-- <%= t('admin.md.fullscreen','Layar Penuh') %> Button -->
<button class="btn-action btn-fullscreen" onclick="toggleFullscreen()"> <button class="btn-action btn-fullscreen" onclick="toggle<%= t('admin.md.fullscreen','Layar Penuh') %>()">
<i class="bi bi-fullscreen" id="fs-btn-icon"></i> <i class="bi bi-fullscreen" id="fs-btn-icon"></i>
<span id="fs-btn-text">Fullscreen</span> <span id="fs-btn-text"><%= t('admin.md.fullscreen','Layar Penuh') %></span>
</button> </button>
<!-- Back to Dashboard --> <!-- Back to Dashboard -->
<a href="/admin/mikrotik" class="btn-action btn-back" style="text-decoration: none;"> <a href="/admin/mikrotik" class="btn-action btn-back" style="text-decoration: none;">
<i class="bi bi-box-arrow-left"></i> <i class="bi bi-box-arrow-left"></i>
<span>Keluar</span> <span><%= t('admin.md.exit','Keluar') %></span>
</a> </a>
</div> </div>
</header> </header>
@@ -1079,22 +1079,22 @@
}, 1000); }, 1000);
} }
// Toggle HTML5 Fullscreen Mode // Toggle HTML5 <%= t('admin.md.fullscreen','Layar Penuh') %> Mode
function toggleFullscreen() { function toggle<%= t('admin.md.fullscreen','Layar Penuh') %>() {
const icon = document.getElementById('fs-btn-icon'); const icon = document.getElementById('fs-btn-icon');
const text = document.getElementById('fs-btn-text'); const text = document.getElementById('fs-btn-text');
if (!document.fullscreenElement) { if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().then(() => { document.documentElement.request<%= t('admin.md.fullscreen','Layar Penuh') %>().then(() => {
icon.className = 'bi bi-fullscreen-exit'; icon.className = 'bi bi-fullscreen-exit';
text.textContent = 'Exit Screen'; text.textContent = 'Exit Screen';
}).catch(err => { }).catch(err => {
console.error(`Error enabling fullscreen: ${err.message}`); console.error(`Error enabling fullscreen: ${err.message}`);
}); });
} else { } else {
document.exitFullscreen().then(() => { document.exit<%= t('admin.md.fullscreen','Layar Penuh') %>().then(() => {
icon.className = 'bi bi-fullscreen'; icon.className = 'bi bi-fullscreen';
text.textContent = 'Fullscreen'; text.textContent = '<%= t('admin.md.fullscreen','Layar Penuh') %>';
}); });
} }
} }
+15 -15
View File
@@ -335,15 +335,15 @@
<div class="card mb-3" style="border:none; background:transparent;"> <div class="card mb-3" style="border:none; background:transparent;">
<div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:12px;"> <div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:12px;">
<div style="display:flex; align-items:center; gap:8px;"> <div style="display:flex; align-items:center; gap:8px;">
<h5 class="fw-bold text-white mb-0"><i class="bi bi-cpu text-primary me-2"></i>Daftar Router MikroTik (<%= routers.length %>)</h5> <h5 class="fw-bold text-white mb-0"><i class="bi bi-cpu text-primary me-2"></i><%= t('admin.rt.list','Daftar Router MikroTik') %> (<%= routers.length %>)</h5>
</div> </div>
<div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;"> <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
<div class="d-flex gap-1 bg-dark p-1 rounded-3 border border-secondary border-opacity-25"> <div class="d-flex gap-1 bg-dark p-1 rounded-3 border border-secondary border-opacity-25">
<button type="button" class="view-toggle-btn active" id="btn-view-grid" onclick="switchView('grid')"><i class="bi bi-grid-fill me-1"></i> Grid Kartu</button> <button type="button" class="view-toggle-btn active" id="btn-view-grid" onclick="switchView('grid')"><i class="bi bi-grid-fill me-1"></i> <%= t('admin.rt.view_grid','Grid Kartu') %></button>
<button type="button" class="view-toggle-btn" id="btn-view-table" onclick="switchView('table')"><i class="bi bi-table me-1"></i> Tabel</button> <button type="button" class="view-toggle-btn" id="btn-view-table" onclick="switchView('table')"><i class="bi bi-table me-1"></i> Tabel</button>
</div> </div>
<button type="button" class="btn btn-outline-light btn-sm" onclick="openIsolirScriptModal()"><i class="bi bi-filetype-txt me-1"></i> Script Isolir</button> <button type="button" class="btn btn-outline-light btn-sm" onclick="openIsolirScriptModal()"><i class="bi bi-filetype-txt me-1"></i> <%= t('admin.rt.script_isolir','Script Isolir') %></button>
<button class="btn btn-p btn-sm" onclick="openModal('addModal')"><i class="bi bi-plus-lg me-1"></i> Tambah Router</button> <button class="btn btn-p btn-sm" onclick="openModal('addModal')"><i class="bi bi-plus-lg me-1"></i> <%= t('admin.rt.add','Tambah Router') %></button>
</div> </div>
</div> </div>
</div> </div>
@@ -354,7 +354,7 @@
<div class="card p-4 text-center text-muted" style="grid-column: 1 / -1;"> <div class="card p-4 text-center text-muted" style="grid-column: 1 / -1;">
<i class="bi bi-hdd-network fs-1 mb-2"></i> <i class="bi bi-hdd-network fs-1 mb-2"></i>
<h6><%= t('admin.routers.empty', 'Belum ada router yang ditambahkan') %></h6> <h6><%= t('admin.routers.empty', 'Belum ada router yang ditambahkan') %></h6>
<button class="btn btn-p btn-sm mt-2" onclick="openModal('addModal')"><i class="bi bi-plus-lg me-1"></i> Tambah Router Sekarang</button> <button class="btn btn-p btn-sm mt-2" onclick="openModal('addModal')"><i class="bi bi-plus-lg me-1"></i> <%= t('admin.rt.add_now','Tambah Router Sekarang') %></button>
</div> </div>
<% } %> <% } %>
@@ -409,7 +409,7 @@
</div> </div>
<div class="rc-spec-box"> <div class="rc-spec-box">
<div class="rc-spec-val text-success" id="spec-pppoe-<%= r.id %>">-</div> <div class="rc-spec-val text-success" id="spec-pppoe-<%= r.id %>">-</div>
<div class="rc-spec-lbl">PPPoE Aktif</div> <div class="rc-spec-lbl"><%= t('admin.rt.pppoe_active','PPPoE Aktif') %></div>
</div> </div>
<div class="rc-spec-box"> <div class="rc-spec-box">
<div class="rc-spec-val text-warning" id="spec-hs-<%= r.id %>">-</div> <div class="rc-spec-val text-warning" id="spec-hs-<%= r.id %>">-</div>
@@ -419,13 +419,13 @@
</div> </div>
<div class="rc-actions"> <div class="rc-actions">
<button type="button" class="btn-detail-live flex-fill" title="Detail Live Status" onclick="openLiveDetailModal(<%= r.id %>)"> <button type="button" class="btn-detail-live flex-fill" title="<%= t('admin.rt.live_detail','Detail Live Status') %>" onclick="openLiveDetailModal(<%= r.id %>)">
<i class="bi bi-cpu-fill me-1"></i> Detail Live <i class="bi bi-cpu-fill me-1"></i> Detail Live
</button> </button>
<button type="button" class="btn btn-g btn-sm" title="<%= t('admin.routers.actions.test', 'Test Koneksi') %>" onclick="testConn(<%= r.id %>)"> <button type="button" class="btn btn-g btn-sm" title="<%= t('admin.routers.actions.test', 'Test Koneksi') %>" onclick="testConn(<%= r.id %>)">
<i class="bi bi-plug"></i> <i class="bi bi-plug"></i>
</button> </button>
<button type="button" class="btn btn-g btn-sm" title="Setup Firewall Isolir" onclick="setupFirewall(<%= r.id %>)"> <button type="button" class="btn btn-g btn-sm" title="<%= t('admin.rt.setup_fw','Setup Firewall Isolir') %>" onclick="setupFirewall(<%= r.id %>)">
<i class="bi bi-shield-lock"></i> <i class="bi bi-shield-lock"></i>
</button> </button>
<button type="button" class="btn btn-g btn-sm" title="<%= t('common.edit', 'Edit') %>" onclick="editRouter(<%= i %>)"> <button type="button" class="btn btn-g btn-sm" title="<%= t('common.edit', 'Edit') %>" onclick="editRouter(<%= i %>)">
@@ -473,12 +473,12 @@
<div class="act"> <div class="act">
<button type="button" class="act-btn" title="Aksi" onclick="actToggle(event, this)"><i class="bi bi-three-dots-vertical"></i></button> <button type="button" class="act-btn" title="Aksi" onclick="actToggle(event, this)"><i class="bi bi-three-dots-vertical"></i></button>
<div class="act-menu"> <div class="act-menu">
<button type="button" class="act-item primary" onclick="openLiveDetailModal(<%= r.id %>)"><i class="bi bi-cpu"></i> Detail Live Status</button> <button type="button" class="act-item primary" onclick="openLiveDetailModal(<%= r.id %>)"><i class="bi bi-cpu"></i> <%= t('admin.rt.live_detail','Detail Live Status') %></button>
<button type="button" class="act-item" onclick="testConn(<%= r.id %>)"><i class="bi bi-plug"></i> <%= t('admin.dash.test_conn','Test Koneksi') %></button> <button type="button" class="act-item" onclick="testConn(<%= r.id %>)"><i class="bi bi-plug"></i> <%= t('admin.dash.test_conn','Test Koneksi') %></button>
<button type="button" class="act-item" onclick="setupFirewall(<%= r.id %>)"><i class="bi bi-shield-lock"></i> Setup Firewall Isolir</button> <button type="button" class="act-item" onclick="setupFirewall(<%= r.id %>)"><i class="bi bi-shield-lock"></i> <%= t('admin.rt.setup_fw','Setup Firewall Isolir') %></button>
<div class="act-sep"></div> <div class="act-sep"></div>
<button type="button" class="act-item" onclick="editRouter(<%= i %>)"><i class="bi bi-pencil"></i> Edit Router</button> <button type="button" class="act-item" onclick="editRouter(<%= i %>)"><i class="bi bi-pencil"></i> <%= t('admin.rt.edit','Edit Router') %></button>
<button type="submit" form="rdel-<%= r.id %>" class="act-item danger"><i class="bi bi-trash"></i> Hapus Router</button> <button type="submit" form="rdel-<%= r.id %>" class="act-item danger"><i class="bi bi-trash"></i> <%= t('admin.rt.del','Hapus Router') %></button>
</div> </div>
</div> </div>
</td> </td>
@@ -495,7 +495,7 @@
<div class="mo" id="routerLiveModal"> <div class="mo" id="routerLiveModal">
<div class="mb" style="max-width: 740px;"> <div class="mb" style="max-width: 740px;">
<div class="mh"> <div class="mh">
<span class="mt"><i class="bi bi-cpu-fill text-primary me-2"></i> <span id="modal-router-name">Detail Live Router</span></span> <span class="mt"><i class="bi bi-cpu-fill text-primary me-2"></i> <span id="modal-router-name"><%= t('admin.rt.live_title','Detail Live Router') %></span></span>
<button class="mc" onclick="closeModal('routerLiveModal')">&times;</button> <button class="mc" onclick="closeModal('routerLiveModal')">&times;</button>
</div> </div>
<div class="mbody" id="modal-router-body" style="min-height:360px;"> <div class="mbody" id="modal-router-body" style="min-height:360px;">
@@ -511,7 +511,7 @@
</div> </div>
</div> </div>
<!-- Modal Script & Setup Firewall Isolir Portal --> <!-- Modal Script & <%= t('admin.rt.setup_fw','Setup Firewall Isolir') %> Portal -->
<div class="mo" id="isolirScriptModal"> <div class="mo" id="isolirScriptModal">
<div class="mb" style="max-width:740px"> <div class="mb" style="max-width:740px">
<div class="mh"> <div class="mh">
@@ -836,7 +836,7 @@ async function openLiveDetailModal(id) {
<div class="mt-stats"> <div class="mt-stats">
<div class="mt-stat"><span>Pelanggan DB</span><b>${l.customersCount}</b></div> <div class="mt-stat"><span>Pelanggan DB</span><b>${l.customersCount}</b></div>
<div class="mt-stat"><span>PPPoE Aktif</span><b class="mt-ok">${l.activePppoe}</b></div> <div class="mt-stat"><span><%= t('admin.rt.pppoe_active','PPPoE Aktif') %></span><b class="mt-ok">${l.activePppoe}</b></div>
<div class="mt-stat"><span>Hotspot Aktif</span><b class="mt-warn">${l.activeHotspot}</b></div> <div class="mt-stat"><span>Hotspot Aktif</span><b class="mt-warn">${l.activeHotspot}</b></div>
</div> </div>
</div> </div>
+19 -19
View File
@@ -46,12 +46,12 @@
<form method="POST" action="/admin/whatsapp/templates"> <form method="POST" action="/admin/whatsapp/templates">
<div class="card mb16"> <div class="card mb16">
<div class="card-hd"> <div class="card-hd">
<h6><i class="bi bi-chat-left-quote-fill"></i> Pengaturan Template Notifikasi WhatsApp</h6> <h6><i class="bi bi-chat-left-quote-fill"></i> <%= t('admin.wt.title','Pengaturan Template Notifikasi WhatsApp') %></h6>
</div> </div>
<div class="card-bd template-grid"> <div class="card-bd template-grid">
<div> <div>
<div class="fg"> <div class="fg">
<label class="fl">Template Pengingat Tagihan (Bulanan / Manual)</label> <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> <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;"> <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)">(Username PPPoE, opsional)</span>, <b>{{nama}}</b>, <b>{{tagihan}}</b>, <b>{{rincian}}</b>, <b>{{paket}}</b>, <b>{{link}}</b>.</span>
@@ -59,7 +59,7 @@
</div> </div>
<div class="fg" style="margin-top: 20px;"> <div class="fg" style="margin-top: 20px;">
<label class="fl">Template Tagihan QRIS (Manual)</label> <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> <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;"> <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)">(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>.
@@ -69,7 +69,7 @@
<div> <div>
<div class="fg"> <div class="fg">
<label class="fl">Template Pembayaran Sukses (Lunas)</label> <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> <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;"> <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)">(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).
@@ -77,7 +77,7 @@
</div> </div>
<div class="fg" style="margin-top: 20px;"> <div class="fg" style="margin-top: 20px;">
<label class="fl">Template Pemberitahuan Isolir (Otomatis / Manual)</label> <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> <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;"> <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)">(Username PPPoE, opsional)</span>, <b>{{nama}}</b>, <b>{{paket}}</b>, <b>{{tagihan}}</b>, <b>{{link}}</b>.
@@ -85,19 +85,19 @@
</div> </div>
<div class="fg" style="margin-top: 20px;"> <div class="fg" style="margin-top: 20px;">
<label class="fl">Template Peringatan Tunggakan (menuju nonaktif)</label> <label class="fl"><%= t('admin.wt.lbl_arrears','Template Peringatan Tunggakan (menuju nonaktif)') %></label>
<div class="fg" style="margin-top: 20px;"> <div class="fg" style="margin-top: 20px;">
<label class="fl">Template Auto-Lunas (Rp 0 / auto_isolate=0)</label> <label class="fl"><%= t('admin.wt.lbl_autopaid','Template Auto-Lunas (Rp 0 / auto_isolate=0)') %></label>
<textarea class="fc" id="tmpl_autolunas" name="whatsapp_autolunas_message" rows="7" required style="width: 100%;"><%= templates.whatsapp_autolunas_message %></textarea> <textarea class="fc" id="tmpl_autolunas" name="whatsapp_autolunas_message" rows="7" required style="width: 100%;"><%= templates.whatsapp_autolunas_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>, <b>{{nama}}</b>, <b>{{tagihan}}</b>, <b>{{rincian}}</b>, <b>{{company}}</b>.</div> <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>, <b>{{nama}}</b>, <b>{{tagihan}}</b>, <b>{{rincian}}</b>, <b>{{company}}</b>.</div>
</div> </div>
<div class="fg" style="margin-top: 20px;"> <div class="fg" style="margin-top: 20px;">
<label class="fl">Template Auto-Nonaktif (menunggak)</label> <label class="fl"><%= t('admin.wt.lbl_autoinactive','Template Auto-Nonaktif (menunggak)') %></label>
<textarea class="fc" id="tmpl_deactivated" name="whatsapp_deactivated_message" rows="7" required style="width: 100%;"><%= templates.whatsapp_deactivated_message %></textarea> <textarea class="fc" id="tmpl_deactivated" name="whatsapp_deactivated_message" rows="7" required style="width: 100%;"><%= templates.whatsapp_deactivated_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>, <b>{{nama}}</b>, <b>{{bulan_nunggak}}</b>, <b>{{batas}}</b>, <b>{{link}}</b>, <b>{{company}}</b>.</div> <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>, <b>{{nama}}</b>, <b>{{bulan_nunggak}}</b>, <b>{{batas}}</b>, <b>{{link}}</b>, <b>{{company}}</b>.</div>
</div> </div>
<div class="fg" style="margin-top: 20px;"> <div class="fg" style="margin-top: 20px;">
<label class="fl">Template Auto-Hapus (bulan ke-4)</label> <label class="fl"><%= t('admin.wt.lbl_autodelete','Template Auto-Hapus (bulan ke-4)') %></label>
<textarea class="fc" id="tmpl_deleted" name="whatsapp_deleted_message" rows="7" required style="width: 100%;"><%= templates.whatsapp_deleted_message %></textarea> <textarea class="fc" id="tmpl_deleted" name="whatsapp_deleted_message" rows="7" required style="width: 100%;"><%= templates.whatsapp_deleted_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>, <b>{{nama}}</b>, <b>{{bulan_nunggak}}</b>, <b>{{company}}</b>.</div> <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>, <b>{{nama}}</b>, <b>{{bulan_nunggak}}</b>, <b>{{company}}</b>.</div>
</div> </div>
@@ -112,11 +112,11 @@
<div style="display:flex; gap:8px; flex-wrap:wrap;"> <div style="display:flex; gap:8px; flex-wrap:wrap;">
<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-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-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> Konversi Format Meta Cloud API</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>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 id="presetStatus" style="flex-basis:100%; font-size:12px; display:none"></div>
</div> </div>
<button type="submit" class="btn btn-p"><i class="bi bi-save"></i> Simpan Template</button> <button type="submit" class="btn btn-p"><i class="bi bi-save"></i> <%= t('admin.wt.save','Simpan Template') %></button>
</div> </div>
</div> </div>
</form> </form>
@@ -125,17 +125,17 @@
<div class="mo" id="metaConverterModal"> <div class="mo" id="metaConverterModal">
<div class="mb" style="max-width: 680px;"> <div class="mb" style="max-width: 680px;">
<div class="mh"> <div class="mh">
<span class="mt"><i class="bi bi-meta"></i> Tool Konversi Template Meta WhatsApp Cloud API</span> <span class="mt"><i class="bi bi-meta"></i> <%= t('admin.wt.tool_title','Tool Konversi Template Meta WhatsApp Cloud API') %></span>
<button type="button" class="mc" onclick="closeModal('metaConverterModal')">&times;</button> <button type="button" class="mc" onclick="closeModal('metaConverterModal')">&times;</button>
</div> </div>
<div class="mbody" style="max-height:70vh; overflow-y:auto;"> <div class="mbody" style="max-height:70vh; overflow-y:auto;">
<div class="fg mb12"> <div class="fg mb12">
<label class="fl">Pilih Template yang Akan Dikonversi ke Format Meta:</label> <label class="fl"><%= t('admin.wt.pick_tmpl','Pilih Template yang Akan Dikonversi ke Format Meta:') %></label>
<select class="fs" id="metaSourceSelect" onchange="convertCurrentTemplate()"> <select class="fs" id="metaSourceSelect" onchange="convertCurrentTemplate()">
<option value="tmpl_auto_billing">Template Pengingat Tagihan</option> <option value="tmpl_auto_billing"><%= t('admin.wt.opt_reminder','Template Pengingat Tagihan') %></option>
<option value="tmpl_billing_qris">Template Tagihan QRIS</option> <option value="tmpl_billing_qris"><%= t('admin.wt.opt_qris','Template Tagihan QRIS') %></option>
<option value="tmpl_payment_success">Template Pembayaran Sukses</option> <option value="tmpl_payment_success"><%= t('admin.wt.opt_success','Template Pembayaran Sukses') %></option>
<option value="tmpl_isolir">Template Pemberitahuan Isolir</option> <option value="tmpl_isolir"><%= t('admin.wt.opt_isolir','Template Pemberitahuan Isolir') %></option>
</select> </select>
</div> </div>
@@ -145,7 +145,7 @@
</div> </div>
<div class="fg mb12"> <div class="fg mb12">
<label class="fl">Daftar Variabel Urutan Parameter (Components Payload):</label> <label class="fl"><%= t('admin.wt.vars','Daftar Variabel Urutan Parameter (Components Payload):') %></label>
<div id="metaParamMapping" style="background:var(--bg3); padding:10px 14px; border-radius:8px; font-size:12px; font-family:monospace;"></div> <div id="metaParamMapping" style="background:var(--bg3); padding:10px 14px; border-radius:8px; font-size:12px; font-family:monospace;"></div>
</div> </div>
</div> </div>
@@ -204,7 +204,7 @@ function applyPreset(set, msg) {
}); });
highlightField(document.querySelector('[name="whatsapp_auto_billing_message"]')); highlightField(document.querySelector('[name="whatsapp_auto_billing_message"]'));
var names = set.map(function (p) { return p[2]; }).join(' / '); var names = set.map(function (p) { return p[2]; }).join(' / ');
setPresetStatus('Dimuat ' + ok + ' template (' + names + '). Klik "Simpan Template" untuk menyimpan.\n' + (msg || '')); setPresetStatus('Dimuat ' + ok + ' template (' + names + '). Klik "<%= t('admin.wt.save','Simpan Template') %>" untuk menyimpan.\n' + (msg || ''));
} }
function fillOfficialReceiptPreset() { function fillOfficialReceiptPreset() {
applyPreset([ applyPreset([