diff --git a/assets/css/custom.css b/assets/css/custom.css
new file mode 100644
index 0000000..7b89b67
--- /dev/null
+++ b/assets/css/custom.css
@@ -0,0 +1,119 @@
+:root {
+ --primary-color: #0f172a;
+ --secondary-color: #3b82f6;
+ --bg-color: #f8fafc;
+ --surface-color: #ffffff;
+ --border-color: #e2e8f0;
+ --text-main: #1e293b;
+ --text-muted: #64748b;
+}
+
+body {
+ font-family: 'Inter', -apple-system, sans-serif;
+ background-color: var(--bg-color);
+ color: var(--text-main);
+ margin: 0;
+ padding: 0;
+}
+
+.sidebar {
+ width: 260px;
+ height: 100vh;
+ background-color: var(--primary-color);
+ color: white;
+ position: fixed;
+ left: 0;
+ top: 0;
+ padding: 1.5rem;
+ z-index: 1000;
+}
+
+.sidebar-brand {
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin-bottom: 2rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.nav-link {
+ color: #94a3b8;
+ padding: 0.75rem 1rem;
+ border-radius: 0.375rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ text-decoration: none;
+ transition: all 0.2s;
+ margin-bottom: 0.25rem;
+}
+
+.nav-link:hover, .nav-link.active {
+ background-color: rgba(255, 255, 255, 0.1);
+ color: white;
+}
+
+.main-content {
+ margin-left: 260px;
+ padding: 2rem;
+}
+
+.top-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+.card {
+ background-color: var(--surface-color);
+ border: 1px solid var(--border-color);
+ border-radius: 0.375rem;
+ padding: 1.5rem;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.btn-primary {
+ background-color: var(--secondary-color);
+ border-color: var(--secondary-color);
+ padding: 0.5rem 1.25rem;
+ font-weight: 500;
+}
+
+.table thead th {
+ background-color: #f1f5f9;
+ font-weight: 600;
+ text-transform: uppercase;
+ font-size: 0.75rem;
+ letter-spacing: 0.05em;
+ color: var(--text-muted);
+ border-bottom-width: 1px;
+}
+
+.stats-card {
+ display: flex;
+ flex-direction: column;
+}
+
+.stats-card .label {
+ font-size: 0.875rem;
+ color: var(--text-muted);
+ margin-bottom: 0.5rem;
+}
+
+.last-child-no-border:last-child {
+ border-bottom: none !important;
+ margin-bottom: 0 !important;
+ padding-bottom: 0 !important;
+}
+
+.user-profile {
+ padding: 0.5rem;
+ border-radius: 2rem;
+ transition: background-color 0.2s;
+}
+
+.user-profile:hover {
+ background-color: #f1f5f9;
+}
diff --git a/assets/js/main.js b/assets/js/main.js
new file mode 100644
index 0000000..2fe824c
--- /dev/null
+++ b/assets/js/main.js
@@ -0,0 +1,13 @@
+// Main JS for SiWarga
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('SiWarga Initialized');
+
+ // Auto-hide alerts after 5 seconds
+ const alerts = document.querySelectorAll('.alert');
+ alerts.forEach(alert => {
+ setTimeout(() => {
+ const bsAlert = new bootstrap.Alert(alert);
+ bsAlert.close();
+ }, 5000);
+ });
+});
diff --git a/db/migrations/001_initial_schema.sql b/db/migrations/001_initial_schema.sql
new file mode 100644
index 0000000..b602bfe
--- /dev/null
+++ b/db/migrations/001_initial_schema.sql
@@ -0,0 +1,14 @@
+-- Initial schema for SiWarga
+CREATE TABLE IF NOT EXISTS jenis_iuran (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ nama VARCHAR(100) NOT NULL,
+ nominal_default DECIMAL(15, 2) DEFAULT 0.00,
+ deskripsi TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
+
+-- Sample data for demo
+INSERT IGNORE INTO jenis_iuran (nama, nominal_default, deskripsi) VALUES
+('Iuran Keamanan', 50000.00, 'Iuran bulanan untuk satpam dan ronda'),
+('Iuran Kebersihan', 30000.00, 'Iuran bulanan untuk angkut sampah'),
+('Iuran Kematian', 10000.00, 'Iuran dana sosial untuk santunan');
diff --git a/db/migrations/002_full_schema.sql b/db/migrations/002_full_schema.sql
new file mode 100644
index 0000000..bf763f4
--- /dev/null
+++ b/db/migrations/002_full_schema.sql
@@ -0,0 +1,56 @@
+-- Full schema for SiWarga
+CREATE TABLE IF NOT EXISTS warga (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ nama VARCHAR(255) NOT NULL,
+ blok VARCHAR(10) NOT NULL,
+ nomor_rumah VARCHAR(10) NOT NULL,
+ nik VARCHAR(20),
+ kk VARCHAR(20),
+ telepon VARCHAR(20),
+ status ENUM('aktif', 'non-aktif') DEFAULT 'aktif',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
+
+CREATE TABLE IF NOT EXISTS pembayaran (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ warga_id INT NOT NULL,
+ jenis_iuran_id INT NOT NULL,
+ periode VARCHAR(7) NOT NULL, -- Format: YYYY-MM
+ jumlah DECIMAL(15, 2) NOT NULL,
+ tanggal_bayar DATE NOT NULL,
+ catatan TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (warga_id) REFERENCES warga(id) ON DELETE CASCADE,
+ FOREIGN KEY (jenis_iuran_id) REFERENCES jenis_iuran(id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE IF NOT EXISTS pengeluaran (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ jenis_iuran_id INT, -- Bisa NULL jika dari kas umum
+ kategori VARCHAR(100) NOT NULL,
+ jumlah DECIMAL(15, 2) NOT NULL,
+ tanggal DATE NOT NULL,
+ keterangan TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (jenis_iuran_id) REFERENCES jenis_iuran(id) ON DELETE SET NULL
+) ENGINE=InnoDB;
+
+CREATE TABLE IF NOT EXISTS pengurus (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ nama VARCHAR(255) NOT NULL,
+ jabatan VARCHAR(100) NOT NULL,
+ telepon VARCHAR(20),
+ urutan INT DEFAULT 0,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
+
+-- Sample data for demo
+INSERT IGNORE INTO warga (nama, blok, nomor_rumah, nik, kk, telepon) VALUES
+('Budi Santoso', 'A', '10', '3201010101010001', '3201010101010002', '081234567890'),
+('Siti Aminah', 'A', '11', '3201010101010003', '3201010101010004', '081234567891'),
+('Agus Wijaya', 'B', '05', '3201010101010005', '3201010101010006', '081234567892');
+
+INSERT IGNORE INTO pengurus (nama, jabatan, urutan) VALUES
+('Budi Santoso', 'Ketua RT', 1),
+('Siti Aminah', 'Sekretaris', 2),
+('Agus Wijaya', 'Bendahara', 3);
diff --git a/db/migrations/003_add_users_table.sql b/db/migrations/003_add_users_table.sql
new file mode 100644
index 0000000..4c6eff8
--- /dev/null
+++ b/db/migrations/003_add_users_table.sql
@@ -0,0 +1,14 @@
+-- Migration to add users table for authentication
+CREATE TABLE IF NOT EXISTS users (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(50) NOT NULL UNIQUE,
+ password VARCHAR(255) NOT NULL,
+ nama_lengkap VARCHAR(255),
+ role ENUM('admin', 'pengurus') DEFAULT 'admin',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
+
+-- Insert default admin user (password: password123)
+-- Using password_hash('password123', PASSWORD_DEFAULT) in PHP
+INSERT IGNORE INTO users (username, password, nama_lengkap, role) VALUES
+('admin', '$2y$10$314hi5BLY.dImKKtXWRd7.bpTmTTQz3AxKzYCK0tNs0NNr09eIBlK', 'Administrator RT', 'admin');
diff --git a/index.php b/index.php
index 7205f3d..9641d55 100644
--- a/index.php
+++ b/index.php
@@ -1,150 +1,152 @@
query("SELECT COUNT(*) FROM warga WHERE status = 'aktif'")->fetchColumn();
+$total_jenis_iuran = $db->query("SELECT COUNT(*) FROM jenis_iuran")->fetchColumn();
+$total_pemasukan = $db->query("SELECT SUM(jumlah) FROM pembayaran")->fetchColumn() ?: 0;
+$total_pengeluaran = $db->query("SELECT SUM(jumlah) FROM pengeluaran")->fetchColumn() ?: 0;
+$saldo = $total_pemasukan - $total_pengeluaran;
+
+// Recent payments
+$recent_payments = $db->query("
+ SELECT p.*, w.nama as nama_warga, j.nama as nama_iuran
+ FROM pembayaran p
+ JOIN warga w ON p.warga_id = w.id
+ JOIN jenis_iuran j ON p.jenis_iuran_id = j.id
+ ORDER BY p.tanggal_bayar DESC LIMIT 5
+")->fetchAll();
+
+// Get Pengurus
+$pengurus = $db->query("SELECT * FROM pengurus ORDER BY urutan ASC")->fetchAll();
?>
-
-
-
-
-
- New Style
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
Dashboard
+
= date('d F Y') ?>
+
+
+
+
+
+ Total Warga Aktif
+ = number_format($total_warga) ?>
+
-
-
- Page updated: = htmlspecialchars($now) ?> (UTC)
-
-
-
+
+
+ Saldo Kas (Total)
+ Rp = number_format($saldo, 0, ',', '.') ?>
+
+
+
+
+ Total Pemasukan
+ Rp = number_format($total_pemasukan, 0, ',', '.') ?>
+
+
+
+
+ Total Pengeluaran
+ Rp = number_format($total_pengeluaran, 0, ',', '.') ?>
+
+
+
+
+
+
+
+
+
Pembayaran Terbaru
+
Lihat Semua
+
+
+
+
+
+ Warga
+ Iuran
+ Periode
+ Jumlah
+ Tanggal
+
+
+
+
+
+ Belum ada data pembayaran.
+
+
+
+
+ = htmlspecialchars($p['nama_warga']) ?>
+ = htmlspecialchars($p['nama_iuran']) ?>
+ = $p['periode'] ?>
+ Rp = number_format($p['jumlah'], 0, ',', '.') ?>
+ = date('d/m/Y', strtotime($p['tanggal_bayar'])) ?>
+
+
+
+
+
+
+
+
+
+
+
Struktur Pengurus
+
+
Belum ada data pengurus.
+
+
+
+
+
+
+
+
+
= htmlspecialchars($pg['jabatan']) ?>
+
= htmlspecialchars($pg['nama']) ?>
+
+
+
+
+
+
+
+
+
Ringkasan per Iuran
+ query("
+ SELECT j.nama, j.id,
+ (SELECT SUM(p.jumlah) FROM pembayaran p WHERE p.jenis_iuran_id = j.id) as masuk,
+ (SELECT SUM(e.jumlah) FROM pengeluaran e WHERE e.jenis_iuran_id = j.id) as keluar
+ FROM jenis_iuran j
+ ")->fetchAll();
+ ?>
+
+
+
+
+ = htmlspecialchars($is['nama']) ?>
+ Rp = number_format($saldo_iuran, 0, ',', '.') ?>
+
+
+ 0 ? min(100, ($saldo_iuran / $masuk) * 100) : 0;
+ if ($saldo_iuran < 0) $percent = 0;
+ ?>
+
+
+
+
+
+
+
+
+
diff --git a/iuran.php b/iuran.php
new file mode 100644
index 0000000..9ec6b14
--- /dev/null
+++ b/iuran.php
@@ -0,0 +1,139 @@
+prepare("UPDATE jenis_iuran SET nama=?, nominal_default=?, deskripsi=? WHERE id=?");
+ $stmt->execute([$nama, $nominal, $deskripsi, $id]);
+ $message = '
Jenis iuran berhasil diperbarui.
';
+ } else {
+ $stmt = $db->prepare("INSERT INTO jenis_iuran (nama, nominal_default, deskripsi) VALUES (?, ?, ?)");
+ $stmt->execute([$nama, $nominal, $deskripsi]);
+ $message = '
Jenis iuran berhasil ditambahkan.
';
+ }
+ }
+ } elseif ($action === 'delete') {
+ $id = $_POST['id'] ?? 0;
+ try {
+ $stmt = $db->prepare("DELETE FROM jenis_iuran WHERE id = ?");
+ $stmt->execute([$id]);
+ $message = '
Jenis iuran berhasil dihapus.
';
+ } catch (Exception $e) {
+ $message = '
Gagal menghapus! Jenis iuran ini mungkin sudah memiliki data pembayaran terkait.
';
+ }
+ }
+}
+
+$items = $db->query("SELECT * FROM jenis_iuran ORDER BY nama ASC")->fetchAll();
+?>
+
+
+
Jenis Iuran
+
+ Tambah Iuran
+
+
+
+= $message ?>
+
+
+
+
+
+
+ Nama Iuran
+ Nominal Default
+ Deskripsi
+ Aksi
+
+
+
+
+
+ = htmlspecialchars($item['nama']) ?>
+ Rp = number_format((float)$item['nominal_default'], 0, ',', '.') ?>
+ = htmlspecialchars($item['deskripsi']) ?>
+
+
+
+
+
+
+
+
+
+
+ Belum ada data jenis iuran.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/laporan.php b/laporan.php
new file mode 100644
index 0000000..96d9c1e
--- /dev/null
+++ b/laporan.php
@@ -0,0 +1,214 @@
+query("SELECT * FROM jenis_iuran ORDER BY nama ASC")->fetchAll();
+
+// Handle Export CSV
+if (isset($_GET['export'])) {
+ header('Content-Type: text/csv');
+ header('Content-Disposition: attachment; filename="laporan_siwarga_'.date('Ymd').'.csv"');
+
+ $output = fopen('php://output', 'w');
+ fputcsv($output, ['Tanggal', 'Tipe', 'Kategori/Iuran', 'Warga', 'Keterangan', 'Masuk', 'Keluar']);
+
+ $sql = "
+ SELECT tanggal_bayar as tgl, 'Masuk' as tipe, j.nama as kategori, w.nama as warga, p.catatan as ket, p.jumlah as masuk, 0 as keluar
+ FROM pembayaran p
+ JOIN warga w ON p.warga_id = w.id
+ JOIN jenis_iuran j ON p.jenis_iuran_id = j.id
+ WHERE p.tanggal_bayar BETWEEN ? AND ?
+ ";
+ $params = [$start_date, $end_date];
+ if ($jenis_iuran_id) {
+ $sql .= " AND p.jenis_iuran_id = ?";
+ $params[] = $jenis_iuran_id;
+ }
+
+ $sql .= " UNION ALL
+ SELECT tanggal as tgl, 'Keluar' as tipe, IFNULL(j.nama, 'Kas Umum') as kategori, '-' as warga, keterangan as ket, 0 as masuk, jumlah as keluar
+ FROM pengeluaran e
+ LEFT JOIN jenis_iuran j ON e.jenis_iuran_id = j.id
+ WHERE e.tanggal BETWEEN ? AND ?
+ ";
+ $params[] = $start_date;
+ $params[] = $end_date;
+ if ($jenis_iuran_id) {
+ $sql .= " AND e.jenis_iuran_id = ?";
+ $params[] = $jenis_iuran_id;
+ }
+
+ $sql .= " ORDER BY tgl ASC";
+
+ $stmt = $db->prepare($sql);
+ $stmt->execute($params);
+
+ while ($row = $stmt->fetch()) {
+ fputcsv($output, [
+ $row['tgl'], $row['tipe'], $row['kategori'], $row['warga'], $row['ket'], $row['masuk'], $row['keluar']
+ ]);
+ }
+ fclose($output);
+ exit;
+}
+
+// Fetch Report Data
+$params = [$start_date, $end_date];
+$where_p = "WHERE p.tanggal_bayar BETWEEN ? AND ?";
+$where_e = "WHERE e.tanggal BETWEEN ? AND ?";
+
+if ($jenis_iuran_id) {
+ $where_p .= " AND p.jenis_iuran_id = ?";
+ $where_e .= " AND e.jenis_iuran_id = ?";
+ $params = [$start_date, $end_date, $jenis_iuran_id];
+}
+
+$pemasukan = $db->prepare("
+ SELECT p.*, w.nama as nama_warga, j.nama as nama_iuran
+ FROM pembayaran p
+ JOIN warga w ON p.warga_id = w.id
+ JOIN jenis_iuran j ON p.jenis_iuran_id = j.id
+ $where_p
+ ORDER BY p.tanggal_bayar DESC
+");
+$pemasukan->execute($params);
+$data_pemasukan = $pemasukan->fetchAll();
+
+$pengeluaran = $db->prepare("
+ SELECT e.*, j.nama as nama_iuran
+ FROM pengeluaran e
+ LEFT JOIN jenis_iuran j ON e.jenis_iuran_id = j.id
+ $where_e
+ ORDER BY e.tanggal DESC
+");
+$pengeluaran->execute($params);
+$data_pengeluaran = $pengeluaran->fetchAll();
+
+$total_masuk = array_sum(array_column($data_pemasukan, 'jumlah'));
+$total_keluar = array_sum(array_column($data_pengeluaran, 'jumlah'));
+?>
+
+
+
+
+
+
+ Jenis Iuran
+
+ Semua Iuran
+
+ >= htmlspecialchars($i['nama']) ?>
+
+
+
+
+ Dari Tanggal
+
+
+
+ Sampai Tanggal
+
+
+
+ Filter Laporan
+
+
+
+
+
+
+
+
Total Pemasukan
+
Rp = number_format($total_masuk, 0, ',', '.') ?>
+
+
+
+
+
Total Pengeluaran
+
Rp = number_format($total_keluar, 0, ',', '.') ?>
+
+
+
+
+
Saldo Akhir Periode
+
Rp = number_format($total_masuk - $total_keluar, 0, ',', '.') ?>
+
+
+
+
+
+
+
+
Rincian Pemasukan
+
+
+
+
+ Tgl
+ Warga / Iuran
+ Jumlah
+
+
+
+
+
+ = date('d/m', strtotime($p['tanggal_bayar'])) ?>
+
+ = htmlspecialchars($p['nama_warga']) ?>
+ = htmlspecialchars($p['nama_iuran']) ?> (= $p['periode'] ?>)
+
+ Rp = number_format($p['jumlah'], 0, ',', '.') ?>
+
+
+
+ Tidak ada data.
+
+
+
+
+
+
+
+
+
Rincian Pengeluaran
+
+
+
+
+ Tgl
+ Kategori / Keterangan
+ Jumlah
+
+
+
+
+
+ = date('d/m', strtotime($e['tanggal'])) ?>
+
+ = htmlspecialchars($e['kategori']) ?>
+ = htmlspecialchars($e['keterangan']) ?>
+
+ Rp = number_format($e['jumlah'], 0, ',', '.') ?>
+
+
+
+ Tidak ada data.
+
+
+
+
+
+
+
+
+
diff --git a/layout_footer.php b/layout_footer.php
new file mode 100644
index 0000000..2e6db5b
--- /dev/null
+++ b/layout_footer.php
@@ -0,0 +1,6 @@
+
+
+
+
+