From 05dd3f2a67b7b76ce7051dd8c321eb7bd85cd0a8 Mon Sep 17 00:00:00 2001 From: wartana Date: Thu, 22 Jan 2026 07:34:17 +0800 Subject: [PATCH] Enhance SIDAK with two-way KK-KTP linkage, scanner mobile optimization, NIK validation, and UI improvements - Implement bidirectional KK-KTP linkage system (address-based & NIK-based) - Optimize scanner for mobile devices (touch slop, larger hit areas, modal locking) - Add NIK validation (16-digit numeric) with client-side feedback - Set default RT/RW values to '000' for both KK and KTP forms - Change 'Kpl Keluarga' label to 'Kepala Keluarga' - Improve scanner error messages and user feedback - Remove redundant 'Deteksi Dokumen' button - Add database schema updates and Docker support files --- .dockerignore | 18 ++ Dockerfile | 38 +++ INSTALL_DOCKER.md | 67 +++++ README_DOCKER.md | 74 ++++++ admin/kartu/add_kartu.php | 143 ++++++----- admin/kartu/edit_kartu.php | 2 +- admin/pend/add_pend.php | 266 ++++++++++++++------ admin/pend/link_to_kk.php | 62 +++++ admin/scanner_modal.php | 488 ++++++++++++++++++++++++++++--------- dist/css/modern.css | 2 +- docker-compose.yml | 61 +++++ inc/koneksi.php | 14 +- index.php | 7 +- init.sql | 208 ++++++++++++++++ login.php | 7 +- 15 files changed, 1204 insertions(+), 253 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 INSTALL_DOCKER.md create mode 100644 README_DOCKER.md create mode 100644 admin/pend/link_to_kk.php create mode 100644 docker-compose.yml create mode 100644 init.sql diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d84348d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +# 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* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d147c4a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +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"] \ No newline at end of file diff --git a/INSTALL_DOCKER.md b/INSTALL_DOCKER.md new file mode 100644 index 0000000..e8a59cc --- /dev/null +++ b/INSTALL_DOCKER.md @@ -0,0 +1,67 @@ +# SIDAK Docker Setup + +Aplikasi SIDAK berjalan dengan Docker Compose. + +## Services yang berjalan: +1. **Web App** - PHP Apache: http://localhost:8500 +2. **Database** - MariaDB: localhost:3307 +3. **phpMyAdmin** - Database admin: http://localhost:8080 + +## Cara menjalankan: + +### 1. Start aplikasi: +```bash +docker compose up -d +``` + +### 2. Stop aplikasi: +```bash +docker compose down +``` + +### 3. Stop dan hapus data: +```bash +docker compose down -v +``` + +### 4. Melihat logs: +```bash +docker compose logs -f +``` + +## Credentials: + +### Aplikasi SIDAK: +- URL: http://localhost:8500 +- Admin: `admin` / `admin` +- Kaur: `kaur` / `kaur` + +### Database: +- Host: `db` (dalam container) atau `localhost:3307` (dari host) +- Database: `data_penduduk` +- User: `sidak_user` +- Password: `sidak_pass` +- Root password: `rootpassword` + +### phpMyAdmin: +- URL: http://localhost:8080 +- Server: `db` +- Username: `sidak_user` atau `root` +- Password: `sidak_pass` atau `rootpassword` + +## Troubleshooting: + +1. **Port conflict**: Jika port 8500, 8080, atau 3307 sudah digunakan, ubah di `docker-compose.yml` +2. **Database connection error**: Tunggu 30 detik untuk database startup +3. **PHP extensions**: Extensions mysqli, pdo, pdo_mysql sudah diinstall otomatis +4. **File permissions**: Semua file di-mount dari host ke container + +## Struktur file Docker: +- `docker-compose.yml` - Konfigurasi services +- `init.sql` - Skema dan data awal database +- `inc/koneksi.php` - Konfigurasi koneksi database (menggunakan env variables) + +## Catatan: +- Perubahan file PHP langsung terlihat (hot reload) +- Data database persist di volume Docker `sidak_mariadb-data` +- Untuk development, edit file langsung di host \ No newline at end of file diff --git a/README_DOCKER.md b/README_DOCKER.md new file mode 100644 index 0000000..7f813c1 --- /dev/null +++ b/README_DOCKER.md @@ -0,0 +1,74 @@ +# SIDAK Application with Docker + +## Prerequisites +- Docker +- Docker Compose + +## Quick Start + +1. **Start the application:** + ```bash + docker-compose up -d + ``` + +2. **Access the application:** + - Web application: http://localhost:8500 + - phpMyAdmin: http://localhost:8080 + - MySQL/MariaDB: localhost:3306 + +3. **Default credentials:** + - **Application:** + - Admin: `admin` / `admin` + - Kaur: `kaur` / `kaur` + - **Database:** + - Host: `db` + - Database: `data_penduduk` + - User: `sidak_user` + - Password: `sidak_pass` + - Root password: `rootpassword` + +4. **Stop the application:** + ```bash + docker-compose down + ``` + +5. **Stop and remove volumes (clears all data):** + ```bash + docker-compose down -v + ``` + +## Services + +1. **web** - PHP Apache application + - Port: 8500 + - Directory mounted: `/var/www/html` + - Environment variables: DB_HOST, DB_USER, DB_PASS, DB_NAME + +2. **db** - MariaDB database + - Port: 3306 + - Database: `data_penduduk` + - Auto-initialized with schema and sample data + - Data persisted in volume: `mariadb-data` + +3. **phpmyadmin** - Database management + - Port: 8080 + - Connect to host: `db` + +## Development + +- **Hot reload:** Changes to PHP files are reflected immediately due to volume mounting +- **Database persistence:** Data is preserved between container restarts +- **Build custom image:** `docker-compose build` + +## Troubleshooting + +1. **Port conflicts:** Check if ports 8500, 8080, or 3306 are already in use +2. **Database connection issues:** Wait a few seconds for database to initialize +3. **View logs:** `docker-compose logs -f` +4. **Reset database:** `docker-compose down -v && docker-compose up -d` + +## File Structure +- `docker-compose.yml` - Service definitions +- `Dockerfile` - PHP Apache image configuration +- `init.sql` - Database initialization script +- `.dockerignore` - Files to exclude from build context \ No newline at end of file diff --git a/admin/kartu/add_kartu.php b/admin/kartu/add_kartu.php index 8cf19cd..44027dd 100644 --- a/admin/kartu/add_kartu.php +++ b/admin/kartu/add_kartu.php @@ -30,9 +30,9 @@
- +
- +
@@ -46,10 +46,10 @@
- +
- +
@@ -238,27 +238,28 @@ window.addEventListener('load', function() { } //mulai proses simpan data - $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 ""; - 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']); + $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 ""; + 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'])); $sql_simpan = "INSERT INTO tb_kk (no_kk, kepala, desa, rt, rw, kec, kab, prov, foto_kk) VALUES ( '$no_kk', @@ -272,43 +273,67 @@ window.addEventListener('load', function() { '$nama_file')"; $query_simpan = mysqli_query($koneksi, $sql_simpan); - // Process Auto-Linking Members - $linked_count = 0; - $failed_count = 0; - - 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']); - $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++; - } - } - } - } + // 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]; + } + } + } + } mysqli_close($koneksi); - if ($query_simpan) { - $msg_add = ""; - if($linked_count > 0 || $failed_count > 0) { - $msg_add = "
Anggota Terhubung: $linked_count
Tidak Ditemukan: $failed_count"; - } + if ($query_simpan) { + $msg_add = ""; + if($linked_count > 0 || $failed_count > 0) { + $msg_add = "
Anggota Terhubung: $linked_count
Tidak Ditemukan: $failed_count"; + // Add detailed failed members list if any + if (!empty($failed_members)) { + $msg_add .= "

Detail Anggota Tidak Ditemukan:
"; + $msg_add .= "
"; + 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 = "Tambah"; + $msg_add .= "• " . htmlspecialchars($fm['nik']) . " - " . htmlspecialchars($fm['nama']) . " (" . htmlspecialchars($fm['hubungan']) . ") $add_link
"; + } + $msg_add .= "
"; + $msg_add .= "Tambahkan data penduduk yang belum terdaftar melalui menu Tambah Penduduk."; + } + } echo ""; - return; - } + $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 ""; + 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'])); - // 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 ""; - }else{ - echo ""; - }} + $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); + + if ($query_simpan) { + $id_pend_baru = mysqli_insert_id($koneksi); + + // 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 ""; + } else { + // Sudah terhubung + echo ""; + } + } else { + // Tidak ada KK yang cocok + echo ""; + } + } else { + echo ""; + } + mysqli_close($koneksi); + } //selesai proses simpan data diff --git a/admin/pend/link_to_kk.php b/admin/pend/link_to_kk.php new file mode 100644 index 0000000..5a59630 --- /dev/null +++ b/admin/pend/link_to_kk.php @@ -0,0 +1,62 @@ + 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); +?> \ No newline at end of file diff --git a/admin/scanner_modal.php b/admin/scanner_modal.php index 86ef2d3..5a3690f 100644 --- a/admin/scanner_modal.php +++ b/admin/scanner_modal.php @@ -20,11 +20,11 @@ + +