Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
4e3818aac9 SiWarga 2026-02-15 18:49:39 +00:00
17 changed files with 1755 additions and 146 deletions

119
assets/css/custom.css Normal file
View File

@ -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;
}

13
assets/js/main.js Normal file
View File

@ -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);
});
});

View File

@ -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');

View File

@ -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);

View File

@ -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');

290
index.php
View File

@ -1,150 +1,152 @@
<?php <?php
declare(strict_types=1); require_once __DIR__ . '/layout_header.php';
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION; // Fetch stats
$now = date('Y-m-d H:i:s'); $db = db();
$total_warga = $db->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();
?> ?>
<!doctype html>
<html lang="en"> <div class="top-bar">
<head> <h1 class="h3 fw-bold m-0">Dashboard</h1>
<meta charset="utf-8" /> <div class="text-muted"><?= date('d F Y') ?></div>
<meta name="viewport" content="width=device-width, initial-scale=1" /> </div>
<title>New Style</title>
<?php <div class="row g-4 mb-4">
// Read project preview data from environment <div class="col-md-3">
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; <div class="card stats-card">
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; <span class="label">Total Warga Aktif</span>
?> <span class="value"><?= number_format($total_warga) ?></span>
<?php if ($projectDescription): ?> </div>
<!-- Meta description --> </div>
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' /> <div class="col-md-3">
<!-- Open Graph meta tags --> <div class="card stats-card">
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" /> <span class="label">Saldo Kas (Total)</span>
<!-- Twitter meta tags --> <span class="value text-success">Rp <?= number_format($saldo, 0, ',', '.') ?></span>
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" /> </div>
<?php endif; ?> </div>
<?php if ($projectImageUrl): ?> <div class="col-md-3">
<!-- Open Graph image --> <div class="card stats-card">
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" /> <span class="label">Total Pemasukan</span>
<!-- Twitter image --> <span class="value text-primary">Rp <?= number_format($total_pemasukan, 0, ',', '.') ?></span>
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" /> </div>
<?php endif; ?> </div>
<link rel="preconnect" href="https://fonts.googleapis.com"> <div class="col-md-3">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <div class="card stats-card">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet"> <span class="label">Total Pengeluaran</span>
<style> <span class="value text-danger">Rp <?= number_format($total_pengeluaran, 0, ',', '.') ?></span>
:root { </div>
--bg-color-start: #6a11cb; </div>
--bg-color-end: #2575fc; </div>
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01); <div class="row g-4">
--card-border-color: rgba(255, 255, 255, 0.1); <div class="col-md-8">
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head>
<body>
<main>
<div class="card"> <div class="card">
<h1>Analyzing your requirements and generating your website…</h1> <div class="d-flex justify-content-between align-items-center mb-4">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <h2 class="h5 fw-bold m-0">Pembayaran Terbaru</h2>
<span class="sr-only">Loading…</span> <a href="/pembayaran.php" class="btn btn-sm btn-outline-primary">Lihat Semua</a>
</div> </div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p> <div class="table-responsive">
<p class="hint">This page will update automatically as the plan is implemented.</p> <table class="table table-hover">
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p> <thead>
<tr>
<th>Warga</th>
<th>Iuran</th>
<th>Periode</th>
<th>Jumlah</th>
<th>Tanggal</th>
</tr>
</thead>
<tbody>
<?php if (empty($recent_payments)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted">Belum ada data pembayaran.</td>
</tr>
<?php else: ?>
<?php foreach ($recent_payments as $p): ?>
<tr>
<td><?= htmlspecialchars($p['nama_warga']) ?></td>
<td><?= htmlspecialchars($p['nama_iuran']) ?></td>
<td><?= $p['periode'] ?></td>
<td>Rp <?= number_format($p['jumlah'], 0, ',', '.') ?></td>
<td><?= date('d/m/Y', strtotime($p['tanggal_bayar'])) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div> </div>
</main> </div>
<footer> </div>
Page updated: <?= htmlspecialchars($now) ?> (UTC) <div class="col-md-4">
</footer> <div class="card mb-4">
</body> <h2 class="h5 fw-bold mb-4">Struktur Pengurus</h2>
</html> <?php if (empty($pengurus)): ?>
<p class="text-muted small">Belum ada data pengurus.</p>
<?php else: ?>
<ul class="list-unstyled mb-0">
<?php foreach ($pengurus as $pg): ?>
<li class="mb-3 d-flex align-items-center gap-3">
<div class="bg-light rounded-circle p-2" style="width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-person"></i>
</div>
<div>
<div class="fw-bold"><?= htmlspecialchars($pg['jabatan']) ?></div>
<div class="text-muted small"><?= htmlspecialchars($pg['nama']) ?></div>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<div class="card">
<h2 class="h5 fw-bold mb-4">Ringkasan per Iuran</h2>
<?php
$iuran_stats = $db->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();
?>
<?php foreach ($iuran_stats as $is): ?>
<?php
$masuk = $is['masuk'] ?: 0;
$keluar = $is['keluar'] ?: 0;
$saldo_iuran = $masuk - $keluar;
?>
<div class="mb-3 pb-3 border-bottom last-child-no-border">
<div class="d-flex justify-content-between mb-1">
<span class="fw-medium small"><?= htmlspecialchars($is['nama']) ?></span>
<span class="fw-bold small">Rp <?= number_format($saldo_iuran, 0, ',', '.') ?></span>
</div>
<div class="progress mb-1" style="height: 4px;">
<?php
$percent = $masuk > 0 ? min(100, ($saldo_iuran / $masuk) * 100) : 0;
if ($saldo_iuran < 0) $percent = 0;
?>
<div class="progress-bar bg-primary" role="progressbar" style="width: <?= $percent ?>%"></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php require_once __DIR__ . '/layout_footer.php'; ?>

139
iuran.php Normal file
View File

@ -0,0 +1,139 @@
<?php
require_once __DIR__ . '/layout_header.php';
$db = db();
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'save') {
$id = $_POST['id'] ?? null;
$nama = $_POST['nama'] ?? '';
$nominal = $_POST['nominal'] ?? 0;
$deskripsi = $_POST['deskripsi'] ?? '';
if ($nama) {
if ($id) {
$stmt = $db->prepare("UPDATE jenis_iuran SET nama=?, nominal_default=?, deskripsi=? WHERE id=?");
$stmt->execute([$nama, $nominal, $deskripsi, $id]);
$message = '<div class="alert alert-success">Jenis iuran berhasil diperbarui.</div>';
} else {
$stmt = $db->prepare("INSERT INTO jenis_iuran (nama, nominal_default, deskripsi) VALUES (?, ?, ?)");
$stmt->execute([$nama, $nominal, $deskripsi]);
$message = '<div class="alert alert-success">Jenis iuran berhasil ditambahkan.</div>';
}
}
} elseif ($action === 'delete') {
$id = $_POST['id'] ?? 0;
try {
$stmt = $db->prepare("DELETE FROM jenis_iuran WHERE id = ?");
$stmt->execute([$id]);
$message = '<div class="alert alert-success">Jenis iuran berhasil dihapus.</div>';
} catch (Exception $e) {
$message = '<div class="alert alert-danger">Gagal menghapus! Jenis iuran ini mungkin sudah memiliki data pembayaran terkait.</div>';
}
}
}
$items = $db->query("SELECT * FROM jenis_iuran ORDER BY nama ASC")->fetchAll();
?>
<div class="top-bar">
<h1 class="h3 fw-bold m-0">Jenis Iuran</h1>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#iuranModal" onclick="resetForm()">
<i class="bi bi-plus-lg"></i> Tambah Iuran
</button>
</div>
<?= $message ?>
<div class="card">
<div class="table-responsive">
<table class="table align-middle table-hover">
<thead>
<tr>
<th>Nama Iuran</th>
<th>Nominal Default</th>
<th>Deskripsi</th>
<th class="text-end">Aksi</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><span class="fw-bold"><?= htmlspecialchars($item['nama']) ?></span></td>
<td>Rp <?= number_format((float)$item['nominal_default'], 0, ',', '.') ?></td>
<td class="text-muted small"><?= htmlspecialchars($item['deskripsi']) ?></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary me-1" onclick='editIuran(<?= json_encode($item) ?>)'>
<i class="bi bi-pencil"></i>
</button>
<form method="POST" style="display:inline;" onsubmit="return confirm('Yakin ingin menghapus?');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $item['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($items)): ?>
<tr>
<td colspan="4" class="text-center py-4 text-muted">Belum ada data jenis iuran.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="iuranModal" tabindex="-1">
<div class="modal-dialog">
<form method="POST" class="modal-content">
<input type="hidden" name="action" value="save">
<div class="modal-header">
<h5 class="modal-title" id="modalTitle">Tambah Jenis Iuran</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id" id="iuranId">
<div class="mb-3">
<label class="form-label">Nama Iuran</label>
<input type="text" name="nama" id="iuranNama" class="form-control" placeholder="Contoh: Iuran Keamanan" required>
</div>
<div class="mb-3">
<label class="form-label">Nominal Default (Rp)</label>
<input type="number" name="nominal" id="iuranNominal" class="form-control" value="0" required>
</div>
<div class="mb-3">
<label class="form-label">Deskripsi</label>
<textarea name="deskripsi" id="iuranDeskripsi" class="form-control" rows="3" placeholder="Keterangan singkat..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Simpan</button>
</div>
</form>
</div>
</div>
<script>
function editIuran(item) {
document.getElementById('modalTitle').innerText = 'Edit Jenis Iuran';
document.getElementById('iuranId').value = item.id;
document.getElementById('iuranNama').value = item.nama;
document.getElementById('iuranNominal').value = item.nominal_default;
document.getElementById('iuranDeskripsi').value = item.deskripsi;
new bootstrap.Modal(document.getElementById('iuranModal')).show();
}
function resetForm() {
document.getElementById('modalTitle').innerText = 'Tambah Jenis Iuran';
document.getElementById('iuranId').value = '';
document.getElementById('iuranNama').value = '';
document.getElementById('iuranNominal').value = '0';
document.getElementById('iuranDeskripsi').value = '';
}
</script>
<?php require_once __DIR__ . '/layout_footer.php'; ?>

