Compare commits

2 Commits
master ... main

371 changed files with 1494 additions and 6265 deletions

View File

@@ -1,18 +0,0 @@
# Docker ignore file
.git
.gitignore
node_modules
npm-debug.log
yarn-debug.log
yarn-error.log
.DS_Store
Thumbs.db
.vscode
.idea
*.log
php_server.pid
php_server.log
.env
docker-compose.override.yml
*.md
README*

View File

@@ -1,9 +0,0 @@
# Google Gemini API Configuration
# Get your API key from: https://makersuite.google.com/app/apikey
GEMINI_API_KEY=your_gemini_api_key_here
# Database Configuration (if needed in future)
# DB_HOST=localhost
# DB_NAME=sidak_db
# DB_USER=root
# DB_PASSWORD=

8
.gitignore vendored Executable file → Normal file
View File

@@ -13,12 +13,10 @@ Thumbs.db
# Dependencies (if any in future) # Dependencies (if any in future)
node_modules/ node_modules/
vendor/ vendor/
!plugins/vendor/
# Uploads # Uploads
foto/ foto/
# API Keys and Environment Variables # Debug files
admin/api/config_api.php debug_ai.txt
.env debug_log.txt
.env.*.local

View File

@@ -1,38 +0,0 @@
FROM php:8.2-apache
# Install necessary PHP extensions and tools
RUN apt-get update && apt-get install -y \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libzip-dev \
zip \
unzip \
curl \
git \
mariadb-client \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install mysqli pdo pdo_mysql zip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Enable Apache rewrite module
RUN a2enmod rewrite
# Set working directory
WORKDIR /var/www/html
# Copy application files
COPY . /var/www/html/
# Set proper permissions
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html
# Update koneksi.php to use environment variables
RUN sed -i 's/\$koneksi = new mysqli ("localhost","sidak_user","sidak_pass","data_penduduk");/\$db_host = getenv("DB_HOST") ?: "localhost";\n\$db_user = getenv("DB_USER") ?: "sidak_user";\n\$db_pass = getenv("DB_PASS") ?: "sidak_pass";\n\$db_name = getenv("DB_NAME") ?: "data_penduduk";\n\$koneksi = new mysqli (\$db_host,\$db_user,\$db_pass,\$db_name);/g' inc/koneksi.php
EXPOSE 80
CMD ["apache2-foreground"]

View File

@@ -1,115 +0,0 @@
# 🔐 Keamanan API Key SIDAK
## **⚠️ PENTING: API Key Terdeteksi di Repository Git**
File `admin/api/config_api.php` sebelumnya mengandung kunci API Gemini yang terekspos di repository Git. Kunci ini telah diamankan dengan sistem environment variables.
## **🛡️ Langkah-Langkah Pengamanan yang Telah Dilakukan:**
1. **API Key dihapus dari Git tracking:**
```bash
git rm --cached admin/api/config_api.php
```
2. **File konfigurasi dimodifikasi untuk menggunakan environment variables:**
- `admin/api/config_api.php` sekarang membaca dari file `.env`
- Gunakan template `.env.example` untuk membuat `.env`
3. **File sensitif ditambahkan ke `.gitignore`:**
```
admin/api/config_api.php
.env
.env.*.local
```
## **🚀 Deployment Instructions:**
### **1. Untuk Development Lokal:**
```bash
# Salin template .env
cp .env.example .env
# Edit .env dengan API key Anda
nano .env # atau editor favorit Anda
```
### **2. Untuk Production Server:**
```bash
# Buat file .env di server
cat > /var/www/sidak/.env << 'EOF'
# Google Gemini API Configuration
GEMINI_API_KEY=your_actual_production_key_here
EOF
# Pastikan permission aman
chmod 600 /var/www/sidak/.env
chown www-data:www-data /var/www/sidak/.env
```
### **3. Get New API Key (jika perlu):**
1. Kunjungi [Google AI Studio](https://makersuite.google.com/app/apikey)
2. Login dengan akun Google
3. Create API Key → Copy key baru
4. Update file `.env` di server
## **📁 Struktur File yang Aman:**
```
sidak/
├── .env # ⚠️ JANGAN commit (sudah di .gitignore)
├── .env.example # ✅ Template aman untuk commit
├── .gitignore # ✅ Menyertakan .env dan config_api.php
├── admin/
│ └── api/
│ ├── config_api.php # ✅ Membaca dari environment variables
│ └── ocr_helper.php # ✅ Menggunakan GEMINI_API_KEY dari config
└── README_API_SECURITY.md # ✅ Dokumentasi ini
```
## **🔧 Testing Configuration:**
Untuk memastikan konfigurasi bekerja:
```php
<?php
// Test script: test_api_config.php
require_once 'admin/api/config_api.php';
echo "GEMINI_API_KEY: " . (defined('GEMINI_API_KEY') ? '✅ Set' : '❌ Not set') . "\n";
echo "GEMINI_API_URL: " . GEMINI_API_URL . "\n";
if (empty(GEMINI_API_KEY)) {
echo "❌ ERROR: API key tidak terdeteksi.\n";
echo "Pastikan file .env ada dan berisi GEMINI_API_KEY=your_key\n";
} else {
echo "✅ Konfigurasi API siap digunakan.\n";
}
?>
```
## **🔄 Jika Terjadi Masalah:**
### **Masalah: "API key not set"**
**Solusi:**
1. Pastikan file `.env` ada di root directory
2. Pastikan permission file `.env` dapat dibaca oleh PHP
3. Restart web server jika perlu: `sudo service apache2 restart`
### **Masalah: "403 Forbidden" dari Gemini API**
**Solusi:**
1. Periksa apakah API key valid di [Google AI Studio](https://makersuite.google.com/app/apikey)
2. Pastikan billing enabled di Google Cloud Console
3. Cek quota usage di Google Cloud Console
## **📞 Support:**
Jika menemukan masalah keamanan:
1. **Segera putar API key** di Google AI Studio
2. Update file `.env` di semua environment
3. Hubungi developer: [wartana@example.com]
---
**⚠️ REMINDER:** JANGAN pernah commit file `.env` atau `admin/api/config_api.php` ke repository Git. Selalu gunakan `.env.example` sebagai template.
**Terakhir diperbarui:** 22 Januari 2026

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

5
admin/api/config_api.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
// Google Gemini API Configuration
define('GEMINI_API_KEY', 'AIzaSyDp9crq4QWN15xBXbDY2FBXdUoRg1LgM1M');
define('GEMINI_API_URL', 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent');
?>

77
admin/api/ocr_helper.php Executable file → Normal file
View File

@@ -1,7 +1,6 @@
<?php <?php
header('Content-Type: application/json'); header('Content-Type: application/json');
require_once 'config_api.php'; require_once 'config_api.php';
set_time_limit(300);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'message' => 'Invalid request method']); echo json_encode(['success' => false, 'message' => 'Invalid request method']);
@@ -20,27 +19,31 @@ $mimeType = mime_content_type($imagePath);
// Construct Prompt based on Type // Construct Prompt based on Type
if ($type === 'kk') { if ($type === 'kk') {
$promptText = "Extract strictly from this Indonesian Family Card (Kartu Keluarga). Return ONLY a raw JSON object with these keys: 'no_kk' (16 digits), 'kepala_keluarga' (Name), 'alamat', 'rt', 'rw', 'desa', 'kecamatan', 'kabupaten', 'provinsi', 'kode_pos', 'anggota': [ { 'nik': '...', 'nama': '...', 'hubungan': '...' } ] (Array of all family members found in the table. 'hubungan' examples: KEPALA KELUARGA, ISTRI, ANAK, FAMILI LAIN). Value must be string. If not found, use empty string."; $promptText = "Extract strictly from this Indonesian Family Card (Kartu Keluarga). Return ONLY a raw JSON object with these keys: 'no_kk' (16 digits), 'kepala_keluarga' (Name), 'alamat', 'rt', 'rw', 'desa', 'kecamatan', 'kabupaten', 'provinsi', 'kode_pos', 'anggota': [ { 'nik': '...', 'nama': '...', 'hubungan': '...', 'tempat_lh': '...', 'tgl_lh': 'YYYY-MM-DD', 'jekel': 'LK/PR', 'agama': '...', 'kawin': '...', 'pekerjaan': '...', 'kewarganegaraan': 'WNI/WNA' } ] (Array of all family members found in the table. 'hubungan' examples: KEPALA KELUARGA, ISTRI, ANAK, FAMILI LAIN). Value must be string. If not found, use empty string.";
} else { } else {
// Default to KTP // Default to KTP
$promptText = "Extract data from this Indonesian KTP. Return ONLY a raw JSON object with keys: 'nik' (16 digits), 'nama' (Name), 'tempat_lh' (Place of Birth), 'tgl_lh' (YYYY-MM-DD), 'je_kel' (LK/PR), 'alamat' (Street only), 'rt', 'rw', 'desa' (Kel/Desa), 'kecamatan', 'kabupaten' (or Kota), 'provinsi', 'agama', 'status' (Sudah/Belum/Cerai Hidup/Cerai Mati), 'pekerjaan', 'kewarganegaraan' (WNI/WNA). Clean up text. If field is unclear, return empty string."; $promptText = "Extract data from this Indonesian KTP. Return ONLY a raw JSON object with keys: 'nik' (16 digits), 'nama' (Name), 'tempat_lh' (Place of Birth), 'tgl_lh' (YYYY-MM-DD), 'je_kel' (LK/PR), 'alamat' (Street only), 'rt', 'rw', 'desa' (Kel/Desa), 'kecamatan', 'kabupaten' (or Kota), 'provinsi', 'agama', 'status' (Sudah/Belum/Cerai Hidup/Cerai Mati), 'pekerjaan', 'kewarganegaraan' (WNI/WNA). Clean up text. If field is unclear, return empty string.";
} }
// Payload for Ollama App // Payload for Gemini
$data = [ $data = [
"model" => "qwen2.5vl:3b", "contents" => [
"messages" => [
[ [
"role" => "user", "parts" => [
"content" => $promptText, ["text" => $promptText],
"images" => [$imageData] [
"inline_data" => [
"mime_type" => $mimeType,
"data" => $imageData
]
]
]
] ]
], ]
"stream" => false
]; ];
// Send Request // Send Request
$ch = curl_init(OLLAMA_API_URL); $ch = curl_init(GEMINI_API_URL . '?key=' . GEMINI_API_KEY);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
@@ -64,13 +67,14 @@ if (isset($result['error'])) {
exit; exit;
} }
if (!isset($result['message']['content'])) { if (!isset($result['candidates'][0]['content']['parts'][0]['text'])) {
echo json_encode(['success' => false, 'message' => 'No text returned from AI', 'raw' => $result]); echo json_encode(['success' => false, 'message' => 'No text returned from AI', 'raw' => $result]);
exit; exit;
} }
// Parse AI Response // Parse AI Response
$rawText = $result['message']['content']; $rawText = $result['candidates'][0]['content']['parts'][0]['text'];
file_put_contents('../../debug_ai.txt', "AI Response:\n" . $rawText . "\n-------------------\n", FILE_APPEND);
// Remove Markdown Code Blocks if any (```json ... ```) // Remove Markdown Code Blocks if any (```json ... ```)
$cleanJson = preg_replace('/^```json\s*|\s*```$/', '', trim($rawText)); $cleanJson = preg_replace('/^```json\s*|\s*```$/', '', trim($rawText));
@@ -88,52 +92,5 @@ if (!$parsedData) {
exit; exit;
} }
if ($type === 'ktp') {
// 1. Parse Status Perkawinan
if (!empty($parsedData['status'])) {
$st = strtoupper($parsedData['status']);
if (strpos($st, 'BELUM') !== false) {
$parsedData['status'] = 'Belum';
} elseif (strpos($st, 'CERAI MATI') !== false) {
$parsedData['status'] = 'Cerai Mati';
} elseif (strpos($st, 'CERAI HIDUP') !== false) {
$parsedData['status'] = 'Cerai Hidup';
} elseif (strpos($st, 'KAWIN') !== false) {
$parsedData['status'] = 'Sudah';
}
}
// 2. Parse Jenis Kelamin
if (!empty($parsedData['je_kel'])) {
$jk = strtoupper($parsedData['je_kel']);
if (strpos($jk, 'LAKI') !== false || $jk === 'L') {
$parsedData['je_kel'] = 'LK';
} elseif (strpos($jk, 'PEREMPUAN') !== false || $jk === 'P') {
$parsedData['je_kel'] = 'PR';
}
}
// 3. Parse Tempat & Tanggal Lahir (KTP format: "TEMPAT, DD-MM-YYYY")
if (!empty($parsedData['tempat_lh'])) {
$ttl = $parsedData['tempat_lh'];
if (strpos($ttl, ',') !== false) {
[$tempat, $tgl] = explode(',', $ttl, 2);
$parsedData['tempat_lh'] = trim($tempat);
if (empty($parsedData['tgl_lh'])) {
$parsedData['tgl_lh'] = trim($tgl);
}
}
}
// 4. Format Tanggal Lahir to YYYY-MM-DD for HTML input[type=date]
if (!empty($parsedData['tgl_lh'])) {
$tgl_lh = trim($parsedData['tgl_lh']);
// If it looks like DD-MM-YYYY or DD/MM/YYYY
if (preg_match('/^(\d{2})[\/\-](\d{2})[\/\-](\d{4})$/', $tgl_lh, $matches)) {
$parsedData['tgl_lh'] = $matches[3] . '-' . $matches[2] . '-' . $matches[1];
}
}
}
echo json_encode(['success' => true, 'data' => $parsedData]); echo json_encode(['success' => true, 'data' => $parsedData]);
?> ?>

102
admin/datang/add_datang.php Executable file → Normal file
View File

