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)
node_modules/
vendor/
!plugins/vendor/
# Uploads
foto/
# API Keys and Environment Variables
admin/api/config_api.php
.env
.env.*.local
# Debug files
debug_ai.txt
debug_log.txt

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
header('Content-Type: application/json');
require_once 'config_api.php';
set_time_limit(300);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'message' => 'Invalid request method']);
@@ -20,27 +19,31 @@ $mimeType = mime_content_type($imagePath);
// Construct Prompt based on Type
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 {
// 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.";
}
// Payload for Ollama App
// Payload for Gemini
$data = [
"model" => "qwen2.5vl:3b",
"messages" => [
"contents" => [
[
"role" => "user",
"content" => $promptText,
"images" => [$imageData]
"parts" => [
["text" => $promptText],
[
"inline_data" => [
"mime_type" => $mimeType,
"data" => $imageData
]
]
]
]
],
"stream" => false
]
];
// 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_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
@@ -64,13 +67,14 @@ if (isset($result['error'])) {
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]);
exit;
}
// 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 ... ```)
$cleanJson = preg_replace('/^```json\s*|\s*```$/', '', trim($rawText));
@@ -88,52 +92,5 @@ if (!$parsedData) {
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]);
?>

166
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-header">
<h3 class="card-title">
@@ -9,40 +6,37 @@ $selected_id = isset($_GET['selected_id']) ? (int)$_GET['selected_id'] : 0;
<form action="" method="post" enctype="multipart/form-data">
<div class="card-body">
<div class="form-group row">
<label class="col-sm-2 col-form-label">Pendatang</label>
<div class="col-sm-6">
<select name="id_pend" id="id_pend" class="form-control select2bs4" 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 class="col-sm-2">
<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
</a>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Tgl Datang</label>
<div class="col-sm-3">
<input type="date" class="form-control" id="tgl_datang" name="tgl_datang" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">NIK</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="nik" name="nik" placeholder="NIK" required>
</div>
</div>
<div class="form-group row">
<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 class="form-group row">
<label class="col-sm-2 col-form-label">Tgl Datang</label>
<div class="col-sm-3">
<input type="date" class="form-control" id="tgl_datang" name="tgl_datang" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Pelapor</label>
@@ -56,9 +50,9 @@ $selected_id = isset($_GET['selected_id']) ? (int)$_GET['selected_id'] : 0;
while ($row = mysqli_fetch_array($hasil)) {
?>
<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>
<?php
}
@@ -77,51 +71,51 @@ $selected_id = isset($_GET['selected_id']) ? (int)$_GET['selected_id'] : 0;
<?php
if (isset ($_POST['Simpan'])){
//mulai proses simpan data
// Sanitize Input to prevent SQL Injection & Syntax Errors
$id_pend = (int)$_POST['id_pend']; // Cast to integer for safety
$tgl_datang = mysqli_real_escape_string($koneksi, trim($_POST['tgl_datang']));
$pelapor = (int)$_POST['pelapor']; // Cast to integer for safety
// 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);
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-datang';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-datang';
}
})</script>";
}}
if (isset ($_POST['Simpan'])){
//mulai proses simpan data
$sql_simpan = "INSERT INTO tb_datang (nik, nama_datang, jekel, tgl_datang, pelapor) VALUES (
'".$_POST['nik']."',
'".$_POST['nama_datang']."',
'".$_POST['jekel']."',
'".$_POST['tgl_datang']."',
'".$_POST['pelapor']."')";
$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);
if ($query_simpan && $query_pdd) {
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-datang';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-datang';
}
})</script>";
}}
//selesai proses simpan data

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

@@ -17,7 +17,7 @@
<th>No</th>
<th>NIK</th>
<th>Nama</th>
<th>Jenis Kelamin</th>
<th>Jekel</th>
<th>Tanggal</th>
<th>Pelapor</th>
<th>Aksi</th>
@@ -43,15 +43,7 @@
<?php echo $data['nama_datang']; ?>
</td>
<td>
<?php
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
<?php echo $data['jekel']; ?>
</td>
<td>
<?php echo $data['tgl_datang']; ?>
@@ -64,10 +56,10 @@
class="btn btn-success btn-sm">
<i class="fa fa-edit"></i>
</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">
<i class="fa fa-trash"></i>
</a>
</>
</td>
</tr>
@@ -76,26 +68,7 @@
?>
</tbody>
</tfoot>
</table>
</div>
</div>
<!-- /.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>
</table>
</div>
</div>
<!-- /.card-body -->

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

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