214
laporan.php Normal file
View File

@ -0,0 +1,214 @@
<?php
require_once __DIR__ . '/layout_header.php';
$db = db();
$jenis_iuran_id = $_GET['iuran_id'] ?? null;
$start_date = $_GET['start_date'] ?? date('Y-m-01');
$end_date = $_GET['end_date'] ?? date('Y-m-d');
// Fetch Jenis Iuran for filter
$iuran_list = $db->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'));
?>
<div class="top-bar">
<h1 class="h3 fw-bold m-0">Laporan Keuangan</h1>
<a href="?export=1&iuran_id=<?= $jenis_iuran_id ?>&start_date=<?= $start_date ?>&end_date=<?= $end_date ?>" class="btn btn-outline-success">
<i class="bi bi-file-earmark-spreadsheet"></i> Export CSV
</a>
</div>
<div class="card mb-4">
<form method="GET" class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label small">Jenis Iuran</label>
<select name="iuran_id" class="form-select">
<option value="">Semua Iuran</option>
<?php foreach ($iuran_list as $i): ?>
<option value="<?= $i['id'] ?>" <?= $jenis_iuran_id == $i['id'] ? 'selected' : '' ?>><?= htmlspecialchars($i['nama']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Dari Tanggal</label>
<input type="date" name="start_date" class="form-control" value="<?= $start_date ?>">
</div>
<div class="col-md-3">
<label class="form-label small">Sampai Tanggal</label>
<input type="date" name="end_date" class="form-control" value="<?= $end_date ?>">
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary w-100">Filter Laporan</button>
</div>
</form>
</div>
<div class="row g-4 mb-4">
<div class="col-md-4">
<div class="card text-center">
<div class="text-muted small mb-1">Total Pemasukan</div>
<div class="h4 fw-bold text-primary">Rp <?= number_format($total_masuk, 0, ',', '.') ?></div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center">
<div class="text-muted small mb-1">Total Pengeluaran</div>
<div class="h4 fw-bold text-danger">Rp <?= number_format($total_keluar, 0, ',', '.') ?></div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center">
<div class="text-muted small mb-1">Saldo Akhir Periode</div>
<div class="h4 fw-bold text-success">Rp <?= number_format($total_masuk - $total_keluar, 0, ',', '.') ?></div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-md-6">
<div class="card">
<h2 class="h5 fw-bold mb-4">Rincian Pemasukan</h2>
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead>
<tr>
<th>Tgl</th>
<th>Warga / Iuran</th>
<th class="text-end">Jumlah</th>
</tr>
</thead>
<tbody>
<?php foreach ($data_pemasukan as $p): ?>
<tr>
<td><span class="small"><?= date('d/m', strtotime($p['tanggal_bayar'])) ?></span></td>
<td>
<div class="fw-bold small"><?= htmlspecialchars($p['nama_warga']) ?></div>
<div class="text-muted" style="font-size: 0.7rem;"><?= htmlspecialchars($p['nama_iuran']) ?> (<?= $p['periode'] ?>)</div>
</td>
<td class="text-end fw-bold text-primary small">Rp <?= number_format($p['jumlah'], 0, ',', '.') ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($data_pemasukan)): ?>
<tr><td colspan="3" class="text-center py-4 text-muted">Tidak ada data.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<h2 class="h5 fw-bold mb-4">Rincian Pengeluaran</h2>
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead>
<tr>
<th>Tgl</th>
<th>Kategori / Keterangan</th>
<th class="text-end">Jumlah</th>
</tr>
</thead>
<tbody>
<?php foreach ($data_pengeluaran as $e): ?>
<tr>
<td><span class="small"><?= date('d/m', strtotime($e['tanggal'])) ?></span></td>
<td>
<div class="fw-bold small"><?= htmlspecialchars($e['kategori']) ?></div>
<div class="text-muted" style="font-size: 0.7rem;"><?= htmlspecialchars($e['keterangan']) ?></div>
</td>
<td class="text-end fw-bold text-danger small">Rp <?= number_format($e['jumlah'], 0, ',', '.') ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($data_pengeluaran)): ?>
<tr><td colspan="3" class="text-center py-4 text-muted">Tidak ada data.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/layout_footer.php'; ?>

