Initial commit

This commit is contained in:
Your Name
2026-06-02 12:40:37 +07:00
commit 20607b0e75
117 changed files with 25598 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!is_array($data)) {
$data = [];
}
$db = getDB();
$households = [];
if (isset($data['household_id']) && $data['household_id'] !== null && $data['household_id'] !== '') {
if (!is_numeric($data['household_id'])) {
http_response_code(400);
echo json_encode(['error' => 'household_id must be numeric']);
exit;
}
$stmt = $db->prepare('SELECT * FROM households WHERE id = ?');
$stmt->execute([(int)$data['household_id']]);
$household = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$household) {
http_response_code(404);
echo json_encode(['error' => 'Household not found']);
exit;
}
$households[] = $household;
} else {
$stmt = $db->query('SELECT * FROM households ORDER BY id ASC');
$households = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
if (empty($households)) {
echo json_encode([
'success' => true,
'message' => 'No households to score',
'computed_count' => 0,
'scores' => []
]);
exit;
}
$scored = [];
foreach ($households as $household) {
$scoreData = computeHouseholdScores($db, $household);
upsertHouseholdScore($db, $household['id'], $scoreData);
$scored[] = [
'household_id' => (int)$household['id'],
'household_code' => $household['household_code'],
'head_name' => $household['head_name'],
'skr' => $scoreData['skr'],
'sag' => $scoreData['sag'],
'spi' => $scoreData['spi'],
'priority_level' => $scoreData['priority_level']
];
}
usort($scored, function ($a, $b) {
if ($a['spi'] === $b['spi']) {
return 0;
}
return ($a['spi'] < $b['spi']) ? 1 : -1;
});
echo json_encode([
'success' => true,
'computed_count' => count($scored),
'scores' => $scored,
'score_version' => 'v1'
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+1294
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Check authentication and authorization
requireRole(['Admin']);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
}
$data = json_decode(file_get_contents('php://input'), true);
// Validate input
if (!isset($data['username']) || !isset($data['email']) || !isset($data['password']) || !isset($data['role'])) {
http_response_code(400);
die(json_encode(['error' => 'Missing required fields: username, email, password, role']));
}
$result = createUser($data['username'], $data['email'], $data['password'], $data['role']);
if ($result['success']) {
http_response_code(201);
} else {
http_response_code(400);
}
die(json_encode($result));
+50
View File
@@ -0,0 +1,50 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!is_array($data) || !isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing household id']);
exit;
}
if (!is_numeric($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'id must be numeric']);
exit;
}
$id = (int)$data['id'];
$db = getDB();
$checkStmt = $db->prepare('SELECT id FROM households WHERE id = ?');
$checkStmt->execute([$id]);
if (!$checkStmt->fetch(PDO::FETCH_ASSOC)) {
http_response_code(404);
echo json_encode(['error' => 'Household not found']);
exit;
}
$deleteStmt = $db->prepare('DELETE FROM households WHERE id = ?');
$deleteStmt->execute([$id]);
echo json_encode([
'success' => true,
'deleted_id' => $id
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+31
View File
@@ -0,0 +1,31 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing road id']);
exit;
}
$db = getDB();
$stmt = $db->prepare('DELETE FROM roads WHERE id = ?');
$stmt->execute([$data['id']]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+51
View File
@@ -0,0 +1,51 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing masjid id']);
exit;
}
$id = (int)$data['id'];
$db = getDB();
$checkStmt = $db->prepare('SELECT id FROM masjids WHERE id = ?');
$checkStmt->execute([$id]);
if (!$checkStmt->fetch(PDO::FETCH_ASSOC)) {
http_response_code(404);
echo json_encode(['error' => 'Masjid not found']);
exit;
}
$childStmt = $db->prepare('SELECT id FROM need_points WHERE masjid_id = ?');
$childStmt->execute([$id]);
$childIds = array_map(function($row) {
return (int)$row['id'];
}, $childStmt->fetchAll(PDO::FETCH_ASSOC));
$deleteStmt = $db->prepare('DELETE FROM masjids WHERE id = ?');
$deleteStmt->execute([$id]);
echo json_encode([
'success' => true,
'deleted_need_points' => count($childIds),
'deleted_need_point_ids' => $childIds
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+39
View File
@@ -0,0 +1,39 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing point id']);
exit;
}
$id = (int)$data['id'];
$db = getDB();
$deleteStmt = $db->prepare('DELETE FROM need_points WHERE id = ?');
$deleteStmt->execute([$id]);
if ($deleteStmt->rowCount() === 0) {
http_response_code(404);
echo json_encode(['error' => 'Point berkebutuhan not found']);
exit;
}
echo json_encode(['success' => true]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+31
View File
@@ -0,0 +1,31 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing marker id']);
exit;
}
$db = getDB();
$stmt = $db->prepare('DELETE FROM markers WHERE id = ?');
$stmt->execute([$data['id']]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+31
View File
@@ -0,0 +1,31 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing parcel id']);
exit;
}
$db = getDB();
$stmt = $db->prepare('DELETE FROM land_parcels WHERE id = ?');
$stmt->execute([$data['id']]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+58
View File
@@ -0,0 +1,58 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Check authentication and authorization
requireRole(['Admin']);
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
}
$data = json_decode(file_get_contents('php://input'), true);
// Validate input
if (!isset($data['id'])) {
http_response_code(400);
die(json_encode(['error' => 'Missing required field: id']));
}
try {
$db = getDB();
// Prevent deactivating the only admin
$adminCount = $db->query('SELECT COUNT(*) as cnt FROM users WHERE role = \'Admin\' AND is_active = 1')->fetch(PDO::FETCH_ASSOC)['cnt'];
$stmt = $db->prepare('SELECT role FROM users WHERE id = ?');
$stmt->execute([$data['id']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) {
http_response_code(404);
die(json_encode(['error' => 'User not found']));
}
if ($user['role'] === 'Admin' && $adminCount <= 1) {
http_response_code(400);
die(json_encode(['error' => 'Cannot deactivate the only admin user']));
}
// Deactivate user (soft delete)
$updateStmt = $db->prepare('
UPDATE users
SET is_active = 0, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
');
$updateStmt->execute([$data['id']]);
http_response_code(200);
die(json_encode([
'success' => true,
'message' => 'User deactivated successfully'
]));
} catch (PDOException $e) {
http_response_code(500);
die(json_encode(['error' => 'Database error: ' . $e->getMessage()]));
}
+133
View File
@@ -0,0 +1,133 @@
<?php
header('Content-Type: application/geo+json');
header('Content-Disposition: attachment; filename="export.geojson"');
require_once 'config.php';
try {
$db = getDB();
// Get type parameter
$type = isset($_GET['type']) ? $_GET['type'] : 'roads';
// Validate type
$valid_types = ['roads', 'parcels', 'pontianak_areas'];
if (!in_array($type, $valid_types)) {
throw new Exception('Invalid type. Must be: ' . implode(', ', $valid_types));
}
$features = [];
if ($type === 'roads') {
// Export roads as LineString features
$stmt = $db->prepare('SELECT id, name, road_type, length_meters, condition_status, coordinates, created_at FROM roads');
$stmt->execute();
$roads = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($roads as $road) {
$coordinates = json_decode($road['coordinates'], true);
// Convert [lat, lng] to [lng, lat] for GeoJSON
$geojson_coords = [];
foreach ($coordinates as $coord) {
$geojson_coords[] = [$coord[1], $coord[0]];
}
$features[] = [
'type' => 'Feature',
'geometry' => [
'type' => 'LineString',
'coordinates' => $geojson_coords
],
'properties' => [
'id' => $road['id'],
'name' => $road['name'],
'road_type' => $road['road_type'],
'length_meters' => floatval($road['length_meters']),
'length_km' => floatval($road['length_meters']) / 1000,
'condition_status' => $road['condition_status'],
'created_at' => $road['created_at']
]
];
}
} else if ($type === 'parcels') {
// Export parcels as Polygon features
$stmt = $db->prepare('SELECT id, name, certificate_type, area_sqm, coordinates, created_at FROM land_parcels');
$stmt->execute();
$parcels = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($parcels as $parcel) {
$coordinates = json_decode($parcel['coordinates'], true);
// Convert [lat, lng] to [lng, lat] for GeoJSON
$geojson_coords = [];
foreach ($coordinates as $coord) {
$geojson_coords[] = [$coord[1], $coord[0]];
}
// Close the polygon
if ($geojson_coords[0] !== end($geojson_coords)) {
$geojson_coords[] = $geojson_coords[0];
}
$features[] = [
'type' => 'Feature',
'geometry' => [
'type' => 'Polygon',
'coordinates' => [$geojson_coords]
],
'properties' => [
'id' => $parcel['id'],
'name' => $parcel['name'],
'certificate_type' => $parcel['certificate_type'],
'area_sqm' => floatval($parcel['area_sqm']),
'area_hectares' => floatval($parcel['area_sqm']) / 10000,
'created_at' => $parcel['created_at']
]
];
}
} else if ($type === 'pontianak_areas') {
// Export Pontianak areas as features
$stmt = $db->prepare('SELECT id, name, area_type, geometry, population FROM pontianak_areas ORDER BY area_type, name');
$stmt->execute();
$areas = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($areas as $area) {
$geometry = json_decode($area['geometry'], true);
$features[] = [
'type' => 'Feature',
'geometry' => $geometry,
'properties' => [
'id' => $area['id'],
'name' => $area['name'],
'area_type' => $area['area_type'],
'population' => intval($area['population'])
]
];
}
}
// Build FeatureCollection
$geojson = [
'type' => 'FeatureCollection',
'features' => $features
];
// Output GeoJSON
echo json_encode($geojson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>
+51
View File
@@ -0,0 +1,51 @@
<?php
// Backfill missing household fields for existing need_points
header('Content-Type: application/json');
require_once 'config.php';
try {
$db = getDB();
// Fetch points missing any of the new fields
$stmt = $db->query("SELECT id, name FROM need_points WHERE household_name IS NULL OR TRIM(household_name) = '' OR head_name IS NULL OR TRIM(head_name) = '' OR economic_status IS NULL OR TRIM(economic_status) = ''");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)) {
echo json_encode(['success' => true, 'updated' => 0, 'message' => 'No need_points required backfill.']);
exit;
}
$updateStmt = $db->prepare('UPDATE need_points SET household_name = ?, head_name = ?, economic_status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?');
$firstNames = ['Ahmad','Budi','Siti','Aulia','Rizal','Dewi','Agus','Fitri','Hendra','Yulia'];
$lastNames = ['Santoso','Wijaya','Pratama','Hidayat','Saputra','Nur','Ibrahim','Setiawan','Rahman','Putri'];
$statuses = ['sangat_miskin','miskin','rentan','cukup','baik'];
$db->beginTransaction();
$updated = 0;
$examples = [];
foreach ($rows as $r) {
$id = (int)$r['id'];
$hhName = 'Keluarga ' . substr(md5($r['name'] . $id . microtime(true)), 0, 8);
$headName = $firstNames[array_rand($firstNames)] . ' ' . $lastNames[array_rand($lastNames)];
$econ = $statuses[array_rand($statuses)];
$updateStmt->execute([$hhName, $headName, $econ, $id]);
$updated++;
if (count($examples) < 8) {
$examples[] = ['id' => $id, 'household_name' => $hhName, 'head_name' => $headName, 'economic_status' => $econ];
}
}
$db->commit();
echo json_encode(['success' => true, 'updated' => $updated, 'examples' => $examples]);
} catch (Exception $e) {
if (isset($db) && $db->inTransaction()) $db->rollBack();
http_response_code(500);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
// If called from CLI, also print a newline
if (php_sapi_name() === 'cli') echo "\n";
?>
+79
View File
@@ -0,0 +1,79 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
try {
$db = getDB();
// Derive province totals from seeded need-point names.
// The peta seed data stores the region name inside need_points.name, so we
// map those region labels back to provinces and rank them by count.
$provinceMap = [
'DKI Jakarta' => ['Jakarta'],
'Jawa Barat' => ['Bandung', 'Bekasi'],
'Jawa Tengah' => ['Semarang'],
'DI Yogyakarta' => ['Yogyakarta'],
'Jawa Timur' => ['Surabaya', 'Malang'],
'Sumatera Utara' => ['Medan'],
'Sumatera Selatan' => ['Palembang'],
'Riau' => ['Pekanbaru'],
'Jambi' => ['Jambi'],
'Sumatera Barat' => ['Padang'],
'Sulawesi Selatan' => ['Makassar'],
'Sulawesi Utara' => ['Manado'],
'Sulawesi Tenggara' => ['Kendari'],
'Sulawesi Tengah' => ['Palu'],
'Kalimantan Selatan' => ['Banjarmasin'],
'Kalimantan Barat' => ['Pontianak'],
'Kalimantan Timur' => ['Samarinda'],
'Kalimantan Tengah' => ['Palangkaraya'],
'Papua' => ['Jayapura'],
'Nusa Tenggara Barat' => ['Mataram'],
'Nusa Tenggara Timur' => ['Kupang'],
'Maluku' => ['Ambon'],
'Bali' => ['Denpasar']
];
$needPoints = $db->query('SELECT name FROM need_points')->fetchAll(PDO::FETCH_COLUMN);
$provinceCounts = [];
foreach ($provinceMap as $province => $regions) {
$provinceCounts[$province] = 0;
foreach ($needPoints as $pointName) {
foreach ($regions as $region) {
if (stripos($pointName, $region) !== false) {
$provinceCounts[$province]++;
break;
}
}
}
}
arsort($provinceCounts);
$topProvinces = array_slice($provinceCounts, 0, 5, true);
$areaStats = [];
foreach ($topProvinces as $provinceName => $count) {
$areaStats[] = [
'id' => null,
'area_name' => $provinceName,
'area_type' => 'province',
'household_count' => (int)$count,
'avg_poverty_score' => 0,
'verified_count' => 0,
'assisted_count' => 0
];
}
$response = [
'success' => true,
'areas' => $areaStats
];
http_response_code(200);
die(json_encode($response));
} catch (PDOException $e) {
http_response_code(500);
die(json_encode(['error' => 'Database error: ' . $e->getMessage()]));
}
+128
View File
@@ -0,0 +1,128 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$sql = '
SELECT
ad.*,
h.household_code,
h.head_name,
h.verification_status AS household_verification_status
FROM assistance_distributions ad
JOIN households h ON h.id = ad.household_id
WHERE 1 = 1
';
$params = [];
if (isset($_GET['household_id']) && $_GET['household_id'] !== '') {
if (!is_numeric($_GET['household_id'])) {
http_response_code(400);
echo json_encode(['error' => 'household_id must be numeric']);
exit;
}
$sql .= ' AND ad.household_id = ?';
$params[] = (int)$_GET['household_id'];
}
if (isset($_GET['assistance_type']) && $_GET['assistance_type'] !== '') {
$error = null;
$assistanceType = normalizeAssistanceType($_GET['assistance_type'], $error);
if ($assistanceType === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$sql .= ' AND ad.assistance_type = ?';
$params[] = $assistanceType;
}
if (isset($_GET['delivery_status']) && $_GET['delivery_status'] !== '') {
$error = null;
$deliveryStatus = normalizeDeliveryStatus($_GET['delivery_status'], $error);
if ($deliveryStatus === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$sql .= ' AND ad.delivery_status = ?';
$params[] = $deliveryStatus;
}
if (isset($_GET['date_from']) && $_GET['date_from'] !== '') {
$dateFrom = trim((string)$_GET['date_from']);
$dateObj = DateTime::createFromFormat('Y-m-d', $dateFrom);
if (!$dateObj || $dateObj->format('Y-m-d') !== $dateFrom) {
http_response_code(400);
echo json_encode(['error' => 'date_from must be in YYYY-MM-DD format']);
exit;
}
$sql .= ' AND ad.distribution_date >= ?';
$params[] = $dateFrom;
}
if (isset($_GET['date_to']) && $_GET['date_to'] !== '') {
$dateTo = trim((string)$_GET['date_to']);
$dateObj = DateTime::createFromFormat('Y-m-d', $dateTo);
if (!$dateObj || $dateObj->format('Y-m-d') !== $dateTo) {
http_response_code(400);
echo json_encode(['error' => 'date_to must be in YYYY-MM-DD format']);
exit;
}
$sql .= ' AND ad.distribution_date <= ?';
$params[] = $dateTo;
}
$limit = 200;
if (isset($_GET['limit']) && $_GET['limit'] !== '') {
if (!is_numeric($_GET['limit'])) {
http_response_code(400);
echo json_encode(['error' => 'limit must be numeric']);
exit;
}
$limit = max(1, min((int)$_GET['limit'], 1000));
}
$sql .= ' ORDER BY ad.distribution_date DESC, ad.created_at DESC LIMIT ' . $limit;
$stmt = $db->prepare($sql);
$stmt->execute($params);
$distributions = $stmt->fetchAll(PDO::FETCH_ASSOC);
$summarySql = '
SELECT
COUNT(*) AS total_records,
COALESCE(SUM(assistance_value), 0) AS total_assistance_value,
COALESCE(SUM(CASE WHEN delivery_status = \'delivered\' THEN assistance_value ELSE 0 END), 0) AS delivered_assistance_value
FROM assistance_distributions
';
$summaryStmt = $db->query($summarySql);
$summary = $summaryStmt->fetch(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'count' => count($distributions),
'distributions' => $distributions,
'summary' => $summary
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+479
View File
@@ -0,0 +1,479 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
function decodeGeometry($rawGeometry) {
if (is_array($rawGeometry)) {
return $rawGeometry;
}
$decoded = json_decode($rawGeometry, true);
return is_array($decoded) ? $decoded : null;
}
function geometryRings($geometry) {
if (!is_array($geometry) || !isset($geometry['type']) || !isset($geometry['coordinates'])) {
return [];
}
if ($geometry['type'] === 'Polygon') {
return [$geometry['coordinates']];
}
if ($geometry['type'] === 'MultiPolygon') {
return $geometry['coordinates'];
}
return [];
}
function projectLonLatToMeters($lng, $lat, $referenceLat) {
$earthRadius = 6371000.0;
$x = deg2rad((float)$lng) * $earthRadius * cos(deg2rad((float)$referenceLat));
$y = deg2rad((float)$lat) * $earthRadius;
return [$x, $y];
}
function ringAreaSquareMeters($ring) {
if (!is_array($ring) || count($ring) < 4) {
return 0.0;
}
$sumLat = 0.0;
$count = 0;
foreach ($ring as $point) {
if (is_array($point) && count($point) >= 2) {
$sumLat += (float)$point[1];
$count++;
}
}
if ($count === 0) {
return 0.0;
}
$referenceLat = $sumLat / $count;
$projected = [];
foreach ($ring as $point) {
if (is_array($point) && count($point) >= 2) {
$projected[] = projectLonLatToMeters((float)$point[0], (float)$point[1], $referenceLat);
}
}
if (count($projected) < 4) {
return 0.0;
}
$area = 0.0;
$maxIndex = count($projected) - 1;
for ($i = 0; $i < $maxIndex; $i++) {
$area += ($projected[$i][0] * $projected[$i + 1][1]) - ($projected[$i + 1][0] * $projected[$i][1]);
}
return abs($area / 2.0);
}
function geometryAreaKm2($geometry) {
$polygons = geometryRings($geometry);
if (empty($polygons)) {
return 0.0;
}
$totalSquareMeters = 0.0;
foreach ($polygons as $polygonRings) {
if (!is_array($polygonRings) || empty($polygonRings)) {
continue;
}
$outerArea = ringAreaSquareMeters($polygonRings[0]);
$holeArea = 0.0;
for ($i = 1; $i < count($polygonRings); $i++) {
$holeArea += ringAreaSquareMeters($polygonRings[$i]);
}
$totalSquareMeters += max($outerArea - $holeArea, 0.0);
}
return $totalSquareMeters / 1000000.0;
}
function pointInRing($lng, $lat, $ring) {
if (!is_array($ring) || count($ring) < 4) {
return false;
}
$inside = false;
$lastIndex = count($ring) - 1;
for ($i = 0, $j = $lastIndex; $i <= $lastIndex; $j = $i++) {
$xi = (float)$ring[$i][0];
$yi = (float)$ring[$i][1];
$xj = (float)$ring[$j][0];
$yj = (float)$ring[$j][1];
$intersect = (($yi > $lat) !== ($yj > $lat)) &&
($lng < ($xj - $xi) * ($lat - $yi) / (($yj - $yi) ?: 1e-12) + $xi);
if ($intersect) {
$inside = !$inside;
}
}
return $inside;
}
function pointInGeometry($lng, $lat, $geometry) {
$polygons = geometryRings($geometry);
if (empty($polygons)) {
return false;
}
foreach ($polygons as $polygonRings) {
if (!is_array($polygonRings) || empty($polygonRings)) {
continue;
}
if (!pointInRing($lng, $lat, $polygonRings[0])) {
continue;
}
$insideHole = false;
for ($i = 1; $i < count($polygonRings); $i++) {
if (pointInRing($lng, $lat, $polygonRings[$i])) {
$insideHole = true;
break;
}
}
if (!$insideHole) {
return true;
}
}
return false;
}
function geometryCentroid($geometry) {
$polygons = geometryRings($geometry);
$sumLng = 0.0;
$sumLat = 0.0;
$count = 0;
foreach ($polygons as $polygonRings) {
if (!is_array($polygonRings) || empty($polygonRings) || !is_array($polygonRings[0])) {
continue;
}
foreach ($polygonRings[0] as $point) {
if (is_array($point) && count($point) >= 2) {
$sumLng += (float)$point[0];
$sumLat += (float)$point[1];
$count++;
}
}
}
if ($count === 0) {
return ['lng' => 109.33, 'lat' => -0.02];
}
return [
'lng' => $sumLng / $count,
'lat' => $sumLat / $count
];
}
function haversineMeters($lat1, $lng1, $lat2, $lng2) {
$earthRadius = 6371000.0;
$dLat = deg2rad((float)$lat2 - (float)$lat1);
$dLng = deg2rad((float)$lng2 - (float)$lng1);
$a = sin($dLat / 2) * sin($dLat / 2)
+ cos(deg2rad((float)$lat1)) * cos(deg2rad((float)$lat2))
* sin($dLng / 2) * sin($dLng / 2);
$c = 2 * atan2(sqrt($a), sqrt(max(1 - $a, 0.0)));
return $earthRadius * $c;
}
function roadCentroidLngLat($coordinates) {
if (!is_array($coordinates) || empty($coordinates)) {
return null;
}
$sumLat = 0.0;
$sumLng = 0.0;
$count = 0;
foreach ($coordinates as $pair) {
if (is_array($pair) && count($pair) >= 2 && is_numeric($pair[0]) && is_numeric($pair[1])) {
// Road coordinates are stored as [lat, lng]
$sumLat += (float)$pair[0];
$sumLng += (float)$pair[1];
$count++;
}
}
if ($count === 0) {
return null;
}
return [
'lng' => $sumLng / $count,
'lat' => $sumLat / $count
];
}
function deterministicRoadDensity($populationDensity, $areaId) {
$seed = ($areaId * 1103515245 + 12345) & 0x7fffffff;
$variation = 0.8 + (($seed % 1000) / 1000.0) * 0.6;
$base = 0.9 + (log(max($populationDensity, 1.0) + 1.0) / log(10)) * 1.4;
return round($base * $variation, 6);
}
function deterministicDamagedDensity($roadDensity, $areaId) {
$seed = ($areaId * 214013 + 2531011) & 0x7fffffff;
$ratio = 0.14 + (($seed % 1000) / 1000.0) * 0.18;
return round($roadDensity * $ratio, 6);
}
try {
$db = getDB();
// Get layer type parameter
$layer_type = isset($_GET['layer_type']) ? $_GET['layer_type'] : 'population';
$admin_level = isset($_GET['admin_level']) ? strtolower($_GET['admin_level']) : 'kecamatan';
// Validate layer type
$valid_layers = ['population', 'road_density', 'damaged_roads_density', 'city_boundary'];
if (!in_array($layer_type, $valid_layers)) {
throw new Exception('Invalid layer type. Must be: ' . implode(', ', $valid_layers));
}
// Special handling for city boundary
if ($layer_type === 'city_boundary') {
$stmt = $db->prepare('SELECT id, name, geometry FROM pontianak_areas WHERE area_type = ?');
$stmt->execute(['city']);
$cities = $stmt->fetchAll(PDO::FETCH_ASSOC);
$data_items = [];
foreach ($cities as $city) {
$data_items[] = [
'area_id' => $city['id'],
'area_name' => $city['name'],
'value' => 0, // No numeric value for boundary
'geometry' => json_decode($city['geometry'], true)
];
}
http_response_code(200);
echo json_encode([
'success' => true,
'layer_type' => 'city_boundary',
'data' => $data_items,
'min_value' => 0.0,
'max_value' => 1.0,
'admin_level' => 'city',
'value_source' => 'boundary'
]);
exit;
}
$valid_admin_levels = ['kecamatan', 'kelurahan'];
if (!in_array($admin_level, $valid_admin_levels)) {
throw new Exception('Invalid admin_level. Must be: ' . implode(', ', $valid_admin_levels));
}
// Get Pontianak areas by selected admin level
$stmt = $db->prepare('SELECT id, name, geometry, population FROM pontianak_areas WHERE area_type = ? ORDER BY name');
$stmt->execute([$admin_level]);
$areas = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($areas)) {
// Fallback to kecamatan when kelurahan data does not exist.
if ($admin_level === 'kelurahan') {
$stmt->execute(['kecamatan']);
$areas = $stmt->fetchAll(PDO::FETCH_ASSOC);
$admin_level = 'kecamatan';
}
}
if (empty($areas)) {
throw new Exception('No Pontianak areas found for selected admin level. Run seed_pontianak_data.php?force=true first.');
}
$areaMetrics = [];
foreach ($areas as $area) {
$geometry = decodeGeometry($area['geometry']);
if ($geometry === null) {
continue;
}
$areaKm2 = geometryAreaKm2($geometry);
if ($areaKm2 <= 0) {
$areaKm2 = 0.01;
}
$population = (float)$area['population'];
$populationDensity = $population / $areaKm2;
$areaMetrics[(int)$area['id']] = [
'id' => (int)$area['id'],
'name' => $area['name'],
'geometry' => $geometry,
'area_km2' => $areaKm2,
'centroid' => geometryCentroid($geometry),
'population' => $population,
'population_density' => $populationDensity
];
}
if (empty($areaMetrics)) {
throw new Exception('Area geometry data is invalid for selected admin level.');
}
$areaRoadCounts = [];
$areaDamagedRoadCounts = [];
foreach ($areaMetrics as $metric) {
$areaRoadCounts[$metric['id']] = 0;
$areaDamagedRoadCounts[$metric['id']] = 0;
}
$roadsExist = false;
$valueSource = 'computed';
if ($layer_type === 'road_density' || $layer_type === 'damaged_roads_density') {
$roadStmt = $db->query('SELECT id, coordinates, condition_status FROM roads');
$roads = $roadStmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($roads as $road) {
$coordinates = json_decode($road['coordinates'], true);
$centroid = roadCentroidLngLat($coordinates);
if ($centroid === null) {
continue;
}
$roadsExist = true;
$assignedAreaId = null;
foreach ($areaMetrics as $areaMetric) {
if (pointInGeometry($centroid['lng'], $centroid['lat'], $areaMetric['geometry'])) {
$assignedAreaId = $areaMetric['id'];
break;
}
}
if ($assignedAreaId === null) {
$nearestDistance = null;
foreach ($areaMetrics as $areaMetric) {
$distance = haversineMeters(
$centroid['lat'],
$centroid['lng'],
$areaMetric['centroid']['lat'],
$areaMetric['centroid']['lng']
);
if ($nearestDistance === null || $distance < $nearestDistance) {
$nearestDistance = $distance;
$assignedAreaId = $areaMetric['id'];
}
}
}
if ($assignedAreaId !== null) {
$areaRoadCounts[$assignedAreaId]++;
if (in_array($road['condition_status'], ['minor_damage', 'major_damage'])) {
$areaDamagedRoadCounts[$assignedAreaId]++;
}
}
}
if (!$roadsExist) {
$valueSource = 'dummy';
}
}
$data_items = [];
$values = [];
foreach ($areaMetrics as $areaMetric) {
$area_id = $areaMetric['id'];
$geometry = $areaMetric['geometry'];
$value = 0;
// Calculate value based on layer type
if ($layer_type === 'population') {
$value = $areaMetric['population_density'];
} else if ($layer_type === 'road_density') {
if ($roadsExist) {
$value = $areaRoadCounts[$area_id] / $areaMetric['area_km2'];
} else {
$value = deterministicRoadDensity($areaMetric['population_density'], $area_id);
}
} else if ($layer_type === 'damaged_roads_density') {
if ($roadsExist) {
$value = $areaDamagedRoadCounts[$area_id] / $areaMetric['area_km2'];
} else {
$fallbackRoadDensity = deterministicRoadDensity($areaMetric['population_density'], $area_id);
$value = deterministicDamagedDensity($fallbackRoadDensity, $area_id);
}
}
$value = (float)$value;
$values[] = $value;
$data_items[] = [
'area_id' => $area_id,
'area_name' => $areaMetric['name'],
'value' => $value,
'geometry' => $geometry,
'area_km2' => round($areaMetric['area_km2'], 6),
'population' => $areaMetric['population'],
'population_density' => round($areaMetric['population_density'], 6),
'value_source' => $valueSource
];
}
// Calculate min and max values
$min_value = count($values) > 0 ? min($values) : 0;
$max_value = count($values) > 0 ? max($values) : 0;
// If all values are same, set reasonable range
if ($min_value === $max_value && $min_value > 0) {
$min_value = $min_value * 0.8;
$max_value = $max_value * 1.2;
} else if ($min_value === $max_value) {
$min_value = 0;
$max_value = 1;
}
// Return success response
http_response_code(200);
echo json_encode([
'success' => true,
'layer_type' => $layer_type,
'data' => $data_items,
'min_value' => floatval($min_value),
'max_value' => floatval($max_value),
'admin_level' => $admin_level,
'value_source' => $valueSource
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>
+49
View File
@@ -0,0 +1,49 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
}
try {
$db = getDB();
$stmt = $db->prepare('SELECT key_value FROM score_metadata WHERE key_name = ? LIMIT 1');
$stmt->execute(['target_nasional']);
$targetNasional = $stmt->fetchColumn();
$stmt->execute(['population_reference']);
$populationReference = $stmt->fetchColumn();
if ($targetNasional === false || $targetNasional === null || $targetNasional === '') {
$targetNasional = '7.50';
}
if ($populationReference === false || $populationReference === null || $populationReference === '') {
$populationReference = '288315899';
}
echo json_encode([
'success' => true,
'configuration' => [
'target_nasional' => (float)$targetNasional,
'population_reference' => (int)$populationReference
]
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
?>
+28
View File
@@ -0,0 +1,28 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Handle CORS
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$user = getCurrentUser();
if ($user) {
http_response_code(200);
die(json_encode(['success' => true, 'user' => $user]));
} else {
http_response_code(401);
die(json_encode(['success' => false, 'user' => null]));
}
}
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
+83
View File
@@ -0,0 +1,83 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Get current user to restrict access (optional for MVP)
$user = getCurrentUser();
try {
$db = getDB();
// Get total households
$householdTotal = $db->query('SELECT COUNT(*) as count FROM households')->fetch(PDO::FETCH_ASSOC)['count'];
// Get households by verification status
$statusBreakdown = $db->query('
SELECT verification_status, COUNT(*) as count
FROM households
GROUP BY verification_status
')->fetchAll(PDO::FETCH_ASSOC);
$statusMap = [];
foreach ($statusBreakdown as $row) {
$statusMap[$row['verification_status']] = $row['count'];
}
// Get average SKR (poverty score)
$skrAvg = $db->query('
SELECT
AVG(skr) as avg_skr,
COUNT(*) as scored_count
FROM household_scores
')->fetch(PDO::FETCH_ASSOC);
// Get households assisted
$assistedCount = $db->query('
SELECT COUNT(DISTINCT household_id) as count
FROM assistance_distributions
WHERE delivery_status = \'delivered\'
')->fetch(PDO::FETCH_ASSOC)['count'];
// Get priority level breakdown
$priorityBreakdown = $db->query('
SELECT priority_level, COUNT(*) as count
FROM household_scores
GROUP BY priority_level
')->fetchAll(PDO::FETCH_ASSOC);
$priorityMap = [];
foreach ($priorityBreakdown as $row) {
$priorityMap[$row['priority_level']] = $row['count'];
}
// Get assistance type breakdown
$assistanceTypes = $db->query('
SELECT
assistance_type,
COUNT(*) as count,
SUM(assistance_value) as total_value
FROM assistance_distributions
GROUP BY assistance_type
')->fetchAll(PDO::FETCH_ASSOC);
$response = [
'success' => true,
'summary' => [
'total_households' => $householdTotal,
'avg_poverty_score' => round($skrAvg['avg_skr'] ?? 0, 2),
'households_scored' => $skrAvg['scored_count'] ?? 0,
'households_assisted' => $assistedCount,
'percentage_assisted' => $householdTotal > 0 ? round(($assistedCount / $householdTotal) * 100, 1) : 0
],
'status_breakdown' => $statusMap,
'priority_breakdown' => $priorityMap,
'assistance_by_type' => $assistanceTypes
];
http_response_code(200);
die(json_encode($response));
} catch (PDOException $e) {
http_response_code(500);
die(json_encode(['error' => 'Database error: ' . $e->getMessage()]));
}
+98
View File
@@ -0,0 +1,98 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Get current user to restrict access
$user = getCurrentUser();
try {
$db = getDB();
// Get actual need points count from database (real poor population data)
$needPointsStats = $db->query('SELECT COUNT(*) as total FROM need_points')->fetch(PDO::FETCH_ASSOC);
$totalNeedPoints = (int)$needPointsStats['total'];
// Get configuration from database: population reference and target nasional
$configStmt = $db->prepare('SELECT key_value FROM score_metadata WHERE key_name = ? LIMIT 1');
$configStmt->execute(['population_reference']);
$populationReference = (int)$configStmt->fetchColumn();
if (!$populationReference || $populationReference <= 0) {
$populationReference = 288315899; // Default fallback
}
$configStmt->execute(['target_nasional']);
$targetNasional = (float)$configStmt->fetchColumn();
if (!$targetNasional || $targetNasional <= 0) {
$targetNasional = 7.50; // Default fallback
}
// Calculate poverty percentage from real data
$currentPovertyPercentage = ($totalNeedPoints / $populationReference) * 100;
$response = [
'success' => true,
'charts' => [
// Chart 1: Absolute population comparison (real data from system)
'chart1' => [
'title' => 'Total Penduduk Miskin vs Total Populasi Indonesia',
'type' => 'bar',
'labels' => [
'Penduduk Miskin',
'Total Populasi Indonesia'
],
'data' => [
$totalNeedPoints,
$populationReference
],
'units' => [
'jiwa',
'jiwa'
]
],
// Chart 2: Percentage comparison against national target (real data)
'chart2' => [
'title' => 'Persentase Kemiskinan vs Target Nasional',
'type' => 'bar',
'labels' => [
'Persentase Kemiskinan',
'Target Nasional'
],
'data' => [
round($currentPovertyPercentage, 6),
$targetNasional
],
'units' => [
'%',
'%'
],
'current_poverty' => round($currentPovertyPercentage, 6),
'target' => $targetNasional
]
],
'statistics' => [
'total_need_points' => $totalNeedPoints,
'population_reference' => $populationReference,
'current_poverty_percentage' => round($currentPovertyPercentage, 6),
'national_target' => $targetNasional
]
];
http_response_code(200);
die(json_encode($response));
} catch (PDOException $e) {
http_response_code(500);
die(json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]));
} catch (Exception $e) {
http_response_code(400);
die(json_encode([
'success' => false,
'error' => 'Error: ' . $e->getMessage()
]));
}
?>
+123
View File
@@ -0,0 +1,123 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
syncNeedPointsToHouseholds($db);
ensureHouseholdScoresForAllHouseholds($db);
$scoreCountStmt = $db->query('SELECT COUNT(*) AS count FROM household_scores');
$scoreCount = (int)$scoreCountStmt->fetch(PDO::FETCH_ASSOC)['count'];
$sql = '
SELECT
hs.household_id,
hs.skr,
hs.sag,
hs.spi,
hs.priority_level,
hs.score_version,
hs.computed_at,
h.household_code,
h.head_name,
h.economic_status,
h.monthly_income,
h.dependents_count,
h.verification_status,
CASE WHEN EXISTS (
SELECT 1
FROM assistance_distributions ad
WHERE ad.household_id = hs.household_id
AND ad.delivery_status = \'delivered\'
) THEN 1 ELSE 0 END AS assisted,
m.name AS masjid_name,
pa.name AS area_name
FROM household_scores hs
JOIN households h ON h.id = hs.household_id
LEFT JOIN masjids m ON m.id = h.masjid_id
LEFT JOIN pontianak_areas pa ON pa.id = h.pontianak_area_id
WHERE 1 = 1
';
$params = [];
if (isset($_GET['priority_level']) && $_GET['priority_level'] !== '') {
$allowed = ['very_high', 'high', 'medium', 'low'];
$priority = strtolower(trim((string)$_GET['priority_level']));
if (!in_array($priority, $allowed, true)) {
http_response_code(400);
echo json_encode(['error' => 'priority_level is invalid']);
exit;
}
$sql .= ' AND hs.priority_level = ?';
$params[] = $priority;
}
if (isset($_GET['min_spi']) && $_GET['min_spi'] !== '') {
if (!is_numeric($_GET['min_spi'])) {
http_response_code(400);
echo json_encode(['error' => 'min_spi must be numeric']);
exit;
}
$minSpi = (float)$_GET['min_spi'];
$sql .= ' AND hs.spi >= ?';
$params[] = $minSpi;
}
$includeAssisted = isset($_GET['include_assisted']) && $_GET['include_assisted'] !== '' && $_GET['include_assisted'] !== '0';
if (!$includeAssisted) {
$sql .= ' AND NOT EXISTS (SELECT 1 FROM assistance_distributions ad WHERE ad.household_id = hs.household_id AND ad.delivery_status = \'delivered\')';
}
$limit = 100;
if (isset($_GET['limit']) && $_GET['limit'] !== '') {
if (!is_numeric($_GET['limit'])) {
http_response_code(400);
echo json_encode(['error' => 'limit must be numeric']);
exit;
}
$limit = max(1, min((int)$_GET['limit'], 5000));
}
$offset = 0;
if (isset($_GET['offset']) && $_GET['offset'] !== '') {
if (!is_numeric($_GET['offset'])) {
http_response_code(400);
echo json_encode(['error' => 'offset must be numeric']);
exit;
}
$offset = max(0, (int)$_GET['offset']);
}
$sql .= ' ORDER BY hs.spi DESC, hs.skr DESC, hs.computed_at DESC LIMIT ' . $limit . ' OFFSET ' . $offset;
$stmt = $db->prepare($sql);
$stmt->execute($params);
$ranking = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'count' => count($ranking),
'limit' => $limit,
'offset' => $offset,
'assisted_count' => (int)$db->query("SELECT COUNT(DISTINCT household_id) AS count FROM assistance_distributions WHERE delivery_status = 'delivered'")->fetch(PDO::FETCH_ASSOC)['count'],
'ranking' => $ranking,
'score_version' => 'v1'
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+104
View File
@@ -0,0 +1,104 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$syncResult = syncNeedPointsToHouseholds($db);
$computedMissing = ensureHouseholdScoresForAllHouseholds($db);
$totalStmt = $db->query('SELECT COUNT(*) AS total_households FROM households');
$totalHouseholds = (int)$totalStmt->fetch(PDO::FETCH_ASSOC)['total_households'];
$scoreTotalStmt = $db->query('SELECT COUNT(*) AS scored_households FROM household_scores');
$scoredHouseholds = (int)$scoreTotalStmt->fetch(PDO::FETCH_ASSOC)['scored_households'];
$assistedStmt = $db->query("\n SELECT COUNT(DISTINCT household_id) AS assisted_households\n FROM assistance_distributions\n WHERE delivery_status = 'delivered'\n ");
$assistedHouseholds = (int)$assistedStmt->fetch(PDO::FETCH_ASSOC)['assisted_households'];
$priorityStmt = $db->query('
SELECT
priority_level,
COUNT(*) AS count
FROM household_scores hs
JOIN households h ON h.id = hs.household_id
WHERE NOT EXISTS (
SELECT 1
FROM assistance_distributions ad
WHERE ad.household_id = hs.household_id
AND ad.delivery_status = \'delivered\'
)
GROUP BY priority_level
');
$priorityRows = $priorityStmt->fetchAll(PDO::FETCH_ASSOC);
$priorityCounts = [
'very_high' => 0,
'high' => 0,
'medium' => 0,
'low' => 0
];
foreach ($priorityRows as $row) {
$priority = $row['priority_level'];
$priorityCounts[$priority] = (int)$row['count'];
}
$avgStmt = $db->query('
SELECT
COALESCE(AVG(skr), 0) AS avg_skr,
COALESCE(AVG(sag), 0) AS avg_sag,
COALESCE(AVG(spi), 0) AS avg_spi,
MAX(computed_at) AS latest_computed_at
FROM household_scores
');
$averages = $avgStmt->fetch(PDO::FETCH_ASSOC);
$topStmt = $db->query('
SELECT
hs.household_id,
h.household_code,
h.head_name,
hs.spi,
hs.priority_level
FROM household_scores hs
JOIN households h ON h.id = hs.household_id
WHERE NOT EXISTS (
SELECT 1
FROM assistance_distributions ad
WHERE ad.household_id = hs.household_id
AND ad.delivery_status = \'delivered\'
)
ORDER BY hs.spi DESC, hs.skr DESC
LIMIT 5
');
$topPriorities = $topStmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'summary' => [
'total_households' => $totalHouseholds,
'scored_households' => $scoredHouseholds,
'assisted_households' => $assistedHouseholds,
'priority_counts' => $priorityCounts,
'averages' => $averages,
'top_priorities' => $topPriorities,
'score_version' => 'v1',
'sync_result' => $syncResult,
'computed_missing_scores' => $computedMissing
]
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+74
View File
@@ -0,0 +1,74 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$totalsStmt = $db->query('
SELECT
COUNT(*) AS total_households,
SUM(CASE WHEN verification_status = \'unverified\' THEN 1 ELSE 0 END) AS unverified_count,
SUM(CASE WHEN verification_status = \'field_verified\' THEN 1 ELSE 0 END) AS field_verified_count,
SUM(CASE WHEN verification_status = \'admin_approved\' THEN 1 ELSE 0 END) AS admin_approved_count,
SUM(CASE WHEN verification_status = \'rejected\' THEN 1 ELSE 0 END) AS rejected_count,
SUM(CASE WHEN verification_status = \'needs_review\' THEN 1 ELSE 0 END) AS needs_review_count,
COALESCE(AVG(monthly_income), 0) AS avg_monthly_income
FROM households
');
$totals = $totalsStmt->fetch(PDO::FETCH_ASSOC);
$assistanceStmt = $db->query('
SELECT
COUNT(*) AS total_distributions,
COUNT(DISTINCT household_id) AS assisted_households,
COALESCE(SUM(assistance_value), 0) AS total_assistance_value,
COALESCE(SUM(CASE WHEN delivery_status = \'delivered\' THEN assistance_value ELSE 0 END), 0) AS delivered_assistance_value
FROM assistance_distributions
');
$assistance = $assistanceStmt->fetch(PDO::FETCH_ASSOC);
$verificationStmt = $db->query('
SELECT
COUNT(*) AS total_verifications,
SUM(CASE WHEN DATE(verified_at) >= DATE(\'now\', \'-7 day\') THEN 1 ELSE 0 END) AS verifications_last_7_days,
COALESCE(AVG(data_confidence_score), 0) AS avg_confidence_score
FROM verification_logs
');
$verification = $verificationStmt->fetch(PDO::FETCH_ASSOC);
$topMasjidStmt = $db->query('
SELECT
m.id,
m.name,
COUNT(h.id) AS household_count
FROM masjids m
LEFT JOIN households h ON h.masjid_id = m.id
GROUP BY m.id, m.name
ORDER BY household_count DESC, m.name ASC
LIMIT 5
');
$topMasjidCoverage = $topMasjidStmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'summary' => [
'totals' => $totals,
'assistance' => $assistance,
'verification' => $verification,
'top_masjid_coverage' => $topMasjidCoverage
]
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+111
View File
@@ -0,0 +1,111 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
syncNeedPointsToHouseholds($db);
ensureHouseholdScoresForAllHouseholds($db);
$sql = '
SELECT
h.*,
np.name AS need_point_name,
m.name AS masjid_name,
pa.name AS area_name,
(
SELECT COUNT(*)
FROM assistance_distributions ad
WHERE ad.household_id = h.id
) AS assistance_count,
(
SELECT MAX(vl.verified_at)
FROM verification_logs vl
WHERE vl.household_id = h.id
) AS last_verified_at
FROM households h
LEFT JOIN need_points np ON np.id = h.need_point_id
LEFT JOIN masjids m ON m.id = h.masjid_id
LEFT JOIN pontianak_areas pa ON pa.id = h.pontianak_area_id
WHERE 1 = 1
';
$params = [];
if (isset($_GET['verification_status']) && $_GET['verification_status'] !== '') {
$error = null;
$status = normalizeVerificationStatus($_GET['verification_status'], $error, true);
if ($status === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$sql .= ' AND h.verification_status = ?';
$params[] = $status;
}
if (isset($_GET['masjid_id']) && $_GET['masjid_id'] !== '') {
if (!is_numeric($_GET['masjid_id'])) {
http_response_code(400);
echo json_encode(['error' => 'masjid_id must be numeric']);
exit;
}
$sql .= ' AND h.masjid_id = ?';
$params[] = (int)$_GET['masjid_id'];
}
if (isset($_GET['pontianak_area_id']) && $_GET['pontianak_area_id'] !== '') {
if (!is_numeric($_GET['pontianak_area_id'])) {
http_response_code(400);
echo json_encode(['error' => 'pontianak_area_id must be numeric']);
exit;
}
$sql .= ' AND h.pontianak_area_id = ?';
$params[] = (int)$_GET['pontianak_area_id'];
}
if (isset($_GET['q']) && trim((string)$_GET['q']) !== '') {
$q = '%' . trim((string)$_GET['q']) . '%';
$sql .= ' AND (h.household_code LIKE ? OR h.head_name LIKE ?)';
$params[] = $q;
$params[] = $q;
}
$limit = 200;
if (isset($_GET['limit']) && $_GET['limit'] !== '') {
if (!is_numeric($_GET['limit'])) {
http_response_code(400);
echo json_encode(['error' => 'limit must be numeric']);
exit;
}
$limit = max(1, min((int)$_GET['limit'], 1000));
}
$sql .= ' ORDER BY h.created_at DESC LIMIT ' . $limit;
$stmt = $db->prepare($sql);
$stmt->execute($params);
$households = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'count' => count($households),
'households' => $households
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+31
View File
@@ -0,0 +1,31 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$stmt = $db->query('SELECT id, name, road_type, coordinates, length_meters, created_at, updated_at FROM roads ORDER BY created_at DESC');
$roads = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Decode coordinates JSON for each road
foreach ($roads as &$road) {
$road['coordinates'] = json_decode($road['coordinates'], true);
}
echo json_encode([
'success' => true,
'lines' => $roads
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+41
View File
@@ -0,0 +1,41 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$stmt = $db->query('
SELECT
m.id,
m.name,
m.latitude,
m.longitude,
m.radius_meters,
m.created_at,
m.updated_at,
COUNT(np.id) AS need_point_count
FROM masjids m
LEFT JOIN need_points np ON np.masjid_id = m.id
GROUP BY m.id
ORDER BY m.created_at DESC
');
$masjids = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'masjids' => $masjids
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+36
View File
@@ -0,0 +1,36 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$stmt = $db->query("\n SELECT\n np.id,\n np.name,\n np.latitude,\n np.longitude,\n np.masjid_id,\n np.household_name,\n np.head_name,\n np.economic_status,\n np.is_verified,\n CASE WHEN EXISTS (\n SELECT 1\n FROM households h\n JOIN assistance_distributions ad ON ad.household_id = h.id\n WHERE h.need_point_id = np.id\n AND ad.delivery_status = 'delivered'\n ) THEN 1 ELSE 0 END AS assisted,\n np.created_at,\n np.updated_at,\n m.name AS masjid_name,\n m.latitude AS masjid_latitude,\n m.longitude AS masjid_longitude,\n m.radius_meters\n FROM need_points np\n JOIN masjids m ON m.id = np.masjid_id\n ORDER BY np.created_at DESC\n ");
$points = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($points as &$point) {
$point['distance_meters'] = haversineDistanceMeters(
(float)$point['latitude'],
(float)$point['longitude'],
(float)$point['masjid_latitude'],
(float)$point['masjid_longitude']
);
}
unset($point);
echo json_encode([
'success' => true,
'points' => $points
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$stmt = $db->query('SELECT id, name, nomor_spbu, latitude, longitude, open_24_hours, created_at FROM markers ORDER BY created_at DESC');
$markers = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'markers' => $markers
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+31
View File
@@ -0,0 +1,31 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$stmt = $db->query('SELECT id, name, certificate_type, coordinates, area_sqm, created_at, updated_at FROM land_parcels ORDER BY created_at DESC');
$parcels = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Decode coordinates JSON for each parcel
foreach ($parcels as &$parcel) {
$parcel['coordinates'] = json_decode($parcel['coordinates'], true);
}
echo json_encode([
'success' => true,
'polygons' => $parcels
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+50
View File
@@ -0,0 +1,50 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
try {
$db = getDB();
// Get optional filter parameter
$area_type = isset($_GET['area_type']) ? $_GET['area_type'] : '';
// Build SQL query
$sql = 'SELECT id, name, area_type, geometry, population, created_at, updated_at FROM pontianak_areas';
if ($area_type) {
$sql .= ' WHERE area_type = ?';
$stmt = $db->prepare($sql);
$stmt->execute([$area_type]);
} else {
$stmt = $db->prepare($sql);
$stmt->execute();
}
$areas = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Decode geometry JSON for each area
foreach ($areas as &$area) {
$area['geometry'] = json_decode($area['geometry'], true);
}
// Return success response
http_response_code(200);
echo json_encode([
'success' => true,
'areas' => $areas
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>
+33
View File
@@ -0,0 +1,33 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Check authentication and authorization
requireRole(['Admin']);
try {
$db = getDB();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// Get all users
$stmt = $db->query('
SELECT id, username, email, role, is_active, created_at
FROM users
ORDER BY created_at DESC
');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
http_response_code(200);
die(json_encode([
'success' => true,
'users' => $users
]));
}
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
} catch (PDOException $e) {
http_response_code(500);
die(json_encode(['error' => 'Database error: ' . $e->getMessage()]));
}
+109
View File
@@ -0,0 +1,109 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
try {
$db = getDB();
$sql = '
SELECT
vl.*,
h.household_code,
h.head_name,
h.verification_status AS current_household_status
FROM verification_logs vl
JOIN households h ON h.id = vl.household_id
WHERE 1 = 1
';
$params = [];
if (isset($_GET['household_id']) && $_GET['household_id'] !== '') {
if (!is_numeric($_GET['household_id'])) {
http_response_code(400);
echo json_encode(['error' => 'household_id must be numeric']);
exit;
}
$sql .= ' AND vl.household_id = ?';
$params[] = (int)$_GET['household_id'];
}
if (isset($_GET['verification_status']) && $_GET['verification_status'] !== '') {
$error = null;
$status = normalizeVerificationStatus($_GET['verification_status'], $error, false);
if ($status === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$sql .= ' AND vl.verification_status = ?';
$params[] = $status;
}
if (isset($_GET['verifier_role']) && trim((string)$_GET['verifier_role']) !== '') {
$sql .= ' AND vl.verifier_role = ?';
$params[] = trim((string)$_GET['verifier_role']);
}
if (isset($_GET['date_from']) && $_GET['date_from'] !== '') {
$dateFrom = trim((string)$_GET['date_from']);
$dateObj = DateTime::createFromFormat('Y-m-d', $dateFrom);
if (!$dateObj || $dateObj->format('Y-m-d') !== $dateFrom) {
http_response_code(400);
echo json_encode(['error' => 'date_from must be in YYYY-MM-DD format']);
exit;
}
$sql .= ' AND DATE(vl.verified_at) >= ?';
$params[] = $dateFrom;
}
if (isset($_GET['date_to']) && $_GET['date_to'] !== '') {
$dateTo = trim((string)$_GET['date_to']);
$dateObj = DateTime::createFromFormat('Y-m-d', $dateTo);
if (!$dateObj || $dateObj->format('Y-m-d') !== $dateTo) {
http_response_code(400);
echo json_encode(['error' => 'date_to must be in YYYY-MM-DD format']);
exit;
}
$sql .= ' AND DATE(vl.verified_at) <= ?';
$params[] = $dateTo;
}
$limit = 200;
if (isset($_GET['limit']) && $_GET['limit'] !== '') {
if (!is_numeric($_GET['limit'])) {
http_response_code(400);
echo json_encode(['error' => 'limit must be numeric']);
exit;
}
$limit = max(1, min((int)$_GET['limit'], 1000));
}
$sql .= ' ORDER BY vl.verified_at DESC, vl.created_at DESC LIMIT ' . $limit;
$stmt = $db->prepare($sql);
$stmt->execute($params);
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'count' => count($logs),
'verification_logs' => $logs
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+35
View File
@@ -0,0 +1,35 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Handle CORS
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['email']) || !isset($data['password'])) {
http_response_code(400);
die(json_encode(['error' => 'Email and password are required']));
}
$result = loginUser($data['email'], $data['password']);
if ($result['success']) {
http_response_code(200);
} else {
http_response_code(401);
}
die(json_encode($result));
}
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
+22
View File
@@ -0,0 +1,22 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Handle CORS
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$result = logoutUser();
http_response_code(200);
die(json_encode($result));
}
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
+134
View File
@@ -0,0 +1,134 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!is_array($data)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON body']);
exit;
}
if (!isset($data['household_id']) || !is_numeric($data['household_id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing or invalid household_id']);
exit;
}
$householdId = (int)$data['household_id'];
$error = null;
$assistanceType = normalizeAssistanceType($data['assistance_type'] ?? null, $error);
if ($assistanceType === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$assistanceValue = normalizeAssistanceValue($data['assistance_value'] ?? null, $error);
if ($assistanceValue === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$assistanceFrequency = normalizeAssistanceFrequency($data['assistance_frequency'] ?? null, $error);
if ($assistanceFrequency === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$deliveryStatus = normalizeDeliveryStatus($data['delivery_status'] ?? null, $error);
if ($deliveryStatus === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$distributionDate = isset($data['distribution_date']) && trim((string)$data['distribution_date']) !== ''
? trim((string)$data['distribution_date'])
: date('Y-m-d');
$dateObj = DateTime::createFromFormat('Y-m-d', $distributionDate);
if (!$dateObj || $dateObj->format('Y-m-d') !== $distributionDate) {
http_response_code(400);
echo json_encode(['error' => 'distribution_date must be in YYYY-MM-DD format']);
exit;
}
$deliveredBy = isset($data['delivered_by']) ? trim((string)$data['delivered_by']) : null;
if ($deliveredBy === '') {
$deliveredBy = null;
}
$notes = isset($data['notes']) ? trim((string)$data['notes']) : null;
if ($notes === '') {
$notes = null;
}
$db = getDB();
if (!householdExists($db, $householdId)) {
http_response_code(404);
echo json_encode(['error' => 'Household not found']);
exit;
}
$insertStmt = $db->prepare('
INSERT INTO assistance_distributions (
household_id,
assistance_type,
assistance_value,
assistance_frequency,
distribution_date,
delivery_status,
delivered_by,
notes,
updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
');
$insertStmt->execute([
$householdId,
$assistanceType,
$assistanceValue,
$assistanceFrequency,
$distributionDate,
$deliveryStatus,
$deliveredBy,
$notes
]);
$id = (int)$db->lastInsertId();
$resultStmt = $db->prepare('
SELECT
ad.*,
h.household_code,
h.head_name
FROM assistance_distributions ad
JOIN households h ON h.id = ad.household_id
WHERE ad.id = ?
');
$resultStmt->execute([$id]);
$distribution = $resultStmt->fetch(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'distribution' => $distribution
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+53
View File
@@ -0,0 +1,53 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
}
requireRole('Admin');
try {
$data = json_decode(file_get_contents('php://input'), true);
$targetNasional = isset($data['target_nasional']) ? (float)$data['target_nasional'] : null;
$populationReference = isset($data['population_reference']) ? (int)$data['population_reference'] : null;
if ($targetNasional === null || $targetNasional < 0 || $targetNasional > 100) {
http_response_code(400);
die(json_encode(['error' => 'Target nasional must be between 0 and 100']));
}
if ($populationReference === null || $populationReference < 1) {
http_response_code(400);
die(json_encode(['error' => 'Population reference must be greater than 0']));
}
$db = getDB();
$stmt = $db->prepare('INSERT INTO score_metadata (key_name, key_value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON CONFLICT(key_name) DO UPDATE SET key_value = excluded.key_value, updated_at = CURRENT_TIMESTAMP');
$stmt->execute(['target_nasional', number_format($targetNasional, 2, '.', '')]);
$stmt->execute(['population_reference', (string)$populationReference]);
echo json_encode([
'success' => true,
'configuration' => [
'target_nasional' => $targetNasional,
'population_reference' => $populationReference
]
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
?>
+240
View File
@@ -0,0 +1,240 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!is_array($data)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON body']);
exit;
}
if (!isset($data['head_name']) || trim((string)$data['head_name']) === '') {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: head_name']);
exit;
}
if (!isset($data['latitude']) || !isset($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required fields: latitude and longitude']);
exit;
}
$error = null;
if (!validateCoordinates($data['latitude'], $data['longitude'], $error)) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$db = getDB();
$headName = trim((string)$data['head_name']);
$latitude = (float)$data['latitude'];
$longitude = (float)$data['longitude'];
$monthlyIncome = normalizeMonthlyIncome($data['monthly_income'] ?? null, $error);
if ($monthlyIncome === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$dependentsCount = normalizeDependentsCount($data['dependents_count'] ?? null, $error);
if ($dependentsCount === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$housingConditionScore = normalizeHousingConditionScore($data['housing_condition_score'] ?? null, $error);
if ($housingConditionScore === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$employmentStatus = normalizeEmploymentStatus($data['employment_status'] ?? null, $error);
if ($employmentStatus === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$economicStatus = isset($data['economic_status']) ? strtolower(trim((string)$data['economic_status'])) : 'unknown';
$allowedEconomicStatuses = ['sangat_miskin', 'miskin', 'rentan', 'cukup', 'baik', 'unknown'];
if (!in_array($economicStatus, $allowedEconomicStatuses, true)) {
http_response_code(400);
echo json_encode(['error' => 'economic_status is invalid']);
exit;
}
$verificationStatus = normalizeVerificationStatus($data['verification_status'] ?? null, $error, true);
if ($verificationStatus === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$needPointId = null;
if (array_key_exists('need_point_id', $data) && $data['need_point_id'] !== null && $data['need_point_id'] !== '') {
if (!is_numeric($data['need_point_id'])) {
http_response_code(400);
echo json_encode(['error' => 'need_point_id must be numeric']);
exit;
}
$needPointId = (int)$data['need_point_id'];
$needPointStmt = $db->prepare('SELECT id, masjid_id FROM need_points WHERE id = ?');
$needPointStmt->execute([$needPointId]);
$needPoint = $needPointStmt->fetch(PDO::FETCH_ASSOC);
if (!$needPoint) {
http_response_code(400);
echo json_encode(['error' => 'need_point_id is not found']);
exit;
}
}
$masjidId = null;
if (array_key_exists('masjid_id', $data) && $data['masjid_id'] !== null && $data['masjid_id'] !== '') {
if (!is_numeric($data['masjid_id'])) {
http_response_code(400);
echo json_encode(['error' => 'masjid_id must be numeric']);
exit;
}
$masjidId = (int)$data['masjid_id'];
$masjidStmt = $db->prepare('SELECT id FROM masjids WHERE id = ?');
$masjidStmt->execute([$masjidId]);
if (!$masjidStmt->fetch(PDO::FETCH_ASSOC)) {
http_response_code(400);
echo json_encode(['error' => 'masjid_id is not found']);
exit;
}
}
if ($masjidId === null) {
if ($needPointId !== null) {
$npMasjidStmt = $db->prepare('SELECT masjid_id FROM need_points WHERE id = ?');
$npMasjidStmt->execute([$needPointId]);
$npMasjid = $npMasjidStmt->fetch(PDO::FETCH_ASSOC);
if ($npMasjid && isset($npMasjid['masjid_id'])) {
$masjidId = (int)$npMasjid['masjid_id'];
}
}
if ($masjidId === null) {
$nearestMasjid = findNearestMasjidCoveringPoint($db, $latitude, $longitude);
if ($nearestMasjid) {
$masjidId = (int)$nearestMasjid['id'];
}
}
}
$pontianakAreaId = null;
if (array_key_exists('pontianak_area_id', $data) && $data['pontianak_area_id'] !== null && $data['pontianak_area_id'] !== '') {
if (!is_numeric($data['pontianak_area_id'])) {
http_response_code(400);
echo json_encode(['error' => 'pontianak_area_id must be numeric']);
exit;
}
$pontianakAreaId = (int)$data['pontianak_area_id'];
$areaStmt = $db->prepare('SELECT id FROM pontianak_areas WHERE id = ?');
$areaStmt->execute([$pontianakAreaId]);
if (!$areaStmt->fetch(PDO::FETCH_ASSOC)) {
http_response_code(400);
echo json_encode(['error' => 'pontianak_area_id is not found']);
exit;
}
}
$householdCode = normalizeHouseholdCode($db, $data['household_code'] ?? null);
$nikHash = null;
if (array_key_exists('nik', $data)) {
$nikHash = normalizeNikHashFromInput($data['nik']);
} else if (array_key_exists('nik_hash', $data)) {
$nikHash = trim((string)$data['nik_hash']);
if ($nikHash === '') {
$nikHash = null;
}
}
$vulnerabilityNotes = isset($data['vulnerability_notes']) ? trim((string)$data['vulnerability_notes']) : null;
if ($vulnerabilityNotes === '') {
$vulnerabilityNotes = null;
}
$insertStmt = $db->prepare('
INSERT INTO households (
household_code,
head_name,
nik_hash,
latitude,
longitude,
need_point_id,
masjid_id,
pontianak_area_id,
monthly_income,
dependents_count,
housing_condition_score,
employment_status,
economic_status,
verification_status,
vulnerability_notes
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
');
$insertStmt->execute([
$householdCode,
$headName,
$nikHash,
$latitude,
$longitude,
$needPointId,
$masjidId,
$pontianakAreaId,
$monthlyIncome,
$dependentsCount,
$housingConditionScore,
$employmentStatus,
$economicStatus,
$verificationStatus,
$vulnerabilityNotes
]);
$id = (int)$db->lastInsertId();
$household = getHouseholdById($db, $id);
echo json_encode([
'success' => true,
'household' => $household
]);
} catch (PDOException $e) {
if ($e->getCode() === '23000') {
http_response_code(409);
echo json_encode(['error' => 'Duplicate household_code or need_point_id mapping']);
exit;
}
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+113
View File
@@ -0,0 +1,113 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
// Validate required fields
if (!isset($data['name']) || !isset($data['road_type']) || !isset($data['coordinates']) || !isset($data['length_meters'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required fields: name, road_type, coordinates, or length_meters']);
exit;
}
// Validate road_type
$validTypes = ['Nasional', 'Provinsi', 'Kabupaten'];
if (!in_array($data['road_type'], $validTypes)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid road_type. Must be: Nasional, Provinsi, or Kabupaten']);
exit;
}
// Validate coordinates array
if (!is_array($data['coordinates']) || count($data['coordinates']) < 2) {
http_response_code(400);
echo json_encode(['error' => 'Coordinates must be an array with at least 2 points']);
exit;
}
// Validate each coordinate
foreach ($data['coordinates'] as $coord) {
if (!is_array($coord) || count($coord) !== 2) {
http_response_code(400);
echo json_encode(['error' => 'Each coordinate must be [latitude, longitude]']);
exit;
}
$lat = floatval($coord[0]);
$lng = floatval($coord[1]);
if (!is_numeric($coord[0]) || !is_numeric($coord[1])) {
http_response_code(400);
echo json_encode(['error' => 'Coordinates must be numeric values']);
exit;
}
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
http_response_code(400);
echo json_encode(['error' => 'Invalid coordinate range. Latitude: -90 to 90, Longitude: -180 to 180']);
exit;
}
}
// Validate name is not empty
if (empty(trim($data['name']))) {
http_response_code(400);
echo json_encode(['error' => 'Road name cannot be empty']);
exit;
}
// Validate length_meters is positive
if (!is_numeric($data['length_meters']) || $data['length_meters'] <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Length must be a positive number']);
exit;
}
// Validate condition_status if provided
$condition_status = isset($data['condition_status']) ? $data['condition_status'] : 'normal';
$validConditions = ['normal', 'minor_damage', 'major_damage'];
if (!in_array($condition_status, $validConditions)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid condition_status. Must be: normal, minor_damage, or major_damage']);
exit;
}
$db = getDB();
// Store coordinates as JSON
$coordinatesJson = json_encode($data['coordinates']);
$stmt = $db->prepare('INSERT INTO roads (name, road_type, coordinates, length_meters, condition_status) VALUES (?, ?, ?, ?, ?)');
$stmt->execute([
$data['name'],
$data['road_type'],
$coordinatesJson,
floatval($data['length_meters']),
$condition_status
]);
echo json_encode([
'success' => true,
'id' => $db->lastInsertId(),
'name' => $data['name'],
'road_type' => $data['road_type'],
'coordinates' => $data['coordinates'],
'length_meters' => floatval($data['length_meters']),
'condition_status' => $condition_status,
'created_at' => date('Y-m-d H:i:s')
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+76
View File
@@ -0,0 +1,76 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['name']) || !isset($data['latitude']) || !isset($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required fields: name, latitude, or longitude']);
exit;
}
$name = trim($data['name']);
if ($name === '') {
http_response_code(400);
echo json_encode(['error' => 'Name cannot be empty']);
exit;
}
if (!is_numeric($data['latitude']) || !is_numeric($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Latitude and longitude must be numeric values']);
exit;
}
$latitude = (float)$data['latitude'];
$longitude = (float)$data['longitude'];
if ($latitude < -90 || $latitude > 90) {
http_response_code(400);
echo json_encode(['error' => 'Latitude must be between -90 and 90']);
exit;
}
if ($longitude < -180 || $longitude > 180) {
http_response_code(400);
echo json_encode(['error' => 'Longitude must be between -180 and 180']);
exit;
}
$radiusError = null;
$radius = normalizeMasjidRadius(isset($data['radius_meters']) ? $data['radius_meters'] : null, $radiusError);
if ($radius === null) {
http_response_code(400);
echo json_encode(['error' => $radiusError]);
exit;
}
$db = getDB();
$stmt = $db->prepare('INSERT INTO masjids (name, latitude, longitude, radius_meters) VALUES (?, ?, ?, ?)');
$stmt->execute([$name, $latitude, $longitude, $radius]);
echo json_encode([
'success' => true,
'masjid' => [
'id' => (int)$db->lastInsertId(),
'name' => $name,
'latitude' => $latitude,
'longitude' => $longitude,
'radius_meters' => $radius
]
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+91
View File
@@ -0,0 +1,91 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['name']) || !isset($data['latitude']) || !isset($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required fields: name, latitude, or longitude']);
exit;
}
$name = trim($data['name']);
if ($name === '') {
http_response_code(400);
echo json_encode(['error' => 'Name cannot be empty']);
exit;
}
if (!is_numeric($data['latitude']) || !is_numeric($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Latitude and longitude must be numeric values']);
exit;
}
$latitude = (float)$data['latitude'];
$longitude = (float)$data['longitude'];
if ($latitude < -90 || $latitude > 90) {
http_response_code(400);
echo json_encode(['error' => 'Latitude must be between -90 and 90']);
exit;
}
if ($longitude < -180 || $longitude > 180) {
http_response_code(400);
echo json_encode(['error' => 'Longitude must be between -180 and 180']);
exit;
}
$db = getDB();
$nearestMasjid = findNearestMasjidCoveringPoint($db, $latitude, $longitude);
if ($nearestMasjid === null) {
http_response_code(400);
echo json_encode(['error' => 'Point must be inside at least one masjid radius']);
exit;
}
// optional household details
$householdName = isset($data['household_name']) ? trim($data['household_name']) : null;
$headName = isset($data['head_name']) ? trim($data['head_name']) : null;
$economicStatus = isset($data['economic_status']) ? trim($data['economic_status']) : null;
// generate defaults when missing
if (!$householdName) $householdName = 'Keluarga ' . substr(md5(uniqid('', true)), 0, 8);
if (!$headName) {
$firstNames = ['Ahmad','Budi','Siti','Aulia','Rizal','Dewi','Agus','Fitri','Hendra','Yulia'];
$lastNames = ['Santoso','Wijaya','Pratama','Hidayat','Saputra','Nur','Ibrahim','Setiawan','Rahman','Putri'];
$headName = $firstNames[array_rand($firstNames)] . ' ' . $lastNames[array_rand($lastNames)];
}
if (!$economicStatus) {
$statuses = ['sangat_miskin','miskin','rentan','cukup','baik'];
$economicStatus = $statuses[array_rand($statuses)];
}
$insertStmt = $db->prepare('INSERT INTO need_points (name, latitude, longitude, masjid_id, household_name, head_name, economic_status) VALUES (?, ?, ?, ?, ?, ?, ?)');
$insertStmt->execute([$name, $latitude, $longitude, (int)$nearestMasjid['id'], $householdName, $headName, $economicStatus]);
$pointId = (int)$db->lastInsertId();
$point = getNeedPointWithMasjid($db, $pointId);
$point['distance_meters'] = $nearestMasjid['distance_meters'];
echo json_encode([
'success' => true,
'point' => $point
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+56
View File
@@ -0,0 +1,56 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0); // Don't display errors in output
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
// Validate required fields
if (!isset($data['name']) || !isset($data['latitude']) || !isset($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required fields: name, latitude, or longitude']);
exit;
}
// Validate coordinates are numeric
if (!is_numeric($data['latitude']) || !is_numeric($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Latitude and longitude must be numeric values']);
exit;
}
$db = getDB();
$stmt = $db->prepare('INSERT INTO markers (name, nomor_spbu, latitude, longitude, open_24_hours) VALUES (?, ?, ?, ?, ?)');
$open_24_hours = isset($data['open_24_hours']) ? (int)$data['open_24_hours'] : 0;
$nomor_spbu = isset($data['nomor_spbu']) ? $data['nomor_spbu'] : null;
$stmt->execute([
$data['name'],
$nomor_spbu,
(float)$data['latitude'],
(float)$data['longitude'],
$open_24_hours
]);
echo json_encode([
'success' => true,
'id' => $db->lastInsertId(),
'name' => $data['name'],
'nomor_spbu' => $nomor_spbu,
'latitude' => (float)$data['latitude'],
'longitude' => (float)$data['longitude'],
'open_24_hours' => $open_24_hours
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+102
View File
@@ -0,0 +1,102 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
// Validate required fields
if (!isset($data['name']) || !isset($data['certificate_type']) || !isset($data['coordinates']) || !isset($data['area_sqm'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required fields: name, certificate_type, coordinates, or area_sqm']);
exit;
}
// Validate certificate_type
$validTypes = ['SHM', 'HGB', 'HGU', 'HP'];
if (!in_array($data['certificate_type'], $validTypes)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid certificate_type. Must be: SHM, HGB, HGU, or HP']);
exit;
}
// Validate coordinates array (minimum 3 for polygon)
if (!is_array($data['coordinates']) || count($data['coordinates']) < 3) {
http_response_code(400);
echo json_encode(['error' => 'Coordinates must be an array with at least 3 points for a polygon']);
exit;
}
// Validate each coordinate
foreach ($data['coordinates'] as $coord) {
if (!is_array($coord) || count($coord) !== 2) {
http_response_code(400);
echo json_encode(['error' => 'Each coordinate must be [latitude, longitude]']);
exit;
}
$lat = floatval($coord[0]);
$lng = floatval($coord[1]);
if (!is_numeric($coord[0]) || !is_numeric($coord[1])) {
http_response_code(400);
echo json_encode(['error' => 'Coordinates must be numeric values']);
exit;
}
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
http_response_code(400);
echo json_encode(['error' => 'Invalid coordinate range. Latitude: -90 to 90, Longitude: -180 to 180']);
exit;
}
}
// Validate name is not empty
if (empty(trim($data['name']))) {
http_response_code(400);
echo json_encode(['error' => 'Parcel name cannot be empty']);
exit;
}
// Validate area_sqm is positive
if (!is_numeric($data['area_sqm']) || $data['area_sqm'] <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Area must be a positive number']);
exit;
}
$db = getDB();
// Store coordinates as JSON
$coordinatesJson = json_encode($data['coordinates']);
$stmt = $db->prepare('INSERT INTO land_parcels (name, certificate_type, coordinates, area_sqm) VALUES (?, ?, ?, ?)');
$stmt->execute([
$data['name'],
$data['certificate_type'],
$coordinatesJson,
floatval($data['area_sqm'])
]);
echo json_encode([
'success' => true,
'id' => $db->lastInsertId(),
'name' => $data['name'],
'certificate_type' => $data['certificate_type'],
'coordinates' => $data['coordinates'],
'area_sqm' => floatval($data['area_sqm']),
'created_at' => date('Y-m-d H:i:s')
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+138
View File
@@ -0,0 +1,138 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!is_array($data)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON body']);
exit;
}
if (!isset($data['household_id']) || !is_numeric($data['household_id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing or invalid household_id']);
exit;
}
if (!isset($data['verifier_name']) || trim((string)$data['verifier_name']) === '') {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: verifier_name']);
exit;
}
$error = null;
$verificationStatus = normalizeVerificationStatus($data['verification_status'] ?? null, $error, false);
if ($verificationStatus === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$evidencePhotoCount = normalizeEvidencePhotoCount($data['evidence_photo_count'] ?? null, $error);
if ($evidencePhotoCount === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$dataConfidenceScore = normalizeDataConfidenceScore($data['data_confidence_score'] ?? null, $error);
if ($dataConfidenceScore === null && $error !== null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$householdId = (int)$data['household_id'];
$verifierName = trim((string)$data['verifier_name']);
$verifierRole = isset($data['verifier_role']) ? trim((string)$data['verifier_role']) : null;
if ($verifierRole === '') {
$verifierRole = null;
}
$fieldNote = isset($data['field_note']) ? trim((string)$data['field_note']) : null;
if ($fieldNote === '') {
$fieldNote = null;
}
$verifiedAt = isset($data['verified_at']) && trim((string)$data['verified_at']) !== ''
? trim((string)$data['verified_at'])
: date('Y-m-d H:i:s');
$verifiedDateTime = DateTime::createFromFormat('Y-m-d H:i:s', $verifiedAt);
if (!$verifiedDateTime || $verifiedDateTime->format('Y-m-d H:i:s') !== $verifiedAt) {
http_response_code(400);
echo json_encode(['error' => 'verified_at must be in YYYY-MM-DD HH:MM:SS format']);
exit;
}
$db = getDB();
if (!householdExists($db, $householdId)) {
http_response_code(404);
echo json_encode(['error' => 'Household not found']);
exit;
}
$insertStmt = $db->prepare('
INSERT INTO verification_logs (
household_id,
verification_status,
verifier_name,
verifier_role,
field_note,
evidence_photo_count,
data_confidence_score,
verified_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
');
$insertStmt->execute([
$householdId,
$verificationStatus,
$verifierName,
$verifierRole,
$fieldNote,
$evidencePhotoCount,
$dataConfidenceScore,
$verifiedAt
]);
$updateHouseholdStmt = $db->prepare('UPDATE households SET verification_status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?');
$updateHouseholdStmt->execute([$verificationStatus, $householdId]);
$id = (int)$db->lastInsertId();
$resultStmt = $db->prepare('
SELECT
vl.*,
h.household_code,
h.head_name
FROM verification_logs vl
JOIN households h ON h.id = vl.household_id
WHERE vl.id = ?
');
$resultStmt->execute([$id]);
$verificationLog = $resultStmt->fetch(PDO::FETCH_ASSOC);
$household = getHouseholdById($db, $householdId);
echo json_encode([
'success' => true,
'verification_log' => $verificationLog,
'household' => $household
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+99
View File
@@ -0,0 +1,99 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
try {
$db = getDB();
// Get filter parameters
$search = isset($_GET['search']) ? '%' . $_GET['search'] . '%' : '';
$from_date = isset($_GET['from_date']) ? $_GET['from_date'] : '';
$to_date = isset($_GET['to_date']) ? $_GET['to_date'] : '';
$certificate_type = isset($_GET['certificate_type']) ? $_GET['certificate_type'] : '';
// Build dynamic WHERE clause
$where_clauses = [];
$params = [];
if ($search !== '%' && $search !== '%') {
$where_clauses[] = '(name LIKE ?)';
$params[] = $search;
}
if ($from_date) {
$where_clauses[] = '(DATE(created_at) >= ?)';
$params[] = $from_date;
}
if ($to_date) {
$where_clauses[] = '(DATE(created_at) <= ?)';
$params[] = $to_date;
}
if ($certificate_type) {
$where_clauses[] = '(certificate_type = ?)';
$params[] = $certificate_type;
}
// Build SQL query
$sql = 'SELECT id, name, certificate_type, coordinates, area_sqm, created_at, updated_at FROM land_parcels';
if (count($where_clauses) > 0) {
$sql .= ' WHERE ' . implode(' AND ', $where_clauses);
}
$sql .= ' ORDER BY created_at DESC';
// Execute query
$stmt = $db->prepare($sql);
$stmt->execute($params);
$parcels = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Decode coordinates JSON for each parcel
foreach ($parcels as &$parcel) {
$parcel['coordinates'] = json_decode($parcel['coordinates'], true);
}
// Count total for query
$count_sql = 'SELECT COUNT(*) as total FROM land_parcels' . (count($where_clauses) > 0 ? ' WHERE ' . implode(' AND ', $where_clauses) : '');
$count_stmt = $db->prepare($count_sql);
$count_stmt->execute($params);
$total = $count_stmt->fetch(PDO::FETCH_ASSOC)['total'];
// Prepare filters applied info
$filters_applied = [];
if ($search !== '%' && $search !== '%') {
$filters_applied['search'] = str_replace('%', '', $search);
}
if ($from_date || $to_date) {
$filters_applied['date_range'] = [];
if ($from_date) $filters_applied['date_range'][] = $from_date;
if ($to_date) $filters_applied['date_range'][] = $to_date;
}
if ($certificate_type) {
$filters_applied['certificate_type'] = $certificate_type;
}
// Return success response
http_response_code(200);
echo json_encode([
'success' => true,
'polygons' => $parcels,
'total' => $total,
'filters_applied' => $filters_applied
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>
+99
View File
@@ -0,0 +1,99 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
try {
$db = getDB();
// Get filter parameters
$search = isset($_GET['search']) ? '%' . $_GET['search'] . '%' : '';
$from_date = isset($_GET['from_date']) ? $_GET['from_date'] : '';
$to_date = isset($_GET['to_date']) ? $_GET['to_date'] : '';
$condition_status = isset($_GET['condition_status']) ? $_GET['condition_status'] : '';
// Build dynamic WHERE clause
$where_clauses = [];
$params = [];
if ($search !== '%' && $search !== '%') {
$where_clauses[] = '(name LIKE ?)';
$params[] = $search;
}
if ($from_date) {
$where_clauses[] = '(DATE(created_at) >= ?)';
$params[] = $from_date;
}
if ($to_date) {
$where_clauses[] = '(DATE(created_at) <= ?)';
$params[] = $to_date;
}
if ($condition_status) {
$where_clauses[] = '(condition_status = ?)';
$params[] = $condition_status;
}
// Build SQL query
$sql = 'SELECT id, name, road_type, coordinates, length_meters, condition_status, created_at, updated_at FROM roads';
if (count($where_clauses) > 0) {
$sql .= ' WHERE ' . implode(' AND ', $where_clauses);
}
$sql .= ' ORDER BY created_at DESC';
// Execute query
$stmt = $db->prepare($sql);
$stmt->execute($params);
$roads = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Decode coordinates JSON for each road
foreach ($roads as &$road) {
$road['coordinates'] = json_decode($road['coordinates'], true);
}
// Count total for unfiltered query
$count_sql = 'SELECT COUNT(*) as total FROM roads' . (count($where_clauses) > 0 ? ' WHERE ' . implode(' AND ', $where_clauses) : '');
$count_stmt = $db->prepare($count_sql);
$count_stmt->execute($params);
$total = $count_stmt->fetch(PDO::FETCH_ASSOC)['total'];
// Prepare filters applied info
$filters_applied = [];
if ($search !== '%' && $search !== '%') {
$filters_applied['search'] = str_replace('%', '', $search);
}
if ($from_date || $to_date) {
$filters_applied['date_range'] = [];
if ($from_date) $filters_applied['date_range'][] = $from_date;
if ($to_date) $filters_applied['date_range'][] = $to_date;
}
if ($condition_status) {
$filters_applied['condition_status'] = $condition_status;
}
// Return success response
http_response_code(200);
echo json_encode([
'success' => true,
'lines' => $roads,
'total' => $total,
'filters_applied' => $filters_applied
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>
+136
View File
@@ -0,0 +1,136 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
try {
$db = getDB();
// Check if force parameter is set
$force = isset($_POST['force']) || isset($_GET['force']);
// Pontianak administrative data (dummy but more realistic non-rectangular polygons)
// GeoJSON coordinate order is [longitude, latitude].
$pontianak_data = [
[
'name' => 'Pontianak',
'area_type' => 'city',
'population' => 690000,
'geometry' => '{"type":"Polygon","coordinates":[[[109.320000,-0.005000],[109.330000,-0.004000],[109.340000,-0.005500],[109.345000,-0.008000],[109.348000,-0.012000],[109.350000,-0.016000],[109.348000,-0.020000],[109.340000,-0.023000],[109.330000,-0.024000],[109.320000,-0.023500],[109.310000,-0.022000],[109.305000,-0.019000],[109.303000,-0.016000],[109.302000,-0.012000],[109.305000,-0.008000],[109.310000,-0.006000],[109.318000,-0.004500],[109.325000,-0.003500],[109.329000,-0.004000],[109.320000,-0.005000]]]}'
],
[
'name' => 'Pontianak Barat',
'area_type' => 'kecamatan',
'population' => 112000,
'geometry' => '{"type":"Polygon","coordinates":[[[109.305000,-0.010000],[109.315000,-0.009500],[109.318000,-0.013000],[109.315000,-0.015000],[109.305000,-0.016000],[109.302000,-0.013000],[109.305000,-0.010000]]]}'
],
[
'name' => 'Pontianak Utara',
'area_type' => 'kecamatan',
'population' => 126000,
'geometry' => '{"type":"Polygon","coordinates":[[[109.318000,-0.005000],[109.327000,-0.004500],[109.330000,-0.007000],[109.328000,-0.009500],[109.320000,-0.010000],[109.315000,-0.007500],[109.318000,-0.005000]]]}'
],
[
'name' => 'Pontianak Kota',
'area_type' => 'kecamatan',
'population' => 148000,
'geometry' => '{"type":"Polygon","coordinates":[[[109.320000,-0.010000],[109.327000,-0.009000],[109.330000,-0.011000],[109.328000,-0.015000],[109.320000,-0.015500],[109.315000,-0.013000],[109.320000,-0.010000]]]}'
],
[
'name' => 'Pontianak Timur',
'area_type' => 'kecamatan',
'population' => 94000,
'geometry' => '{"type":"Polygon","coordinates":[[[109.328000,-0.009500],[109.340000,-0.010000],[109.345000,-0.012500],[109.338000,-0.016000],[109.328000,-0.015000],[109.328000,-0.009500]]]}'
],
[
'name' => 'Pontianak Selatan',
'area_type' => 'kecamatan',
'population' => 132000,
'geometry' => '{"type":"Polygon","coordinates":[[[109.315000,-0.015000],[109.328000,-0.014500],[109.330000,-0.018000],[109.318000,-0.021000],[109.308000,-0.020000],[109.315000,-0.015000]]]}'
],
[
'name' => 'Pontianak Tenggara',
'area_type' => 'kecamatan',
'population' => 91000,
'geometry' => '{"type":"Polygon","coordinates":[[[109.328000,-0.015000],[109.338000,-0.016000],[109.340000,-0.019000],[109.330000,-0.021000],[109.320000,-0.020000],[109.328000,-0.015000]]]}'
]
];
// Check if data already exists
$stmt = $db->prepare('SELECT COUNT(*) as count FROM pontianak_areas');
$stmt->execute();
$count = $stmt->fetch(PDO::FETCH_ASSOC)['count'];
if ($count > 0 && !$force) {
http_response_code(200);
echo json_encode([
'success' => true,
'message' => 'Pontianak data already exists. Use force=true to reseed.'
]);
exit;
}
// Delete existing data if force is true
if ($force && $count > 0) {
$db->exec('DELETE FROM area_statistics');
$db->exec('DELETE FROM pontianak_areas');
}
// Insert Pontianak areas
$stmt = $db->prepare('
INSERT OR IGNORE INTO pontianak_areas (name, area_type, geometry, population)
VALUES (?, ?, ?, ?)
');
$today = date('Y-m-d');
$stats_stmt = $db->prepare('
INSERT OR IGNORE INTO area_statistics (pontianak_area_id, statistic_type, value, data_date)
VALUES (?, ?, ?, ?)
');
$inserted = 0;
foreach ($pontianak_data as $data) {
$stmt->execute([
$data['name'],
$data['area_type'],
$data['geometry'],
$data['population']
]);
$inserted++;
}
// Insert initial area statistics
$areas_stmt = $db->prepare('SELECT id, population FROM pontianak_areas');
$areas_stmt->execute();
$areas = $areas_stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($areas as $area) {
// Insert population density statistic
$stats_stmt->execute([
$area['id'],
'population_density',
(float)$area['population'],
$today
]);
}
http_response_code(200);
echo json_encode([
'success' => true,
'message' => 'Pontianak data seeded successfully',
'areas_inserted' => $inserted
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>
+256
View File
@@ -0,0 +1,256 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
function randomFloat($min, $max) {
return $min + (mt_rand() / mt_getrandmax()) * ($max - $min);
}
function buildGridLocation($bbox, $index, $total) {
$rows = max(1, (int)ceil(sqrt($total)));
$cols = max(1, (int)ceil($total / $rows));
$row = intdiv($index, $cols);
$col = $index % $cols;
$latStep = ($bbox['max_lat'] - $bbox['min_lat']) / $rows;
$lngStep = ($bbox['max_lng'] - $bbox['min_lng']) / $cols;
$baseLat = $bbox['min_lat'] + ($row + 0.5) * $latStep;
$baseLng = $bbox['min_lng'] + ($col + 0.5) * $lngStep;
$latMin = max($bbox['min_lat'], $baseLat - $latStep * 0.35);
$latMax = min($bbox['max_lat'], $baseLat + $latStep * 0.35);
$lngMin = max($bbox['min_lng'], $baseLng - $lngStep * 0.35);
$lngMax = min($bbox['max_lng'], $baseLng + $lngStep * 0.35);
return [
'latitude' => randomFloat($latMin, $latMax),
'longitude' => randomFloat($lngMin, $lngMax)
];
}
function distanceSquared($lat1, $lng1, $lat2, $lng2) {
$dLat = $lat1 - $lat2;
$dLng = $lng1 - $lng2;
return ($dLat * $dLat) + ($dLng * $dLng);
}
function findNearestMasjidId($latitude, $longitude, $masjids) {
$nearestId = null;
$nearestDistance = null;
foreach ($masjids as $masjid) {
$distance = distanceSquared($latitude, $longitude, $masjid['latitude'], $masjid['longitude']);
if ($nearestDistance === null || $distance < $nearestDistance) {
$nearestDistance = $distance;
$nearestId = $masjid['id'];
}
}
return $nearestId;
}
try {
$db = getDB();
$force = isset($_POST['force']) || isset($_GET['force']);
// Load GeoJSON land polygons to avoid placing points at sea
$geojsonPath = __DIR__ . '/../export.geojson';
$landPolygons = [];
if (file_exists($geojsonPath)) {
$gj = json_decode(file_get_contents($geojsonPath), true);
if ($gj && isset($gj['features']) && is_array($gj['features'])) {
foreach ($gj['features'] as $feature) {
if (!isset($feature['geometry'])) continue;
$geom = $feature['geometry'];
if ($geom['type'] === 'Polygon') {
$landPolygons[] = $geom['coordinates'];
} elseif ($geom['type'] === 'MultiPolygon') {
foreach ($geom['coordinates'] as $poly) $landPolygons[] = $poly;
}
}
}
}
function pointInRing($lat, $lng, $ring) {
$inside = false;
$j = count($ring) - 1;
for ($i = 0; $i < count($ring); $i++) {
$xi = $ring[$i][0]; $yi = $ring[$i][1];
$xj = $ring[$j][0]; $yj = $ring[$j][1];
$intersect = (($yi > $lat) != ($yj > $lat)) && ($lng < ($xj - $xi) * ($lat - $yi) / ($yj - $yi + 0.0) + $xi);
if ($intersect) $inside = !$inside;
$j = $i;
}
return $inside;
}
function isPointOnLand($lat, $lng, $landPolygons) {
if (empty($landPolygons)) return true; // if no polygons available, default to true
foreach ($landPolygons as $poly) {
// poly is array of rings; test outer ring first
$outer = $poly[0];
if (pointInRing($lat, $lng, $outer)) {
// ensure not in hole
$isInHole = false;
for ($r = 1; $r < count($poly); $r++) {
if (pointInRing($lat, $lng, $poly[$r])) {
$isInHole = true;
break;
}
}
if (!$isInHole) return true;
}
}
return false;
}
$regional_data = [
['region' => 'Jakarta', 'province' => 'DKI Jakarta', 'bbox' => ['min_lat' => -6.40, 'max_lat' => -5.90, 'min_lng' => 106.60, 'max_lng' => 106.95], 'masjids' => 15, 'need_points' => 2435],
['region' => 'Bandung', 'province' => 'Jawa Barat', 'bbox' => ['min_lat' => -7.50, 'max_lat' => -5.90, 'min_lng' => 106.00, 'max_lng' => 108.10], 'masjids' => 10, 'need_points' => 1500],
['region' => 'Bekasi', 'province' => 'Jawa Barat', 'bbox' => ['min_lat' => -7.20, 'max_lat' => -6.00, 'min_lng' => 106.50, 'max_lng' => 108.00], 'masjids' => 6, 'need_points' => 800],
['region' => 'Semarang', 'province' => 'Jawa Tengah', 'bbox' => ['min_lat' => -8.10, 'max_lat' => -5.90, 'min_lng' => 108.30, 'max_lng' => 111.20], 'masjids' => 9, 'need_points' => 1400],
['region' => 'Yogyakarta', 'province' => 'DI Yogyakarta', 'bbox' => ['min_lat' => -8.20, 'max_lat' => -7.30, 'min_lng' => 109.90, 'max_lng' => 110.90], 'masjids' => 7, 'need_points' => 900],
['region' => 'Surabaya', 'province' => 'Jawa Timur', 'bbox' => ['min_lat' => -8.80, 'max_lat' => -6.80, 'min_lng' => 111.00, 'max_lng' => 114.50], 'masjids' => 12, 'need_points' => 1800],
['region' => 'Malang', 'province' => 'Jawa Timur', 'bbox' => ['min_lat' => -8.60, 'max_lat' => -7.30, 'min_lng' => 110.80, 'max_lng' => 113.20], 'masjids' => 5, 'need_points' => 700],
['region' => 'Medan', 'province' => 'Sumatera Utara', 'bbox' => ['min_lat' => 1.00, 'max_lat' => 4.50, 'min_lng' => 98.00, 'max_lng' => 100.50], 'masjids' => 11, 'need_points' => 1600],
['region' => 'Palembang', 'province' => 'Sumatera Selatan', 'bbox' => ['min_lat' => -4.20, 'max_lat' => -2.00, 'min_lng' => 103.00, 'max_lng' => 106.10], 'masjids' => 8, 'need_points' => 1100],
['region' => 'Pekanbaru', 'province' => 'Riau', 'bbox' => ['min_lat' => -0.60, 'max_lat' => 2.20, 'min_lng' => 100.00, 'max_lng' => 102.90], 'masjids' => 6, 'need_points' => 850],
['region' => 'Jambi', 'province' => 'Jambi', 'bbox' => ['min_lat' => -2.30, 'max_lat' => 0.50, 'min_lng' => 101.00, 'max_lng' => 104.20], 'masjids' => 4, 'need_points' => 600],
['region' => 'Padang', 'province' => 'Sumatera Barat', 'bbox' => ['min_lat' => -2.90, 'max_lat' => 1.00, 'min_lng' => 99.40, 'max_lng' => 101.80], 'masjids' => 5, 'need_points' => 700],
['region' => 'Makassar', 'province' => 'Sulawesi Selatan', 'bbox' => ['min_lat' => -6.50, 'max_lat' => -2.50, 'min_lng' => 118.00, 'max_lng' => 121.80], 'masjids' => 9, 'need_points' => 1300],
['region' => 'Manado', 'province' => 'Sulawesi Utara', 'bbox' => ['min_lat' => 0.80, 'max_lat' => 4.00, 'min_lng' => 123.00, 'max_lng' => 125.80], 'masjids' => 4, 'need_points' => 550],
['region' => 'Kendari', 'province' => 'Sulawesi Tenggara', 'bbox' => ['min_lat' => -6.50, 'max_lat' => -2.00, 'min_lng' => 120.00, 'max_lng' => 124.50], 'masjids' => 3, 'need_points' => 400],
['region' => 'Palu', 'province' => 'Sulawesi Tengah', 'bbox' => ['min_lat' => -3.00, 'max_lat' => 2.00, 'min_lng' => 118.00, 'max_lng' => 123.50], 'masjids' => 4, 'need_points' => 500],
['region' => 'Banjarmasin', 'province' => 'Kalimantan Selatan', 'bbox' => ['min_lat' => -4.20, 'max_lat' => -1.00, 'min_lng' => 114.00, 'max_lng' => 116.80], 'masjids' => 7, 'need_points' => 1000],
['region' => 'Pontianak', 'province' => 'Kalimantan Barat', 'bbox' => ['min_lat' => -3.50, 'max_lat' => 3.00, 'min_lng' => 108.00, 'max_lng' => 112.80], 'masjids' => 5, 'need_points' => 700],
['region' => 'Samarinda', 'province' => 'Kalimantan Timur', 'bbox' => ['min_lat' => -2.80, 'max_lat' => 2.00, 'min_lng' => 115.00, 'max_lng' => 119.80], 'masjids' => 6, 'need_points' => 850],
['region' => 'Palangkaraya', 'province' => 'Kalimantan Tengah', 'bbox' => ['min_lat' => -3.80, 'max_lat' => 1.20, 'min_lng' => 112.00, 'max_lng' => 116.50], 'masjids' => 3, 'need_points' => 450],
['region' => 'Jayapura', 'province' => 'Papua', 'bbox' => ['min_lat' => -9.50, 'max_lat' => -1.00, 'min_lng' => 130.00, 'max_lng' => 141.50], 'masjids' => 4, 'need_points' => 700],
['region' => 'Mataram', 'province' => 'Nusa Tenggara Barat', 'bbox' => ['min_lat' => -9.20, 'max_lat' => -7.00, 'min_lng' => 115.20, 'max_lng' => 119.00], 'masjids' => 3, 'need_points' => 450],
['region' => 'Kupang', 'province' => 'Nusa Tenggara Timur', 'bbox' => ['min_lat' => -11.20, 'max_lat' => -8.00, 'min_lng' => 118.50, 'max_lng' => 125.50], 'masjids' => 2, 'need_points' => 300],
['region' => 'Ambon', 'province' => 'Maluku', 'bbox' => ['min_lat' => -8.50, 'max_lat' => -1.00, 'min_lng' => 125.00, 'max_lng' => 134.50], 'masjids' => 2, 'need_points' => 250],
['region' => 'Denpasar', 'province' => 'Bali', 'bbox' => ['min_lat' => -9.20, 'max_lat' => -8.00, 'min_lng' => 114.50, 'max_lng' => 115.80], 'masjids' => 1, 'need_points' => 100]
];
$stmt = $db->prepare('SELECT COUNT(*) as count FROM masjids');
$stmt->execute();
$masjidCount = (int)$stmt->fetch(PDO::FETCH_ASSOC)['count'];
if ($masjidCount > 20 && !$force) {
http_response_code(200);
echo json_encode([
'success' => true,
'message' => 'Regional poverty data already exists. Use force=true to reseed.',
'current_masjid_count' => $masjidCount
]);
exit;
}
if ($force && $masjidCount > 0) {
$db->exec('DELETE FROM need_points');
$db->exec('DELETE FROM masjids');
}
$db->beginTransaction();
$masjidStmt = $db->prepare('INSERT INTO masjids (name, latitude, longitude, radius_meters) VALUES (?, ?, ?, ?)');
$needPointStmt = $db->prepare('INSERT INTO need_points (name, latitude, longitude, masjid_id, household_name, head_name, economic_status) VALUES (?, ?, ?, ?, ?, ?, ?)');
$insertedMasjids = 0;
$insertedNeedPoints = 0;
foreach ($regional_data as $region) {
$provinceMasjids = [];
for ($i = 0; $i < $region['masjids']; $i++) {
// ensure masjid sits on land (try a few attempts)
$attempt = 0;
do {
$location = buildGridLocation($region['bbox'], $i, $region['masjids']);
$attempt++;
} while ($attempt < 8 && !isPointOnLand($location['latitude'], $location['longitude'], $landPolygons));
$latSpan = $region['bbox']['max_lat'] - $region['bbox']['min_lat'];
$lngSpan = $region['bbox']['max_lng'] - $region['bbox']['min_lng'];
$coverageEstimate = (int)round((($latSpan + $lngSpan) * 111000) / max(1, $region['masjids']) * 0.55);
$radiusMeters = max(20000, min(180000, $coverageEstimate + rand(3000, 12000)));
$masjidStmt->execute([
'Masjid ' . $region['region'] . ' - ' . ($i + 1),
$location['latitude'],
$location['longitude'],
$radiusMeters
]);
$provinceMasjids[] = [
'id' => (int)$db->lastInsertId(),
'latitude' => $location['latitude'],
'longitude' => $location['longitude']
];
$insertedMasjids++;
}
for ($i = 0; $i < $region['need_points']; $i++) {
// ensure need point lands on land; allow several tries
$attempt = 0;
do {
$pointLocation = buildGridLocation($region['bbox'], $i, $region['need_points']);
$attempt++;
} while ($attempt < 12 && !isPointOnLand($pointLocation['latitude'], $pointLocation['longitude'], $landPolygons));
$nearestMasjidId = findNearestMasjidId($pointLocation['latitude'], $pointLocation['longitude'], $provinceMasjids);
// generate household and head names and economic status
$hhName = 'Keluarga ' . $region['region'] . ' - ' . ($i + 1);
$firstNames = ['Ahmad','Budi','Siti','Aulia','Rizal','Dewi','Agus','Fitri','Hendra','Yulia'];
$lastNames = ['Santoso','Wijaya','Pratama','Hidayat','Saputra','Nur','Ibrahim','Setiawan','Rahman','Putri'];
$headName = $firstNames[array_rand($firstNames)] . ' ' . $lastNames[array_rand($lastNames)];
$statuses = ['sangat_miskin','miskin','rentan','cukup','baik'];
$econ = $statuses[array_rand($statuses)];
$needPointStmt->execute([
'Keluarga Miskin ' . $region['region'] . ' - ' . ($i + 1),
$pointLocation['latitude'],
$pointLocation['longitude'],
$nearestMasjidId,
$hhName,
$headName,
$econ
]);
$insertedNeedPoints++;
}
}
$db->commit();
http_response_code(200);
echo json_encode([
'success' => true,
'message' => 'Regional poverty assistance data seeded successfully',
'masjids_inserted' => $insertedMasjids,
'need_points_inserted' => $insertedNeedPoints,
'regions_covered' => count($regional_data),
'total_need_points_capacity' => 21935
]);
} catch (PDOException $e) {
if (isset($db) && $db->inTransaction()) {
$db->rollBack();
}
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Database error: ' . $e->getMessage()
]);
} catch (Exception $e) {
if (isset($db) && $db->inTransaction()) {
$db->rollBack();
}
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>
+24
View File
@@ -0,0 +1,24 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
try {
$db = getDB();
$syncResult = syncNeedPointsToHouseholds($db);
$computedScores = ensureHouseholdScoresForAllHouseholds($db);
echo json_encode([
'success' => true,
'sync_result' => $syncResult,
'computed_missing_scores' => $computedScores
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
+48
View File
@@ -0,0 +1,48 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
exit;
}
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: id']);
exit;
}
$id = (int)$data['id'];
$db = getDB();
$existing = getNeedPointWithMasjid($db, $id);
if (!$existing) {
http_response_code(404);
echo json_encode(['error' => 'Need point not found']);
exit;
}
$current = (int)($existing['is_verified'] ?? 0);
$next = $current === 1 ? 0 : 1;
$stmt = $db->prepare('UPDATE need_points SET is_verified = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?');
$stmt->execute([$next, $id]);
$updated = getNeedPointWithMasjid($db, $id);
echo json_encode([
'success' => true,
'point' => $updated
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
?>
+269
View File
@@ -0,0 +1,269 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!is_array($data) || !isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: id']);
exit;
}
$id = (int)$data['id'];
$db = getDB();
$existingStmt = $db->prepare('SELECT * FROM households WHERE id = ?');
$existingStmt->execute([$id]);
$existing = $existingStmt->fetch(PDO::FETCH_ASSOC);
if (!$existing) {
http_response_code(404);
echo json_encode(['error' => 'Household not found']);
exit;
}
$updates = [];
$params = [];
$error = null;
if (array_key_exists('household_code', $data)) {
$householdCode = trim((string)$data['household_code']);
if ($householdCode === '') {
http_response_code(400);
echo json_encode(['error' => 'household_code cannot be empty']);
exit;
}
$updates[] = 'household_code = ?';
$params[] = $householdCode;
}
if (array_key_exists('head_name', $data)) {
$headName = trim((string)$data['head_name']);
if ($headName === '') {
http_response_code(400);
echo json_encode(['error' => 'head_name cannot be empty']);
exit;
}
$updates[] = 'head_name = ?';
$params[] = $headName;
}
if (array_key_exists('nik', $data) || array_key_exists('nik_hash', $data)) {
if (array_key_exists('nik', $data)) {
$nikHash = normalizeNikHashFromInput($data['nik']);
} else {
$nikHash = trim((string)$data['nik_hash']);
if ($nikHash === '') {
$nikHash = null;
}
}
$updates[] = 'nik_hash = ?';
$params[] = $nikHash;
}
$hasLatitude = array_key_exists('latitude', $data);
$hasLongitude = array_key_exists('longitude', $data);
if ($hasLatitude || $hasLongitude) {
$latitude = $hasLatitude ? $data['latitude'] : $existing['latitude'];
$longitude = $hasLongitude ? $data['longitude'] : $existing['longitude'];
if (!validateCoordinates($latitude, $longitude, $error)) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$updates[] = 'latitude = ?';
$params[] = (float)$latitude;
$updates[] = 'longitude = ?';
$params[] = (float)$longitude;
}
if (array_key_exists('need_point_id', $data)) {
if ($data['need_point_id'] === null || $data['need_point_id'] === '') {
$needPointId = null;
} else {
if (!is_numeric($data['need_point_id'])) {
http_response_code(400);
echo json_encode(['error' => 'need_point_id must be numeric']);
exit;
}
$needPointId = (int)$data['need_point_id'];
$npStmt = $db->prepare('SELECT id FROM need_points WHERE id = ?');
$npStmt->execute([$needPointId]);
if (!$npStmt->fetch(PDO::FETCH_ASSOC)) {
http_response_code(400);
echo json_encode(['error' => 'need_point_id is not found']);
exit;
}
}
$updates[] = 'need_point_id = ?';
$params[] = $needPointId;
}
if (array_key_exists('masjid_id', $data)) {
if ($data['masjid_id'] === null || $data['masjid_id'] === '') {
$masjidId = null;
} else {
if (!is_numeric($data['masjid_id'])) {
http_response_code(400);
echo json_encode(['error' => 'masjid_id must be numeric']);
exit;
}
$masjidId = (int)$data['masjid_id'];
$masjidStmt = $db->prepare('SELECT id FROM masjids WHERE id = ?');
$masjidStmt->execute([$masjidId]);
if (!$masjidStmt->fetch(PDO::FETCH_ASSOC)) {
http_response_code(400);
echo json_encode(['error' => 'masjid_id is not found']);
exit;
}
}
$updates[] = 'masjid_id = ?';
$params[] = $masjidId;
}
if (array_key_exists('pontianak_area_id', $data)) {
if ($data['pontianak_area_id'] === null || $data['pontianak_area_id'] === '') {
$areaId = null;
} else {
if (!is_numeric($data['pontianak_area_id'])) {
http_response_code(400);
echo json_encode(['error' => 'pontianak_area_id must be numeric']);
exit;
}
$areaId = (int)$data['pontianak_area_id'];
$areaStmt = $db->prepare('SELECT id FROM pontianak_areas WHERE id = ?');
$areaStmt->execute([$areaId]);
if (!$areaStmt->fetch(PDO::FETCH_ASSOC)) {
http_response_code(400);
echo json_encode(['error' => 'pontianak_area_id is not found']);
exit;
}
}
$updates[] = 'pontianak_area_id = ?';
$params[] = $areaId;
}
if (array_key_exists('monthly_income', $data)) {
$monthlyIncome = normalizeMonthlyIncome($data['monthly_income'], $error);
if ($monthlyIncome === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$updates[] = 'monthly_income = ?';
$params[] = $monthlyIncome;
}
if (array_key_exists('dependents_count', $data)) {
$dependentsCount = normalizeDependentsCount($data['dependents_count'], $error);
if ($dependentsCount === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$updates[] = 'dependents_count = ?';
$params[] = $dependentsCount;
}
if (array_key_exists('housing_condition_score', $data)) {
$housingConditionScore = normalizeHousingConditionScore($data['housing_condition_score'], $error);
if ($housingConditionScore === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$updates[] = 'housing_condition_score = ?';
$params[] = $housingConditionScore;
}
if (array_key_exists('employment_status', $data)) {
$employmentStatus = normalizeEmploymentStatus($data['employment_status'], $error);
if ($employmentStatus === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$updates[] = 'employment_status = ?';
$params[] = $employmentStatus;
}
if (array_key_exists('verification_status', $data)) {
$verificationStatus = normalizeVerificationStatus($data['verification_status'], $error, true);
if ($verificationStatus === null) {
http_response_code(400);
echo json_encode(['error' => $error]);
exit;
}
$updates[] = 'verification_status = ?';
$params[] = $verificationStatus;
}
if (array_key_exists('vulnerability_notes', $data)) {
$vulnerabilityNotes = trim((string)$data['vulnerability_notes']);
if ($vulnerabilityNotes === '') {
$vulnerabilityNotes = null;
}
$updates[] = 'vulnerability_notes = ?';
$params[] = $vulnerabilityNotes;
}
if (empty($updates)) {
http_response_code(400);
echo json_encode(['error' => 'No fields to update']);
exit;
}
$updates[] = 'updated_at = CURRENT_TIMESTAMP';
$params[] = $id;
$sql = 'UPDATE households SET ' . implode(', ', $updates) . ' WHERE id = ?';
$updateStmt = $db->prepare($sql);
$updateStmt->execute($params);
$household = getHouseholdById($db, $id);
echo json_encode([
'success' => true,
'household' => $household
]);
} catch (PDOException $e) {
if ($e->getCode() === '23000') {
http_response_code(409);
echo json_encode(['error' => 'Duplicate household_code or need_point_id mapping']);
exit;
}
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+143
View File
@@ -0,0 +1,143 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
// Validate required fields
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: id']);
exit;
}
$id = intval($data['id']);
$db = getDB();
// Determine what type of update this is
$hasCoordinates = isset($data['coordinates']) && isset($data['length_meters']);
$hasName = isset($data['name']);
$hasRoadType = isset($data['road_type']);
$hasConditionStatus = isset($data['condition_status']);
// Full edit (multiple fields including name or road_type)
if ($hasName || $hasRoadType || $hasCoordinates || $hasConditionStatus) {
$updates = [];
$params = [];
if ($hasName) {
if (empty($data['name'])) {
http_response_code(400);
echo json_encode(['error' => 'Name cannot be empty']);
exit;
}
$updates[] = 'name = ?';
$params[] = $data['name'];
}
if ($hasRoadType) {
$validTypes = ['Nasional', 'Provinsi', 'Kabupaten'];
if (!in_array($data['road_type'], $validTypes)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid road_type. Must be: Nasional, Provinsi, or Kabupaten']);
exit;
}
$updates[] = 'road_type = ?';
$params[] = $data['road_type'];
}
if ($hasConditionStatus) {
$validConditions = ['normal', 'minor_damage', 'major_damage'];
if (!in_array($data['condition_status'], $validConditions)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid condition_status. Must be: normal, minor_damage, or major_damage']);
exit;
}
$updates[] = 'condition_status = ?';
$params[] = $data['condition_status'];
}
if ($hasCoordinates) {
// Validate coordinates array
if (!is_array($data['coordinates']) || count($data['coordinates']) < 2) {
http_response_code(400);
echo json_encode(['error' => 'Coordinates must be an array with at least 2 points']);
exit;
}
// Validate each coordinate
foreach ($data['coordinates'] as $coord) {
if (!is_array($coord) || count($coord) !== 2 || !is_numeric($coord[0]) || !is_numeric($coord[1])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid coordinate format']);
exit;
}
$lat = floatval($coord[0]);
$lng = floatval($coord[1]);
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
http_response_code(400);
echo json_encode(['error' => 'Invalid coordinate range']);
exit;
}
}
// Validate length_meters is positive
if (!is_numeric($data['length_meters']) || $data['length_meters'] <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Length must be a positive number']);
exit;
}
$updates[] = 'coordinates = ?';
$updates[] = 'length_meters = ?';
$params[] = json_encode($data['coordinates']);
$params[] = floatval($data['length_meters']);
}
if (empty($updates)) {
http_response_code(400);
echo json_encode(['error' => 'No fields to update']);
exit;
}
// Add updated_at and id to params
$updates[] = 'updated_at = CURRENT_TIMESTAMP';
$params[] = $id;
$sql = 'UPDATE roads SET ' . implode(', ', $updates) . ' WHERE id = ?';
$stmt = $db->prepare($sql);
$stmt->execute($params);
// Fetch updated record
$getStmt = $db->prepare('SELECT * FROM roads WHERE id = ?');
$getStmt->execute([$id]);
$road = $getStmt->fetch(PDO::FETCH_ASSOC);
if ($road) {
$road['coordinates'] = json_decode($road['coordinates'], true);
}
echo json_encode([
'success' => true,
'line' => $road
]);
} else {
http_response_code(400);
echo json_encode(['error' => 'No fields to update']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+125
View File
@@ -0,0 +1,125 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: id']);
exit;
}
$id = (int)$data['id'];
$db = getDB();
$existingStmt = $db->prepare('SELECT id, name, latitude, longitude, radius_meters FROM masjids WHERE id = ?');
$existingStmt->execute([$id]);
$existing = $existingStmt->fetch(PDO::FETCH_ASSOC);
if (!$existing) {
http_response_code(404);
echo json_encode(['error' => 'Masjid not found']);
exit;
}
$updates = [];
$params = [];
if (isset($data['name'])) {
$name = trim($data['name']);
if ($name === '') {
http_response_code(400);
echo json_encode(['error' => 'Name cannot be empty']);
exit;
}
$updates[] = 'name = ?';
$params[] = $name;
}
$hasLatitude = array_key_exists('latitude', $data);
$hasLongitude = array_key_exists('longitude', $data);
if ($hasLatitude || $hasLongitude) {
$latitude = $hasLatitude ? $data['latitude'] : $existing['latitude'];
$longitude = $hasLongitude ? $data['longitude'] : $existing['longitude'];
if (!is_numeric($latitude) || !is_numeric($longitude)) {
http_response_code(400);
echo json_encode(['error' => 'Latitude and longitude must be numeric values']);
exit;
}
$latitude = (float)$latitude;
$longitude = (float)$longitude;
if ($latitude < -90 || $latitude > 90) {
http_response_code(400);
echo json_encode(['error' => 'Latitude must be between -90 and 90']);
exit;
}
if ($longitude < -180 || $longitude > 180) {
http_response_code(400);
echo json_encode(['error' => 'Longitude must be between -180 and 180']);
exit;
}
$updates[] = 'latitude = ?';
$updates[] = 'longitude = ?';
$params[] = $latitude;
$params[] = $longitude;
}
if (array_key_exists('radius_meters', $data)) {
$radiusError = null;
$radius = normalizeMasjidRadius($data['radius_meters'], $radiusError);
if ($radius === null) {
http_response_code(400);
echo json_encode(['error' => $radiusError]);
exit;
}
$updates[] = 'radius_meters = ?';
$params[] = $radius;
}
if (empty($updates)) {
http_response_code(400);
echo json_encode(['error' => 'No fields to update']);
exit;
}
$updates[] = 'updated_at = CURRENT_TIMESTAMP';
$params[] = $id;
$updateSql = 'UPDATE masjids SET ' . implode(', ', $updates) . ' WHERE id = ?';
$updateStmt = $db->prepare($updateSql);
$updateStmt->execute($params);
$resultStmt = $db->prepare('SELECT id, name, latitude, longitude, radius_meters, created_at, updated_at FROM masjids WHERE id = ?');
$resultStmt->execute([$id]);
$masjid = $resultStmt->fetch(PDO::FETCH_ASSOC);
$cleanupResult = cleanupNeedPointsOutsideMasjidCoverage($db, $id);
echo json_encode([
'success' => true,
'masjid' => $masjid,
'deleted_need_points' => $cleanupResult['deleted_count'],
'deleted_need_point_ids' => $cleanupResult['deleted_ids']
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+102
View File
@@ -0,0 +1,102 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: id']);
exit;
}
$id = (int)$data['id'];
$db = getDB();
$existing = getNeedPointWithMasjid($db, $id);
if (!$existing) {
http_response_code(404);
echo json_encode(['error' => 'Point berkebutuhan not found']);
exit;
}
$newName = isset($data['name']) ? trim($data['name']) : $existing['name'];
if ($newName === '') {
http_response_code(400);
echo json_encode(['error' => 'Name cannot be empty']);
exit;
}
$newLatitude = array_key_exists('latitude', $data) ? $data['latitude'] : $existing['latitude'];
$newLongitude = array_key_exists('longitude', $data) ? $data['longitude'] : $existing['longitude'];
if (!is_numeric($newLatitude) || !is_numeric($newLongitude)) {
http_response_code(400);
echo json_encode(['error' => 'Latitude and longitude must be numeric values']);
exit;
}
$newLatitude = (float)$newLatitude;
$newLongitude = (float)$newLongitude;
if ($newLatitude < -90 || $newLatitude > 90) {
http_response_code(400);
echo json_encode(['error' => 'Latitude must be between -90 and 90']);
exit;
}
if ($newLongitude < -180 || $newLongitude > 180) {
http_response_code(400);
echo json_encode(['error' => 'Longitude must be between -180 and 180']);
exit;
}
$nearestMasjid = findNearestMasjidCoveringPoint($db, $newLatitude, $newLongitude);
if ($nearestMasjid === null) {
http_response_code(400);
echo json_encode(['error' => 'Point must remain inside at least one masjid radius']);
exit;
}
// allow updating household fields as well
$newHouseholdName = array_key_exists('household_name', $data) ? trim($data['household_name']) : $existing['household_name'];
$newHeadName = array_key_exists('head_name', $data) ? trim($data['head_name']) : $existing['head_name'];
$newEconomic = array_key_exists('economic_status', $data) ? trim($data['economic_status']) : $existing['economic_status'];
$updateStmt = $db->prepare('
UPDATE need_points
SET name = ?, latitude = ?, longitude = ?, masjid_id = ?, household_name = ?, head_name = ?, economic_status = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
');
$updateStmt->execute([
$newName,
$newLatitude,
$newLongitude,
(int)$nearestMasjid['id'],
$newHouseholdName,
$newHeadName,
$newEconomic,
$id
]);
$updated = getNeedPointWithMasjid($db, $id);
$updated['distance_meters'] = $nearestMasjid['distance_meters'];
echo json_encode([
'success' => true,
'point' => $updated
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+154
View File
@@ -0,0 +1,154 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
// Validate required fields
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: id']);
exit;
}
$id = (int)$data['id'];
$db = getDB();
// Determine what type of update this is
$hasCoordinates = isset($data['latitude']) && isset($data['longitude']);
$hasName = isset($data['name']);
$hasNomorSpbu = isset($data['nomor_spbu']);
$hasStatus = isset($data['open_24_hours']);
// Full edit (multiple fields including coordinates or name)
if ($hasName || ($hasCoordinates && ($hasName || $hasNomorSpbu || $hasStatus))) {
$updates = [];
$params = [];
if ($hasName) {
if (empty($data['name'])) {
http_response_code(400);
echo json_encode(['error' => 'Name cannot be empty']);
exit;
}
$updates[] = 'name = ?';
$params[] = $data['name'];
}
if ($hasNomorSpbu) {
$updates[] = 'nomor_spbu = ?';
$params[] = $data['nomor_spbu'] ?: null;
}
if ($hasCoordinates) {
if (!is_numeric($data['latitude']) || !is_numeric($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Latitude and longitude must be numeric values']);
exit;
}
$latitude = (float)$data['latitude'];
$longitude = (float)$data['longitude'];
if ($latitude < -90 || $latitude > 90 || $longitude < -180 || $longitude > 180) {
http_response_code(400);
echo json_encode(['error' => 'Invalid coordinates']);
exit;
}
$updates[] = 'latitude = ?';
$updates[] = 'longitude = ?';
$params[] = $latitude;
$params[] = $longitude;
}
if ($hasStatus) {
$updates[] = 'open_24_hours = ?';
$params[] = (int)$data['open_24_hours'];
}
if (empty($updates)) {
http_response_code(400);
echo json_encode(['error' => 'No fields to update']);
exit;
}
// Add id to params
$params[] = $id;
$sql = 'UPDATE markers SET ' . implode(', ', $updates) . ' WHERE id = ?';
$stmt = $db->prepare($sql);
$stmt->execute($params);
// Fetch updated record
$getStmt = $db->prepare('SELECT * FROM markers WHERE id = ?');
$getStmt->execute([$id]);
$marker = $getStmt->fetch(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'marker' => $marker
]);
}
// Coordinates-only update (drag)
else if ($hasCoordinates) {
if (!is_numeric($data['latitude']) || !is_numeric($data['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Latitude and longitude must be numeric values']);
exit;
}
$latitude = (float)$data['latitude'];
$longitude = (float)$data['longitude'];
if ($latitude < -90 || $latitude > 90) {
http_response_code(400);
echo json_encode(['error' => 'Latitude must be between -90 and 90']);
exit;
}
if ($longitude < -180 || $longitude > 180) {
http_response_code(400);
echo json_encode(['error' => 'Longitude must be between -180 and 180']);
exit;
}
$stmt = $db->prepare('UPDATE markers SET latitude = ?, longitude = ? WHERE id = ?');
$stmt->execute([$latitude, $longitude, $id]);
echo json_encode([
'success' => true,
'id' => $id,
'latitude' => $latitude,
'longitude' => $longitude
]);
}
// Status-only update (toggle)
else if ($hasStatus) {
$open_24_hours = (int)$data['open_24_hours'];
$stmt = $db->prepare('UPDATE markers SET open_24_hours = ? WHERE id = ?');
$stmt->execute([$open_24_hours, $id]);
echo json_encode([
'success' => true,
'id' => $id,
'open_24_hours' => $open_24_hours
]);
} else {
http_response_code(400);
echo json_encode(['error' => 'No fields to update']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+131
View File
@@ -0,0 +1,131 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
header('Content-Type: application/json');
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$data = json_decode(file_get_contents('php://input'), true);
// Validate required fields
if (!isset($data['id'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required field: id']);
exit;
}
$id = intval($data['id']);
$db = getDB();
// Determine what type of update this is
$hasCoordinates = isset($data['coordinates']) && isset($data['area_sqm']);
$hasName = isset($data['name']);
$hasCertificateType = isset($data['certificate_type']);
// Full edit (multiple fields including name or certificate_type)
if ($hasName || $hasCertificateType || $hasCoordinates) {
$updates = [];
$params = [];
if ($hasName) {
if (empty($data['name'])) {
http_response_code(400);
echo json_encode(['error' => 'Name cannot be empty']);
exit;
}
$updates[] = 'name = ?';
$params[] = $data['name'];
}
if ($hasCertificateType) {
$validTypes = ['SHM', 'HGB', 'HGU', 'HP'];
if (!in_array($data['certificate_type'], $validTypes)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid certificate_type. Must be: SHM, HGB, HGU, or HP']);
exit;
}
$updates[] = 'certificate_type = ?';
$params[] = $data['certificate_type'];
}
if ($hasCoordinates) {
// Validate coordinates array (minimum 3 for polygon)
if (!is_array($data['coordinates']) || count($data['coordinates']) < 3) {
http_response_code(400);
echo json_encode(['error' => 'Coordinates must be an array with at least 3 points']);
exit;
}
// Validate each coordinate
foreach ($data['coordinates'] as $coord) {
if (!is_array($coord) || count($coord) !== 2 || !is_numeric($coord[0]) || !is_numeric($coord[1])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid coordinate format']);
exit;
}
$lat = floatval($coord[0]);
$lng = floatval($coord[1]);
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
http_response_code(400);
echo json_encode(['error' => 'Invalid coordinate range']);
exit;
}
}
// Validate area_sqm is positive
if (!is_numeric($data['area_sqm']) || $data['area_sqm'] <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Area must be a positive number']);
exit;
}
$updates[] = 'coordinates = ?';
$updates[] = 'area_sqm = ?';
$params[] = json_encode($data['coordinates']);
$params[] = floatval($data['area_sqm']);
}
if (empty($updates)) {
http_response_code(400);
echo json_encode(['error' => 'No fields to update']);
exit;
}
// Add updated_at and id to params
$updates[] = 'updated_at = CURRENT_TIMESTAMP';
$params[] = $id;
$sql = 'UPDATE land_parcels SET ' . implode(', ', $updates) . ' WHERE id = ?';
$stmt = $db->prepare($sql);
$stmt->execute($params);
// Fetch updated record
$getStmt = $db->prepare('SELECT * FROM land_parcels WHERE id = ?');
$getStmt->execute([$id]);
$parcel = $getStmt->fetch(PDO::FETCH_ASSOC);
if ($parcel) {
$parcel['coordinates'] = json_decode($parcel['coordinates'], true);
}
echo json_encode([
'success' => true,
'polygon' => $parcel
]);
} else {
http_response_code(400);
echo json_encode(['error' => 'No fields to update']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>
+51
View File
@@ -0,0 +1,51 @@
<?php
header('Content-Type: application/json');
require_once 'config.php';
// Check authentication and authorization
requireRole(['Admin']);
if ($_SERVER['REQUEST_METHOD'] !== 'PUT') {
http_response_code(405);
die(json_encode(['error' => 'Method not allowed']));
}
$data = json_decode(file_get_contents('php://input'), true);
// Validate input
if (!isset($data['id']) || !isset($data['role'])) {
http_response_code(400);
die(json_encode(['error' => 'Missing required fields: id, role']));
}
// Validate role
$validRoles = ['Admin', 'Petugas', 'Pimpinan'];
if (!in_array($data['role'], $validRoles)) {
http_response_code(400);
die(json_encode(['error' => 'Invalid role. Must be Admin, Petugas, or Pimpinan']));
}
try {
$db = getDB();
$stmt = $db->prepare('
UPDATE users
SET role = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
');
$stmt->execute([$data['role'], $data['id']]);
if ($stmt->rowCount() > 0) {
http_response_code(200);
die(json_encode([
'success' => true,
'message' => 'User role updated successfully'
]));
} else {
http_response_code(404);
die(json_encode(['error' => 'User not found']));
}
} catch (PDOException $e) {
http_response_code(500);
die(json_encode(['error' => 'Database error: ' . $e->getMessage()]));
}