@@ -1,14 +1,11 @@
<?php
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
FROM tb_datang d
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);
$data_cek = mysqli_fetch_array($query_cek,MYSQLI_BOTH);
}
if(isset($_GET['kode'])){
$sql_cek = "SELECT d.id_datang, d.nik, d.nama_datang, d.jekel, d.tgl_datang, p.id_pend, p.nama from
tb_datang d inner join tb_pdd p on d.pelapor=p.id_pend WHERE id_datang='".$_GET['kode']."'";
$query_cek = mysqli_query($koneksi, $sql_cek);
$data_cek = mysqli_fetch_array($query_cek,MYSQLI_BOTH);
}
?>
<div class="card card-success">
@@ -27,51 +24,43 @@
</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">
<label class="col-sm-2 col-form-label">NIK</label>
<div class="col-sm-6">
<input type="text" class="form-control" value="<?php echo htmlspecialchars($data_cek['nik'], ENT_QUOTES); ?>" readonly>
<input type="hidden" name="nik" value="<?php echo htmlspecialchars($data_cek['nik'], ENT_QUOTES); ?>">
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Nama</label>
<div class="col-sm-6">
<input type="text" class="form-control" value="<?php echo htmlspecialchars($data_cek['nama_datang'], ENT_QUOTES); ?>" readonly>
<input type="hidden" name="nama_datang" value="<?php echo htmlspecialchars($data_cek['nama_datang'], ENT_QUOTES); ?>">
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Jenis Kelamin</label>
<div class="col-sm-3">
<input type="text" class="form-control" value="<?php
$display_jekel = $data_cek['jekel'];
if ($display_jekel == 'LK') {
$display_jekel = 'LAKI-LAKI';
} elseif ($display_jekel == 'PR') {
$display_jekel = 'PEREMPUAN';
}
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">
<label class="col-sm-2 col-form-label">Tgl Lahir</label>
<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>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Tgl Datang</label>
<div class="form-group row">
<label class="col-sm-2 col-form-label">NIK</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="nik" name="nik" value="<?php echo $data_cek['nik']; ?>"
required>
</div>
</div>
<div class="form-group row">
<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" value="<?php echo $data_cek['nama_datang']; ?>"
required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Jenis Kelamin</label>
<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); ?>"
<select name="jekel" id="jekel" class="form-control">
<option value="">-- Pilih jekel --</option>
<?php
//menhecek data yg dipilih sebelumnya
if ($data_cek['jekel'] == "LK") echo "<option value='LK' selected>LK</option>";
else echo "<option value='LK'>LK</option>";
if ($data_cek['jekel'] == "PR") echo "<option value='PR' selected>PR</option>";
else echo "<option value='PR'>PR</option>";
?>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Tgl Datang</label>
<div class="col-sm-3">
<input type="date" class="form-control" id="tgl_datang" name="tgl_datang" value="<?php echo $data_cek['tgl_datang']; ?>"
required>
</div>
</div>
@@ -83,14 +72,15 @@
<option selected="">- Pilih -</option>
<?php
// ambil data dari database
$query = "select * from tb_pdd where status='Ada'";
$query = "select * from tb_pdd";
$hasil = mysqli_query($koneksi, $query);
while ($row = mysqli_fetch_array($hasil)) {
?>
<option value="<?php echo $row['id_pend'] ?>" <?=$data_cek['id_pelapor']==$row['id_pend'] ? "selected" : null ?>>
<?php echo htmlspecialchars($row['nik'], ENT_QUOTES); ?>
<option value="<?php echo $row['id_pend'] ?>" <?=$data_cek[
'id_pend']==$row[ 'id_pend'] ? "selected" : null ?>>
<?php echo $row['nik'] ?>
-
<?php echo htmlspecialchars($row['nama'], ENT_QUOTES); ?>
<?php echo $row['nama'] ?>
</option>
<?php
}
@@ -108,44 +98,31 @@
</form>
</div>
<?php
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
nik='$nik',
nama_datang='$nama_datang',
jekel='$jekel',
tgl_datang='$tgl_datang',
pelapor='$pelapor'
WHERE id_datang='$id_datang'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi);
if ($query_ubah) {
echo "<script>
Swal.fire({title: 'Ubah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value)
{window.location = 'index.php?page=data-datang';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Ubah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value)
{window.location = 'index.php?page=data-datang';
}
})</script>";
}}
<?php
if (isset ($_POST['Ubah'])){
$sql_ubah = "UPDATE tb_datang SET
nik='".$_POST['nik']."',
nama_datang='".$_POST['nama_datang']."',
jekel='".$_POST['jekel']."',
tgl_datang='".$_POST['tgl_datang']."',
pelapor='".$_POST['pelapor']."'
WHERE id_datang='".$_POST['id_datang']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi);
if ($query_ubah) {
echo "<script>
Swal.fire({title: 'Ubah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value)
{window.location = 'index.php?page=data-datang';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Ubah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value)
{window.location = 'index.php?page=data-datang';
}
})</script>";
}}

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