6
layout_footer.php Normal file
View File

@ -0,0 +1,6 @@
</div> <!-- End main-content -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/assets/js/main.js?v=<?= time() ?>"></script>
</body>
</html>

80
layout_header.php Normal file
View File

@ -0,0 +1,80 @@
<?php
session_start();
require_once __DIR__ . '/db/config.php';
// Cek autentikasi
if (!isset($_SESSION['user_id']) && basename($_SERVER['PHP_SELF']) !== 'login.php') {
header('Location: login.php');
exit;
}
$current_page = basename($_SERVER['PHP_SELF']);
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $_SERVER['PROJECT_NAME'] ?? 'SiWarga' ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/assets/css/custom.css?v=<?= time() ?>">
<meta name="description" content="<?= htmlspecialchars($_SERVER['PROJECT_DESCRIPTION'] ?? '') ?>">
</head>
<body>
<div class="sidebar">
<div class="sidebar-brand">
<i class="bi bi-house-door-fill text-primary"></i>
<span>SiWarga</span>
</div>
<nav class="nav flex-column">
<a class="nav-link <?= $current_page == 'index.php' ? 'active' : '' ?>" href="/index.php">
<i class="bi bi-speedometer2"></i> Dashboard
</a>
<a class="nav-link <?= $current_page == 'warga.php' ? 'active' : '' ?>" href="/warga.php">
<i class="bi bi-people"></i> Warga
</a>
<a class="nav-link <?= $current_page == 'iuran.php' ? 'active' : '' ?>" href="/iuran.php">
<i class="bi bi-cash-stack"></i> Jenis Iuran
</a>
<a class="nav-link <?= $current_page == 'pembayaran.php' ? 'active' : '' ?>" href="/pembayaran.php">
<i class="bi bi-wallet2"></i> Pembayaran
</a>
<a class="nav-link <?= $current_page == 'pengeluaran.php' ? 'active' : '' ?>" href="/pengeluaran.php">
<i class="bi bi-cart-dash"></i> Pengeluaran
</a>
<a class="nav-link <?= $current_page == 'laporan.php' ? 'active' : '' ?>" href="/laporan.php">
<i class="bi bi-file-earmark-text"></i> Laporan
</a>
<a class="nav-link <?= $current_page == 'pengurus.php' ? 'active' : '' ?>" href="/pengurus.php">
<i class="bi bi-diagram-3"></i> Kepengurusan
</a>
<?php if (($_SESSION['role'] ?? '') === 'admin'): ?>
<a class="nav-link <?= $current_page == 'users.php' ? 'active' : '' ?>" href="/users.php">
<i class="bi bi-person-gear"></i> Manajemen Pengguna
</a>
<?php endif; ?>
<hr class="text-secondary mx-3">
<a class="nav-link text-danger" href="/logout.php">
<i class="bi bi-box-arrow-right"></i> Logout
</a>
</nav>
</div>
<div class="main-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0">Aplikasi SiWarga</h4>
<div class="user-profile d-flex align-items-center gap-2">
<div class="text-end d-none d-md-block">
<div class="fw-semibold" style="font-size: 0.875rem;"><?= htmlspecialchars($_SESSION['nama_lengkap'] ?? 'User') ?></div>
<div class="text-muted" style="font-size: 0.75rem;"><?= ucfirst($_SESSION['role'] ?? 'Admin') ?></div>
</div>
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<i class="bi bi-person"></i>
</div>
</div>
</div>