@@ -1,6 +1,3 @@
<?php
$selected_id = isset($_GET['selected_id']) ? (int)$_GET['selected_id'] : 0;
?>
<div class="card card-primary"> <div class="card card-primary">
<div class="card-header"> <div class="card-header">
<h3 class="card-title"> <h3 class="card-title">
@@ -10,30 +7,27 @@ $selected_id = isset($_GET['selected_id']) ? (int)$_GET['selected_id'] : 0;
<div class="card-body"> <div class="card-body">
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">Pendatang</label> <label class="col-sm-2 col-form-label">NIK</label>
<div class="col-sm-6"> <div class="col-sm-6">
<select name="id_pend" id="id_pend" class="form-control select2bs4" required> <input type="text" class="form-control" id="nik" name="nik" placeholder="NIK" required>
<option value="" <?php echo ($selected_id == 0) ? 'selected="selected"' : ''; ?>>- Pilih Penduduk -</option>
<?php
// ambil data dari database
$query = "select * from tb_pdd where status='Ada'";
$hasil = mysqli_query($koneksi, $query);
while ($row = mysqli_fetch_array($hasil)) {
?>
<option value="<?php echo $row['id_pend'] ?>" <?php echo ($row['id_pend'] == $selected_id) ? 'selected="selected"' : ''; ?>>
<?php echo $row['nik'] ?>
-
<?php echo $row['nama'] ?>
</option>
<?php
}
?>
</select>
</div> </div>
<div class="col-sm-2"> </div>
<a href="?page=add-pend&return_to=add-datang" class="btn btn-outline-primary btn-sm">
<i class="fa fa-plus"></i> Tambah Penduduk Baru <div class="form-group row">
</a> <label class="col-sm-2 col-form-label">Nama</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="nama_datang" name="nama_datang" placeholder="Nama Pendatang" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Jenis Kelain</label>
<div class="col-sm-3">
<select name="jekel" id="jekel" class="form-control">
<option>- Pilih -</option>
<option>LK</option>
<option>PR</option>
</select>
</div> </div>
</div> </div>
@@ -56,9 +50,9 @@ $selected_id = isset($_GET['selected_id']) ? (int)$_GET['selected_id'] : 0;
while ($row = mysqli_fetch_array($hasil)) { while ($row = mysqli_fetch_array($hasil)) {
?> ?>
<option value="<?php echo $row['id_pend'] ?>"> <option value="<?php echo $row['id_pend'] ?>">
<?php echo htmlspecialchars($row['nik'], ENT_QUOTES); ?> <?php echo $row['nik'] ?>
- -
<?php echo htmlspecialchars($row['nama'], ENT_QUOTES); ?> <?php echo $row['nama'] ?>
</option> </option>
<?php <?php
} }
@@ -79,37 +73,37 @@ $selected_id = isset($_GET['selected_id']) ? (int)$_GET['selected_id'] : 0;
if (isset ($_POST['Simpan'])){ if (isset ($_POST['Simpan'])){
//mulai proses simpan data //mulai proses simpan data
$sql_simpan = "INSERT INTO tb_datang (nik, nama_datang, jekel, tgl_datang, pelapor) VALUES (
// Sanitize Input to prevent SQL Injection & Syntax Errors '".$_POST['nik']."',
$id_pend = (int)$_POST['id_pend']; // Cast to integer for safety '".$_POST['nama_datang']."',
$tgl_datang = mysqli_real_escape_string($koneksi, trim($_POST['tgl_datang'])); '".$_POST['jekel']."',
$pelapor = (int)$_POST['pelapor']; // Cast to integer for safety '".$_POST['tgl_datang']."',
'".$_POST['pelapor']."')";
// Ambil data penduduk yang dipilih
$sql_pend = "SELECT nik, nama, jekel FROM tb_pdd WHERE id_pend='$id_pend'";
$q_pend = mysqli_query($koneksi, $sql_pend);
$d_pend = mysqli_fetch_array($q_pend);
if (!$d_pend) {
die("Data penduduk tidak ditemukan. Silakan pilih penduduk yang valid.");
}
$nik = mysqli_real_escape_string($koneksi, trim($d_pend['nik']));
$nama_datang = mysqli_real_escape_string($koneksi, trim($d_pend['nama']));
$jekel = mysqli_real_escape_string($koneksi, trim($d_pend['jekel']));
$sql_simpan = "INSERT INTO tb_datang (id_pend, nik, nama_datang, jekel, tgl_datang, pelapor) VALUES (
'$id_pend',
'$nik',
'$nama_datang',
'$jekel',
'$tgl_datang',
'$pelapor')";
$query_simpan = mysqli_query($koneksi, $sql_simpan); $query_simpan = mysqli_query($koneksi, $sql_simpan);
// Otomatis tambah ke Data Penduduk
// Ambil data alamat dari pelapor
$id_pelapor = $_POST['pelapor'];
$sql_pelapor = "SELECT desa, rt, rw FROM tb_pdd WHERE id_pend='$id_pelapor'";
$q_pelapor = mysqli_query($koneksi, $sql_pelapor);
$d_pelapor = mysqli_fetch_array($q_pelapor);
$desa = $d_pelapor['desa'];
$rt = $d_pelapor['rt'];
$rw = $d_pelapor['rw'];
$sql_pdd = "INSERT INTO tb_pdd (nik, nama, tempat_lh, tgl_lh, jekel, desa, rt, rw, agama, kawin, pekerjaan, status) VALUES (
'".$_POST['nik']."',
'".$_POST['nama_datang']."',
'-',
'0000-00-00',
'".$_POST['jekel']."',
'$desa', '$rt', '$rw',
'-', '-', '-', 'Ada')";
$query_pdd = mysqli_query($koneksi, $sql_pdd);
mysqli_close($koneksi); mysqli_close($koneksi);
if ($query_simpan) { if ($query_simpan && $query_pdd) {
echo "<script> echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK' Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){ }).then((result) => {if (result.value){

37
admin/datang/data_datang.php Executable file → Normal file
View File

@@ -17,7 +17,7 @@
<th>No</th> <th>No</th>
<th>NIK</th> <th>NIK</th>
<th>Nama</th> <th>Nama</th>
<th>Jenis Kelamin</th> <th>Jekel</th>
<th>Tanggal</th> <th>Tanggal</th>
<th>Pelapor</th> <th>Pelapor</th>
<th>Aksi</th> <th>Aksi</th>
@@ -43,15 +43,7 @@
<?php echo $data['nama_datang']; ?> <?php echo $data['nama_datang']; ?>
</td> </td>
<td> <td>
<?php <?php echo $data['jekel']; ?>
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
</td> </td>
<td> <td>
<?php echo $data['tgl_datang']; ?> <?php echo $data['tgl_datang']; ?>
@@ -64,10 +56,10 @@
class="btn btn-success btn-sm"> class="btn btn-success btn-sm">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
</a> </a>
<a href="?page=del-datang&kode=<?php echo $data['id_datang']; ?>" onclick="confirmDelete(event)" <a href="?page=del-datang&kode=<?php echo $data['id_datang']; ?>" onclick="return confirm('Apakah anda yakin hapus data ini ?')"
title="Hapus" class="btn btn-danger btn-sm"> title="Hapus" class="btn btn-danger btn-sm">
<i class="fa fa-trash"></i> <i class="fa fa-trash"></i>
</a> </>
</td> </td>
</tr> </tr>
@@ -76,26 +68,7 @@
?> ?>
</tbody> </tbody>
</tfoot> </tfoot>
</table> </table>
</div> </div>
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->
<script>
function confirmDelete(event) {
event.preventDefault();
Swal.fire({
title: 'Konfirmasi Hapus',
text: 'Apakah Anda yakin ingin menghapus data ini?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, Hapus',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = event.currentTarget.href;
}
});
}
</script>

0
admin/datang/del_datang.php Executable file → Normal file
View File

79
admin/datang/edit_datang.php Executable file → Normal file
View File

@@ -1,11 +1,8 @@
<?php <?php
if(isset($_GET['kode'])){ if(isset($_GET['kode'])){
$sql_cek = "SELECT d.id_datang, d.id_pend, d.nik, d.nama_datang, d.jekel, d.tgl_datang, p.id_pend as id_pelapor, p.nama as nama_pelapor, pd.tgl_lh $sql_cek = "SELECT d.id_datang, d.nik, d.nama_datang, d.jekel, d.tgl_datang, p.id_pend, p.nama from
FROM tb_datang d tb_datang d inner join tb_pdd p on d.pelapor=p.id_pend WHERE id_datang='".$_GET['kode']."'";
INNER JOIN tb_pdd p ON d.pelapor=p.id_pend
LEFT JOIN tb_pdd pd ON d.id_pend=pd.id_pend
WHERE d.id_datang='".$_GET['kode']."'";
$query_cek = mysqli_query($koneksi, $sql_cek); $query_cek = mysqli_query($koneksi, $sql_cek);
$data_cek = mysqli_fetch_array($query_cek,MYSQLI_BOTH); $data_cek = mysqli_fetch_array($query_cek,MYSQLI_BOTH);
} }
@@ -27,51 +24,43 @@
</div> </div>
</div> </div>
<input type="hidden" id="id_pend" name="id_pend" value="<?php echo isset($data_cek['id_pend']) ? $data_cek['id_pend'] : ''; ?>">
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">NIK</label> <label class="col-sm-2 col-form-label">NIK</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="text" class="form-control" value="<?php echo htmlspecialchars($data_cek['nik'], ENT_QUOTES); ?>" readonly> <input type="text" class="form-control" id="nik" name="nik" value="<?php echo $data_cek['nik']; ?>"
<input type="hidden" name="nik" value="<?php echo htmlspecialchars($data_cek['nik'], ENT_QUOTES); ?>"> required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">Nama</label> <label class="col-sm-2 col-form-label">Nama</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="text" class="form-control" value="<?php echo htmlspecialchars($data_cek['nama_datang'], ENT_QUOTES); ?>" readonly> <input type="text" class="form-control" id="nama_datang" name="nama_datang" value="<?php echo $data_cek['nama_datang']; ?>"
<input type="hidden" name="nama_datang" value="<?php echo htmlspecialchars($data_cek['nama_datang'], ENT_QUOTES); ?>"> required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">Jenis Kelamin</label> <label class="col-sm-2 col-form-label">Jenis Kelamin</label>
<div class="col-sm-3"> <div class="col-sm-3">
<input type="text" class="form-control" value="<?php <select name="jekel" id="jekel" class="form-control">
$display_jekel = $data_cek['jekel']; <option value="">-- Pilih jekel --</option>
if ($display_jekel == 'LK') { <?php
$display_jekel = 'LAKI-LAKI'; //menhecek data yg dipilih sebelumnya
} elseif ($display_jekel == 'PR') { if ($data_cek['jekel'] == "LK") echo "<option value='LK' selected>LK</option>";
$display_jekel = 'PEREMPUAN'; else echo "<option value='LK'>LK</option>";
}
echo htmlspecialchars($display_jekel, ENT_QUOTES);
?>" readonly>
<input type="hidden" name="jekel" value="<?php echo htmlspecialchars($data_cek['jekel'], ENT_QUOTES); ?>">
</div>
</div>
<div class="form-group row"> if ($data_cek['jekel'] == "PR") echo "<option value='PR' selected>PR</option>";
<label class="col-sm-2 col-form-label">Tgl Lahir</label> else echo "<option value='PR'>PR</option>";
<div class="col-sm-3"> ?>
<input type="text" class="form-control" value="<?php echo htmlspecialchars(isset($data_cek['tgl_lh']) ? $data_cek['tgl_lh'] : '-', ENT_QUOTES); ?>" readonly> </select>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">Tgl Datang</label> <label class="col-sm-2 col-form-label">Tgl Datang</label>
<div class="col-sm-3"> <div class="col-sm-3">
<input type="date" class="form-control" id="tgl_datang" name="tgl_datang" value="<?php echo htmlspecialchars($data_cek['tgl_datang'], ENT_QUOTES); ?>" <input type="date" class="form-control" id="tgl_datang" name="tgl_datang" value="<?php echo $data_cek['tgl_datang']; ?>"
required> required>
</div> </div>
</div> </div>
@@ -83,14 +72,15 @@
<option selected="">- Pilih -</option> <option selected="">- Pilih -</option>
<?php <?php
// ambil data dari database // ambil data dari database
$query = "select * from tb_pdd where status='Ada'"; $query = "select * from tb_pdd";
$hasil = mysqli_query($koneksi, $query); $hasil = mysqli_query($koneksi, $query);
while ($row = mysqli_fetch_array($hasil)) { while ($row = mysqli_fetch_array($hasil)) {
?> ?>
<option value="<?php echo $row['id_pend'] ?>" <?=$data_cek['id_pelapor']==$row['id_pend'] ? "selected" : null ?>> <option value="<?php echo $row['id_pend'] ?>" <?=$data_cek[
<?php echo htmlspecialchars($row['nik'], ENT_QUOTES); ?> 'id_pend']==$row[ 'id_pend'] ? "selected" : null ?>>
<?php echo $row['nik'] ?>
- -
<?php echo htmlspecialchars($row['nama'], ENT_QUOTES); ?> <?php echo $row['nama'] ?>
</option> </option>
<?php <?php
} }
@@ -111,27 +101,14 @@
<?php <?php
if (isset ($_POST['Ubah'])){ if (isset ($_POST['Ubah'])){
// Sanitize Input to prevent SQL Injection & Syntax Errors
$id_datang = (int)$_POST['id_datang'];
$nik = mysqli_real_escape_string($koneksi, trim($_POST['nik']));
$nama_datang = mysqli_real_escape_string($koneksi, trim($_POST['nama_datang']));
$jekel = mysqli_real_escape_string($koneksi, trim($_POST['jekel']));
$tgl_datang = mysqli_real_escape_string($koneksi, trim($_POST['tgl_datang']));
$pelapor = (int)$_POST['pelapor'];
// Update tb_datang table
$sql_ubah = "UPDATE tb_datang SET $sql_ubah = "UPDATE tb_datang SET
nik='$nik', nik='".$_POST['nik']."',
nama_datang='$nama_datang', nama_datang='".$_POST['nama_datang']."',
jekel='$jekel', jekel='".$_POST['jekel']."',
tgl_datang='$tgl_datang', tgl_datang='".$_POST['tgl_datang']."',
pelapor='$pelapor' pelapor='".$_POST['pelapor']."'
WHERE id_datang='$id_datang'"; WHERE id_datang='".$_POST['id_datang']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah); $query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi); mysqli_close($koneksi);
if ($query_ubah) { if ($query_ubah) {

128
admin/kartu/add_kartu.php Executable file → Normal file
View File

@@ -30,9 +30,9 @@
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">Kepala Keluarga</label> <label class="col-sm-2 col-form-label">Kpl Keluarga</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="text" class="form-control" id="kepala" name="kepala" placeholder="Kepala Keluarga" required> <input type="text" class="form-control" id="kepala" name="kepala" placeholder="Kpl Keluarga" required>
</div> </div>
</div> </div>
@@ -46,10 +46,10 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">RT/RW</label> <label class="col-sm-2 col-form-label">RT/RW</label>
<div class="col-sm-3"> <div class="col-sm-3">
<input type="text" class="form-control" id="rt" name="rt" placeholder="RT" value="000" required> <input type="text" class="form-control" id="rt" name="rt" placeholder="RT" required>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
<input type="text" class="form-control" id="rw" name="rw" placeholder="RW" value="000" required> <input type="text" class="form-control" id="rw" name="rw" placeholder="RW" required>
</div> </div>
</div> </div>
@@ -166,18 +166,7 @@ window.addEventListener('load', function() {
<tr><td>Kabupaten</td><td>${d.kabupaten || '-'}</td></tr> <tr><td>Kabupaten</td><td>${d.kabupaten || '-'}</td></tr>
<tr><td>Provinsi</td><td>${d.provinsi || '-'}</td></tr> <tr><td>Provinsi</td><td>${d.provinsi || '-'}</td></tr>
</table> </table>
<p class="mb-0 text-muted">Gunakan data ini?</p>
<hr>
<strong>Ditemukan ${d.anggota ? d.anggota.length : 0} Anggota:</strong>
<div style="max-height: 150px; overflow-y: auto; background: #f8f9fa; border: 1px solid #ddd; padding: 5px;">
${d.anggota && d.anggota.length > 0 ?
'<ul style="padding-left: 20px; margin-bottom: 0;">' +
d.anggota.map(m => `<li><b>${m.nama}</b><br><small>${m.nik} (${m.hubungan})</small></li>`).join('') +
'</ul>'
: '<i class="text-muted">Tidak ada anggota terdeteksi</i>'}
</div>
<p class="mb-0 text-muted mt-2">Gunakan data ini?</p>
</div> </div>
`, `,
icon: 'question', icon: 'question',
@@ -190,8 +179,8 @@ window.addEventListener('load', function() {
if(d.anggota) document.getElementById('anggota_json').value = JSON.stringify(d.anggota); if(d.anggota) document.getElementById('anggota_json').value = JSON.stringify(d.anggota);
if(d.kepala_keluarga) document.getElementsByName('kepala')[0].value = d.kepala_keluarga; if(d.kepala_keluarga) document.getElementsByName('kepala')[0].value = d.kepala_keluarga;
if(d.desa) document.getElementsByName('desa')[0].value = d.desa; if(d.desa) document.getElementsByName('desa')[0].value = d.desa;
if(d.rt) document.getElementsByName('rt')[0].value = d.rt; document.getElementsByName('rt')[0].value = d.rt || '000';
if(d.rw) document.getElementsByName('rw')[0].value = d.rw; document.getElementsByName('rw')[0].value = d.rw || '000';
if(d.kecamatan) document.getElementsByName('kec')[0].value = d.kecamatan; if(d.kecamatan) document.getElementsByName('kec')[0].value = d.kecamatan;
if(d.kabupaten) document.getElementsByName('kab')[0].value = d.kabupaten; if(d.kabupaten) document.getElementsByName('kab')[0].value = d.kabupaten;
if(d.provinsi) document.getElementsByName('prov')[0].value = d.provinsi; if(d.provinsi) document.getElementsByName('prov')[0].value = d.provinsi;
@@ -238,9 +227,8 @@ window.addEventListener('load', function() {
} }
//mulai proses simpan data //mulai proses simpan data
$no_kk_raw = trim($_POST['no_kk']); $no_kk = $_POST['no_kk'];
$no_kk_clean = mysqli_real_escape_string($koneksi, $no_kk_raw); $cek_kk = mysqli_query($koneksi, "SELECT * FROM tb_kk WHERE no_kk='$no_kk'");
$cek_kk = mysqli_query($koneksi, "SELECT * FROM tb_kk WHERE no_kk='$no_kk_clean'");
if(mysqli_num_rows($cek_kk) > 0){ if(mysqli_num_rows($cek_kk) > 0){
echo "<script> echo "<script>
Swal.fire({title: 'Gagal',text: 'No KK sudah terdaftar dalam sistem!',icon: 'error',confirmButtonText: 'OK' Swal.fire({title: 'Gagal',text: 'No KK sudah terdaftar dalam sistem!',icon: 'error',confirmButtonText: 'OK'
@@ -252,14 +240,14 @@ window.addEventListener('load', function() {
} }
// Sanitize Inputs // Sanitize Inputs
$no_kk = $no_kk_clean; $no_kk = mysqli_real_escape_string($koneksi, $_POST['no_kk']);
$kepala = mysqli_real_escape_string($koneksi, trim($_POST['kepala'])); $kepala = mysqli_real_escape_string($koneksi, $_POST['kepala']);
$desa = mysqli_real_escape_string($koneksi, trim($_POST['desa'])); $desa = mysqli_real_escape_string($koneksi, $_POST['desa']);
$rt = mysqli_real_escape_string($koneksi, trim($_POST['rt'])); $rt = mysqli_real_escape_string($koneksi, $_POST['rt']);
$rw = mysqli_real_escape_string($koneksi, trim($_POST['rw'])); $rw = mysqli_real_escape_string($koneksi, $_POST['rw']);
$kec = mysqli_real_escape_string($koneksi, trim($_POST['kec'])); $kec = mysqli_real_escape_string($koneksi, $_POST['kec']);
$kab = mysqli_real_escape_string($koneksi, trim($_POST['kab'])); $kab = mysqli_real_escape_string($koneksi, $_POST['kab']);
$prov = mysqli_real_escape_string($koneksi, trim($_POST['prov'])); $prov = mysqli_real_escape_string($koneksi, $_POST['prov']);
$sql_simpan = "INSERT INTO tb_kk (no_kk, kepala, desa, rt, rw, kec, kab, prov, foto_kk) VALUES ( $sql_simpan = "INSERT INTO tb_kk (no_kk, kepala, desa, rt, rw, kec, kab, prov, foto_kk) VALUES (
'$no_kk', '$no_kk',
@@ -274,69 +262,79 @@ window.addEventListener('load', function() {
$query_simpan = mysqli_query($koneksi, $sql_simpan); $query_simpan = mysqli_query($koneksi, $sql_simpan);
// Process Auto-Linking Members // Process Auto-Linking Members
$linked_count = 0;
$failed_count = 0;
$failed_members = []; // Store failed members for detailed feedback
if ($query_simpan && !empty($_POST['anggota_json'])) { if ($query_simpan && !empty($_POST['anggota_json'])) {
file_put_contents('debug_log.txt', "----------------------------------------\n", FILE_APPEND);
file_put_contents('debug_log.txt', "Saving KK ID: " . mysqli_insert_id($koneksi) . "\n", FILE_APPEND);
file_put_contents('debug_log.txt', "Raw JSON: " . $_POST['anggota_json'] . "\n", FILE_APPEND);
$id_kk_baru = mysqli_insert_id($koneksi); $id_kk_baru = mysqli_insert_id($koneksi);
$anggota_list = json_decode($_POST['anggota_json'], true); $anggota_list = json_decode($_POST['anggota_json'], true);
if (is_array($anggota_list)) { if (is_array($anggota_list)) {
foreach ($anggota_list as $mem) { foreach ($anggota_list as $mem) {
$nik_mem = mysqli_real_escape_string($koneksi, $mem['nik']); $nik_mem = mysqli_real_escape_string($koneksi, $mem['nik']);
$nama_mem = isset($mem['nama']) ? mysqli_real_escape_string($koneksi, $mem['nama']) : '';
$hub_mem = mysqli_real_escape_string($koneksi, $mem['hubungan']); $hub_mem = mysqli_real_escape_string($koneksi, $mem['hubungan']);
file_put_contents('debug_log.txt', "Processing NIK: $nik_mem\n", FILE_APPEND);
// Search Resident by NIK // Search Resident by NIK
$sql_cek_pend = "SELECT id_pend FROM tb_pdd WHERE nik='$nik_mem'"; $sql_cek_pend = "SELECT id_pend FROM tb_pdd WHERE nik='$nik_mem'";
$q_cek_pend = mysqli_query($koneksi, $sql_cek_pend); $q_cek_pend = mysqli_query($koneksi, $sql_cek_pend);
if ($row_pend = mysqli_fetch_assoc($q_cek_pend)) { if ($row_pend = mysqli_fetch_assoc($q_cek_pend)) {
$id_pend_found = $row_pend['id_pend']; $id_pend_found = $row_pend['id_pend'];
file_put_contents('debug_log.txt', "Found Existing ID: $id_pend_found. Linking...\n", FILE_APPEND);
// Insert into tb_anggota // Insert into tb_anggota
$sql_add_ang = "INSERT INTO tb_anggota (id_kk, id_pend, hubungan) VALUES ('$id_kk_baru', '$id_pend_found', '$hub_mem')"; $sql_add_ang = "INSERT INTO tb_anggota (id_kk, id_pend, hubungan) VALUES ('$id_kk_baru', '$id_pend_found', '$hub_mem')";
mysqli_query($koneksi, $sql_add_ang); if (!mysqli_query($koneksi, $sql_add_ang)) {
$linked_count++; file_put_contents('debug_log.txt', "Link Error: " . mysqli_error($koneksi) . "\n", FILE_APPEND);
} else {
file_put_contents('debug_log.txt', "Success Link ID $id_pend_found\n", FILE_APPEND);
}
} else { } else {
$failed_count++; // Create New Resident if not found
$failed_members[] = ['nik' => $nik_mem, 'nama' => $nama_mem, 'hubungan' => $hub_mem]; file_put_contents('debug_log.txt', "NIK Not Found. Creating New.\n", FILE_APPEND);
$nama_mem = mysqli_real_escape_string($koneksi, $mem['nama']);
$tempat_lh = mysqli_real_escape_string($koneksi, $mem['tempat_lh'] ?? '-');
$tgl_lh = mysqli_real_escape_string($koneksi, $mem['tgl_lh'] ?? '0000-00-00');
$jekel = mysqli_real_escape_string($koneksi, $mem['jekel'] ?? 'LK');
$agama = mysqli_real_escape_string($koneksi, $mem['agama'] ?? 'Islam');
$kawin = mysqli_real_escape_string($koneksi, $mem['kawin'] ?? 'Belum Kawin');
$pekerjaan = mysqli_real_escape_string($koneksi, $mem['pekerjaan'] ?? 'Pelajar/Mahasiswa');
$kewarganegaraan = mysqli_real_escape_string($koneksi, $mem['kewarganegaraan'] ?? 'WNI');
$sql_new_pend = "INSERT INTO tb_pdd (
nik, nama, tempat_lh, tgl_lh, jekel,
desa, rt, rw, agama, kawin, pekerjaan, status, kwn
) VALUES (
'$nik_mem', '$nama_mem', '$tempat_lh', '$tgl_lh', '$jekel',
'$desa', '$rt', '$rw', '$agama', '$kawin', '$pekerjaan', 'Ada', '$kewarganegaraan'
)";
if (mysqli_query($koneksi, $sql_new_pend)) {
$id_pend_new = mysqli_insert_id($koneksi);
file_put_contents('debug_log.txt', "Created New ID: $id_pend_new. Linking...\n", FILE_APPEND);
// Link to KK
$sql_add_ang = "INSERT INTO tb_anggota (id_kk, id_pend, hubungan) VALUES ('$id_kk_baru', '$id_pend_new', '$hub_mem')";
mysqli_query($koneksi, $sql_add_ang);
} else {
file_put_contents('debug_log.txt', "Error Creating Resident: " . mysqli_error($koneksi) . "\n", FILE_APPEND);
}
} }
} }
} else {
file_put_contents('debug_log.txt', "JSON Decode Failed or Not Array\n", FILE_APPEND);
} }
} }
mysqli_close($koneksi); mysqli_close($koneksi);
if ($query_simpan) { if ($query_simpan) {
$msg_add = "";
if($linked_count > 0 || $failed_count > 0) {
$msg_add = "<br>Anggota Terhubung: <b>$linked_count</b><br>Tidak Ditemukan: <b>$failed_count</b>";
// Add detailed failed members list if any
if (!empty($failed_members)) {
$msg_add .= "<br><br><b>Detail Anggota Tidak Ditemukan:</b><br>";
$msg_add .= "<div style='max-height: 150px; overflow-y: auto; background: #f8f9fa; border: 1px solid #ddd; padding: 5px; font-size: 0.9rem;'>";
foreach ($failed_members as $fm) {
$url_params = http_build_query([
'nik' => $fm['nik'],
'nama' => $fm['nama'],
'desa' => $desa,
'rt' => $rt,
'rw' => $rw,
'kecamatan' => $kec,
'kabupaten' => $kab,
'provinsi' => $prov
]);
$add_link = "<a href='index.php?page=add-pend&$url_params' class='btn btn-xs btn-outline-primary ml-2'>Tambah</a>";
$msg_add .= "• <b>" . htmlspecialchars($fm['nik']) . "</b> - " . htmlspecialchars($fm['nama']) . " (" . htmlspecialchars($fm['hubungan']) . ") $add_link<br>";
}
$msg_add .= "</div>";
$msg_add .= "<small class='text-muted'>Tambahkan data penduduk yang belum terdaftar melalui menu <a href='index.php?page=add-pend'>Tambah Penduduk</a>.</small>";
}
}
echo "<script> echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',html: 'Data KK disimpan.$msg_add',icon: 'success',confirmButtonText: 'OK' Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){ }).then((result) => {if (result.value){
window.location = 'index.php?page=data-kartu'; window.location = 'index.php?page=data-kartu';
} }

33
admin/kartu/anggota.php Executable file → Normal file
View File

@@ -97,7 +97,7 @@
<tr> <tr>
<th>NIK</th> <th>NIK</th>
<th>Nama</th> <th>Nama</th>
<th>Jenis Kelamin</th> <th>Jekel</th>
<th>Hub Keluarga</th> <th>Hub Keluarga</th>
<th>Aksi</th> <th>Aksi</th>
</tr> </tr>
@@ -119,21 +119,13 @@
<?php echo $data['nama']; ?> <?php echo $data['nama']; ?>
</td> </td>
<td> <td>
<?php <?php echo $data['jekel']; ?>
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
</td> </td>
<td> <td>
<?php echo $data['hubungan']; ?> <?php echo $data['hubungan']; ?>
</td> </td>
<td> <td>
<a href="?page=del-anggota&kode=<?php echo $data['id_anggota']; ?>" onclick="confirmDelete(event)" <a href="?page=del-anggota&kode=<?php echo $data['id_anggota']; ?>" onclick="return confirm('Apakah anda yakin hapus data ini ?')"
title="Hapus" class="btn btn-danger btn-sm"> title="Hapus" class="btn btn-danger btn-sm">
<i class="fa fa-trash"></i> <i class="fa fa-trash"></i>
</a> </a>
@@ -148,25 +140,6 @@
</table> </table>
</div> </div>
</div> </div>
<script>
function confirmDelete(event) {
event.preventDefault();
Swal.fire({
title: 'Konfirmasi Hapus',
text: 'Apakah Anda yakin ingin menghapus data ini?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, Hapus',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = event.currentTarget.href;
}
});
}
</script>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<a href="?page=data-kartu" title="Kembali" class="btn btn-warning">Kembali</a> <a href="?page=data-kartu" title="Kembali" class="btn btn-warning">Kembali</a>

12
admin/kartu/anggota_full.php Executable file → Normal file
View File

@@ -59,7 +59,7 @@
<th>No</th> <th>No</th>
<th>NIK</th> <th>NIK</th>
<th>Nama</th> <th>Nama</th>
<th>Jenis Kelamin</th> <th>Jekel</th>
<th>Hub Keluarga</th> <th>Hub Keluarga</th>
<th>Status</th> <th>Status</th>
</tr> </tr>
@@ -85,15 +85,7 @@
<?php echo $data['nama']; ?> <?php echo $data['nama']; ?>
</td> </td>
<td> <td>
<?php <?php echo $data['jekel']; ?>
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
</td> </td>
<td> <td>
<?php echo $data['hubungan']; ?> <?php echo $data['hubungan']; ?>

0
admin/kartu/data_kartu.php Executable file → Normal file
View File

0
admin/kartu/del_anggota.php Executable file → Normal file
View File

0
admin/kartu/del_kartu.php Executable file → Normal file
View File

6
admin/kartu/edit_kartu.php Executable file → Normal file
View File

@@ -52,7 +52,7 @@
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">Kepala Keluarga</label> <label class="col-sm-2 col-form-label">Kpl Keluarga</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="text" class="form-control" id="kepala" name="kepala" value="<?php echo $data_cek['kepala']; ?>" <input type="text" class="form-control" id="kepala" name="kepala" value="<?php echo $data_cek['kepala']; ?>"
required> required>
@@ -208,8 +208,8 @@ window.addEventListener('load', function() {
if(d.no_kk) document.getElementsByName('no_kk')[0].value = d.no_kk; if(d.no_kk) document.getElementsByName('no_kk')[0].value = d.no_kk;
if(d.kepala_keluarga) document.getElementsByName('kepala')[0].value = d.kepala_keluarga; if(d.kepala_keluarga) document.getElementsByName('kepala')[0].value = d.kepala_keluarga;
if(d.desa) document.getElementsByName('desa')[0].value = d.desa; if(d.desa) document.getElementsByName('desa')[0].value = d.desa;
if(d.rt) document.getElementsByName('rt')[0].value = d.rt; document.getElementsByName('rt')[0].value = d.rt || '000';
if(d.rw) document.getElementsByName('rw')[0].value = d.rw; document.getElementsByName('rw')[0].value = d.rw || '000';
if(d.kecamatan) document.getElementsByName('kec')[0].value = d.kecamatan; if(d.kecamatan) document.getElementsByName('kec')[0].value = d.kecamatan;
if(d.kabupaten) document.getElementsByName('kab')[0].value = d.kabupaten; if(d.kabupaten) document.getElementsByName('kab')[0].value = d.kabupaten;
if(d.provinsi) document.getElementsByName('prov')[0].value = d.provinsi; if(d.provinsi) document.getElementsByName('prov')[0].value = d.provinsi;

27
admin/lahir/add_lahir.php Executable file → Normal file
View File

@@ -28,7 +28,7 @@
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">Jenis Kelamin</label> <label class="col-sm-2 col-form-label">Jenis Kelain</label>
<div class="col-sm-3"> <div class="col-sm-3">
<select name="jekel" id="jekel" class="form-control"> <select name="jekel" id="jekel" class="form-control">
<option>- Pilih -</option> <option>- Pilih -</option>
@@ -73,23 +73,16 @@
if (isset ($_POST['Simpan'])){ if (isset ($_POST['Simpan'])){
//mulai proses simpan data //mulai proses simpan data
// Sanitize Input to prevent SQL Injection & Syntax Errors
$nik = mysqli_real_escape_string($koneksi, trim($_POST['nik']));
$nama = mysqli_real_escape_string($koneksi, trim($_POST['nama']));
$tgl_lh = mysqli_real_escape_string($koneksi, trim($_POST['tgl_lh']));
$jekel = mysqli_real_escape_string($koneksi, trim($_POST['jekel']));
$id_kk = (int)$_POST['id_kk']; // Cast to integer for safety
$sql_simpan = "INSERT INTO tb_lahir (nama, tgl_lh, jekel, id_kk) VALUES ( $sql_simpan = "INSERT INTO tb_lahir (nama, tgl_lh, jekel, id_kk) VALUES (
'$nama', '".$_POST['nama']."',
'$tgl_lh', '".$_POST['tgl_lh']."',
'$jekel', '".$_POST['jekel']."',
'$id_kk')"; '".$_POST['id_kk']."')";
$query_simpan = mysqli_query($koneksi, $sql_simpan); $query_simpan = mysqli_query($koneksi, $sql_simpan);
// Otomatis tambah ke Data Penduduk // Otomatis tambah ke Data Penduduk
// Ambil data alamat dari KK // Ambil data alamat dari KK
$id_kk = $_POST['id_kk'];
$sql_kk = "SELECT desa, rt, rw FROM tb_kk WHERE id_kk='$id_kk'"; $sql_kk = "SELECT desa, rt, rw FROM tb_kk WHERE id_kk='$id_kk'";
$q_kk = mysqli_query($koneksi, $sql_kk); $q_kk = mysqli_query($koneksi, $sql_kk);
$d_kk = mysqli_fetch_array($q_kk); $d_kk = mysqli_fetch_array($q_kk);
@@ -98,11 +91,11 @@
$rw = $d_kk['rw']; $rw = $d_kk['rw'];
$sql_pdd = "INSERT INTO tb_pdd (nik, nama, tempat_lh, tgl_lh, jekel, desa, rt, rw, agama, kawin, pekerjaan, status) VALUES ( $sql_pdd = "INSERT INTO tb_pdd (nik, nama, tempat_lh, tgl_lh, jekel, desa, rt, rw, agama, kawin, pekerjaan, status) VALUES (
'$nik', '".$_POST['nik']."',
'$nama', '".$_POST['nama']."',
'-', '-',
'$tgl_lh', '".$_POST['tgl_lh']."',
'$jekel', '".$_POST['jekel']."',
'$desa', '$rt', '$rw', '$desa', '$rt', '$rw',
'-', 'Belum', 'Belum/Tidak Bekerja', 'Ada')"; '-', 'Belum', 'Belum/Tidak Bekerja', 'Ada')";
$query_pdd = mysqli_query($koneksi, $sql_pdd); $query_pdd = mysqli_query($koneksi, $sql_pdd);

37
admin/lahir/data_lahir.php Executable file → Normal file
View File

@@ -17,7 +17,7 @@
<th>No</th> <th>No</th>
<th>Nama</th> <th>Nama</th>
<th>Tgl Lahir</th> <th>Tgl Lahir</th>
<th>Jenis Kelamin</th> <th>Jekel</th>
<th>Keluarga</th> <th>Keluarga</th>
<th>Aksi</th> <th>Aksi</th>
</tr> </tr>
@@ -42,15 +42,7 @@
<?php echo $data['tgl_lh']; ?> <?php echo $data['tgl_lh']; ?>
</td> </td>
<td> <td>
<?php <?php echo $data['jekel']; ?>
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
</td> </td>
<td> <td>
<?php echo $data['no_kk']; ?>- <?php echo $data['no_kk']; ?>-
@@ -61,10 +53,10 @@
class="btn btn-success btn-sm"> class="btn btn-success btn-sm">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
</a> </a>
<a href="?page=del-lahir&kode=<?php echo $data['id_lahir']; ?>" onclick="confirmDelete(event)" <a href="?page=del-lahir&kode=<?php echo $data['id_lahir']; ?>" onclick="return confirm('Apakah anda yakin hapus data ini ?')"
title="Hapus" class="btn btn-danger btn-sm"> title="Hapus" class="btn btn-danger btn-sm">
<i class="fa fa-trash"></i> <i class="fa fa-trash"></i>
</a> </>
</td> </td>
</tr> </tr>
@@ -73,26 +65,7 @@
?> ?>
</tbody> </tbody>
</tfoot> </tfoot>
</table> </table>
</div> </div>
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->
<script>
function confirmDelete(event) {
event.preventDefault();
Swal.fire({
title: 'Konfirmasi Hapus',
text: 'Apakah Anda yakin ingin menghapus data ini?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, Hapus',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = event.currentTarget.href;
}
});
}
</script>

0
admin/lahir/del_lahir.php Executable file → Normal file
View File

0
admin/lahir/edit_lahir.php Executable file → Normal file
View File

0
admin/laporan/laporan_klasifikasi.php Executable file → Normal file
View File

14
admin/mendu/add_mendu.php Executable file → Normal file
View File

@@ -56,21 +56,15 @@
if (isset ($_POST['Simpan'])){ if (isset ($_POST['Simpan'])){
//mulai proses simpan data //mulai proses simpan data
// Sanitize Input to prevent SQL Injection & Syntax Errors
$id_pdd = (int)$_POST['id_pdd']; // Cast to integer for safety
$tgl_mendu = mysqli_real_escape_string($koneksi, trim($_POST['tgl_mendu']));
$sebab = mysqli_real_escape_string($koneksi, trim($_POST['sebab']));
$sql_simpan = "INSERT INTO tb_mendu (id_pdd, tgl_mendu, sebab) VALUES ( $sql_simpan = "INSERT INTO tb_mendu (id_pdd, tgl_mendu, sebab) VALUES (
'$id_pdd', '".$_POST['id_pdd']."',
'$tgl_mendu', '".$_POST['tgl_mendu']."',
'$sebab')"; '".$_POST['sebab']."')";
$query_simpan = mysqli_query($koneksi, $sql_simpan); $query_simpan = mysqli_query($koneksi, $sql_simpan);
$sql_ubah = "UPDATE tb_pdd SET $sql_ubah = "UPDATE tb_pdd SET
status='Meninggal' status='Meninggal'
WHERE id_pend='$id_pdd'"; WHERE id_pend='".$_POST['id_pdd']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah); $query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi); mysqli_close($koneksi);

31
admin/mendu/data_mendu.php Executable file → Normal file
View File

@@ -17,7 +17,6 @@
<th>No</th> <th>No</th>
<th>NIK</th> <th>NIK</th>
<th>Nama</th> <th>Nama</th>
<th>Jenis Kelamin</th>
<th>Tanggal</th> <th>Tanggal</th>
<th>Sebab</th> <th>Sebab</th>
<th>Aksi</th> <th>Aksi</th>
@@ -27,7 +26,7 @@
<?php <?php
$no = 1; $no = 1;
$sql = $koneksi->query("SELECT p.id_pend, p.nik, p.nama, p.jekel, m.tgl_mendu, m.sebab, m.id_mendu from $sql = $koneksi->query("SELECT p.id_pend, p.nik, p.nama, m.tgl_mendu, m.sebab, m.id_mendu from
tb_mendu m inner join tb_pdd p on p.id_pend=m.id_pdd"); tb_mendu m inner join tb_pdd p on p.id_pend=m.id_pdd");
while ($data= $sql->fetch_assoc()) { while ($data= $sql->fetch_assoc()) {
?> ?>
@@ -42,9 +41,6 @@
<td> <td>
<?php echo $data['nama']; ?> <?php echo $data['nama']; ?>
</td> </td>
<td>
<?php echo $data['jekel'] == 'LK' ? 'LAKI-LAKI' : 'PEREMPUAN'; ?>
</td>
<td> <td>
<?php echo $data['tgl_mendu']; ?> <?php echo $data['tgl_mendu']; ?>
</td> </td>
@@ -60,10 +56,10 @@
class="btn btn-success btn-sm"> class="btn btn-success btn-sm">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
</a> </a>
<a href="?page=del-mendu&kode=<?php echo $data['id_pend']; ?>" onclick="confirmDelete(event)" <a href="?page=del-mendu&kode=<?php echo $data['id_pend']; ?>" onclick="return confirm('Apakah anda yakin hapus data ini ?')"
title="Hapus" class="btn btn-danger btn-sm"> title="Hapus" class="btn btn-danger btn-sm">
<i class="fa fa-trash"></i> <i class="fa fa-trash"></i>
</a> </>
</td> </td>
</tr> </tr>
@@ -72,26 +68,7 @@
?> ?>
</tbody> </tbody>
</tfoot> </tfoot>
</table> </table>
</div> </div>
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->
<script>
function confirmDelete(event) {
event.preventDefault();
Swal.fire({
title: 'Konfirmasi Hapus',
text: 'Apakah Anda yakin ingin menghapus data ini?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, Hapus',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = event.currentTarget.href;
}
});
}
</script>

0
admin/mendu/del_mendu.php Executable file → Normal file
View File

0
admin/mendu/edit_mendu.php Executable file → Normal file
View File

10
admin/mendu/view_mendu.php Executable file → Normal file
View File

@@ -90,15 +90,7 @@
<b>Jenis Kelamin</b> <b>Jenis Kelamin</b>
</td> </td>
<td>: <td>:
<?php <?php echo $data_cek['jekel']; ?>
if ($data_cek['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data_cek['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data_cek['jekel'];
}
?>
</td> </td>
</tr> </tr>
<tr> <tr>

260
admin/pend/add_pend.php Executable file → Normal file
View File

@@ -1,6 +1,3 @@
<?php
$return_to = isset($_GET['return_to']) ? mysqli_real_escape_string($koneksi, trim($_GET['return_to'])) : '';
?>
<div class="card card-primary"> <div class="card card-primary">
<div class="card-header"> <div class="card-header">
<h3 class="card-title"> <h3 class="card-title">
@@ -91,10 +88,10 @@ $return_to = isset($_GET['return_to']) ? mysqli_real_escape_string($koneksi, tri
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label">RT/RW</label> <label class="col-sm-2 col-form-label">RT/RW</label>
<div class="col-sm-3"> <div class="col-sm-3">
<input type="text" class="form-control" id="rt" name="rt" placeholder="RT" value="000" required> <input type="text" class="form-control" id="rt" name="rt" placeholder="RT" required>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
<input type="text" class="form-control" id="rw" name="rw" placeholder="RW" value="000" required> <input type="text" class="form-control" id="rw" name="rw" placeholder="RW" required>
</div> </div>
</div> </div>
@@ -132,12 +129,6 @@ $return_to = isset($_GET['return_to']) ? mysqli_real_escape_string($koneksi, tri
</div> </div>
</div> </div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">No. Telepon</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="phone" name="phone" placeholder="Nomor Telepon">
</div>
</div>
</div> </div>
@@ -150,57 +141,6 @@ $return_to = isset($_GET['return_to']) ? mysqli_real_escape_string($koneksi, tri
<script> <script>
window.addEventListener('load', function() { window.addEventListener('load', function() {
// Pre-fill form from URL parameters
var urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('nik')) document.getElementsByName('nik')[0].value = urlParams.get('nik');
if (urlParams.has('nama')) document.getElementsByName('nama')[0].value = urlParams.get('nama');
if (urlParams.has('desa')) document.getElementsByName('desa')[0].value = urlParams.get('desa');
if (urlParams.has('rt')) document.getElementsByName('rt')[0].value = urlParams.get('rt');
if (urlParams.has('rw')) document.getElementsByName('rw')[0].value = urlParams.get('rw');
if (urlParams.has('kecamatan')) document.getElementsByName('kecamatan')[0].value = urlParams.get('kecamatan');
if (urlParams.has('kabupaten')) document.getElementsByName('kabupaten')[0].value = urlParams.get('kabupaten');
if (urlParams.has('provinsi')) document.getElementsByName('provinsi')[0].value = urlParams.get('provinsi');
// NIK Validation (16 digits, numeric)
var nikInput = document.getElementsByName('nik')[0];
function validateNIK(nik) {
nik = nik.trim();
if (nik.length !== 16) return false;
return /^\d+$/.test(nik); // Only digits
}
function showNIKError(message) {
Swal.fire({
icon: 'warning',
title: 'Format NIK Salah',
text: message,
confirmButtonText: 'OK'
});
}
// Validate on blur
if (nikInput) {
nikInput.addEventListener('blur', function() {
var nik = this.value.trim();
if (nik === '') return;
if (!validateNIK(nik)) {
showNIKError('NIK harus terdiri dari 16 digit angka.');
this.focus();
}
});
}
// Validate on form submit
var form = document.querySelector('form');
if (form) {
form.addEventListener('submit', function(e) {
var nik = nikInput.value.trim();
if (nik !== '' && !validateNIK(nik)) {
e.preventDefault();
showNIKError('NIK harus terdiri dari 16 digit angka.');
nikInput.focus();
return false;
}
});
}
// Scanner Logic // Scanner Logic
var inputImage = document.getElementById('foto_ktp'); var inputImage = document.getElementById('foto_ktp');
var preview = document.getElementById('preview_ktp'); var preview = document.getElementById('preview_ktp');
@@ -391,39 +331,71 @@ window.addEventListener('load', function() {
} }
//mulai proses simpan data //mulai proses simpan data
$nik = mysqli_real_escape_string($koneksi, trim($_POST['nik'])); $nik = $_POST['nik'];
$cek_nik = mysqli_query($koneksi, "SELECT * FROM tb_pdd WHERE nik='$nik'"); $cek_nik = mysqli_query($koneksi, "SELECT * FROM tb_pdd WHERE nik='$nik'");
if(mysqli_num_rows($cek_nik) > 0){ if(mysqli_num_rows($cek_nik) > 0){
echo "<script> // AUTO-LINKING: Instead of blocking, we UPDATE the existing record with Scan Data
Swal.fire({title: 'Gagal',text: 'NIK sudah terdaftar dalam sistem!',icon: 'error',confirmButtonText: 'OK' // Sanitize input first before UPDATE
}).then((result) => {if (result.value){ $nama = mysqli_real_escape_string($koneksi, $_POST['nama']);
window.location = 'index.php?page=add-pend" . (!empty($return_to) ? '&return_to=' . $return_to : '') . "'; $tempat_lh = mysqli_real_escape_string($koneksi, $_POST['tempat_lh']);
} $tgl_lh = mysqli_real_escape_string($koneksi, $_POST['tgl_lh']);
})</script>"; $jekel = mysqli_real_escape_string($koneksi, $_POST['jekel']);
return; $desa = mysqli_real_escape_string($koneksi, $_POST['desa']);
$rt = mysqli_real_escape_string($koneksi, $_POST['rt']);
$rw = mysqli_real_escape_string($koneksi, $_POST['rw']);
$agama = mysqli_real_escape_string($koneksi, $_POST['agama']);
$kawin = mysqli_real_escape_string($koneksi, $_POST['kawin']);
$pekerjaan = mysqli_real_escape_string($koneksi, $_POST['pekerjaan']);
$kewarganegaraan = mysqli_real_escape_string($koneksi, $_POST['kewarganegaraan']);
$sql_update = "UPDATE tb_pdd SET
nama='$nama',
tempat_lh='$tempat_lh',
tgl_lh='$tgl_lh',
jekel='$jekel',
desa='$desa',
rt='$rt',
rw='$rw',
agama='$agama',
kawin='$kawin',
pekerjaan='$pekerjaan',
kewarganegaraan='$kewarganegaraan',
foto_ktp='$nama_file'
WHERE nik='$nik'";
$query_update = mysqli_query($koneksi, $sql_update);
if ($query_update) {
echo "<script>
Swal.fire({title: 'Data Diperbarui',text: 'NIK sudah ada, data penduduk telah diperbarui dengan hasil scan KTP.',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-pend';
}
})</script>";
return;
}
} }
// Sanitize Input to prevent SQL Injection & Syntax Errors // Sanitize Input to prevent SQL Injection & Syntax Errors
$nama = mysqli_real_escape_string($koneksi, trim($_POST['nama'])); $nik = mysqli_real_escape_string($koneksi, $_POST['nik']);
$tempat_lh = mysqli_real_escape_string($koneksi, trim($_POST['tempat_lh'])); $nama = mysqli_real_escape_string($koneksi, $_POST['nama']);
$tgl_lh = mysqli_real_escape_string($koneksi, trim($_POST['tgl_lh'])); $tempat_lh = mysqli_real_escape_string($koneksi, $_POST['tempat_lh']);
$jekel = mysqli_real_escape_string($koneksi, trim($_POST['jekel'])); $tgl_lh = mysqli_real_escape_string($koneksi, $_POST['tgl_lh']);
$desa = mysqli_real_escape_string($koneksi, trim($_POST['desa'])); $jekel = mysqli_real_escape_string($koneksi, $_POST['jekel']);
$rt = mysqli_real_escape_string($koneksi, trim($_POST['rt'])); $desa = mysqli_real_escape_string($koneksi, $_POST['desa']);
$rw = mysqli_real_escape_string($koneksi, trim($_POST['rw'])); $rt = mysqli_real_escape_string($koneksi, $_POST['rt']);
$agama = mysqli_real_escape_string($koneksi, trim($_POST['agama'])); $rw = mysqli_real_escape_string($koneksi, $_POST['rw']);
$kawin = mysqli_real_escape_string($koneksi, trim($_POST['kawin'])); $agama = mysqli_real_escape_string($koneksi, $_POST['agama']);
$pekerjaan = mysqli_real_escape_string($koneksi, trim($_POST['pekerjaan'])); $kawin = mysqli_real_escape_string($koneksi, $_POST['kawin']);
$kecamatan = mysqli_real_escape_string($koneksi, trim($_POST['kecamatan'])); $pekerjaan = mysqli_real_escape_string($koneksi, $_POST['pekerjaan']);
$kabupaten = mysqli_real_escape_string($koneksi, trim($_POST['kabupaten'])); $kecamatan = mysqli_real_escape_string($koneksi, $_POST['kecamatan']);
$provinsi = mysqli_real_escape_string($koneksi, trim($_POST['provinsi'])); $kabupaten = mysqli_real_escape_string($koneksi, $_POST['kabupaten']);
$kewarganegaraan = mysqli_real_escape_string($koneksi, trim($_POST['kewarganegaraan'])); $provinsi = mysqli_real_escape_string($koneksi, $_POST['provinsi']);
$phone = mysqli_real_escape_string($koneksi, trim($_POST['phone'])); $kewarganegaraan = mysqli_real_escape_string($koneksi, $_POST['kewarganegaraan']);
$sql_simpan = "INSERT INTO tb_pdd (nik, nama, tempat_lh, tgl_lh, jekel, desa, rt, rw, agama, kawin, pekerjaan, foto_ktp, status, kecamatan, kabupaten, provinsi, kewarganegaraan) VALUES (
$sql_simpan = "INSERT INTO tb_pdd (nik, nama, tempat_lh, tgl_lh, jekel, desa, rt, rw, agama, kawin, pekerjaan, foto_ktp, status, kecamatan, kabupaten, provinsi, kewarganegaraan, phone) VALUES ( '$nik',
'$nik', '$nama',
'$nama',
'$tempat_lh', '$tempat_lh',
'$tgl_lh', '$tgl_lh',
'$jekel', '$jekel',
@@ -438,99 +410,23 @@ window.addEventListener('load', function() {
'$kecamatan', '$kecamatan',
'$kabupaten', '$kabupaten',
'$provinsi', '$provinsi',
'$kewarganegaraan', '$phone')"; '$kewarganegaraan')";
$query_simpan = mysqli_query($koneksi, $sql_simpan); $query_simpan = mysqli_query($koneksi, $sql_simpan);
if ($query_simpan) {
$id_pend_baru = mysqli_insert_id($koneksi);
$redirect_url = !empty($return_to) ? "index.php?page=" . $return_to . "&selected_id=" . $id_pend_baru : "index.php?page=data-pend";
// KTP → KK: Cari KK yang cocok berdasarkan alamat
$sql_cari_kk = "SELECT k.id_kk, k.no_kk, k.kepala, k.desa, k.rt, k.rw
FROM tb_kk k
WHERE k.desa='$desa' AND k.rt='$rt' AND k.rw='$rw'
AND k.kec='$kecamatan' AND k.kab='$kabupaten' AND k.prov='$provinsi'";
$q_cari_kk = mysqli_query($koneksi, $sql_cari_kk);
$kk_cocok = mysqli_fetch_assoc($q_cari_kk);
if ($kk_cocok) {
// Tawarkan untuk menghubungkan dengan KK
$no_kk = $kk_cocok['no_kk'];
$kepala_kk = $kk_cocok['kepala'];
$id_kk = $kk_cocok['id_kk'];
// Cek apakah sudah terhubung
$sql_cek_hubungan = "SELECT * FROM tb_anggota WHERE id_kk='$id_kk' AND id_pend='$id_pend_baru'";
$q_cek_hubungan = mysqli_query($koneksi, $sql_cek_hubungan);
if (mysqli_num_rows($q_cek_hubungan) == 0) {
// Simpan sementara data untuk konfirmasi JavaScript
$_SESSION['kk_link_data'] = [
'id_pend' => $id_pend_baru,
'id_kk' => $id_kk,
'no_kk' => $no_kk,
'kepala_kk' => $kepala_kk,
'nama_pend' => $nama
];
echo "<script>
Swal.fire({
title: 'Data Penduduk Disimpan!',
html: '<b>Data berhasil disimpan.</b><br><br>Ditemukan KK dengan alamat yang sama:<br><b>No KK: $no_kk</b><br>Kepala Keluarga: $kepala_kk<br><br>Hubungkan ke KK ini sebagai anggota?',
icon: 'success',
showCancelButton: true,
confirmButtonText: 'Ya, Hubungkan',
cancelButtonText: 'Tidak, Simpan Saja'
}).then((result) => {
if (result.value) {
// Kirim permintaan untuk menghubungkan
fetch('admin/pend/link_to_kk.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'id_pend=$id_pend_baru&id_kk=$id_kk&hubungan=ANGGOTA'
}).then(resp => resp.json()).then(data => {
if(data.success) {
Swal.fire('Berhasil!', 'Data telah dihubungkan dengan KK.', 'success').then(() => {
window.location = '$redirect_url';
});
} else {
Swal.fire('Gagal', 'Gagal menghubungkan: ' + data.message, 'error').then(() => {
window.location = '$redirect_url';
});
}
});
} else {
window.location = '$redirect_url';
}
});
</script>";
} else {
// Sudah terhubung
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: 'Data telah terhubung dengan KK $no_kk',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = '$redirect_url';
}
})</script>";
}
} else {
// Tidak ada KK yang cocok
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = '$redirect_url';
}
})</script>";
}
} else {
$error_redirect = 'index.php?page=add-pend' . (!empty($return_to) ? '&return_to=' . $return_to : '');
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '" . mysqli_error($koneksi) . "',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = '$error_redirect';
}
})</script>";
}
mysqli_close($koneksi); mysqli_close($koneksi);
}
if ($query_simpan) {
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-pend';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '<?php echo mysqli_error($koneksi); ?>',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-pend';
}
})</script>";
}}
//selesai proses simpan data //selesai proses simpan data

12
admin/pend/data_pend.php Executable file → Normal file
View File

@@ -17,7 +17,7 @@
<th>No</th> <th>No</th>
<th>NIK</th> <th>NIK</th>
<th>Nama</th> <th>Nama</th>
<th>Jenis Kelamin</th> <th>JK</th>
<th>Alamat</th> <th>Alamat</th>
<th>No KK</th> <th>No KK</th>
<th>Aksi</th> <th>Aksi</th>
@@ -59,15 +59,7 @@
<?php echo $data['nama']; ?> <?php echo $data['nama']; ?>
</td> </td>
<td> <td>
<?php <?php echo $data['jekel']; ?>
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
</td> </td>
<td> <td>
<?php echo $data['desa']; ?> <?php echo $data['desa']; ?>

0
admin/pend/del_pend.php Executable file → Normal file
View File

12
admin/pend/edit_pend.php Executable file → Normal file
View File

@@ -180,13 +180,6 @@
</div> </div>
</div> </div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">No. Telepon</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="phone" name="phone" value="<?php echo isset($data_cek['phone']) ? $data_cek['phone'] : ''; ?>" placeholder="Nomor Telepon">
</div>
</div>
</div> </div>
<div class="card-footer"> <div class="card-footer">
@@ -389,7 +382,6 @@
$kabupaten = mysqli_real_escape_string($koneksi, $_POST['kabupaten']); $kabupaten = mysqli_real_escape_string($koneksi, $_POST['kabupaten']);
$provinsi = mysqli_real_escape_string($koneksi, $_POST['provinsi']); $provinsi = mysqli_real_escape_string($koneksi, $_POST['provinsi']);
$kewarganegaraan = mysqli_real_escape_string($koneksi, $_POST['kewarganegaraan']); $kewarganegaraan = mysqli_real_escape_string($koneksi, $_POST['kewarganegaraan']);
$phone = mysqli_real_escape_string($koneksi, $_POST['phone']);
if($has_new_photo){ if($has_new_photo){
$foto= $data_cek['foto_ktp']; $foto= $data_cek['foto_ktp'];
@@ -412,7 +404,6 @@
kabupaten='$kabupaten', kabupaten='$kabupaten',
provinsi='$provinsi', provinsi='$provinsi',
kewarganegaraan='$kewarganegaraan', kewarganegaraan='$kewarganegaraan',
phone='$phone',
foto_ktp='$nama_file' foto_ktp='$nama_file'
WHERE id_pend='$id_pend'"; WHERE id_pend='$id_pend'";
$query_ubah = mysqli_query($koneksi, $sql_ubah); $query_ubah = mysqli_query($koneksi, $sql_ubah);
@@ -433,8 +424,7 @@
kecamatan='$kecamatan', kecamatan='$kecamatan',
kabupaten='$kabupaten', kabupaten='$kabupaten',
provinsi='$provinsi', provinsi='$provinsi',
kewarganegaraan='$kewarganegaraan', kewarganegaraan='$kewarganegaraan'
phone='$phone'
WHERE id_pend='$id_pend'"; WHERE id_pend='$id_pend'";
$query_ubah = mysqli_query($koneksi, $sql_ubah); $query_ubah = mysqli_query($koneksi, $sql_ubah);
} }

View File

@@ -1,62 +0,0 @@
<?php
session_start();
include "../inc/koneksi.php";
header('Content-Type: application/json');
if (!isset($_SESSION["ses_username"])) {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'message' => 'Invalid request method']);
exit;
}
$id_pend = isset($_POST['id_pend']) ? intval($_POST['id_pend']) : 0;
$id_kk = isset($_POST['id_kk']) ? intval($_POST['id_kk']) : 0;
$hubungan = isset($_POST['hubungan']) ? mysqli_real_escape_string($koneksi, $_POST['hubungan']) : 'ANGGOTA';
if ($id_pend <= 0 || $id_kk <= 0) {
echo json_encode(['success' => false, 'message' => 'Invalid ID']);
exit;
}
// Cek apakah sudah terhubung
$sql_cek = "SELECT * FROM tb_anggota WHERE id_kk='$id_kk' AND id_pend='$id_pend'";
$q_cek = mysqli_query($koneksi, $sql_cek);
if (mysqli_num_rows($q_cek) > 0) {
echo json_encode(['success' => false, 'message' => 'Sudah terhubung']);
exit;
}
// Cek apakah penduduk ada
$sql_cek_pend = "SELECT * FROM tb_pdd WHERE id_pend='$id_pend'";
$q_cek_pend = mysqli_query($koneksi, $sql_cek_pend);
if (mysqli_num_rows($q_cek_pend) == 0) {
echo json_encode(['success' => false, 'message' => 'Data penduduk tidak ditemukan']);
exit;
}
// Cek apakah KK ada
$sql_cek_kk = "SELECT * FROM tb_kk WHERE id_kk='$id_kk'";
$q_cek_kk = mysqli_query($koneksi, $sql_cek_kk);
if (mysqli_num_rows($q_cek_kk) == 0) {
echo json_encode(['success' => false, 'message' => 'Data KK tidak ditemukan']);
exit;
}
// Hubungkan
$sql_link = "INSERT INTO tb_anggota (id_kk, id_pend, hubungan) VALUES ('$id_kk', '$id_pend', '$hubungan')";
$q_link = mysqli_query($koneksi, $sql_link);
if ($q_link) {
echo json_encode(['success' => true, 'message' => 'Berhasil dihubungkan']);
} else {
echo json_encode(['success' => false, 'message' => 'Database error: ' . mysqli_error($koneksi)]);
}
mysqli_close($koneksi);
?>

8
admin/pend/view_pend.php Executable file → Normal file
View File

@@ -135,14 +135,6 @@
<?php echo $data_cek['kewarganegaraan']; ?> <?php echo $data_cek['kewarganegaraan']; ?>
</td> </td>
</tr> </tr>
<tr>
<td style="width: 150px">
<b>No. Telepon</b>
</td>
<td>:
<?php echo isset($data_cek['phone']) ? $data_cek['phone'] : ''; ?>
</td>
</tr>
</tbody> </tbody>

28
admin/pengguna/add_pengguna.php Executable file → Normal file
View File

@@ -31,9 +31,9 @@
<label class="col-sm-2 col-form-label">Level</label> <label class="col-sm-2 col-form-label">Level</label>
<div class="col-sm-4"> <div class="col-sm-4">
<select name="level" id="level" class="form-control"> <select name="level" id="level" class="form-control">
<option value="">- Pilih -</option> <option>- Pilih -</option>
<option value="Administrator">Administrator</option> <option>Administrator</option>
<option value="Kaur Pemerintah">Kaur Pemerintah</option> <option>Kaur Pemerintah</option>
</select> </select>
</div> </div>
</div> </div>
@@ -49,26 +49,12 @@
<?php <?php
if (isset ($_POST['Simpan'])){ if (isset ($_POST['Simpan'])){
// Map level untuk database (enum: 'admin', 'kaur')
$level_map = [
'Administrator' => 'admin',
'Kaur Pemerintah' => 'kaur'
];
$level_db = isset($level_map[$_POST['level']]) ? $level_map[$_POST['level']] : $_POST['level'];
// Sanitize Input to prevent SQL Injection & Syntax Errors
$nama_pengguna = mysqli_real_escape_string($koneksi, trim($_POST['nama_pengguna']));
$username = mysqli_real_escape_string($koneksi, trim($_POST['username']));
$password_raw = trim($_POST['password']);
$password_hash = MD5($password_raw);
$level_db = mysqli_real_escape_string($koneksi, $level_db);
//mulai proses simpan data //mulai proses simpan data
$sql_simpan = "INSERT INTO tb_pengguna (nama_pengguna,username,password,level) VALUES ( $sql_simpan = "INSERT INTO tb_pengguna (nama_pengguna,username,password,level) VALUES (
'$nama_pengguna', '".$_POST['nama_pengguna']."',
'$username', '".$_POST['username']."',
'$password_hash', '".$_POST['password']."',
'$level_db')"; '".$_POST['level']."')";
$query_simpan = mysqli_query($koneksi, $sql_simpan); $query_simpan = mysqli_query($koneksi, $sql_simpan);
mysqli_close($koneksi); mysqli_close($koneksi);

32
admin/pengguna/data_pengguna.php Executable file → Normal file
View File

@@ -25,13 +25,8 @@
<?php <?php
$no = 1; $no = 1;
$level_map = [
'admin' => 'Administrator',
'kaur' => 'Kaur Pemerintah'
];
$sql = $koneksi->query("select * from tb_pengguna"); $sql = $koneksi->query("select * from tb_pengguna");
while ($data= $sql->fetch_assoc()) { while ($data= $sql->fetch_assoc()) {
$level_display = isset($level_map[$data['level']]) ? $level_map[$data['level']] : $data['level'];
?> ?>
<tr> <tr>
@@ -45,17 +40,17 @@
<?php echo $data['username']; ?> <?php echo $data['username']; ?>
</td> </td>
<td> <td>
<?php echo $level_display; ?> <?php echo $data['level']; ?>
</td> </td>
<td> <td>
<a href="?page=edit-pengguna&kode=<?php echo $data['id_pengguna']; ?>" title="Ubah" <a href="?page=edit-pengguna&kode=<?php echo $data['id_pengguna']; ?>" title="Ubah"
class="btn btn-success btn-sm"> class="btn btn-success btn-sm">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
</a> </a>
<a href="?page=del-pengguna&kode=<?php echo $data['id_pengguna']; ?>" onclick="confirmDelete(event)" <a href="?page=del-pengguna&kode=<?php echo $data['id_pengguna']; ?>" onclick="return confirm('Apakah anda yakin hapus data ini ?')"
title="Hapus" class="btn btn-danger btn-sm"> title="Hapus" class="btn btn-danger btn-sm">
<i class="fa fa-trash"></i> <i class="fa fa-trash"></i>
</a> </>
</td> </td>
</tr> </tr>
@@ -64,26 +59,7 @@
?> ?>
</tbody> </tbody>
</tfoot> </tfoot>
</table> </table>
</div> </div>
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->
<script>
function confirmDelete(event) {
event.preventDefault();
Swal.fire({
title: 'Konfirmasi Hapus',
text: 'Apakah Anda yakin ingin menghapus data ini?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, Hapus',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = event.currentTarget.href;
}
});
}
</script>

0
admin/pengguna/del_pengguna.php Executable file → Normal file
View File

31
admin/pengguna/edit_pengguna.php Executable file → Normal file
View File

@@ -49,24 +49,12 @@
<select name="level" id="level" class="form-control"> <select name="level" id="level" class="form-control">
<option value="">-- Pilih Level --</option> <option value="">-- Pilih Level --</option>
<?php <?php
// Mapping level dari database ke tampilan //menhecek data yg dipilih sebelumnya
$level_display_map = [ if ($data_cek['level'] == "Administrator") echo "<option value='Administrator' selected>Administrator</option>";
'admin' => 'Administrator', else echo "<option value='Administrator'>Administrator</option>";
'kaur' => 'Kaur Pemerintah',
'Administrator' => 'Administrator', // backward compatibility
'Kaur Pemerintah' => 'Kaur Pemerintah'
];
$current_level = $data_cek['level'];
$current_display = isset($level_display_map[$current_level]) ? $level_display_map[$current_level] : $current_level;
// Opsi Administrator if ($data_cek['level'] == "Kaur Pemerintah") echo "<option value='Kaur Pemerintah' selected>Kaur Pemerintah</option>";
if ($current_display == "Administrator") { else echo "<option value='Kaur Pemerintah'>Kaur Pemerintah</option>";
echo "<option value='Administrator' selected>Administrator</option>";
echo "<option value='Kaur Pemerintah'>Kaur Pemerintah</option>";
} else {
echo "<option value='Administrator'>Administrator</option>";
echo "<option value='Kaur Pemerintah' selected>Kaur Pemerintah</option>";
}
?> ?>
</select> </select>
</div> </div>
@@ -85,18 +73,11 @@
<?php <?php
if (isset ($_POST['Ubah'])){ if (isset ($_POST['Ubah'])){
// Map level untuk database (enum: 'admin', 'kaur')
$level_map = [
'Administrator' => 'admin',
'Kaur Pemerintah' => 'kaur'
];
$level_db = isset($level_map[$_POST['level']]) ? $level_map[$_POST['level']] : $_POST['level'];
$sql_ubah = "UPDATE tb_pengguna SET $sql_ubah = "UPDATE tb_pengguna SET
nama_pengguna='".$_POST['nama_pengguna']."', nama_pengguna='".$_POST['nama_pengguna']."',
username='".$_POST['username']."', username='".$_POST['username']."',
password='".$_POST['password']."', password='".$_POST['password']."',
level='".$level_db."' level='".$_POST['level']."'
WHERE id_pengguna='".$_POST['id_pengguna']."'"; WHERE id_pengguna='".$_POST['id_pengguna']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah); $query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi); mysqli_close($koneksi);

14
admin/pindah/add_pindah.php Executable file → Normal file
View File

@@ -56,21 +56,15 @@
if (isset ($_POST['Simpan'])){ if (isset ($_POST['Simpan'])){
//mulai proses simpan data //mulai proses simpan data
// Sanitize Input to prevent SQL Injection & Syntax Errors
$id_pdd = (int)$_POST['id_pdd']; // Cast to integer for safety
$tgl_pindah = mysqli_real_escape_string($koneksi, trim($_POST['tgl_pindah']));
$alasan = mysqli_real_escape_string($koneksi, trim($_POST['alasan']));
$sql_simpan = "INSERT INTO tb_pindah (id_pdd, tgl_pindah, alasan) VALUES ( $sql_simpan = "INSERT INTO tb_pindah (id_pdd, tgl_pindah, alasan) VALUES (
'$id_pdd', '".$_POST['id_pdd']."',
'$tgl_pindah', '".$_POST['tgl_pindah']."',
'$alasan')"; '".$_POST['alasan']."')";
$query_simpan = mysqli_query($koneksi, $sql_simpan); $query_simpan = mysqli_query($koneksi, $sql_simpan);
$sql_ubah = "UPDATE tb_pdd SET $sql_ubah = "UPDATE tb_pdd SET
status='Pindah' status='Pindah'
WHERE id_pend='$id_pdd'"; WHERE id_pend='".$_POST['id_pdd']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah); $query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi); mysqli_close($koneksi);

31
admin/pindah/data_pindah.php Executable file → Normal file
View File

@@ -17,7 +17,6 @@
<th>No</th> <th>No</th>
<th>NIK</th> <th>NIK</th>
<th>Nama</th> <th>Nama</th>
<th>Jenis Kelamin</th>
<th>Tanggal</th> <th>Tanggal</th>
<th>Alasan</th> <th>Alasan</th>
<th>Aksi</th> <th>Aksi</th>
@@ -27,7 +26,7 @@
<?php <?php
$no = 1; $no = 1;
$sql = $koneksi->query("SELECT p.id_pend, p.nik, p.nama, p.jekel, d.tgl_pindah, d.alasan, d.id_pindah from $sql = $koneksi->query("SELECT p.id_pend, p.nik, p.nama, d.tgl_pindah, d.alasan, d.id_pindah from
tb_pindah d inner join tb_pdd p on p.id_pend=d.id_pdd"); tb_pindah d inner join tb_pdd p on p.id_pend=d.id_pdd");
while ($data= $sql->fetch_assoc()) { while ($data= $sql->fetch_assoc()) {
?> ?>
@@ -42,9 +41,6 @@
<td> <td>
<?php echo $data['nama']; ?> <?php echo $data['nama']; ?>
</td> </td>
<td>
<?php echo $data['jekel'] == 'LK' ? 'LAKI-LAKI' : 'PEREMPUAN'; ?>
</td>
<td> <td>
<?php echo $data['tgl_pindah']; ?> <?php echo $data['tgl_pindah']; ?>
</td> </td>
@@ -60,10 +56,10 @@
class="btn btn-success btn-sm"> class="btn btn-success btn-sm">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
</a> </a>
<a href="?page=del-pindah&kode=<?php echo $data['id_pend']; ?>" onclick="confirmDelete(event)" <a href="?page=del-pindah&kode=<?php echo $data['id_pend']; ?>" onclick="return confirm('Apakah anda yakin hapus data ini ?')"
title="Hapus" class="btn btn-danger btn-sm"> title="Hapus" class="btn btn-danger btn-sm">
<i class="fa fa-trash"></i> <i class="fa fa-trash"></i>
</a> </>
</td> </td>
</tr> </tr>
@@ -72,26 +68,7 @@
?> ?>
</tbody> </tbody>
</tfoot> </tfoot>
</table> </table>
</div> </div>
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->
<script>
function confirmDelete(event) {
event.preventDefault();
Swal.fire({
title: 'Konfirmasi Hapus',
text: 'Apakah Anda yakin ingin menghapus data ini?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, Hapus',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = event.currentTarget.href;
}
});
}
</script>

0
admin/pindah/del_pindah.php Executable file → Normal file
View File

0
admin/pindah/edit_pindah.php Executable file → Normal file
View File

10
admin/pindah/view_pindah.php Executable file → Normal file
View File

@@ -90,15 +90,7 @@
<b>Jenis Kelamin</b> <b>Jenis Kelamin</b>
</td> </td>
<td>: <td>:
<?php <?php echo $data_cek['jekel']; ?>
if ($data_cek['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data_cek['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data_cek['jekel'];
}
?>
</td> </td>
</tr> </tr>
<tr> <tr>

0
admin/profil/data_profil.php Executable file → Normal file
View File

858
admin/scanner_modal.php Executable file → Normal file
View File

@@ -3,33 +3,29 @@
<div class="modal fade" id="modalScanner" tabindex="-1" role="dialog" aria-labelledby="modalScannerLabel" aria-hidden="true" data-backdrop="static"> <div class="modal fade" id="modalScanner" tabindex="-1" role="dialog" aria-labelledby="modalScannerLabel" aria-hidden="true" data-backdrop="static">
<div class="modal-dialog modal-xl" role="document"> <div class="modal-dialog modal-xl" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalScannerLabel"><i class="fas fa-expand"></i> Smart Scanner</h5> <h5 class="modal-title" id="modalScannerLabel"><i class="fas fa-expand"></i> Smart Scanner</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body text-center bg-dark p-0" style="position: relative; overflow: hidden; height: 80vh;"> <div class="modal-body text-center bg-dark p-0" style="position: relative; overflow: hidden; height: 80vh;">
<div id="scanner-container" style="position: relative; margin: auto; display: inline-block;"> <!-- Container for Canvases -->
<canvas id="canvas-image" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas> <!-- Container for Canvases -->
<canvas id="canvas-overlay" style="position: absolute; left: 0; top: 0; z-index: 2; cursor: crosshair;"></canvas> <div id="scanner-container" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); box-shadow: 0 0 10px rgba(0,0,0,0.5);">
</div> <canvas id="canvas-image" style="position: absolute; left: 0; top: 0; z-index: 10;"></canvas>
<canvas id="canvas-overlay" style="position: absolute; left: 0; top: 0; z-index: 20; cursor: crosshair;"></canvas>
<div id="scanner-loading" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: white; display: none;"> </div>
<i class="fas fa-spinner fa-spin fa-3x"></i><br>Detecting Document... <div id="scanner-loading" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: white; display: none; z-index: 50; text-align: center;">
</div> <i class="fas fa-spinner fa-spin fa-3x"></i><br><span style="margin-top: 10px; display: block;">Sedang menganalisa...</span>
</div>
<!-- Mobile Help Tips -->
<div id="mobile-help" class="d-none d-md-none d-lg-none" style="position: absolute; bottom: 10px; left: 0; right: 0; text-align: center; color: white; background: rgba(0,0,0,0.7); padding: 5px; font-size: 12px;">
<span>📍 Sentuh & geser titik biru untuk atur sudut dokumen</span>
</div>
</div> </div>
<div class="modal-footer justify-content-between"> <div class="modal-footer justify-content-between">
<div> <div>
<button type="button" class="btn btn-secondary" id="btnScanRotateLeft" title="Putar Kiri (-90°)"><i class="fas fa-undo"></i></button> <button type="button" class="btn btn-secondary" id="btnScanRotateLeft" title="Putar Kiri (-90°)"><i class="fas fa-undo"></i></button>
<button type="button" class="btn btn-secondary" id="btnScanRotateRight" title="Putar Kanan (+90°)"><i class="fas fa-redo"></i></button> <button type="button" class="btn btn-secondary" id="btnScanRotateRight" title="Putar Kanan (+90°)"><i class="fas fa-redo"></i></button>
<button type="button" class="btn btn-warning" id="btnScanReset"><i class="fas fa-sync"></i> Reset Sudut</button> <button type="button" class="btn btn-warning" id="btnScanReset"><i class="fas fa-sync"></i> Reset Sudut</button>
</div> </div>
<div> <div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
<button type="button" class="btn btn-primary" id="btnScanSave"><i class="fas fa-check"></i> Simpan Hasil Scan</button> <button type="button" class="btn btn-primary" id="btnScanSave"><i class="fas fa-check"></i> Simpan Hasil Scan</button>
@@ -39,341 +35,31 @@
</div> </div>
</div> </div>
<style>
/* Disable all transitions and hover effects for scanner modal */
#modalScanner .card,
#modalScanner .modal-content,
#modalScanner .modal-body,
#modalScanner .modal-header,
#modalScanner .modal-footer,
#modalScanner .modal-dialog,
#modalScanner #scanner-container,
#modalScanner #scanner-container * {
transition: none !important;
}
#modalScanner .card:hover,
#modalScanner .modal-content:hover,
#modalScanner .modal-body:hover,
#modalScanner .modal-header:hover,
#modalScanner .modal-footer:hover,
#modalScanner .modal-dialog:hover,
#modalScanner #scanner-container:hover,
#modalScanner #scanner-container *:hover {
transform: none !important;
}
/* Ensure canvas is fully visible */
#canvas-image {
opacity: 1 !important;
filter: none !important;
background-color: white !important;
}
#canvas-overlay {
opacity: 1 !important;
filter: none !important;
}
/* Prevent canvas from moving on hover */
#canvas-image:hover,
#canvas-overlay:hover {
transform: none !important;
}
/* Prevent modal dragging and improve touch handling on mobile */
#modalScanner .modal-dialog,
#modalScanner .modal-content,
#modalScanner .modal-header,
#modalScanner .modal-footer {
touch-action: none !important; /* Prevent browser touch gestures (pan, zoom, swipe) */
user-select: none !important; /* Prevent text selection during drag */
-webkit-user-select: none !important;
-webkit-touch-callout: none !important;
}
/* Allow touch interaction only on canvas and buttons */
#modalScanner .modal-body,
#canvas-image,
#canvas-overlay {
touch-action: manipulation !important; /* Allow pinch-zoom and pan on canvas only */
}
/* Prevent modal backdrop from responding to touch */
.modal-backdrop {
touch-action: none !important;
}
/* Lock modal position on mobile */
@media (max-width: 768px) {
#modalScanner {
padding-right: 0 !important; /* Prevent shift from scrollbar */
}
#modalScanner .modal-dialog {
margin: 0 !important;
max-height: 100vh !important;
height: 100vh !important;
width: 100vw !important;
max-width: 100vw !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
transform: none !important;
transition: none !important;
}
#modalScanner .modal-content {
border-radius: 0 !important;
height: 100vh !important;
max-height: 100vh !important;
width: 100vw !important;
max-width: 100vw !important;
overflow: hidden !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
transform: none !important;
transition: none !important;
}
#modalScanner .modal-body {
height: calc(100vh - 120px) !important; /* Account for header and footer */
overflow: hidden !important;
}
/* Prevent any modal movement */
.modal-open #modalScanner {
overflow: hidden !important;
}
/* Larger touch target for close button on mobile */
#modalScanner .modal-header .close {
padding: 20px !important;
font-size: 2rem !important;
line-height: 1 !important;
margin: -10px -10px -10px auto !important;
}
/* Larger touch targets for all buttons */
#modalScanner .modal-footer .btn {
min-height: 44px !important;
min-width: 44px !important;
padding: 10px 15px !important;
font-size: 16px !important; /* Prevent zoom on iOS */
}
/* Larger corner points for touch */
#canvas-overlay {
touch-action: manipulation;
}
/* Help text */
#mobile-help {
font-size: 14px !important;
padding: 10px !important;
}
}
/* Larger hit area for corner points for touch devices */
#canvas-overlay {
touch-action: pinch-zoom;
}
</style>
<script> <script>
window.addEventListener('load', function() { window.addEventListener('load', function() {
// Function to load scanner libraries dynamically
function loadScannerLibs() {
return new Promise(function(resolve, reject) {
// Check if both libraries are already loaded AND OpenCV runtime is ready
if (typeof cv !== 'undefined' && cv.Mat && typeof jscanify !== 'undefined') {
resolve();
return;
}
// Load OpenCV first (jscanify depends on it)
function loadOpenCV() {
return new Promise(function(resolveOpenCV, rejectOpenCV) {
if (typeof cv !== 'undefined') {
// OpenCV already loaded, check if runtime ready
if (cv.Mat) {
resolveOpenCV();
} else if (cv.onRuntimeInitialized) {
// Wait for runtime initialization
var existingCallback = cv.onRuntimeInitialized;
cv.onRuntimeInitialized = function() {
if (typeof existingCallback === 'function') {
existingCallback();
}
resolveOpenCV();
};
} else {
// OpenCV loaded but no runtime callback mechanism
// Poll for cv.Mat
var checkInterval = setInterval(function() {
if (cv.Mat) {
clearInterval(checkInterval);
resolveOpenCV();
}
}, 100);
// Timeout after 10 seconds
setTimeout(function() {
clearInterval(checkInterval);
rejectOpenCV(new Error('OpenCV runtime initialization timeout'));
}, 10000);
}
} else {
// Load OpenCV script
var opencvScript = document.createElement('script');
opencvScript.src = '../../plugins/vendor/opencv/opencv.js';
opencvScript.onload = function() {
console.log('OpenCV.js loaded dynamically');
// Wait for runtime initialization
if (cv.onRuntimeInitialized) {
var existingCallback = cv.onRuntimeInitialized;
cv.onRuntimeInitialized = function() {
if (typeof existingCallback === 'function') {
existingCallback();
}
resolveOpenCV();
};
} else {
// Fallback: wait a bit then resolve
setTimeout(resolveOpenCV, 5000);
}
};
opencvScript.onerror = function() {
console.error('Failed to load OpenCV.js');
rejectOpenCV(new Error('Failed to load OpenCV.js'));
};
document.head.appendChild(opencvScript);
}
});
}
// Load jscanify after OpenCV is ready
function loadJscanify() {
return new Promise(function(resolveJscanify, rejectJscanify) {
if (typeof jscanify !== 'undefined') {
resolveJscanify();
return;
}
var jscanifyScript = document.createElement('script');
jscanifyScript.src = '../../plugins/vendor/jscanify/jscanify.min.js';
jscanifyScript.onload = function() {
console.log('jscanify loaded dynamically');
resolveJscanify();
};
jscanifyScript.onerror = function() {
console.error('Failed to load jscanify');
rejectJscanify(new Error('Failed to load jscanify'));
};
document.head.appendChild(jscanifyScript);
});
}
// Load all libraries in sequence
loadOpenCV()
.then(function() {
console.log('OpenCV ready, loading jscanify...');
return loadJscanify();
})
.then(function() {
console.log('All scanner libraries loaded');
resolve();
})
.catch(function(error) {
console.error('Failed to load scanner libraries:', error);
reject(error);
});
});
}
// Load libraries and then initialize scanner
loadScannerLibs().then(function() {
// Give OpenCV a moment to initialize runtime
setTimeout(function() {
// Scanner Variables // Scanner Variables
var scannerModal = $('#modalScanner'); var scannerModal = $('#modalScanner');
var canvasImage = document.getElementById('canvas-image'); var canvasImage = document.getElementById('canvas-image');
var canvasOverlay = document.getElementById('canvas-overlay'); var canvasOverlay = document.getElementById('canvas-overlay');
var ctxImg = canvasImage.getContext('2d'); var ctxImg = canvasImage.getContext('2d');
var ctxOver = canvasOverlay.getContext('2d'); var ctxOver = canvasOverlay.getContext('2d');
const IS_TOUCH_DEVICE = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
var scanner = null; var scanner = new jscanify();
var originalImg = new Image();
function initScanner() { // Corner Points (tl, tr, bl, br)
// Check if OpenCV is loaded var corners = [];
if (typeof cv === 'undefined') { var activePoint = null;
console.warn('OpenCV not loaded yet'); var isDragging = false;
return false;
}
// Check if OpenCV runtime is already initialized // Config
if (cv.Mat) { const POINT_RADIUS = 15;
try { const POINT_COLOR = '#007bff';
scanner = new jscanify(); const LINE_COLOR = '#00ff00';
console.log('Scanner initialized successfully (runtime ready)'); const LINE_WIDTH = 3;
return true;
} catch (e) {
console.error('Failed to initialize jscanify:', e);
return false;
}
}
// Runtime not initialized yet, set up callback // --- Public Function to Open Scanner ---
if (cv.onRuntimeInitialized) { window.openScanner = function(file) {
// Save any existing callback
var existingCallback = cv.onRuntimeInitialized;
cv.onRuntimeInitialized = function() {
// Call existing callback first if it's a function
if (typeof existingCallback === 'function') {
existingCallback();
}
// Now initialize scanner
try {
scanner = new jscanify();
console.log('Scanner initialized after runtime ready (callback)');
} catch (e) {
console.error('Failed to initialize jscanify after runtime:', e);
}
};
console.log('Waiting for OpenCV runtime initialization...');
return false;
}
// Should not reach here
console.warn('OpenCV loaded but no runtime initialization mechanism');
return false;
}
// Try to initialize scanner immediately
initScanner();
var originalImg = new Image();
// Mode is always smart scan now
var currentMode = 'smart';
// Corner Points (tl, tr, bl, br)
var corners = [];
var activePoint = null;
var isDragging = false;
var touchStartPos = null;
var isTouchInteraction = false;
var touchOffset = null;
// Config - Extra large circles for easy mobile touch, thin lines
const POINT_RADIUS = 50; // Extra large circle radius for easy mobile touch
const POINT_COLOR = 'rgba(0, 123, 255, 0.15)'; // Transparent blue (15% opacity)
const POINT_OUTLINE_COLOR = '#007bff'; // Blue outline
const POINT_OUTLINE_WIDTH = 2; // Thin outline
const LINE_COLOR = 'rgba(0, 255, 0, 0.8)'; // Semi-transparent green lines
const LINE_WIDTH = 1.5; // Very thin lines
const TOUCH_RADIUS_MULTIPLIER = 3.5; // Extra large hit area for touch devices
const TOUCH_SLOP = 5; // pixels threshold before dragging starts
// Mode is always smart scan (no toggle needed)
// --- Public Function to Open Scanner ---
window.openScanner = function(file) {
if (!file) return; if (!file) return;
// Reset State // Reset State
@@ -390,7 +76,10 @@ window.addEventListener('load', function() {
function checkReady() { function checkReady() {
if (imgLoaded && modalShown) { if (imgLoaded && modalShown) {
setupScannerCanvas(); // Defer execution to allow UI (spinner) to render first
setTimeout(function() {
initScanner();
}, 100);
} }
} }
@@ -405,151 +94,77 @@ window.addEventListener('load', function() {
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
// 2. Show Modal & Listen // 2. Show Modal & Listen
scannerModal.off('shown.bs.modal'); // Remove old listeners scannerModal.off('shown.bs.modal'); // Remove old listeners
scannerModal.on('shown.bs.modal', function() { scannerModal.on('shown.bs.modal', function() {
modalShown = true; modalShown = true;
checkReady(); checkReady();
});
// Prevent body scrolling on mobile
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
// Prevent modal dragging on mobile
var modalDialog = document.querySelector('#modalScanner .modal-dialog');
var modalContent = document.querySelector('#modalScanner .modal-content');
var modalHeader = document.querySelector('#modalScanner .modal-header');
var modalFooter = document.querySelector('#modalScanner .modal-footer');
var preventTouch = function(e) {
// Allow touch on canvas elements and buttons
var target = e.target;
var isCanvas = target.id === 'canvas-overlay' || target.id === 'canvas-image' ||
target.closest('#scanner-container');
var isButton = target.tagName === 'BUTTON' || target.closest('button');
if (!isCanvas && !isButton) {
e.preventDefault();
e.stopPropagation();
}
};
if (modalDialog) {
modalDialog.addEventListener('touchstart', preventTouch, { passive: false });
modalDialog.addEventListener('touchmove', preventTouch, { passive: false });
}
if (modalContent) {
modalContent.addEventListener('touchstart', preventTouch, { passive: false });
modalContent.addEventListener('touchmove', preventTouch, { passive: false });
}
if (modalHeader) {
modalHeader.addEventListener('touchstart', preventTouch, { passive: false });
modalHeader.addEventListener('touchmove', preventTouch, { passive: false });
}
if (modalFooter) {
modalFooter.addEventListener('touchstart', preventTouch, { passive: false });
modalFooter.addEventListener('touchmove', preventTouch, { passive: false });
}
});
// Restore scrolling when modal is hidden
scannerModal.on('hidden.bs.modal', function() {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
});
scannerModal.modal('show'); scannerModal.modal('show');
}; };
function detectDocument(scale, w, h, showAlert = false) { function initScanner() {
// Detect Contour using jscanify // Resize Canvas to fit screen but keep aspect ratio
try { var maxWidth = $('#modalScanner .modal-body').width() - 20;
// jscanify expects an image element, we can pass originalImg but we need to map coordinates var maxHeight = $('#modalScanner .modal-body').height() - 20;
// Wait, jscanify uses OpenCV which might not be ready.
if (typeof cv !== 'undefined' && cv.Mat) {
// We need to work on the original image for detection, then scale points
var contour = scanner ? scanner.findPaper(originalImg) : null;
// contour returns { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner } each {x, y}
if (contour) { var scale = Math.min(maxWidth / originalImg.width, maxHeight / originalImg.height);
// Order: TL, TR, BR, BL (clockwise) var w = originalImg.width * scale;
corners = [ var h = originalImg.height * scale;
{ x: contour.topLeftCorner.x * scale, y: contour.topLeftCorner.y * scale },
{ x: contour.topRightCorner.x * scale, y: contour.topRightCorner.y * scale },
{ x: contour.bottomRightCorner.x * scale, y: contour.bottomRightCorner.y * scale },
{ x: contour.bottomLeftCorner.x * scale, y: contour.bottomLeftCorner.y * scale }
];
} else { canvasImage.width = w;
defaultCorners(w, h); canvasImage.height = h;
if (showAlert) { canvasOverlay.width = w;
Swal.fire({ canvasOverlay.height = h;
icon: 'warning',
title: 'Deteksi Gagal',
text: 'Dokumen tidak terdeteksi. Silakan atur sudut secara manual.',
confirmButtonText: 'OK'
});
}
}
} else {
console.warn("OpenCV not ready yet");
defaultCorners(w, h);
if (showAlert) {
Swal.fire({
icon: 'warning',
title: 'Scanner Tidak Siap',
text: 'OpenCV belum siap. Silakan atur sudut secara manual.',
confirmButtonText: 'OK'
});
}
}
} catch(e) {
console.error("Scanner Error:", e);
defaultCorners(w, h); // Fallback
if (showAlert) {
Swal.fire({
icon: 'error',
title: 'Error Scanner',
text: 'Terjadi kesalahan saat mendeteksi dokumen: ' + e.message,
confirmButtonText: 'OK'
});
}
}
drawOverlay();
}
function setupScannerCanvas() { // Resize container
// Resize Canvas to fit screen but keep aspect ratio $('#scanner-container').css({ width: w, height: h });
var maxWidth = $('#modalScanner .modal-body').width() - 20;
var maxHeight = $('#modalScanner .modal-body').height() - 20;
var scale = Math.min(maxWidth / originalImg.width, maxHeight / originalImg.height); // Draw Image
var w = originalImg.width * scale; ctxImg.drawImage(originalImg, 0, 0, w, h);
var h = originalImg.height * scale;
canvasImage.width = w; // Detect Contour using jscanify
canvasImage.height = h; try {
canvasOverlay.width = w; // jscanify expects an image element, we can pass originalImg but we need to map coordinates
canvasOverlay.height = h; // Wait, jscanify uses OpenCV which might not be ready.
if (typeof cv !== 'undefined' && cv.Mat) {
// We need to work on the original image for detection, then scale points
var contour = scanner.findPaper(originalImg);
// contour returns { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner } each {x, y}
// Resize container if (contour) {
$('#scanner-container').css({ width: w, height: h, marginTop: '10px' }); corners = [
{ x: contour.topLeftCorner.x * scale, y: contour.topLeftCorner.y * scale },
{ x: contour.topRightCorner.x * scale, y: contour.topRightCorner.y * scale },
{ x: contour.bottomRightCorner.x * scale, y: contour.bottomRightCorner.y * scale }, // Order: tr -> br -> bl ?? No, usually tl, tr, br, bl order for polygon drawing
{ x: contour.bottomLeftCorner.x * scale, y: contour.bottomLeftCorner.y * scale }
];
// Reorder primarily for logic: TL, TR, BR, BL
corners = [
{ x: contour.topLeftCorner.x * scale, y: contour.topLeftCorner.y * scale },
{ x: contour.topRightCorner.x * scale, y: contour.topRightCorner.y * scale },
{ x: contour.bottomRightCorner.x * scale, y: contour.bottomRightCorner.y * scale },
{ x: contour.bottomLeftCorner.x * scale, y: contour.bottomLeftCorner.y * scale }
];
// Draw Image } else {
ctxImg.drawImage(originalImg, 0, 0, w, h); defaultCorners(w, h);
}
} else {
console.warn("OpenCV not ready yet");
defaultCorners(w, h);
}
} catch(e) {
console.error("Scanner Error:", e);
defaultCorners(w, h); // Fallback
}
detectDocument(scale, w, h, false); $('#scanner-loading').hide();
$('#scanner-loading').hide(); drawOverlay();
}
// No cropper needed - smart scan only function defaultCorners(w, h) {
}
function defaultCorners(w, h) {
// Default 20% margin // Default 20% margin
var mX = w * 0.1; var mX = w * 0.1;
var mY = h * 0.1; var mY = h * 0.1;
@@ -566,7 +181,7 @@ window.addEventListener('load', function() {
if (corners.length < 4) return; if (corners.length < 4) return;
// Draw thin cropping lines // Draw Lines
ctxOver.beginPath(); ctxOver.beginPath();
ctxOver.lineWidth = LINE_WIDTH; ctxOver.lineWidth = LINE_WIDTH;
ctxOver.strokeStyle = LINE_COLOR; ctxOver.strokeStyle = LINE_COLOR;
@@ -577,19 +192,14 @@ window.addEventListener('load', function() {
ctxOver.closePath(); ctxOver.closePath();
ctxOver.stroke(); ctxOver.stroke();
// Draw transparent circles with thin outline // Draw Points
ctxOver.fillStyle = POINT_COLOR;
corners.forEach(p => { corners.forEach(p => {
// Draw transparent inner circle
ctxOver.beginPath(); ctxOver.beginPath();
ctxOver.arc(p.x, p.y, POINT_RADIUS, 0, Math.PI * 2); ctxOver.arc(p.x, p.y, POINT_RADIUS, 0, Math.PI * 2);
ctxOver.fillStyle = POINT_COLOR;
ctxOver.fill(); ctxOver.fill();
ctxOver.strokeStyle = 'white';
// Draw thin blue outline ctxOver.lineWidth = 2;
ctxOver.beginPath();
ctxOver.arc(p.x, p.y, POINT_RADIUS, 0, Math.PI * 2);
ctxOver.lineWidth = POINT_OUTLINE_WIDTH;
ctxOver.strokeStyle = POINT_OUTLINE_COLOR;
ctxOver.stroke(); ctxOver.stroke();
}); });
} }
@@ -604,192 +214,106 @@ window.addEventListener('load', function() {
}; };
} }
function isInside(pos, point) { function isInside(pos, point) {
var dx = pos.x - point.x; var dx = pos.x - point.x;
var dy = pos.y - point.y; var dy = pos.y - point.y;
return dx * dx + dy * dy <= POINT_RADIUS * POINT_RADIUS * 2; // Bigger hit area return dx * dx + dy * dy <= POINT_RADIUS * POINT_RADIUS * 2; // Bigger hit area
} }
function getClosestCorner(pos, isTouch) { canvasOverlay.addEventListener('mousedown', function(e) { handleStart(getMousePos(e)); });
var closestIdx = -1; canvasOverlay.addEventListener('touchstart', function(e) { handleStart(getMousePos(e)); e.preventDefault(); }, {passive: false});
var closestDist = Infinity;
var radius = POINT_RADIUS * (isTouch ? TOUCH_RADIUS_MULTIPLIER : 1.5); // Larger radius for touch
var radiusSq = radius * radius;
corners.forEach((p, i) => { window.addEventListener('mousemove', function(e) { if(isDragging) handleMove(getMousePos(e)); }); // Window to catch drag out
var dx = pos.x - p.x; canvasOverlay.addEventListener('touchmove', function(e) { if(isDragging) handleMove(getMousePos(e)); e.preventDefault(); }, {passive: false});
var dy = pos.y - p.y;
var distSq = dx * dx + dy * dy;
if (distSq < radiusSq && distSq < closestDist) {
closestDist = distSq;
closestIdx = i;
}
});
return closestIdx;
}
canvasOverlay.addEventListener('mousedown', function(e) {
handleStart(getMousePos(e), false);
e.stopPropagation();
});
canvasOverlay.addEventListener('touchstart', function(e) {
var pos = getMousePos(e);
handleStart(pos, true);
e.preventDefault();
e.stopPropagation();
}, {passive: false});
window.addEventListener('mousemove', function(e) { if(isDragging) handleMove(getMousePos(e)); }); // Window to catch drag out
canvasOverlay.addEventListener('touchmove', function(e) {
if(isDragging) {
handleMove(getMousePos(e));
e.preventDefault();
e.stopPropagation();
}
}, {passive: false});
window.addEventListener('mouseup', function() { handleEnd(); }); window.addEventListener('mouseup', function() { handleEnd(); });
window.addEventListener('touchend', function() { handleEnd(); }); window.addEventListener('touchend', function() { handleEnd(); });
function handleStart(pos, isTouch = false) { function handleStart(pos) {
activePoint = getClosestCorner(pos, isTouch); activePoint = null;
if (activePoint !== -1) { corners.forEach((p, i) => {
isTouchInteraction = isTouch; if (isInside(pos, p)) {
touchOffset = { x: pos.x - corners[activePoint].x, y: pos.y - corners[activePoint].y }; activePoint = i;
if (isTouch) { isDragging = true;
touchStartPos = pos; }
// Start dragging immediately but handle slop in handleMove });
isDragging = true; }
} else {
isDragging = true;
touchStartPos = null;
}
} else {
activePoint = null;
isDragging = false;
isTouchInteraction = false;
touchStartPos = null;
touchOffset = null;
}
}
function handleMove(pos) { function handleMove(pos) {
if (activePoint !== null) { if (activePoint !== null) {
// Touch slop detection // Constrain to canvas?? Optional but good
if (isTouchInteraction && touchStartPos) { corners[activePoint].x = pos.x;
var dx = pos.x - touchStartPos.x; corners[activePoint].y = pos.y;
var dy = pos.y - touchStartPos.y; drawOverlay();
var distSq = dx * dx + dy * dy; }
if (distSq < TOUCH_SLOP * TOUCH_SLOP) { }
return; // Ignore small movements until slop exceeded
}
// Slop exceeded, clear touchStartPos so we don't check again
touchStartPos = null;
}
// Apply offset to maintain relative position function handleEnd() {
if (touchOffset) { isDragging = false;
corners[activePoint].x = pos.x - touchOffset.x; activePoint = null;
corners[activePoint].y = pos.y - touchOffset.y; }
} else {
corners[activePoint].x = pos.x;
corners[activePoint].y = pos.y;
}
// Optional: constrain to canvas bounds // --- Rotate Functions ---
var w = canvasOverlay.width; function rotateImage(degree) {
var h = canvasOverlay.height; var offCanvas = document.createElement('canvas');
corners[activePoint].x = Math.max(0, Math.min(w, corners[activePoint].x)); var offCtx = offCanvas.getContext('2d');
corners[activePoint].y = Math.max(0, Math.min(h, corners[activePoint].y));
drawOverlay(); // Swap Width/Height for 90 degree rotation
} offCanvas.width = originalImg.height;
} offCanvas.height = originalImg.width;
function handleEnd() { offCtx.translate(offCanvas.width / 2, offCanvas.height / 2);
isDragging = false; offCtx.rotate(degree * Math.PI / 180);
activePoint = null; offCtx.drawImage(originalImg, -originalImg.width / 2, -originalImg.height / 2);
isTouchInteraction = false;
touchStartPos = null;
touchOffset = null;
}
// --- Rotate Functions --- // Update originalImg
function rotateImage(degree) { var rotatedUrl = offCanvas.toDataURL();
var offCanvas = document.createElement('canvas'); originalImg.onload = function() {
var offCtx = offCanvas.getContext('2d'); initScanner(); // Re-init with new image
}
originalImg.src = rotatedUrl;
}
// Swap Width/Height for 90 degree rotation $('#btnScanRotateLeft').click(function() { rotateImage(-90); });
offCanvas.width = originalImg.height; $('#btnScanRotateRight').click(function() { rotateImage(90); });
offCanvas.height = originalImg.width;
offCtx.translate(offCanvas.width / 2, offCanvas.height / 2); // --- Reset Button ---
offCtx.rotate(degree * Math.PI / 180); $('#btnScanReset').click(function() {
offCtx.drawImage(originalImg, -originalImg.width / 2, -originalImg.height / 2); initScanner();
});
// Update originalImg // --- Save / Extract Button ---
var rotatedUrl = offCanvas.toDataURL(); $('#btnScanSave').click(function() {
originalImg.onload = function() { // Warp Image
// Re-init scanner try {
setupScannerCanvas(); // 1. Get raw points relative to Original Image
} var scaleX = originalImg.width / canvasImage.width;
originalImg.src = rotatedUrl; var scaleY = originalImg.height / canvasImage.height;
}
$('#btnScanRotateLeft').click(function() { rotateImage(-90); }); var tl = { x: corners[0].x * scaleX, y: corners[0].y * scaleY };
$('#btnScanRotateRight').click(function() { rotateImage(90); }); var tr = { x: corners[1].x * scaleX, y: corners[1].y * scaleY };
var br = { x: corners[2].x * scaleX, y: corners[2].y * scaleY };
var bl = { x: corners[3].x * scaleX, y: corners[3].y * scaleY };
// --- Reset Button --- // 2. Calculate dimensions of the crop area
$('#btnScanReset').click(function() { var widthTop = Math.hypot(tr.x - tl.x, tr.y - tl.y);
setupScannerCanvas(); var widthBottom = Math.hypot(br.x - bl.x, br.y - bl.y);
}); var outputWidth = Math.max(widthTop, widthBottom);
var heightLeft = Math.hypot(bl.x - tl.x, bl.y - tl.y);
var heightRight = Math.hypot(br.x - tr.x, br.y - tr.y);
var outputHeight = Math.max(heightLeft, heightRight);
var extractPoints = {
topLeftCorner: tl,
topRightCorner: tr,
bottomRightCorner: br,
bottomLeftCorner: bl
};
// --- Save / Extract Button --- // 3. Extract with dynamic dimensions
$('#btnScanSave').click(function() { var resultCanvas = scanner.extractPaper(originalImg, outputWidth, outputHeight, extractPoints);
try { var base64 = resultCanvas.toDataURL('image/jpeg');
var base64;
// Smart Scan Mode - Use jscanify
// 1. Get raw points relative to Original Image
var scaleX = originalImg.width / canvasImage.width;
var scaleY = originalImg.height / canvasImage.height;
var tl = { x: corners[0].x * scaleX, y: corners[0].y * scaleY };
var tr = { x: corners[1].x * scaleX, y: corners[1].y * scaleY };
var br = { x: corners[2].x * scaleX, y: corners[2].y * scaleY };
var bl = { x: corners[3].x * scaleX, y: corners[3].y * scaleY };
// 2. Calculate dimensions of the crop area
var widthTop = Math.hypot(tr.x - tl.x, tr.y - tl.y);
var widthBottom = Math.hypot(br.x - bl.x, br.y - bl.y);
var outputWidth = Math.max(widthTop, widthBottom);
var heightLeft = Math.hypot(bl.x - tl.x, bl.y - tl.y);
var heightRight = Math.hypot(br.x - tr.x, br.y - tr.y);
var outputHeight = Math.max(heightLeft, heightRight);
var extractPoints = {
topLeftCorner: tl,
topRightCorner: tr,
bottomRightCorner: br,
bottomLeftCorner: bl
};
// 3. Extract with dynamic dimensions
var resultCanvas = null;
if (!scanner || !scanner.extractPaper) {
// Try to initialize scanner if not ready
initScanner();
}
if (scanner && scanner.extractPaper) {
resultCanvas = scanner.extractPaper(originalImg, outputWidth, outputHeight, extractPoints);
} else {
alert("Scanner library not loaded. Please refresh the page.");
return;
}
base64 = resultCanvas.toDataURL('image/jpeg');
if (window.handleScannerResult) { if (window.handleScannerResult) {
window.handleScannerResult(base64); window.handleScannerResult(base64);
@@ -797,18 +321,10 @@ window.addEventListener('load', function() {
scannerModal.modal('hide'); scannerModal.modal('hide');
} catch (e) { } catch (e) {
alert("Gagal memproses gambar: " + e.message); alert("Gagal memproses gambar: " + e.message);
console.error(e); }
} });
});
// Show mobile help if touch device });
if (IS_TOUCH_DEVICE) { </script>
$('#mobile-help').removeClass('d-none');
}
// Mode is always smart scan now
}, 500); }); });
</script>

View File

0
unused/build/js/.jscsrc → build/js/.jscsrc Executable file → Normal file
View File

0
unused/build/js/AdminLTE.js → build/js/AdminLTE.js Executable file → Normal file
View File

View File

View File

View File

View File

0
unused/build/js/Dropdown.js → build/js/Dropdown.js Executable file → Normal file
View File

0
unused/build/js/Layout.js → build/js/Layout.js Executable file → Normal file
View File

0
unused/build/js/PushMenu.js → build/js/PushMenu.js Executable file → Normal file
View File

View File

0
unused/build/js/Toasts.js → build/js/Toasts.js Executable file → Normal file
View File

0
unused/build/js/TodoList.js → build/js/TodoList.js Executable file → Normal file
View File

0
unused/build/js/Treeview.js → build/js/Treeview.js Executable file → Normal file
View File

View File

View File

0
unused/build/npm/Plugins.js → build/npm/Plugins.js Executable file → Normal file
View File

0
unused/build/npm/Publish.js → build/npm/Publish.js Executable file → Normal file
View File

0
unused/build/scss/.csslintrc → build/scss/.csslintrc Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
unused/build/scss/_navs.scss → build/scss/_navs.scss Executable file → Normal file
View File

View File

View File

View File

View File

Some files were not shown because too many files have changed in this diff Show More