@@ -30,9 +30,9 @@
</div>
<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">
<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>
@@ -46,10 +46,10 @@
<div class="form-group row">
<label class="col-sm-2 col-form-label">RT/RW</label>
<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 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>
@@ -166,18 +166,7 @@ window.addEventListener('load', function() {
<tr><td>Kabupaten</td><td>${d.kabupaten || '-'}</td></tr>
<tr><td>Provinsi</td><td>${d.provinsi || '-'}</td></tr>
</table>
<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>
<p class="mb-0 text-muted">Gunakan data ini?</p>
</div>
`,
icon: 'question',
@@ -190,8 +179,8 @@ window.addEventListener('load', function() {
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.desa) document.getElementsByName('desa')[0].value = d.desa;
if(d.rt) document.getElementsByName('rt')[0].value = d.rt;
if(d.rw) document.getElementsByName('rw')[0].value = d.rw;
document.getElementsByName('rt')[0].value = d.rt || '000';
document.getElementsByName('rw')[0].value = d.rw || '000';
if(d.kecamatan) document.getElementsByName('kec')[0].value = d.kecamatan;
if(d.kabupaten) document.getElementsByName('kab')[0].value = d.kabupaten;
if(d.provinsi) document.getElementsByName('prov')[0].value = d.provinsi;
@@ -238,28 +227,27 @@ window.addEventListener('load', function() {
}
//mulai proses simpan data
$no_kk_raw = trim($_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_clean'");
if(mysqli_num_rows($cek_kk) > 0){
echo "<script>
Swal.fire({title: 'Gagal',text: 'No KK sudah terdaftar dalam sistem!',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-kartu';
}
})</script>";
return;
}
// Sanitize Inputs
$no_kk = $no_kk_clean;
$kepala = mysqli_real_escape_string($koneksi, trim($_POST['kepala']));
$desa = mysqli_real_escape_string($koneksi, trim($_POST['desa']));
$rt = mysqli_real_escape_string($koneksi, trim($_POST['rt']));
$rw = mysqli_real_escape_string($koneksi, trim($_POST['rw']));
$kec = mysqli_real_escape_string($koneksi, trim($_POST['kec']));
$kab = mysqli_real_escape_string($koneksi, trim($_POST['kab']));
$prov = mysqli_real_escape_string($koneksi, trim($_POST['prov']));
$no_kk = $_POST['no_kk'];
$cek_kk = mysqli_query($koneksi, "SELECT * FROM tb_kk WHERE no_kk='$no_kk'");
if(mysqli_num_rows($cek_kk) > 0){
echo "<script>
Swal.fire({title: 'Gagal',text: 'No KK sudah terdaftar dalam sistem!',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-kartu';
}
})</script>";
return;
}
// Sanitize Inputs
$no_kk = mysqli_real_escape_string($koneksi, $_POST['no_kk']);
$kepala = mysqli_real_escape_string($koneksi, $_POST['kepala']);
$desa = mysqli_real_escape_string($koneksi, $_POST['desa']);
$rt = mysqli_real_escape_string($koneksi, $_POST['rt']);
$rw = mysqli_real_escape_string($koneksi, $_POST['rw']);
$kec = mysqli_real_escape_string($koneksi, $_POST['kec']);
$kab = mysqli_real_escape_string($koneksi, $_POST['kab']);
$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 (
'$no_kk',
@@ -273,70 +261,80 @@ window.addEventListener('load', function() {
'$nama_file')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
// 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'])) {
$id_kk_baru = mysqli_insert_id($koneksi);
$anggota_list = json_decode($_POST['anggota_json'], true);
if (is_array($anggota_list)) {
foreach ($anggota_list as $mem) {
$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']);
// Search Resident by NIK
$sql_cek_pend = "SELECT id_pend FROM tb_pdd WHERE nik='$nik_mem'";
$q_cek_pend = mysqli_query($koneksi, $sql_cek_pend);
if ($row_pend = mysqli_fetch_assoc($q_cek_pend)) {
$id_pend_found = $row_pend['id_pend'];
// 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')";
mysqli_query($koneksi, $sql_add_ang);
$linked_count++;
} else {
$failed_count++;
$failed_members[] = ['nik' => $nik_mem, 'nama' => $nama_mem, 'hubungan' => $hub_mem];
}
}
}
}
// Process Auto-Linking Members
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);
$anggota_list = json_decode($_POST['anggota_json'], true);
if (is_array($anggota_list)) {
foreach ($anggota_list as $mem) {
$nik_mem = mysqli_real_escape_string($koneksi, $mem['nik']);
$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
$sql_cek_pend = "SELECT id_pend FROM tb_pdd WHERE nik='$nik_mem'";
$q_cek_pend = mysqli_query($koneksi, $sql_cek_pend);
if ($row_pend = mysqli_fetch_assoc($q_cek_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
$sql_add_ang = "INSERT INTO tb_anggota (id_kk, id_pend, hubungan) VALUES ('$id_kk_baru', '$id_pend_found', '$hub_mem')";
if (!mysqli_query($koneksi, $sql_add_ang)) {
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 {
// Create New Resident if not found
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);
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>";
}
}
if ($query_simpan) {
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){
window.location = 'index.php?page=data-kartu';
}

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

@@ -97,7 +97,7 @@
<tr>
<th>NIK</th>
<th>Nama</th>
<th>Jenis Kelamin</th>
<th>Jekel</th>
<th>Hub Keluarga</th>
<th>Aksi</th>
</tr>
@@ -119,21 +119,13 @@
<?php echo $data['nama']; ?>
</td>
<td>
<?php
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
<?php echo $data['jekel']; ?>
</td>
<td>
<?php echo $data['hubungan']; ?>
</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">
<i class="fa fa-trash"></i>
</a>
@@ -143,30 +135,11 @@
<?php
}
?>
</tbody>
</tfoot>
</table>
</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>
</tbody>
</tfoot>
</table>
</div>
</div>
</div>
<div class="card-footer">
<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>NIK</th>
<th>Nama</th>
<th>Jenis Kelamin</th>
<th>Jekel</th>
<th>Hub Keluarga</th>
<th>Status</th>
</tr>
@@ -85,15 +85,7 @@
<?php echo $data['nama']; ?>
</td>
<td>
<?php
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
<?php echo $data['jekel']; ?>
</td>
<td>
<?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 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">
<input type="text" class="form-control" id="kepala" name="kepala" value="<?php echo $data_cek['kepala']; ?>"
required>
@@ -208,8 +208,8 @@ window.addEventListener('load', function() {
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.desa) document.getElementsByName('desa')[0].value = d.desa;
if(d.rt) document.getElementsByName('rt')[0].value = d.rt;
if(d.rw) document.getElementsByName('rw')[0].value = d.rw;
document.getElementsByName('rt')[0].value = d.rt || '000';
document.getElementsByName('rw')[0].value = d.rw || '000';
if(d.kecamatan) document.getElementsByName('kec')[0].value = d.kecamatan;
if(d.kabupaten) document.getElementsByName('kab')[0].value = d.kabupaten;
if(d.provinsi) document.getElementsByName('prov')[0].value = d.provinsi;

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

@@ -27,16 +27,16 @@
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Jenis Kelamin</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 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 class="form-group row">
<label class="col-sm-2 col-form-label">Keluarga</label>
@@ -69,69 +69,62 @@
</form>
</div>
<?php
if (isset ($_POST['Simpan'])){
//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 (
'$nama',
'$tgl_lh',
'$jekel',
'$id_kk')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
// Otomatis tambah ke Data Penduduk
// Ambil data alamat dari KK
$sql_kk = "SELECT desa, rt, rw FROM tb_kk WHERE id_kk='$id_kk'";
$q_kk = mysqli_query($koneksi, $sql_kk);
$d_kk = mysqli_fetch_array($q_kk);
$desa = $d_kk['desa'];
$rt = $d_kk['rt'];
$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 (
'$nik',
'$nama',
'-',
'$tgl_lh',
'$jekel',
'$desa', '$rt', '$rw',
'-', 'Belum', 'Belum/Tidak Bekerja', 'Ada')";
$query_pdd = mysqli_query($koneksi, $sql_pdd);
// Ambil ID Penduduk yang baru dibuat
$id_pend_baru = mysqli_insert_id($koneksi);
// Masukkan ke Anggota KK
$sql_anggota = "INSERT INTO tb_anggota (id_kk, id_pend, hubungan) VALUES (
'$id_kk',
'$id_pend_baru',
'Anak')";
$query_anggota = mysqli_query($koneksi, $sql_anggota);
mysqli_close($koneksi);
if ($query_simpan && $query_pdd && $query_anggota) {
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-lahir';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-lahir';
}
})</script>";
}}
//selesai proses simpan data
<?php
if (isset ($_POST['Simpan'])){
//mulai proses simpan data
$sql_simpan = "INSERT INTO tb_lahir (nama, tgl_lh, jekel, id_kk) VALUES (
'".$_POST['nama']."',
'".$_POST['tgl_lh']."',
'".$_POST['jekel']."',
'".$_POST['id_kk']."')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
// Otomatis tambah ke Data Penduduk
// Ambil data alamat dari KK
$id_kk = $_POST['id_kk'];
$sql_kk = "SELECT desa, rt, rw FROM tb_kk WHERE id_kk='$id_kk'";
$q_kk = mysqli_query($koneksi, $sql_kk);
$d_kk = mysqli_fetch_array($q_kk);
$desa = $d_kk['desa'];
$rt = $d_kk['rt'];
$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 (
'".$_POST['nik']."',
'".$_POST['nama']."',
'-',
'".$_POST['tgl_lh']."',
'".$_POST['jekel']."',
'$desa', '$rt', '$rw',
'-', 'Belum', 'Belum/Tidak Bekerja', 'Ada')";
$query_pdd = mysqli_query($koneksi, $sql_pdd);
// Ambil ID Penduduk yang baru dibuat
$id_pend_baru = mysqli_insert_id($koneksi);
// Masukkan ke Anggota KK
$sql_anggota = "INSERT INTO tb_anggota (id_kk, id_pend, hubungan) VALUES (
'$id_kk',
'$id_pend_baru',
'Anak')";
$query_anggota = mysqli_query($koneksi, $sql_anggota);
mysqli_close($koneksi);
if ($query_simpan && $query_pdd && $query_anggota) {
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-lahir';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-lahir';
}
})</script>";
}}
//selesai proses simpan data

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

@@ -17,7 +17,7 @@
<th>No</th>
<th>Nama</th>
<th>Tgl Lahir</th>
<th>Jenis Kelamin</th>
<th>Jekel</th>
<th>Keluarga</th>
<th>Aksi</th>
</tr>
@@ -42,15 +42,7 @@
<?php echo $data['tgl_lh']; ?>
</td>
<td>
<?php
if ($data['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data['jekel'];
}
?>
<?php echo $data['jekel']; ?>
</td>
<td>
<?php echo $data['no_kk']; ?>-
@@ -61,10 +53,10 @@
class="btn btn-success btn-sm">
<i class="fa fa-edit"></i>
</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">
<i class="fa fa-trash"></i>
</a>
</>
</td>
</tr>
@@ -73,26 +65,7 @@
?>
</tbody>
</tfoot>
</table>
</div>
</div>
<!-- /.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>
</table>
</div>
</div>
<!-- /.card-body -->

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

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

@@ -52,41 +52,35 @@
</form>
</div>
<?php
if (isset ($_POST['Simpan'])){
//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 (
'$id_pdd',
'$tgl_mendu',
'$sebab')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
$sql_ubah = "UPDATE tb_pdd SET
status='Meninggal'
WHERE id_pend='$id_pdd'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi);
if ($query_simpan && $query_ubah) {
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-mendu';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-mendu';
}
})</script>";
}}
//selesai proses simpan data
<?php
if (isset ($_POST['Simpan'])){
//mulai proses simpan data
$sql_simpan = "INSERT INTO tb_mendu (id_pdd, tgl_mendu, sebab) VALUES (
'".$_POST['id_pdd']."',
'".$_POST['tgl_mendu']."',
'".$_POST['sebab']."')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
$sql_ubah = "UPDATE tb_pdd SET
status='Meninggal'
WHERE id_pend='".$_POST['id_pdd']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi);
if ($query_simpan && $query_ubah) {
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-mendu';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-mendu';
}
})</script>";
}}
//selesai proses simpan data

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

@@ -13,21 +13,20 @@
<br>
<table id="example1" class="table table-bordered table-striped">
<thead>
<tr>
<th>No</th>
<th>NIK</th>
<th>Nama</th>
<th>Jenis Kelamin</th>
<th>Tanggal</th>
<th>Sebab</th>
<th>Aksi</th>
</tr>
<tr>
<th>No</th>
<th>NIK</th>
<th>Nama</th>
<th>Tanggal</th>
<th>Sebab</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<?php
$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");
while ($data= $sql->fetch_assoc()) {
?>
@@ -42,12 +41,9 @@
<td>
<?php echo $data['nama']; ?>
</td>
<td>
<?php echo $data['jekel'] == 'LK' ? 'LAKI-LAKI' : 'PEREMPUAN'; ?>
</td>
<td>
<?php echo $data['tgl_mendu']; ?>
</td>
<td>
<?php echo $data['tgl_mendu']; ?>
</td>
<td>
<?php echo $data['sebab']; ?>
</td>
@@ -60,10 +56,10 @@
class="btn btn-success btn-sm">
<i class="fa fa-edit"></i>
</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">
<i class="fa fa-trash"></i>
</a>
</>
</td>
</tr>
@@ -72,26 +68,7 @@
?>
</tbody>
</tfoot>
</table>
</div>
</div>
<!-- /.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>
</table>
</div>
</div>
<!-- /.card-body -->

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>
</td>
<td>:
<?php
if ($data_cek['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data_cek['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data_cek['jekel'];
}
?>
<?php echo $data_cek['jekel']; ?>
</td>
</tr>
<tr>

322
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-header">
<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">
<label class="col-sm-2 col-form-label">RT/RW</label>
<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 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>
@@ -130,16 +127,10 @@ $return_to = isset($_GET['return_to']) ? mysqli_real_escape_string($koneksi, tri
<div class="col-sm-6">
<input type="text" class="form-control" id="kewarganegaraan" name="kewarganegaraan" placeholder="WNI/WNA" required>
</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>
<div class="card-footer">
<input type="submit" name="Simpan" value="Simpan" class="btn btn-info">
@@ -149,62 +140,11 @@ $return_to = isset($_GET['return_to']) ? mysqli_real_escape_string($koneksi, tri
</div>
<script>
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
var inputImage = document.getElementById('foto_ktp');
var preview = document.getElementById('preview_ktp');
var hiddenInput = document.getElementById('foto_cropped');
window.addEventListener('load', function() {
// Scanner Logic
var inputImage = document.getElementById('foto_ktp');
var preview = document.getElementById('preview_ktp');
var hiddenInput = document.getElementById('foto_cropped');
// Defines callback for scanner modal
window.handleScannerResult = function(base64) {
@@ -391,146 +331,102 @@ window.addEventListener('load', function() {
}
//mulai proses simpan data
$nik = mysqli_real_escape_string($koneksi, trim($_POST['nik']));
$cek_nik = mysqli_query($koneksi, "SELECT * FROM tb_pdd WHERE nik='$nik'");
if(mysqli_num_rows($cek_nik) > 0){
echo "<script>
Swal.fire({title: 'Gagal',text: 'NIK sudah terdaftar dalam sistem!',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-pend" . (!empty($return_to) ? '&return_to=' . $return_to : '') . "';
}
})</script>";
return;
}
// Sanitize Input to prevent SQL Injection & Syntax Errors
$nama = mysqli_real_escape_string($koneksi, trim($_POST['nama']));
$tempat_lh = mysqli_real_escape_string($koneksi, trim($_POST['tempat_lh']));
$tgl_lh = mysqli_real_escape_string($koneksi, trim($_POST['tgl_lh']));
$jekel = mysqli_real_escape_string($koneksi, trim($_POST['jekel']));
$desa = mysqli_real_escape_string($koneksi, trim($_POST['desa']));
$rt = mysqli_real_escape_string($koneksi, trim($_POST['rt']));
$rw = mysqli_real_escape_string($koneksi, trim($_POST['rw']));
$agama = mysqli_real_escape_string($koneksi, trim($_POST['agama']));
$kawin = mysqli_real_escape_string($koneksi, trim($_POST['kawin']));
$pekerjaan = mysqli_real_escape_string($koneksi, trim($_POST['pekerjaan']));
$kecamatan = mysqli_real_escape_string($koneksi, trim($_POST['kecamatan']));
$kabupaten = mysqli_real_escape_string($koneksi, trim($_POST['kabupaten']));
$provinsi = mysqli_real_escape_string($koneksi, trim($_POST['provinsi']));
$kewarganegaraan = mysqli_real_escape_string($koneksi, trim($_POST['kewarganegaraan']));
$phone = mysqli_real_escape_string($koneksi, trim($_POST['phone']));
$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',
'$nama',
'$tempat_lh',
'$tgl_lh',
'$jekel',
'$desa',
'$rt',
'$rw',
'$agama',
'$kawin',
'$pekerjaan',
'$nama_file',
'Ada',
'$kecamatan',
'$kabupaten',
'$provinsi',
'$kewarganegaraan', '$phone')";
$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);
}
$nik = $_POST['nik'];
$cek_nik = mysqli_query($koneksi, "SELECT * FROM tb_pdd WHERE nik='$nik'");
if(mysqli_num_rows($cek_nik) > 0){
// AUTO-LINKING: Instead of blocking, we UPDATE the existing record with Scan Data
// Sanitize input first before UPDATE
$nama = mysqli_real_escape_string($koneksi, $_POST['nama']);
$tempat_lh = mysqli_real_escape_string($koneksi, $_POST['tempat_lh']);
$tgl_lh = mysqli_real_escape_string($koneksi, $_POST['tgl_lh']);
$jekel = mysqli_real_escape_string($koneksi, $_POST['jekel']);
$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
$nik = mysqli_real_escape_string($koneksi, $_POST['nik']);
$nama = mysqli_real_escape_string($koneksi, $_POST['nama']);
$tempat_lh = mysqli_real_escape_string($koneksi, $_POST['tempat_lh']);
$tgl_lh = mysqli_real_escape_string($koneksi, $_POST['tgl_lh']);
$jekel = mysqli_real_escape_string($koneksi, $_POST['jekel']);
$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']);
$kecamatan = mysqli_real_escape_string($koneksi, $_POST['kecamatan']);
$kabupaten = mysqli_real_escape_string($koneksi, $_POST['kabupaten']);
$provinsi = mysqli_real_escape_string($koneksi, $_POST['provinsi']);
$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 (
'$nik',
'$nama',
'$tempat_lh',
'$tgl_lh',
'$jekel',
'$desa',
'$rt',
'$rw',
'$agama',
'$kawin',
'$pekerjaan',
'$nama_file',
'Ada',
'$kecamatan',
'$kabupaten',
'$provinsi',
'$kewarganegaraan')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
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

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

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

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

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

@@ -178,16 +178,9 @@
<input type="text" class="form-control" id="kewarganegaraan" name="kewarganegaraan" value="<?php echo $data_cek['kewarganegaraan']; ?>"
required>
</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">
<input type="submit" name="Ubah" value="Simpan" class="btn btn-success">
@@ -389,7 +382,6 @@
$kabupaten = mysqli_real_escape_string($koneksi, $_POST['kabupaten']);
$provinsi = mysqli_real_escape_string($koneksi, $_POST['provinsi']);
$kewarganegaraan = mysqli_real_escape_string($koneksi, $_POST['kewarganegaraan']);
$phone = mysqli_real_escape_string($koneksi, $_POST['phone']);
if($has_new_photo){
$foto= $data_cek['foto_ktp'];
@@ -411,9 +403,8 @@
kecamatan='$kecamatan',
kabupaten='$kabupaten',
provinsi='$provinsi',
kewarganegaraan='$kewarganegaraan',
phone='$phone',
foto_ktp='$nama_file'
kewarganegaraan='$kewarganegaraan',
foto_ktp='$nama_file'
WHERE id_pend='$id_pend'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
@@ -433,8 +424,7 @@
kecamatan='$kecamatan',
kabupaten='$kabupaten',
provinsi='$provinsi',
kewarganegaraan='$kewarganegaraan',
phone='$phone'
kewarganegaraan='$kewarganegaraan'
WHERE id_pend='$id_pend'";
$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);
?>

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

@@ -134,17 +134,9 @@
<td>:
<?php echo $data_cek['kewarganegaraan']; ?>
</td>
</tr>
<tr>
<td style="width: 150px">
<b>No. Telepon</b>
</td>
<td>:
<?php echo isset($data_cek['phone']) ? $data_cek['phone'] : ''; ?>
</td>
</tr>
</tr>
</tbody>
</table>
<div class="card-footer">

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

@@ -30,11 +30,11 @@
<div class="form-group row">
<label class="col-sm-2 col-form-label">Level</label>
<div class="col-sm-4">
<select name="level" id="level" class="form-control">
<option value="">- Pilih -</option>
<option value="Administrator">Administrator</option>
<option value="Kaur Pemerintah">Kaur Pemerintah</option>
</select>
<select name="level" id="level" class="form-control">
<option>- Pilih -</option>
<option>Administrator</option>
<option>Kaur Pemerintah</option>
</select>
</div>
</div>
@@ -46,45 +46,31 @@
</form>
</div>
<?php
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
$sql_simpan = "INSERT INTO tb_pengguna (nama_pengguna,username,password,level) VALUES (
'$nama_pengguna',
'$username',
'$password_hash',
'$level_db')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
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-pengguna';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-pengguna';
}
})</script>";
}}
//selesai proses simpan data
<?php
if (isset ($_POST['Simpan'])){
//mulai proses simpan data
$sql_simpan = "INSERT INTO tb_pengguna (nama_pengguna,username,password,level) VALUES (
'".$_POST['nama_pengguna']."',
'".$_POST['username']."',
'".$_POST['password']."',
'".$_POST['level']."')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
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-pengguna';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-pengguna';
}
})</script>";
}}
//selesai proses simpan data

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

@@ -23,16 +23,11 @@
</thead>
<tbody>
<?php
$no = 1;
$level_map = [
'admin' => 'Administrator',
'kaur' => 'Kaur Pemerintah'
];
$sql = $koneksi->query("select * from tb_pengguna");
while ($data= $sql->fetch_assoc()) {
$level_display = isset($level_map[$data['level']]) ? $level_map[$data['level']] : $data['level'];
?>
<?php
$no = 1;
$sql = $koneksi->query("select * from tb_pengguna");
while ($data= $sql->fetch_assoc()) {
?>
<tr>
<td>
@@ -45,17 +40,17 @@
<?php echo $data['username']; ?>
</td>
<td>
<?php echo $level_display; ?>
<?php echo $data['level']; ?>
</td>
<td>
<a href="?page=edit-pengguna&kode=<?php echo $data['id_pengguna']; ?>" title="Ubah"
class="btn btn-success btn-sm">
<i class="fa fa-edit"></i>
</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">
<i class="fa fa-trash"></i>
</a>
</>
</td>
</tr>
@@ -64,26 +59,7 @@
?>
</tbody>
</tfoot>
</table>
</div>
</div>
<!-- /.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>
</table>
</div>
</div>
<!-- /.card-body -->

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

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

@@ -46,29 +46,17 @@
<div class="form-group row">
<label class="col-sm-2 col-form-label">Level</label>
<div class="col-sm-4">
<select name="level" id="level" class="form-control">
<option value="">-- Pilih Level --</option>
<?php
// Mapping level dari database ke tampilan
$level_display_map = [
'admin' => 'Administrator',
'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 ($current_display == "Administrator") {
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 name="level" id="level" class="form-control">
<option value="">-- Pilih Level --</option>
<?php
//menhecek data yg dipilih sebelumnya
if ($data_cek['level'] == "Administrator") echo "<option value='Administrator' selected>Administrator</option>";
else echo "<option value='Administrator'>Administrator</option>";
if ($data_cek['level'] == "Kaur Pemerintah") echo "<option value='Kaur Pemerintah' selected>Kaur Pemerintah</option>";
else echo "<option value='Kaur Pemerintah'>Kaur Pemerintah</option>";
?>
</select>
</div>
</div>
@@ -84,21 +72,14 @@
<?php
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
nama_pengguna='".$_POST['nama_pengguna']."',
username='".$_POST['username']."',
password='".$_POST['password']."',
level='".$level_db."'
WHERE id_pengguna='".$_POST['id_pengguna']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
if (isset ($_POST['Ubah'])){
$sql_ubah = "UPDATE tb_pengguna SET
nama_pengguna='".$_POST['nama_pengguna']."',
username='".$_POST['username']."',
password='".$_POST['password']."',
level='".$_POST['level']."'
WHERE id_pengguna='".$_POST['id_pengguna']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi);
if ($query_ubah) {

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

@@ -52,41 +52,35 @@
</form>
</div>
<?php
if (isset ($_POST['Simpan'])){
//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 (
'$id_pdd',
'$tgl_pindah',
'$alasan')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
$sql_ubah = "UPDATE tb_pdd SET
status='Pindah'
WHERE id_pend='$id_pdd'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi);
if ($query_simpan && $query_ubah) {
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-pindah';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-pindah';
}
})</script>";
}}
//selesai proses simpan data
<?php
if (isset ($_POST['Simpan'])){
//mulai proses simpan data
$sql_simpan = "INSERT INTO tb_pindah (id_pdd, tgl_pindah, alasan) VALUES (
'".$_POST['id_pdd']."',
'".$_POST['tgl_pindah']."',
'".$_POST['alasan']."')";
$query_simpan = mysqli_query($koneksi, $sql_simpan);
$sql_ubah = "UPDATE tb_pdd SET
status='Pindah'
WHERE id_pend='".$_POST['id_pdd']."'";
$query_ubah = mysqli_query($koneksi, $sql_ubah);
mysqli_close($koneksi);
if ($query_simpan && $query_ubah) {
echo "<script>
Swal.fire({title: 'Tambah Data Berhasil',text: '',icon: 'success',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=data-pindah';
}
})</script>";
}else{
echo "<script>
Swal.fire({title: 'Tambah Data Gagal',text: '',icon: 'error',confirmButtonText: 'OK'
}).then((result) => {if (result.value){
window.location = 'index.php?page=add-pindah';
}
})</script>";
}}
//selesai proses simpan data

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

@@ -13,21 +13,20 @@
<br>
<table id="example1" class="table table-bordered table-striped">
<thead>
<tr>
<th>No</th>
<th>NIK</th>
<th>Nama</th>
<th>Jenis Kelamin</th>
<th>Tanggal</th>
<th>Alasan</th>
<th>Aksi</th>
</tr>
<tr>
<th>No</th>
<th>NIK</th>
<th>Nama</th>
<th>Tanggal</th>
<th>Alasan</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<?php
$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");
while ($data= $sql->fetch_assoc()) {
?>
@@ -42,12 +41,9 @@
<td>
<?php echo $data['nama']; ?>
</td>
<td>
<?php echo $data['jekel'] == 'LK' ? 'LAKI-LAKI' : 'PEREMPUAN'; ?>
</td>
<td>
<?php echo $data['tgl_pindah']; ?>
</td>
<td>
<?php echo $data['tgl_pindah']; ?>
</td>
<td>
<?php echo $data['alasan']; ?>
</td>
@@ -60,10 +56,10 @@
class="btn btn-success btn-sm">
<i class="fa fa-edit"></i>
</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">
<i class="fa fa-trash"></i>
</a>
</>
</td>
</tr>
@@ -72,26 +68,7 @@
?>
</tbody>
</tfoot>
</table>
</div>
</div>
<!-- /.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>
</table>
</div>
</div>
<!-- /.card-body -->

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>
</td>
<td>:
<?php
if ($data_cek['jekel'] == 'LK') {
echo 'LAKI-LAKI';
} elseif ($data_cek['jekel'] == 'PR') {
echo 'PEREMPUAN';
} else {
echo $data_cek['jekel'];
}
?>
<?php echo $data_cek['jekel']; ?>
</td>
</tr>
<tr>

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

888
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-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<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">
<span aria-hidden="true">&times;</span>
</button>
</div>
<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;">
<canvas id="canvas-image" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
<canvas id="canvas-overlay" style="position: absolute; left: 0; top: 0; z-index: 2; cursor: crosshair;"></canvas>
</div>
<div id="scanner-loading" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: white; display: none;">
<i class="fas fa-spinner fa-spin fa-3x"></i><br>Detecting Document...
</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 class="modal-header">
<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">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body text-center bg-dark p-0" style="position: relative; overflow: hidden; height: 80vh;">
<!-- Container for Canvases -->
<!-- Container for Canvases -->
<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);">
<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>
<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;">
<i class="fas fa-spinner fa-spin fa-3x"></i><br><span style="margin-top: 10px; display: block;">Sedang menganalisa...</span>
</div>
</div>
<div class="modal-footer justify-content-between">
<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="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>
</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="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>
</div>
<div>
<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>
@@ -39,341 +35,31 @@
</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>
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
var scannerModal = $('#modalScanner');
var canvasImage = document.getElementById('canvas-image');
var canvasOverlay = document.getElementById('canvas-overlay');
var ctxImg = canvasImage.getContext('2d');
var ctxOver = canvasOverlay.getContext('2d');
const IS_TOUCH_DEVICE = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
var scanner = null;
function initScanner() {
// Check if OpenCV is loaded
if (typeof cv === 'undefined') {
console.warn('OpenCV not loaded yet');
return false;
}
// Check if OpenCV runtime is already initialized
if (cv.Mat) {
try {
scanner = new jscanify();
console.log('Scanner initialized successfully (runtime ready)');
return true;
} catch (e) {
console.error('Failed to initialize jscanify:', e);
return false;
}
}
// Runtime not initialized yet, set up callback
if (cv.onRuntimeInitialized) {
// 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';
var scanner = new jscanify();
var originalImg = new Image();
// Corner Points (tl, tr, bl, br)
var corners = [];
var activePoint = null;
var isDragging = false;
var touchStartPos = null;
var isTouchInteraction = false;
var touchOffset = null;
// Corner Points (tl, tr, bl, br)
var corners = [];
var activePoint = null;
var isDragging = false;
// 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
// Config
const POINT_RADIUS = 15;
const POINT_COLOR = '#007bff';
const LINE_COLOR = '#00ff00';
const LINE_WIDTH = 3;
// Mode is always smart scan (no toggle needed)
// --- Public Function to Open Scanner ---
window.openScanner = function(file) {
// --- Public Function to Open Scanner ---
window.openScanner = function(file) {
if (!file) return;
// Reset State
@@ -390,7 +76,10 @@ window.addEventListener('load', function() {
function checkReady() {
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);
// 2. Show Modal & Listen
scannerModal.off('shown.bs.modal'); // Remove old listeners
scannerModal.on('shown.bs.modal', function() {
modalShown = true;
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 = '';
});
// 2. Show Modal & Listen
scannerModal.off('shown.bs.modal'); // Remove old listeners
scannerModal.on('shown.bs.modal', function() {
modalShown = true;
checkReady();
});
scannerModal.modal('show');
};
function detectDocument(scale, w, h, showAlert = false) {
// Detect Contour using jscanify
try {
// jscanify expects an image element, we can pass originalImg but we need to map coordinates
// 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) {
// Order: TL, TR, BR, BL (clockwise)
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 }
];
function initScanner() {
// Resize Canvas to fit screen but keep aspect ratio
var maxWidth = $('#modalScanner .modal-body').width() - 20;
var maxHeight = $('#modalScanner .modal-body').height() - 20;
var scale = Math.min(maxWidth / originalImg.width, maxHeight / originalImg.height);
var w = originalImg.width * scale;
var h = originalImg.height * scale;
canvasImage.width = w;
canvasImage.height = h;
canvasOverlay.width = w;
canvasOverlay.height = h;
// Resize container
$('#scanner-container').css({ width: w, height: h });
// Draw Image
ctxImg.drawImage(originalImg, 0, 0, w, h);
// Detect Contour using jscanify
try {
// jscanify expects an image element, we can pass originalImg but we need to map coordinates
// 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}
if (contour) {
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 }
];
} else {
defaultCorners(w, h);
if (showAlert) {
Swal.fire({
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();
}
} else {
defaultCorners(w, h);
}
} else {
console.warn("OpenCV not ready yet");
defaultCorners(w, h);
}
} catch(e) {
console.error("Scanner Error:", e);
defaultCorners(w, h); // Fallback
}
$('#scanner-loading').hide();
drawOverlay();
}
function setupScannerCanvas() {
// Resize Canvas to fit screen but keep aspect ratio
var maxWidth = $('#modalScanner .modal-body').width() - 20;
var maxHeight = $('#modalScanner .modal-body').height() - 20;
var scale = Math.min(maxWidth / originalImg.width, maxHeight / originalImg.height);
var w = originalImg.width * scale;
var h = originalImg.height * scale;
canvasImage.width = w;
canvasImage.height = h;
canvasOverlay.width = w;
canvasOverlay.height = h;
// Resize container
$('#scanner-container').css({ width: w, height: h, marginTop: '10px' });
// Draw Image
ctxImg.drawImage(originalImg, 0, 0, w, h);
detectDocument(scale, w, h, false);
$('#scanner-loading').hide();
// No cropper needed - smart scan only
}
function defaultCorners(w, h) {
function defaultCorners(w, h) {
// Default 20% margin
var mX = w * 0.1;
var mY = h * 0.1;
@@ -566,7 +181,7 @@ window.addEventListener('load', function() {
if (corners.length < 4) return;
// Draw thin cropping lines
// Draw Lines
ctxOver.beginPath();
ctxOver.lineWidth = LINE_WIDTH;
ctxOver.strokeStyle = LINE_COLOR;
@@ -577,19 +192,14 @@ window.addEventListener('load', function() {
ctxOver.closePath();
ctxOver.stroke();
// Draw transparent circles with thin outline
// Draw Points
ctxOver.fillStyle = POINT_COLOR;
corners.forEach(p => {
// Draw transparent inner circle
ctxOver.beginPath();
ctxOver.arc(p.x, p.y, POINT_RADIUS, 0, Math.PI * 2);
ctxOver.fillStyle = POINT_COLOR;
ctxOver.fill();
// Draw thin blue outline
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.strokeStyle = 'white';
ctxOver.lineWidth = 2;
ctxOver.stroke();
});
}
@@ -604,192 +214,106 @@ window.addEventListener('load', function() {
};
}
function isInside(pos, point) {
var dx = pos.x - point.x;
var dy = pos.y - point.y;
return dx * dx + dy * dy <= POINT_RADIUS * POINT_RADIUS * 2; // Bigger hit area
}
function getClosestCorner(pos, isTouch) {
var closestIdx = -1;
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) => {
var dx = pos.x - p.x;
var dy = pos.y - p.y;
var distSq = dx * dx + dy * dy;
if (distSq < radiusSq && distSq < closestDist) {
closestDist = distSq;
closestIdx = i;
}
});
return closestIdx;
}
function isInside(pos, point) {
var dx = pos.x - point.x;
var dy = pos.y - point.y;
return dx * dx + dy * dy <= POINT_RADIUS * POINT_RADIUS * 2; // Bigger hit area
}
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});
canvasOverlay.addEventListener('mousedown', function(e) { handleStart(getMousePos(e)); });
canvasOverlay.addEventListener('touchstart', function(e) { handleStart(getMousePos(e)); e.preventDefault(); }, {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('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(); }, {passive: false});
window.addEventListener('mouseup', function() { handleEnd(); });
window.addEventListener('touchend', function() { handleEnd(); });
function handleStart(pos, isTouch = false) {
activePoint = getClosestCorner(pos, isTouch);
if (activePoint !== -1) {
isTouchInteraction = isTouch;
touchOffset = { x: pos.x - corners[activePoint].x, y: pos.y - corners[activePoint].y };
if (isTouch) {
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 handleStart(pos) {
activePoint = null;
corners.forEach((p, i) => {
if (isInside(pos, p)) {
activePoint = i;
isDragging = true;
}
});
}
function handleMove(pos) {
if (activePoint !== null) {
// Touch slop detection
if (isTouchInteraction && touchStartPos) {
var dx = pos.x - touchStartPos.x;
var dy = pos.y - touchStartPos.y;
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;
}
function handleMove(pos) {
if (activePoint !== null) {
// Constrain to canvas?? Optional but good
corners[activePoint].x = pos.x;
corners[activePoint].y = pos.y;
drawOverlay();
}
}
function handleEnd() {
isDragging = false;
activePoint = null;
}
// --- Rotate Functions ---
function rotateImage(degree) {
var offCanvas = document.createElement('canvas');
var offCtx = offCanvas.getContext('2d');
// Swap Width/Height for 90 degree rotation
offCanvas.width = originalImg.height;
offCanvas.height = originalImg.width;
offCtx.translate(offCanvas.width / 2, offCanvas.height / 2);
offCtx.rotate(degree * Math.PI / 180);
offCtx.drawImage(originalImg, -originalImg.width / 2, -originalImg.height / 2);
// Update originalImg
var rotatedUrl = offCanvas.toDataURL();
originalImg.onload = function() {
initScanner(); // Re-init with new image
}
originalImg.src = rotatedUrl;
}
$('#btnScanRotateLeft').click(function() { rotateImage(-90); });
$('#btnScanRotateRight').click(function() { rotateImage(90); });
// --- Reset Button ---
$('#btnScanReset').click(function() {
initScanner();
});
// --- Save / Extract Button ---
$('#btnScanSave').click(function() {
// Warp Image
try {
// 1. Get raw points relative to Original Image
var scaleX = originalImg.width / canvasImage.width;
var scaleY = originalImg.height / canvasImage.height;
// Apply offset to maintain relative position
if (touchOffset) {
corners[activePoint].x = pos.x - touchOffset.x;
corners[activePoint].y = pos.y - touchOffset.y;
} else {
corners[activePoint].x = pos.x;
corners[activePoint].y = pos.y;
}
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 };
// Optional: constrain to canvas bounds
var w = canvasOverlay.width;
var h = canvasOverlay.height;
corners[activePoint].x = Math.max(0, Math.min(w, corners[activePoint].x));
corners[activePoint].y = Math.max(0, Math.min(h, corners[activePoint].y));
// 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);
drawOverlay();
}
}
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);
function handleEnd() {
isDragging = false;
activePoint = null;
isTouchInteraction = false;
touchStartPos = null;
touchOffset = null;
}
// --- Rotate Functions ---
function rotateImage(degree) {
var offCanvas = document.createElement('canvas');
var offCtx = offCanvas.getContext('2d');
// Swap Width/Height for 90 degree rotation
offCanvas.width = originalImg.height;
offCanvas.height = originalImg.width;
offCtx.translate(offCanvas.width / 2, offCanvas.height / 2);
offCtx.rotate(degree * Math.PI / 180);
offCtx.drawImage(originalImg, -originalImg.width / 2, -originalImg.height / 2);
// Update originalImg
var rotatedUrl = offCanvas.toDataURL();
originalImg.onload = function() {
// Re-init scanner
setupScannerCanvas();
}
originalImg.src = rotatedUrl;
}
$('#btnScanRotateLeft').click(function() { rotateImage(-90); });
$('#btnScanRotateRight').click(function() { rotateImage(90); });
// --- Reset Button ---
$('#btnScanReset').click(function() {
setupScannerCanvas();
});
// --- Save / Extract Button ---
$('#btnScanSave').click(function() {
try {
var base64;
var extractPoints = {
topLeftCorner: tl,
topRightCorner: tr,
bottomRightCorner: br,
bottomLeftCorner: bl
};
// 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');
// 3. Extract with dynamic dimensions
var resultCanvas = scanner.extractPaper(originalImg, outputWidth, outputHeight, extractPoints);
var base64 = resultCanvas.toDataURL('image/jpeg');
if (window.handleScannerResult) {
window.handleScannerResult(base64);
@@ -797,18 +321,10 @@ window.addEventListener('load', function() {
scannerModal.modal('hide');
} catch (e) {
alert("Gagal memproses gambar: " + e.message);
console.error(e);
}
});
// Show mobile help if touch device
if (IS_TOUCH_DEVICE) {
$('#mobile-help').removeClass('d-none');
}
// Mode is always smart scan now
}, 500); }); });
} catch (e) {
alert("Gagal memproses gambar: " + e.message);
}
});
</script>
});
</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