122
login.php Normal file
View File

@ -0,0 +1,122 @@
<?php
session_start();
require_once 'db/config.php';
// Jika sudah login, redirect ke dashboard
if (isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if (!empty($username) && !empty($password)) {
try {
$stmt = db()->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['nama_lengkap'] = $user['nama_lengkap'];
$_SESSION['role'] = $user['role'];
header('Location: index.php');
exit;
} else {
$error = 'Username atau password salah.';
}
} catch (PDOException $e) {
$error = 'Terjadi kesalahan sistem.';
}
} else {
$error = 'Silakan isi username dan password.';
}
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login SiWarga - Aplikasi Administrasi RT/RW</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f8fafc;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-card {
width: 100%;
max-width: 400px;
padding: 2rem;
border-radius: 1rem;
background: white;
box-shadow: 0 10px 25px rgba(0,0,0,0.05);
}
.btn-primary {
background-color: #0f172a;
border: none;
padding: 0.75rem;
font-weight: 600;
}
.btn-primary:hover {
background-color: #1e293b;
}
.logo {
text-align: center;
margin-bottom: 2rem;
}
.logo h2 {
font-weight: 700;
color: #0f172a;
margin-bottom: 0;
}
.logo p {
color: #64748b;
font-size: 0.875rem;
}
</style>
</head>
<body>
<div class="login-card">
<div class="logo">
<h2>SiWarga</h2>
<p>Sistem Administrasi RT/RW Digital</p>
</div>
<?php if ($error): ?>
<div class="alert alert-danger" role="alert">
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required autofocus>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-4">
<div class="form-text">
Gunakan <b>admin</b> / <b>password123</b> untuk demo.
</div>
</div>
<button type="submit" class="btn btn-primary w-100">Login ke Sistem</button>
</form>
</div>
</body>
</html>

5
logout.php Normal file
View File

@ -0,0 +1,5 @@
<?php
session_start();
session_destroy();
header('Location: login.php');
exit;

189
pembayaran.php Normal file
View File

@ -0,0 +1,189 @@
<?php
require_once __DIR__ . '/layout_header.php';
$db = db();
$message = '';
// Handle Delete
if (isset($_GET['delete'])) {
$id = (int)$_GET['delete'];
$db->prepare("DELETE FROM pembayaran WHERE id = ?")->execute([$id]);
$message = '<div class="alert alert-success">Catatan pembayaran berhasil dihapus.</div>';
}
// Handle Add
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['save_pembayaran'])) {
$warga_id = $_POST['warga_id'];
$jenis_iuran_id = $_POST['jenis_iuran_id'];
$periode_awal = $_POST['periode_awal']; // Format YYYY-MM
$jumlah_bulan = (int)$_POST['jumlah_bulan'];
$nominal = (float)$_POST['nominal'];
$tanggal_bayar = $_POST['tanggal_bayar'];
$catatan = $_POST['catatan'];
try {
$db->beginTransaction();
$current_periode = $periode_awal;
for ($i = 0; $i < $jumlah_bulan; $i++) {
// Check if already paid
$check = $db->prepare("SELECT id FROM pembayaran WHERE warga_id=? AND jenis_iuran_id=? AND periode=?");
$check->execute([$warga_id, $jenis_iuran_id, $current_periode]);
if ($check->fetch()) {
throw new Exception("Periode $current_periode sudah pernah dibayar untuk warga ini.");
}
$stmt = $db->prepare("INSERT INTO pembayaran (warga_id, jenis_iuran_id, periode, jumlah, tanggal_bayar, catatan) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$warga_id, $jenis_iuran_id, $current_periode, $nominal, $tanggal_bayar, $catatan]);
// Advance to next month
$current_periode = date('Y-m', strtotime($current_periode . " +1 month"));
}
$db->commit();
$message = '<div class="alert alert-success">Pembayaran untuk '.$jumlah_bulan.' periode berhasil dicatat.</div>';
} catch (Exception $e) {
$db->rollBack();
$message = '<div class="alert alert-danger">Gagal: ' . $e->getMessage() . '</div>';
}
}
// Fetch lists
$warga_list = $db->query("SELECT id, nama, blok, nomor_rumah FROM warga WHERE status='aktif' ORDER BY nama ASC")->fetchAll();
$iuran_list = $db->query("SELECT * FROM jenis_iuran ORDER BY nama ASC")->fetchAll();
// Pagination or Limit for history
$history = $db->query("
SELECT p.*, w.nama as nama_warga, w.blok, w.nomor_rumah, 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, p.id DESC
LIMIT 100
")->fetchAll();
?>
<div class="top-bar">
<h1 class="h3 fw-bold m-0">Pembayaran Iuran</h1>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#bayarModal">
<i class="bi bi-plus-lg"></i> Catat Pembayaran
</button>
</div>
<?= $message ?>
<div class="card">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="h5 fw-bold m-0">Riwayat Pembayaran Terbaru</h2>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Tanggal</th>
<th>Warga</th>
<th>Iuran</th>
<th>Periode</th>
<th>Jumlah</th>
<th class="text-end">Aksi</th>
</tr>
</thead>
<tbody>
<?php foreach ($history as $h): ?>
<tr>
<td><?= date('d/m/Y', strtotime($h['tanggal_bayar'])) ?></td>
<td>
<div class="fw-bold"><?= htmlspecialchars($h['nama_warga']) ?></div>
<div class="small text-muted">Blok <?= htmlspecialchars($h['blok']) ?>/<?= htmlspecialchars($h['nomor_rumah']) ?></div>
</td>
<td><?= htmlspecialchars($h['nama_iuran']) ?></td>
<td><span class="badge bg-light text-dark"><?= $h['periode'] ?></span></td>
<td>Rp <?= number_format($h['jumlah'], 0, ',', '.') ?></td>
<td class="text-end">
<a href="?delete=<?= $h['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Yakin ingin menghapus?')">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($history)): ?>
<tr>
<td colspan="6" class="text-center py-4 text-muted">Belum ada riwayat pembayaran.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="bayarModal" tabindex="-1">
<div class="modal-dialog">
<form method="POST" class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Catat Pembayaran Baru</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Warga</label>
<select name="warga_id" class="form-select" required>
<option value="">-- Pilih Warga --</option>
<?php foreach ($warga_list as $w): ?>
<option value="<?= $w['id'] ?>"><?= htmlspecialchars($w['nama']) ?> (<?= $w['blok'] ?>/<?= $w['nomor_rumah'] ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Jenis Iuran</label>
<select name="jenis_iuran_id" id="select_iuran" class="form-select" required onchange="updateNominal()">
<option value="">-- Pilih Iuran --</option>
<?php foreach ($iuran_list as $i): ?>
<option value="<?= $i['id'] ?>" data-nominal="<?= $i['nominal_default'] ?>"><?= htmlspecialchars($i['nama']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Mulai Periode</label>
<input type="month" name="periode_awal" class="form-control" value="<?= date('Y-m') ?>" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Jumlah Bulan</label>
<input type="number" name="jumlah_bulan" class="form-control" value="1" min="1" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">Nominal per Bulan (Rp)</label>
<input type="number" name="nominal" id="input_nominal" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Tanggal Bayar</label>
<input type="date" name="tanggal_bayar" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Catatan (Opsional)</label>
<textarea name="catatan" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" name="save_pembayaran" class="btn btn-primary">Simpan Pembayaran</button>
</div>
</form>
</div>
</div>
<script>
function updateNominal() {
const select = document.getElementById('select_iuran');
const option = select.options[select.selectedIndex];
const nominal = option.getAttribute('data-nominal');
if (nominal) {
document.getElementById('input_nominal').value = nominal;
}
}
</script>
<?php require_once __DIR__ . '/layout_footer.php'; ?>

134
pengeluaran.php Normal file
View File

@ -0,0 +1,134 @@
<?php
require_once __DIR__ . '/layout_header.php';
$db = db();
$message = '';
// Handle Delete
if (isset($_GET['delete'])) {
$id = (int)$_GET['delete'];
$db->prepare("DELETE FROM pengeluaran WHERE id = ?")->execute([$id]);
$message = '<div class="alert alert-success">Catatan pengeluaran berhasil dihapus.</div>';
}
// Handle Add
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['save_pengeluaran'])) {
$jenis_iuran_id = $_POST['jenis_iuran_id'] ?: null;
$kategori = $_POST['kategori'];
$jumlah = $_POST['jumlah'];
$tanggal = $_POST['tanggal'];
$keterangan = $_POST['keterangan'];
$stmt = $db->prepare("INSERT INTO pengeluaran (jenis_iuran_id, kategori, jumlah, tanggal, keterangan) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$jenis_iuran_id, $kategori, $jumlah, $tanggal, $keterangan]);
$message = '<div class="alert alert-success">Pengeluaran berhasil dicatat.</div>';
}
// Fetch lists
$iuran_list = $db->query("SELECT * FROM jenis_iuran ORDER BY nama ASC")->fetchAll();
$pengeluaran_list = $db->query("
SELECT e.*, j.nama as nama_iuran
FROM pengeluaran e
LEFT JOIN jenis_iuran j ON e.jenis_iuran_id = j.id
ORDER BY e.tanggal DESC, e.id DESC
")->fetchAll();
?>
<div class="top-bar">
<h1 class="h3 fw-bold m-0">Pengeluaran Kas</h1>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#keluarModal">
<i class="bi bi-plus-lg"></i> Catat Pengeluaran
</button>
</div>
<?= $message ?>
<div class="card">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Tanggal</th>
<th>Kategori / Keterangan</th>
<th>Sumber Dana</th>
<th>Jumlah</th>
<th class="text-end">Aksi</th>
</tr>
</thead>
<tbody>
<?php foreach ($pengeluaran_list as $e): ?>
<tr>
<td><?= date('d/m/Y', strtotime($e['tanggal'])) ?></td>
<td>
<div class="fw-bold"><?= htmlspecialchars($e['kategori']) ?></div>
<div class="small text-muted"><?= htmlspecialchars($e['keterangan']) ?></div>
</td>
<td>
<?php if ($e['nama_iuran']): ?>
<span class="badge bg-info text-dark">Iuran: <?= htmlspecialchars($e['nama_iuran']) ?></span>
<?php else: ?>
<span class="badge bg-secondary">Kas Umum</span>
<?php endif; ?>
</td>
<td class="text-danger fw-bold">-Rp <?= number_format($e['jumlah'], 0, ',', '.') ?></td>
<td class="text-end">
<a href="?delete=<?= $e['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Yakin ingin menghapus?')">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($pengeluaran_list)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted">Belum ada data pengeluaran.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="keluarModal" tabindex="-1">
<div class="modal-dialog">
<form method="POST" class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Catat Pengeluaran Baru</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Kategori Pengeluaran</label>
<input type="text" name="kategori" class="form-control" placeholder="Contoh: Perbaikan Lampu Jalan" required>
</div>
<div class="mb-3">
<label class="form-label">Sumber Dana (Ambil dari Iuran?)</label>
<select name="jenis_iuran_id" class="form-select">
<option value="">-- Kas Umum (Semua) --</option>
<?php foreach ($iuran_list as $i): ?>
<option value="<?= $i['id'] ?>"><?= htmlspecialchars($i['nama']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Jumlah Pengeluaran (Rp)</label>
<input type="number" name="jumlah" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Tanggal</label>
<input type="date" name="tanggal" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Keterangan Tambahan</label>
<textarea name="keterangan" class="form-control" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" name="save_pengeluaran" class="btn btn-primary">Simpan Pengeluaran</button>
</div>
</form>
</div>
</div>
<?php require_once __DIR__ . '/layout_footer.php'; ?>

151
pengurus.php Normal file
View File

@ -0,0 +1,151 @@
<?php
require_once __DIR__ . '/layout_header.php';
$db = db();
$message = '';
// Handle Delete
if (isset($_GET['delete'])) {
$id = (int)$_GET['delete'];
$db->prepare("DELETE FROM pengurus WHERE id = ?")->execute([$id]);
$message = '<div class="alert alert-success">Data pengurus berhasil dihapus.</div>';
}
// Handle Add/Edit
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['save_pengurus'])) {
$id = $_POST['id'] ?? null;
$nama = $_POST['nama'];
$jabatan = $_POST['jabatan'];
$telepon = $_POST['telepon'];
$urutan = (int)$_POST['urutan'];
if ($id) {
$stmt = $db->prepare("UPDATE pengurus SET nama=?, jabatan=?, telepon=?, urutan=? WHERE id=?");
$stmt->execute([$nama, $jabatan, $telepon, $urutan, $id]);
$message = '<div class="alert alert-success">Data pengurus berhasil diperbarui.</div>';
} else {
$stmt = $db->prepare("INSERT INTO pengurus (nama, jabatan, telepon, urutan) VALUES (?, ?, ?, ?)");
$stmt->execute([$nama, $jabatan, $telepon, $urutan]);
$message = '<div class="alert alert-success">Pengurus baru berhasil ditambahkan.</div>';
}
}
$pengurus_list = $db->query("SELECT * FROM pengurus ORDER BY urutan ASC")->fetchAll();
?>
<div class="top-bar">
<h1 class="h3 fw-bold m-0">Struktur Kepengurusan RT</h1>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#pengurusModal" onclick="resetForm()">
<i class="bi bi-plus-lg"></i> Tambah Pengurus
</button>
</div>
<?= $message ?>
<div class="row g-4">
<div class="col-md-8">
<div class="card">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Urutan</th>
<th>Jabatan</th>
<th>Nama</th>
<th>Telepon</th>
<th class="text-end">Aksi</th>
</tr>
</thead>
<tbody>
<?php foreach ($pengurus_list as $p): ?>
<tr>
<td><?= $p['urutan'] ?></td>
<td><span class="fw-bold"><?= htmlspecialchars($p['jabatan']) ?></span></td>
<td><?= htmlspecialchars($p['nama']) ?></td>
<td><?= htmlspecialchars($p['telepon']) ?></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary me-1" onclick='editPengurus(<?= json_encode($p) ?>)'>
<i class="bi bi-pencil"></i>
</button>
<a href="?delete=<?= $p['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Yakin ingin menghapus?')">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($pengurus_list)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted">Belum ada data pengurus.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-light border-0">
<h5 class="fw-bold mb-3">Informasi</h5>
<p class="small text-muted">Gunakan urutan untuk mengatur tampilan pengurus di Dashboard. Angka lebih kecil akan muncul lebih atas.</p>
<p class="small text-muted">Contoh: Ketua RT (1), Sekretaris (2), Bendahara (3).</p>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="pengurusModal" tabindex="-1">
<div class="modal-dialog">
<form method="POST" class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalTitle">Tambah Pengurus</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id" id="pengurusId">
<div class="mb-3">
<label class="form-label">Nama Lengkap</label>
<input type="text" name="nama" id="pengurusNama" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Jabatan</label>
<input type="text" name="jabatan" id="pengurusJabatan" class="form-control" placeholder="Contoh: Ketua RT" required>
</div>
<div class="mb-3">
<label class="form-label">Telepon</label>
<input type="text" name="telepon" id="pengurusTelepon" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Urutan Tampil</label>
<input type="number" name="urutan" id="pengurusUrutan" class="form-control" value="0">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" name="save_pengurus" class="btn btn-primary">Simpan</button>
</div>
</form>
</div>
</div>
<script>
function editPengurus(p) {
document.getElementById('modalTitle').innerText = 'Edit Pengurus';
document.getElementById('pengurusId').value = p.id;
document.getElementById('pengurusNama').value = p.nama;
document.getElementById('pengurusJabatan').value = p.jabatan;
document.getElementById('pengurusTelepon').value = p.telepon;
document.getElementById('pengurusUrutan').value = p.urutan;
new bootstrap.Modal(document.getElementById('pengurusModal')).show();
}
function resetForm() {
document.getElementById('modalTitle').innerText = 'Tambah Pengurus';
document.getElementById('pengurusId').value = '';
document.getElementById('pengurusNama').value = '';
document.getElementById('pengurusJabatan').value = '';
document.getElementById('pengurusTelepon').value = '';
document.getElementById('pengurusUrutan').value = '0';
}
</script>
<?php require_once __DIR__ . '/layout_footer.php'; ?>

172
users.php Normal file
View File

@ -0,0 +1,172 @@
<?php
require_once __DIR__ . '/layout_header.php';
// Hanya admin yang bisa akses
if ($_SESSION['role'] !== 'admin') {
echo "<div class='alert alert-danger'>Akses ditolak. Hanya Administrator yang dapat mengakses halaman ini.</div>";
require_once __DIR__ . '/layout_footer.php';
exit;
}
$success = '';
$error = '';
// Handle Delete
if (isset($_GET['delete'])) {
$id_to_delete = $_GET['delete'];
// Jangan hapus diri sendiri
if ($id_to_delete == $_SESSION['user_id']) {
$error = "Anda tidak dapat menghapus akun Anda sendiri.";
} else {
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
if ($stmt->execute([$id_to_delete])) {
$success = "Pengguna berhasil dihapus.";
} else {
$error = "Gagal menghapus pengguna.";
}
}
}
// Handle Add User
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$nama_lengkap = $_POST['nama_lengkap'] ?? '';
$role = $_POST['role'] ?? 'pengurus';
if (empty($username) || empty($password) || empty($nama_lengkap)) {
$error = "Semua field harus diisi.";
} else {
// Cek apakah username sudah ada
$check = db()->prepare("SELECT id FROM users WHERE username = ?");
$check->execute([$username]);
if ($check->fetch()) {
$error = "Username sudah digunakan.";
} else {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("INSERT INTO users (username, password, nama_lengkap, role) VALUES (?, ?, ?, ?)");
if ($stmt->execute([$username, $hashed_password, $nama_lengkap, $role])) {
$success = "Pengguna baru berhasil ditambahkan.";
} else {
$error = "Gagal menambahkan pengguna.";
}
}
}
}
// Get all users
$users = db()->query("SELECT * FROM users ORDER BY created_at DESC")->fetchAll();
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h3 mb-0">Manajemen Pengguna</h2>
<p class="text-muted">Kelola akun pengurus aplikasi SiWarga</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="bi bi-person-plus"></i> Tambah Pengguna
</button>
</div>
<?php if ($success): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?= $success ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?= $error ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th>Nama Lengkap</th>
<th>Username</th>
<th>Role</th>
<th>Terdaftar</th>
<th class="text-end">Aksi</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr>
<td>
<div class="d-flex align-items-center">
<div class="bg-light rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 32px; height: 32px;">
<i class="bi bi-person text-secondary"></i>
</div>
<span class="fw-medium"><?= htmlspecialchars($u['nama_lengkap']) ?></span>
</div>
</td>
<td><?= htmlspecialchars($u['username']) ?></td>
<td>
<span class="badge <?= $u['role'] === 'admin' ? 'bg-primary' : 'bg-info' ?>">
<?= ucfirst($u['role']) ?>
</span>
</td>
<td class="text-muted small"><?= date('d/m/Y H:i', strtotime($u['created_at'])) ?></td>
<td class="text-end">
<?php if ($u['id'] != $_SESSION['user_id']): ?>
<a href="?delete=<?= $u['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Yakin ingin menghapus pengguna ini?')">
<i class="bi bi-trash"></i>
</a>
<?php else: ?>
<span class="badge bg-light text-muted">Self</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal Tambah Pengguna -->
<div class="modal fade" id="addUserModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header border-bottom-0">
<h5 class="modal-title fw-bold">Tambah Pengguna Baru</h5>
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#addUserModal"></button>
</div>
<form method="POST">
<input type="hidden" name="action" value="add">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Nama Lengkap</label>
<input type="text" name="nama_lengkap" class="form-control" required placeholder="Contoh: Budi Santoso">
</div>
<div class="mb-3">
<label class="form-label">Username</label>
<input type="text" name="username" class="form-control" required placeholder="Untuk login">
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" name="password" class="form-control" required placeholder="Minimal 6 karakter">
</div>
<div class="mb-0">
<label class="form-label">Role</label>
<select name="role" class="form-select" required>
<option value="pengurus">Pengurus</option>
<option value="admin">Administrator</option>
</select>
</div>
</div>
<div class="modal-footer border-top-0">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary px-4">Simpan</button>
</div>
</form>
</div>
</div>
</div>
<?php require_once __DIR__ . '/layout_footer.php'; ?>

179
warga.php Normal file
View File

@ -0,0 +1,179 @@
<?php
require_once __DIR__ . '/layout_header.php';
$db = db();
$message = '';
// Handle Delete
if (isset($_GET['delete'])) {
$id = (int)$_GET['delete'];
$stmt = $db->prepare("DELETE FROM warga WHERE id = ?");
$stmt->execute([$id]);
$message = '<div class="alert alert-success">Data warga berhasil dihapus.</div>';
}
// Handle Add/Edit
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['save_warga'])) {
$id = $_POST['id'] ?? null;
$nama = $_POST['nama'];
$blok = $_POST['blok'];
$nomor_rumah = $_POST['nomor_rumah'];
$nik = $_POST['nik'];
$kk = $_POST['kk'];
$telepon = $_POST['telepon'];
$status = $_POST['status'] ?? 'aktif';
if ($id) {
$stmt = $db->prepare("UPDATE warga SET nama=?, blok=?, nomor_rumah=?, nik=?, kk=?, telepon=?, status=? WHERE id=?");
$stmt->execute([$nama, $blok, $nomor_rumah, $nik, $kk, $telepon, $status, $id]);
$message = '<div class="alert alert-success">Data warga berhasil diperbarui.</div>';
} else {
$stmt = $db->prepare("INSERT INTO warga (nama, blok, nomor_rumah, nik, kk, telepon, status) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$nama, $blok, $nomor_rumah, $nik, $kk, $telepon, $status]);
$message = '<div class="alert alert-success">Warga baru berhasil ditambahkan.</div>';
}
}
// Fetch all warga
$warga_list = $db->query("SELECT * FROM warga ORDER BY blok ASC, nomor_rumah ASC")->fetchAll();
?>
<div class="top-bar">
<h1 class="h3 fw-bold m-0">Manajemen Warga</h1>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#wargaModal" onclick="resetForm()">
<i class="bi bi-plus-lg"></i> Tambah Warga
</button>
</div>
<?= $message ?>
<div class="card">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Nama</th>
<th>Blok/No</th>
<th>NIK / KK</th>
<th>Telepon</th>
<th>Status</th>
<th class="text-end">Aksi</th>
</tr>
</thead>
<tbody>
<?php foreach ($warga_list as $w): ?>
<tr>
<td>
<div class="fw-bold"><?= htmlspecialchars($w['nama']) ?></div>
</td>
<td><?= htmlspecialchars($w['blok']) ?> / <?= htmlspecialchars($w['nomor_rumah']) ?></td>
<td>
<div class="small">NIK: <?= htmlspecialchars($w['nik']) ?></div>
<div class="small text-muted">KK: <?= htmlspecialchars($w['kk']) ?></div>
</td>
<td><?= htmlspecialchars($w['telepon']) ?></td>
<td>
<span class="badge bg-<?= $w['status'] == 'aktif' ? 'success' : 'secondary' ?>">
<?= ucfirst($w['status']) ?>
</span>
</td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary me-1" onclick='editWarga(<?= json_encode($w) ?>)'>
<i class="bi bi-pencil"></i>
</button>
<a href="?delete=<?= $w['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Yakin ingin menghapus?')">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($warga_list)): ?>
<tr>
<td colspan="6" class="text-center py-4 text-muted">Belum ada data warga.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="wargaModal" tabindex="-1">
<div class="modal-dialog">
<form method="POST" class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalTitle">Tambah Warga</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id" id="wargaId">
<div class="mb-3">
<label class="form-label">Nama Lengkap</label>
<input type="text" name="nama" id="wargaNama" class="form-control" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Blok</label>
<input type="text" name="blok" id="wargaBlok" class="form-control" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Nomor Rumah</label>
<input type="text" name="nomor_rumah" id="wargaNomor" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">NIK</label>
<input type="text" name="nik" id="wargaNIK" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Nomor KK</label>
<input type="text" name="kk" id="wargaKK" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Telepon</label>
<input type="text" name="telepon" id="wargaTelepon" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Status</label>
<select name="status" id="wargaStatus" class="form-select">
<option value="aktif">Aktif</option>
<option value="non-aktif">Non-Aktif</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" name="save_warga" class="btn btn-primary">Simpan</button>
</div>
</form>
</div>
</div>
<script>
function editWarga(warga) {
document.getElementById('modalTitle').innerText = 'Edit Warga';
document.getElementById('wargaId').value = warga.id;
document.getElementById('wargaNama').value = warga.nama;
document.getElementById('wargaBlok').value = warga.blok;
document.getElementById('wargaNomor').value = warga.nomor_rumah;
document.getElementById('wargaNIK').value = warga.nik;
document.getElementById('wargaKK').value = warga.kk;
document.getElementById('wargaTelepon').value = warga.telepon;
document.getElementById('wargaStatus').value = warga.status;
new bootstrap.Modal(document.getElementById('wargaModal')).show();
}
function resetForm() {
document.getElementById('modalTitle').innerText = 'Tambah Warga';
document.getElementById('wargaId').value = '';
document.getElementById('wargaNama').value = '';
document.getElementById('wargaBlok').value = '';
document.getElementById('wargaNomor').value = '';
document.getElementById('wargaNIK').value = '';
document.getElementById('wargaKK').value = '';
document.getElementById('wargaTelepon').value = '';
document.getElementById('wargaStatus').value = 'aktif';
}
</script>
<?php require_once __DIR__ . '/layout_footer.php'; ?>