+479
-139
@@ -43,6 +43,8 @@
|
||||
.registry-item { transition: .18s ease; }
|
||||
.registry-item:hover { transform: translateY(-1px); box-shadow: 0 12px 28px rgba(15, 23, 42, 0.1); }
|
||||
.registry-chip { display: inline-flex; align-items: center; gap: .35rem; border-radius: 9999px; padding: .25rem .6rem; font-size: .7rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
|
||||
#registryDrawer { transition: transform .24s ease, opacity .24s ease; }
|
||||
#registryDrawer.hidden-drawer { transform: translateX(-12px); opacity: 0; pointer-events: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-50 text-slate-800 font-sans">
|
||||
@@ -50,23 +52,10 @@
|
||||
<div class="app-shell">
|
||||
<div id="map"></div>
|
||||
|
||||
<div id="mainPanel" class="absolute left-6 top-[calc(1rem+env(safe-area-inset-top))] z-[2000] w-[260px] max-w-[calc(100vw-3rem)] rounded-2xl border border-slate-200 bg-white/90 p-2.5 shadow-2xl backdrop-blur-md md:w-[290px]">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-base font-black tracking-tight text-indigo-700">GIS</h1>
|
||||
<p id="modeHint" class="hidden mt-1 text-xs text-slate-500">Mode default hanya menampilkan peta dan atribut data yang sudah dibuat.</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button id="toggleMainPanelBtn" class="icon-btn text-slate-700" title="Ciutkan panel">
|
||||
<svg id="toggleMainPanelIcon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
|
||||
</button>
|
||||
<button onclick="toggleInputRegistry()" class="icon-btn text-slate-700" title="Buka registry input">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M8 4v16"/><path d="M8 8h11"/><path d="M8 12h11"/><path d="M8 16h7"/></svg>
|
||||
</button>
|
||||
<button onclick="loadMarkers({ force: true }); loadFeatures(true); loadExternalRoadNetwork(true);" class="icon-btn text-indigo-600" title="Refresh data">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 21h5v-5"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="mainPanel" class="hidden absolute left-6 top-[calc(1rem+env(safe-area-inset-top))] z-[2000] w-[260px] max-w-[calc(100vw-3rem)] rounded-2xl border border-slate-200 bg-white/90 p-2.5 shadow-2xl backdrop-blur-md md:w-[290px]">
|
||||
<div>
|
||||
<h1 class="text-base font-black tracking-tight text-indigo-700">GIS</h1>
|
||||
<p id="modeHint" class="hidden"></p>
|
||||
</div>
|
||||
<div id="mainPanelBody">
|
||||
<div id="connectionStatus" class="mt-1 text-[9px] font-bold uppercase tracking-wide text-amber-500">Mengecek koneksi...</div>
|
||||
@@ -74,25 +63,30 @@
|
||||
<span>SPBU</span>
|
||||
<span id="totalCount" class="text-sm font-black text-indigo-600">0</span>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap gap-1.5">
|
||||
<button class="icon-btn" id="modeViewBtn" title="Lihat data">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modePointBtn" title="Tambah point SPBU">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2C8.13 2 5 5.13 5 9c0 5 7 13 7 13s7-8 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modePolylineBtn" title="Tambah polyline jalan">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 18l6-6 4 4 6-8"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modePolygonBtn" title="Tambah polygon parsil">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l8 5v10l-8 5-8-5V7z"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modeRoadDamageBtn" title="Lapor jalan rusak">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 12h12M12 6v12M2 12h20M12 2v20"/><circle cx="12" cy="12" r="10"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modeChoroplethBtn" title="Lihat peta choropleth kecamatan">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z"/><path d="M9 4v14"/><path d="M15 6v14"/></svg>
|
||||
<div class="mt-2 flex flex-wrap items-center gap-1.5">
|
||||
<button class="icon-btn" id="toggleEditModeBtn" title="Aktifkan mode edit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>
|
||||
</button>
|
||||
<div id="editModeButtons" class="hidden flex flex-wrap gap-1.5">
|
||||
<button class="icon-btn" id="modeViewBtn" title="Lihat data">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modePointBtn" title="Tambah point SPBU">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2C8.13 2 5 5.13 5 9c0 5 7 13 7 13s7-8 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modePolylineBtn" title="Tambah polyline jalan">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 18l6-6 4 4 6-8"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modePolygonBtn" title="Tambah polygon parsil">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l8 5v10l-8 5-8-5V7z"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modeRoadDamageBtn" title="Lapor jalan rusak">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 12h12M12 6v12M2 12h20M12 2v20"/><circle cx="12" cy="12" r="10"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modeChoroplethBtn" title="Lihat peta choropleth kecamatan">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z"/><path d="M9 4v14"/><path d="M15 6v14"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="spatialControls" class="mt-3 hidden rounded-2xl border border-slate-200 bg-white p-3 shadow-sm">
|
||||
<div id="statusControlWrap">
|
||||
@@ -166,6 +160,76 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div id="minimalToolbar" class="hidden absolute right-6 top-[calc(1rem+env(safe-area-inset-top))] z-[2100] flex items-center gap-2 rounded-2xl border border-slate-200 bg-white/90 p-2 shadow-xl backdrop-blur-md">
|
||||
<button class="icon-btn" id="miniModePointBtn" title="Tambah point SPBU">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2C8.13 2 5 5.13 5 9c0 5 7 13 7 13s7-8 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="miniModePolylineBtn" title="Tambah polyline jalan">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 18l6-6 4 4 6-8"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="miniModePolygonBtn" title="Tambah polygon parsil">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l8 5v10l-8 5-8-5V7z"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="toggleRegistryDrawerBtn" title="Buka registry & filter">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="7" y1="12" x2="17" y2="12"/><line x1="10" y1="18" x2="14" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="registryDrawer" class="hidden-drawer absolute right-6 top-[calc(1rem+env(safe-area-inset-top)+4.2rem)] z-[2100] w-[360px] max-w-[calc(100vw-3rem)] rounded-2xl border border-slate-200 bg-white/95 p-3 shadow-2xl backdrop-blur-md">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-sm font-black tracking-tight text-slate-800">Registry</h2>
|
||||
<p class="mt-1 text-[11px] text-slate-500">Filter dan pencarian data input.</p>
|
||||
</div>
|
||||
<button id="registryDrawerCloseBtn" class="icon-btn h-9 w-9 text-slate-600" title="Tutup registry">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-between rounded-xl bg-slate-50 px-3 py-2">
|
||||
<span class="text-[11px] font-bold uppercase tracking-widest text-slate-500">Tercatat</span>
|
||||
<span id="registryCount" class="text-sm font-black text-indigo-600">0</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 grid gap-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Cari Nama / Atribut</label>
|
||||
<input id="registrySearchQuery" type="text" placeholder="Cari nama, kategori, nomor..." class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Jenis</label>
|
||||
<select id="registryTypeFilter" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="all">Semua</option>
|
||||
<option value="point">Poin</option>
|
||||
<option value="polyline">Polyline</option>
|
||||
<option value="polygon">Polygon</option>
|
||||
<option value="roadDamage">Jalan Rusak</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Dari</label>
|
||||
<input id="registryDateFrom" type="date" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Sampai</label>
|
||||
<input id="registryDateTo" type="date" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex gap-2">
|
||||
<button id="registryApplyFilterBtn" class="flex-1 rounded-lg bg-indigo-600 px-4 py-2 text-sm font-bold text-white transition hover:bg-indigo-700">Terapkan</button>
|
||||
<button id="registryResetFilterBtn" class="rounded-lg bg-slate-100 px-4 py-2 text-sm font-bold text-slate-700 transition hover:bg-slate-200">Reset</button>
|
||||
</div>
|
||||
|
||||
<div id="registryList" class="registry-list mt-3 space-y-2 pr-1">
|
||||
<div class="rounded-xl border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-sm text-slate-500">
|
||||
Memuat data input...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="choroplethPanel" class="absolute right-6 bottom-6 z-[2000] hidden w-[320px] max-w-[calc(100vw-3rem)] rounded-2xl border border-slate-200 bg-white/95 p-3 shadow-2xl backdrop-blur-md">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
@@ -247,7 +311,7 @@
|
||||
<div class="mt-4 grid gap-3 sm:grid-cols-2">
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Nama Objek</label>
|
||||
<input id="newFeatureName" type="text" placeholder="Contoh: Jl. Ahmad Yani" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<input id="newFeatureName" type="text" placeholder="Tambahkan nama atribut" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Kategori</label>
|
||||
@@ -352,6 +416,7 @@
|
||||
let activeDrawHandler = null;
|
||||
let currentDrawMode = 'polyline';
|
||||
let appMode = 'view';
|
||||
let isEditModeEnabled = true;
|
||||
let pendingShape = null;
|
||||
let pendingNewFeatureLayer = null;
|
||||
let featureLayerMap = {};
|
||||
@@ -360,6 +425,10 @@
|
||||
let featureRecords = [];
|
||||
let editingFeatureId = null;
|
||||
let choroplethLayer = null;
|
||||
let spbuBuka24JamLayer = L.layerGroup().addTo(map);
|
||||
let spbuTidak24JamLayer = L.layerGroup().addTo(map);
|
||||
let spbuLayerControl = null;
|
||||
let utilityControl = null;
|
||||
let roadDamageLayer = L.featureGroup().addTo(map);
|
||||
let roadNetworkLayer = L.featureGroup().addTo(map);
|
||||
let roadDamageRecords = [];
|
||||
@@ -670,9 +739,38 @@
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
function getRegistryUi() {
|
||||
const drawerPanel = document.getElementById('registryDrawer');
|
||||
const drawerSearch = document.getElementById('registrySearchQuery');
|
||||
|
||||
if (drawerPanel && drawerSearch) {
|
||||
return {
|
||||
panel: drawerPanel,
|
||||
count: document.getElementById('registryCount'),
|
||||
list: document.getElementById('registryList'),
|
||||
search: drawerSearch,
|
||||
type: document.getElementById('registryTypeFilter'),
|
||||
from: document.getElementById('registryDateFrom'),
|
||||
to: document.getElementById('registryDateTo')
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
panel: document.getElementById('inputRegistryPanel'),
|
||||
count: document.getElementById('inputRegistryCount'),
|
||||
list: document.getElementById('inputRegistryList'),
|
||||
search: document.getElementById('inputSearchQuery'),
|
||||
type: document.getElementById('inputTypeFilter'),
|
||||
from: document.getElementById('inputDateFrom'),
|
||||
to: document.getElementById('inputDateTo')
|
||||
};
|
||||
}
|
||||
|
||||
function enforceRegistryDateRange(showFeedback = false) {
|
||||
const fromEl = document.getElementById('inputDateFrom');
|
||||
const toEl = document.getElementById('inputDateTo');
|
||||
const ui = getRegistryUi();
|
||||
const fromEl = ui.from;
|
||||
const toEl = ui.to;
|
||||
if (!fromEl || !toEl) return;
|
||||
const from = fromEl.value;
|
||||
const to = toEl.value;
|
||||
|
||||
@@ -688,11 +786,12 @@
|
||||
}
|
||||
|
||||
function getRegistryFilters() {
|
||||
const ui = getRegistryUi();
|
||||
return {
|
||||
search: document.getElementById('inputSearchQuery').value.toLowerCase().trim(),
|
||||
type: document.getElementById('inputTypeFilter').value,
|
||||
from: document.getElementById('inputDateFrom').value,
|
||||
to: document.getElementById('inputDateTo').value
|
||||
search: String(ui.search && ui.search.value ? ui.search.value : '').toLowerCase().trim(),
|
||||
type: ui.type && ui.type.value ? ui.type.value : 'all',
|
||||
from: ui.from && ui.from.value ? ui.from.value : '',
|
||||
to: ui.to && ui.to.value ? ui.to.value : ''
|
||||
};
|
||||
}
|
||||
|
||||
@@ -754,7 +853,18 @@
|
||||
created_at: feature.created_at || null
|
||||
}));
|
||||
|
||||
return [...pointRecords, ...spatialRecords].sort((left, right) => {
|
||||
const roadDamageRegistryRecords = roadDamageRecords.map(record => ({
|
||||
id: Number(record.id),
|
||||
type: 'roadDamage',
|
||||
title: `Jalan Rusak - ${record.jenis_kerusakan || 'Tanpa jenis'}`,
|
||||
subtitle: record.deskripsi || '-',
|
||||
status: record.severity || '-',
|
||||
created_at: record.created_at || null,
|
||||
latitude: Number(record.latitude),
|
||||
longitude: Number(record.longitude)
|
||||
}));
|
||||
|
||||
return [...pointRecords, ...spatialRecords, ...roadDamageRegistryRecords].sort((left, right) => {
|
||||
const leftDate = parseDateOnly(left.created_at);
|
||||
const rightDate = parseDateOnly(right.created_at);
|
||||
|
||||
@@ -769,6 +879,7 @@
|
||||
if (type === 'point') return 'Poin';
|
||||
if (type === 'polyline') return 'Polyline';
|
||||
if (type === 'polygon') return 'Polygon';
|
||||
if (type === 'roadDamage') return 'Jalan Rusak';
|
||||
return 'Data';
|
||||
}
|
||||
|
||||
@@ -776,6 +887,7 @@
|
||||
if (type === 'point') return 'bg-emerald-100 text-emerald-700';
|
||||
if (type === 'polyline') return 'bg-indigo-100 text-indigo-700';
|
||||
if (type === 'polygon') return 'bg-amber-100 text-amber-700';
|
||||
if (type === 'roadDamage') return 'bg-red-100 text-red-700';
|
||||
return 'bg-slate-100 text-slate-700';
|
||||
}
|
||||
|
||||
@@ -788,6 +900,27 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'roadDamage') {
|
||||
let selectedMarker = null;
|
||||
roadDamageLayer.eachLayer(layer => {
|
||||
if (layer && layer.roadDamageId === id) {
|
||||
selectedMarker = layer;
|
||||
}
|
||||
});
|
||||
|
||||
if (selectedMarker) {
|
||||
map.setView(selectedMarker.getLatLng(), Math.max(map.getZoom(), 16));
|
||||
selectedMarker.openPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
const record = roadDamageRecords.find(item => Number(item.id) === Number(id));
|
||||
if (record && Number.isFinite(Number(record.latitude)) && Number.isFinite(Number(record.longitude))) {
|
||||
map.setView([Number(record.latitude), Number(record.longitude)], Math.max(map.getZoom(), 16));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const layer = featureLayerMap[id];
|
||||
if (!layer) return;
|
||||
if (layer.getBounds) {
|
||||
@@ -797,8 +930,10 @@
|
||||
}
|
||||
|
||||
function renderInputRegistry() {
|
||||
const listEl = document.getElementById('inputRegistryList');
|
||||
const countEl = document.getElementById('inputRegistryCount');
|
||||
const ui = getRegistryUi();
|
||||
const listEl = ui.list;
|
||||
const countEl = ui.count;
|
||||
if (!listEl || !countEl) return;
|
||||
const filters = getRegistryFilters();
|
||||
const records = buildRegistryRecords().filter(record => recordMatchesFilters(record, filters));
|
||||
|
||||
@@ -819,10 +954,14 @@
|
||||
const dateLabel = formatInputDate(record.created_at);
|
||||
const measure = record.type === 'point'
|
||||
? `Telp: ${record.subtitle}`
|
||||
: `Kategori: ${record.subtitle}`;
|
||||
: record.type === 'roadDamage'
|
||||
? `Deskripsi: ${record.subtitle}`
|
||||
: `Kategori: ${record.subtitle}`;
|
||||
const statusLabel = record.type === 'point'
|
||||
? `24 Jam: ${record.status}`
|
||||
: `Status: ${record.status}`;
|
||||
: record.type === 'roadDamage'
|
||||
? `Tingkat: ${record.status}`
|
||||
: `Status: ${record.status}`;
|
||||
|
||||
return `
|
||||
<button type="button" onclick="focusRegistryItem('${record.type}', ${record.id})" class="registry-item w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-left shadow-sm">
|
||||
@@ -847,10 +986,28 @@
|
||||
}
|
||||
|
||||
function toggleInputRegistry(forceState = null) {
|
||||
const panel = document.getElementById('inputRegistryPanel');
|
||||
const shouldOpen = forceState === null ? panel.classList.contains('hidden') : !!forceState;
|
||||
const ui = getRegistryUi();
|
||||
const panel = ui.panel;
|
||||
if (!panel) return;
|
||||
|
||||
const useDrawer = panel.id === 'registryDrawer';
|
||||
const isHidden = useDrawer ? panel.classList.contains('hidden-drawer') : panel.classList.contains('hidden');
|
||||
const shouldOpen = forceState === null ? isHidden : !!forceState;
|
||||
|
||||
if (useDrawer) {
|
||||
panel.classList.toggle('hidden-drawer', !shouldOpen);
|
||||
const triggerBtn = document.getElementById('toggleRegistryDrawerBtn');
|
||||
if (triggerBtn) {
|
||||
triggerBtn.classList.toggle('active', shouldOpen);
|
||||
}
|
||||
const leafletTriggerBtn = document.getElementById('leafletRegistryBtn');
|
||||
if (leafletTriggerBtn) {
|
||||
leafletTriggerBtn.classList.toggle('active', shouldOpen);
|
||||
}
|
||||
} else {
|
||||
panel.classList.toggle('hidden', !shouldOpen);
|
||||
}
|
||||
|
||||
panel.classList.toggle('hidden', !shouldOpen);
|
||||
if (shouldOpen) {
|
||||
renderInputRegistry();
|
||||
}
|
||||
@@ -888,7 +1045,6 @@
|
||||
|
||||
function setDataMode(mode) {
|
||||
appMode = mode;
|
||||
const hint = document.getElementById('modeHint');
|
||||
const statusWrap = document.getElementById('statusControlWrap');
|
||||
const measureWrap = document.getElementById('measureControlWrap');
|
||||
const runActionBtn = document.getElementById('runActionBtn');
|
||||
@@ -900,10 +1056,27 @@
|
||||
polyline: document.getElementById('modePolylineBtn'),
|
||||
polygon: document.getElementById('modePolygonBtn'),
|
||||
choropleth: document.getElementById('modeChoroplethBtn'),
|
||||
roadDamage: document.getElementById('modeRoadDamageBtn')
|
||||
roadDamage: document.getElementById('leafletRoadDamageBtn') || document.getElementById('modeRoadDamageBtn'),
|
||||
miniPoint: document.getElementById('miniModePointBtn'),
|
||||
miniPolyline: document.getElementById('miniModePolylineBtn'),
|
||||
miniPolygon: document.getElementById('miniModePolygonBtn')
|
||||
};
|
||||
|
||||
Object.values(modeButtons).forEach(button => button.classList.remove('active'));
|
||||
Object.values(modeButtons).forEach(button => {
|
||||
if (button) button.classList.remove('active');
|
||||
});
|
||||
if (mode === 'point') {
|
||||
if (modeButtons.point) modeButtons.point.classList.add('active');
|
||||
if (modeButtons.miniPoint) modeButtons.miniPoint.classList.add('active');
|
||||
}
|
||||
if (mode === 'polyline') {
|
||||
if (modeButtons.polyline) modeButtons.polyline.classList.add('active');
|
||||
if (modeButtons.miniPolyline) modeButtons.miniPolyline.classList.add('active');
|
||||
}
|
||||
if (mode === 'polygon') {
|
||||
if (modeButtons.polygon) modeButtons.polygon.classList.add('active');
|
||||
if (modeButtons.miniPolygon) modeButtons.miniPolygon.classList.add('active');
|
||||
}
|
||||
if (modeButtons[mode]) {
|
||||
modeButtons[mode].classList.add('active');
|
||||
}
|
||||
@@ -911,7 +1084,6 @@
|
||||
toggleChoroplethMode(mode === 'choropleth');
|
||||
|
||||
if (mode === 'view') {
|
||||
hint.innerText = 'Mode default: hanya melihat peta dan atribut data yang sudah dibuat.';
|
||||
spatialControls.classList.add('hidden');
|
||||
runActionBtn.innerText = 'Mode Lihat';
|
||||
runActionBtn.disabled = true;
|
||||
@@ -922,7 +1094,6 @@
|
||||
}
|
||||
|
||||
if (mode === 'choropleth') {
|
||||
hint.innerText = 'Mode choropleth aktif: klik kecamatan untuk melihat populasi.';
|
||||
spatialControls.classList.add('hidden');
|
||||
runActionBtn.innerText = 'Choropleth Aktif';
|
||||
runActionBtn.disabled = true;
|
||||
@@ -933,7 +1104,6 @@
|
||||
}
|
||||
|
||||
if (mode === 'roadDamage') {
|
||||
hint.innerText = 'Mode laporan jalan rusak aktif: klik peta untuk menandai lokasi kerusakan.';
|
||||
spatialControls.classList.add('hidden');
|
||||
runActionBtn.innerText = 'Klik Peta';
|
||||
runActionBtn.disabled = true;
|
||||
@@ -948,7 +1118,6 @@
|
||||
clearDraftBtn.disabled = false;
|
||||
|
||||
if (mode === 'point') {
|
||||
hint.innerText = 'Mode point aktif: klik peta untuk menambah SPBU baru.';
|
||||
spatialControls.classList.add('hidden');
|
||||
runActionBtn.innerText = 'Klik Peta';
|
||||
clearDraftBtn.disabled = true;
|
||||
@@ -958,13 +1127,27 @@
|
||||
}
|
||||
|
||||
spatialControls.classList.remove('hidden');
|
||||
hint.innerText = mode === 'polyline'
|
||||
? 'Mode polyline aktif: gambar jalan lalu isi nama pada modal.'
|
||||
: 'Mode polygon aktif: gambar parsil lalu isi nama pada modal.';
|
||||
runActionBtn.innerText = mode === 'polyline' ? 'Gambar Jalan' : 'Gambar Polygon';
|
||||
setSpatialType(mode);
|
||||
}
|
||||
|
||||
function setEditModeEnabled(enabled) {
|
||||
isEditModeEnabled = !!enabled;
|
||||
const toggleBtn = document.getElementById('toggleEditModeBtn');
|
||||
const editButtonsWrap = document.getElementById('editModeButtons');
|
||||
|
||||
if (!toggleBtn || !editButtonsWrap) return;
|
||||
|
||||
editButtonsWrap.classList.toggle('hidden', !isEditModeEnabled);
|
||||
toggleBtn.classList.toggle('active', isEditModeEnabled);
|
||||
toggleBtn.title = isEditModeEnabled ? 'Nonaktifkan mode edit' : 'Aktifkan mode edit';
|
||||
|
||||
if (!isEditModeEnabled) {
|
||||
setDataMode('view');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function openRoadDamageModal(lat, lng) {
|
||||
pendingRoadDamageCoords = { lat, lng };
|
||||
document.getElementById('roadDamageCoords').innerText = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
||||
@@ -1095,8 +1278,12 @@
|
||||
icon: createRoadDamageIcon(record.severity)
|
||||
}).addTo(roadDamageLayer);
|
||||
|
||||
marker.roadDamageId = Number(record.id);
|
||||
|
||||
marker.bindPopup(getRoadDamagePopup(record));
|
||||
});
|
||||
|
||||
renderInputRegistry();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Gagal memuat data jalan rusak:', err);
|
||||
@@ -1322,38 +1509,78 @@
|
||||
map.removeControl(drawControl);
|
||||
drawControl = null;
|
||||
}
|
||||
|
||||
if (type !== 'polyline' && type !== 'polygon') {
|
||||
return;
|
||||
}
|
||||
|
||||
drawControl = new L.Control.Draw({
|
||||
edit: false,
|
||||
position: 'topleft',
|
||||
edit: {
|
||||
featureGroup: featureLayer,
|
||||
edit: true,
|
||||
remove: true
|
||||
},
|
||||
draw: {
|
||||
polygon: type === 'polygon' ? {
|
||||
polygon: {
|
||||
allowIntersection: false,
|
||||
showArea: true,
|
||||
shapeOptions: {
|
||||
color: getFeatureStatusColor('polygon', document.getElementById('featureStatus').value),
|
||||
color: '#3b82f6',
|
||||
weight: 3
|
||||
}
|
||||
} : false,
|
||||
polyline: type === 'polyline' ? {
|
||||
},
|
||||
polyline: {
|
||||
shapeOptions: {
|
||||
color: getFeatureStatusColor('polyline', document.getElementById('featureStatus').value),
|
||||
color: '#16a34a',
|
||||
weight: 5
|
||||
}
|
||||
} : false,
|
||||
rectangle: false,
|
||||
circle: false,
|
||||
},
|
||||
rectangle: true,
|
||||
circle: true,
|
||||
circlemarker: false,
|
||||
marker: false
|
||||
marker: true
|
||||
}
|
||||
});
|
||||
|
||||
map.addControl(drawControl);
|
||||
}
|
||||
|
||||
function openNewLocationPopup(lat, lng) {
|
||||
const content = `
|
||||
<div class="p-4 w-full">
|
||||
<h3 class="font-bold text-indigo-700 border-b pb-2 mb-3 uppercase text-[10px] tracking-widest">Input SPBU Baru</h3>
|
||||
<div class="space-y-3">
|
||||
<input type="text" id="new_nama" placeholder="Nama SPBU" class="w-full border rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm">
|
||||
<input type="text" id="new_nomor" placeholder="Nomor SPBU" class="w-full border rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm">
|
||||
<select id="new_status" class="w-full border rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm">
|
||||
<option value="Ya">Buka 24 Jam</option>
|
||||
<option value="Tidak">Tidak</option>
|
||||
</select>
|
||||
<div class="text-[10px] text-slate-400 bg-slate-50 p-2 rounded border border-dashed text-center">
|
||||
Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)}
|
||||
</div>
|
||||
<button onclick="saveNew(${lat}, ${lng})" class="w-full bg-indigo-600 text-white py-2 rounded font-bold text-sm shadow-lg hover:bg-indigo-700 transition">Simpan</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
L.popup().setLatLng([lat, lng]).setContent(content).openOn(map);
|
||||
}
|
||||
|
||||
function convertCircleToPolygon(circleLayer, vertices = 48) {
|
||||
const center = circleLayer.getLatLng();
|
||||
const radius = Number(circleLayer.getRadius() || 0);
|
||||
const points = [];
|
||||
const latFactor = 111320;
|
||||
const lngFactor = latFactor * Math.cos((center.lat * Math.PI) / 180);
|
||||
|
||||
for (let index = 0; index < vertices; index += 1) {
|
||||
const angle = (index / vertices) * Math.PI * 2;
|
||||
const dx = radius * Math.cos(angle);
|
||||
const dy = radius * Math.sin(angle);
|
||||
const lat = center.lat + (dy / latFactor);
|
||||
const lng = center.lng + (dx / (lngFactor || 1));
|
||||
points.push([lat, lng]);
|
||||
}
|
||||
|
||||
return L.polygon(points);
|
||||
}
|
||||
|
||||
function calculatePolylineLength(layer) {
|
||||
const latlngs = layer.getLatLngs();
|
||||
const points = Array.isArray(latlngs[0]) ? latlngs.flat() : latlngs;
|
||||
@@ -1864,15 +2091,34 @@
|
||||
});
|
||||
|
||||
map.on(L.Draw.Event.CREATED, function(event) {
|
||||
const { layerType, layer } = event;
|
||||
const activeType = document.getElementById('featureType').value;
|
||||
|
||||
if ((activeType === 'polyline' && layerType !== 'polyline') || (activeType === 'polygon' && layerType !== 'polygon')) {
|
||||
let { layerType, layer } = event;
|
||||
if (layerType === 'marker') {
|
||||
const latlng = layer.getLatLng();
|
||||
openNewLocationPopup(latlng.lat, latlng.lng);
|
||||
return;
|
||||
}
|
||||
|
||||
if (layerType === 'circlemarker') {
|
||||
showMessage('Circle marker dinonaktifkan', 'bg-amber-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
if (layerType === 'circle') {
|
||||
layer = convertCircleToPolygon(layer);
|
||||
layerType = 'polygon';
|
||||
}
|
||||
|
||||
const activeType = (layerType === 'polyline') ? 'polyline' : 'polygon';
|
||||
document.getElementById('featureType').value = activeType;
|
||||
|
||||
const statusSelect = document.getElementById('featureStatus');
|
||||
statusSelect.innerHTML = buildFeatureStatusOptions(activeType);
|
||||
if (!statusSelect.value && statusSelect.options.length > 0) {
|
||||
statusSelect.selectedIndex = 0;
|
||||
}
|
||||
|
||||
const color = getFeatureStatusColor(activeType, document.getElementById('featureStatus').value);
|
||||
if (layerType === 'polyline') {
|
||||
if (activeType === 'polyline') {
|
||||
layer.setStyle({ color, weight: 5 });
|
||||
} else {
|
||||
layer.setStyle({ color, fillColor: color, fillOpacity: 0.25, weight: 3 });
|
||||
@@ -1924,10 +2170,65 @@
|
||||
}
|
||||
}
|
||||
|
||||
function isSpbuBuka24Jam(status) {
|
||||
return String(status || '').trim().toLowerCase() === 'ya';
|
||||
}
|
||||
|
||||
function ensureSpbuLayerControl() {
|
||||
if (spbuLayerControl) return;
|
||||
|
||||
spbuLayerControl = L.control.layers(null, {
|
||||
'SPBU Buka 24 Jam': spbuBuka24JamLayer,
|
||||
'SPBU Tidak Buka 24 Jam': spbuTidak24JamLayer
|
||||
}, {
|
||||
collapsed: false,
|
||||
position: 'topright'
|
||||
}).addTo(map);
|
||||
}
|
||||
|
||||
function ensureLeafletUtilityControl() {
|
||||
if (utilityControl) return;
|
||||
|
||||
utilityControl = L.control({ position: 'topleft' });
|
||||
utilityControl.onAdd = function() {
|
||||
const container = L.DomUtil.create('div', 'leaflet-bar');
|
||||
|
||||
const registryBtn = L.DomUtil.create('a', '', container);
|
||||
registryBtn.href = '#';
|
||||
registryBtn.id = 'leafletRegistryBtn';
|
||||
registryBtn.title = 'Buka Registry';
|
||||
registryBtn.innerHTML = 'Rg';
|
||||
|
||||
const roadDamageBtn = L.DomUtil.create('a', '', container);
|
||||
roadDamageBtn.href = '#';
|
||||
roadDamageBtn.id = 'leafletRoadDamageBtn';
|
||||
roadDamageBtn.title = 'Lapor Jalan Rusak';
|
||||
roadDamageBtn.innerHTML = 'Jr';
|
||||
|
||||
L.DomEvent.disableClickPropagation(container);
|
||||
|
||||
L.DomEvent.on(registryBtn, 'click', function(event) {
|
||||
L.DomEvent.stop(event);
|
||||
toggleInputRegistry();
|
||||
});
|
||||
|
||||
L.DomEvent.on(roadDamageBtn, 'click', function(event) {
|
||||
L.DomEvent.stop(event);
|
||||
setDataMode('roadDamage');
|
||||
toggleInputRegistry(false);
|
||||
roadDamageBtn.classList.add('active');
|
||||
showMessage('Mode laporan jalan rusak aktif. Klik peta untuk menandai lokasi.', 'bg-red-600 text-white');
|
||||
});
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
utilityControl.addTo(map);
|
||||
}
|
||||
|
||||
function clearAllMarkers() {
|
||||
map.eachLayer((layer) => {
|
||||
if (layer instanceof L.Marker) map.removeLayer(layer);
|
||||
});
|
||||
spbuBuka24JamLayer.clearLayers();
|
||||
spbuTidak24JamLayer.clearLayers();
|
||||
locationLayerMap = {};
|
||||
}
|
||||
|
||||
@@ -1993,12 +2294,14 @@
|
||||
|
||||
if (isNaN(lat) || isNaN(lng)) return;
|
||||
|
||||
const color = loc.buka_24jam === 'Ya' ? '#16a34a' : '#dc2626';
|
||||
const isOpen24 = isSpbuBuka24Jam(loc.buka_24jam);
|
||||
const color = isOpen24 ? '#16a34a' : '#dc2626';
|
||||
const targetLayer = isOpen24 ? spbuBuka24JamLayer : spbuTidak24JamLayer;
|
||||
|
||||
const marker = L.marker([lat, lng], {
|
||||
icon: createColoredIcon(color),
|
||||
draggable: true
|
||||
}).addTo(map);
|
||||
}).addTo(targetLayer);
|
||||
locationLayerMap[loc.id] = marker;
|
||||
marker.locationData = loc;
|
||||
|
||||
@@ -2035,7 +2338,7 @@
|
||||
|
||||
// HTML untuk isi Popup (View & Edit Mode)
|
||||
function getPopupContent(loc) {
|
||||
const statusClass = loc.buka_24jam === 'Ya' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';
|
||||
const statusClass = isSpbuBuka24Jam(loc.buka_24jam) ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';
|
||||
|
||||
return `
|
||||
<div class="w-full">
|
||||
@@ -2151,26 +2454,7 @@
|
||||
|
||||
if (appMode !== 'point') return;
|
||||
|
||||
const lat = e.latlng.lat;
|
||||
const lng = e.latlng.lng;
|
||||
const content = `
|
||||
<div class="p-4 w-full">
|
||||
<h3 class="font-bold text-indigo-700 border-b pb-2 mb-3 uppercase text-[10px] tracking-widest">Input SPBU Baru</h3>
|
||||
<div class="space-y-3">
|
||||
<input type="text" id="new_nama" placeholder="Nama SPBU" class="w-full border rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm">
|
||||
<input type="text" id="new_nomor" placeholder="Nomor SPBU" class="w-full border rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm">
|
||||
<select id="new_status" class="w-full border rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm">
|
||||
<option value="Ya">Buka 24 Jam</option>
|
||||
<option value="Tidak">Tidak</option>
|
||||
</select>
|
||||
<div class="text-[10px] text-slate-400 bg-slate-50 p-2 rounded border border-dashed text-center">
|
||||
Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)}
|
||||
</div>
|
||||
<button onclick="saveNew(${lat}, ${lng})" class="w-full bg-indigo-600 text-white py-2 rounded font-bold text-sm shadow-lg hover:bg-indigo-700 transition">Simpan</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
L.popup().setLatLng(e.latlng).setContent(content).openOn(map);
|
||||
openNewLocationPopup(e.latlng.lat, e.latlng.lng);
|
||||
});
|
||||
|
||||
function saveNew(lat, lng) {
|
||||
@@ -2245,21 +2529,63 @@
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
ensureSpbuLayerControl();
|
||||
ensureLeafletUtilityControl();
|
||||
updateDrawControl();
|
||||
setDataMode('view');
|
||||
enforceRegistryDateRange(false);
|
||||
loadMarkers({ force: true });
|
||||
loadFeatures(true);
|
||||
loadRoadDamage(true);
|
||||
loadExternalRoadNetwork(true);
|
||||
startAutoRefresh();
|
||||
};
|
||||
|
||||
const toggleEditModeBtn = document.getElementById('toggleEditModeBtn');
|
||||
if (toggleEditModeBtn) {
|
||||
toggleEditModeBtn.addEventListener('click', () => {
|
||||
setEditModeEnabled(!isEditModeEnabled);
|
||||
});
|
||||
}
|
||||
document.getElementById('modeViewBtn').addEventListener('click', () => setDataMode('view'));
|
||||
document.getElementById('modePointBtn').addEventListener('click', () => setDataMode('point'));
|
||||
document.getElementById('modePolylineBtn').addEventListener('click', () => setDataMode('polyline'));
|
||||
document.getElementById('modePolygonBtn').addEventListener('click', () => setDataMode('polygon'));
|
||||
document.getElementById('modeChoroplethBtn').addEventListener('click', () => setDataMode('choropleth'));
|
||||
document.getElementById('modeRoadDamageBtn').addEventListener('click', () => setDataMode('roadDamage'));
|
||||
document.getElementById('toggleMainPanelBtn').addEventListener('click', () => toggleMainPanel());
|
||||
const miniModePointBtn = document.getElementById('miniModePointBtn');
|
||||
if (miniModePointBtn) {
|
||||
miniModePointBtn.addEventListener('click', () => {
|
||||
setDataMode('point');
|
||||
showMessage('Klik peta untuk menambah point SPBU', 'bg-indigo-600 text-white');
|
||||
});
|
||||
}
|
||||
const miniModePolylineBtn = document.getElementById('miniModePolylineBtn');
|
||||
if (miniModePolylineBtn) {
|
||||
miniModePolylineBtn.addEventListener('click', () => {
|
||||
setDataMode('polyline');
|
||||
startDraw();
|
||||
});
|
||||
}
|
||||
const miniModePolygonBtn = document.getElementById('miniModePolygonBtn');
|
||||
if (miniModePolygonBtn) {
|
||||
miniModePolygonBtn.addEventListener('click', () => {
|
||||
setDataMode('polygon');
|
||||
startDraw();
|
||||
});
|
||||
}
|
||||
const toggleRegistryDrawerBtn = document.getElementById('toggleRegistryDrawerBtn');
|
||||
if (toggleRegistryDrawerBtn) {
|
||||
toggleRegistryDrawerBtn.addEventListener('click', () => toggleInputRegistry());
|
||||
}
|
||||
const registryDrawerCloseBtn = document.getElementById('registryDrawerCloseBtn');
|
||||
if (registryDrawerCloseBtn) {
|
||||
registryDrawerCloseBtn.addEventListener('click', () => toggleInputRegistry(false));
|
||||
}
|
||||
const toggleMainPanelBtn = document.getElementById('toggleMainPanelBtn');
|
||||
if (toggleMainPanelBtn) {
|
||||
toggleMainPanelBtn.addEventListener('click', () => toggleMainPanel());
|
||||
}
|
||||
document.getElementById('drawStatus').addEventListener('change', () => {
|
||||
const type = document.getElementById('featureType').value;
|
||||
document.getElementById('featureStatus').value = document.getElementById('drawStatus').value;
|
||||
@@ -2296,38 +2622,52 @@
|
||||
const id = Number(document.getElementById('editFeatureId').value);
|
||||
deleteFeature(id);
|
||||
});
|
||||
document.getElementById('closeInputRegistryBtn').addEventListener('click', () => toggleInputRegistry(false));
|
||||
document.getElementById('inputTypeFilter').addEventListener('change', renderInputRegistry);
|
||||
document.getElementById('inputDateFrom').addEventListener('change', () => {
|
||||
enforceRegistryDateRange(true);
|
||||
renderInputRegistry();
|
||||
});
|
||||
document.getElementById('inputDateTo').addEventListener('change', () => {
|
||||
enforceRegistryDateRange(true);
|
||||
renderInputRegistry();
|
||||
});
|
||||
document.getElementById('applyInputFilterBtn').addEventListener('click', () => {
|
||||
enforceRegistryDateRange(true);
|
||||
renderInputRegistry();
|
||||
});
|
||||
document.getElementById('clearInputFilterBtn').addEventListener('click', () => {
|
||||
document.getElementById('inputSearchQuery').value = '';
|
||||
document.getElementById('inputTypeFilter').value = 'all';
|
||||
document.getElementById('inputDateFrom').value = '';
|
||||
document.getElementById('inputDateTo').value = '';
|
||||
enforceRegistryDateRange(false);
|
||||
renderInputRegistry();
|
||||
});
|
||||
|
||||
// Event listener untuk pencarian real-time
|
||||
document.getElementById('inputSearchQuery').addEventListener('input', () => {
|
||||
renderInputRegistry();
|
||||
});
|
||||
|
||||
// Event listener untuk filter tipe (instant filtering)
|
||||
document.getElementById('inputTypeFilter').addEventListener('change', () => {
|
||||
renderInputRegistry();
|
||||
});
|
||||
const registryUi = getRegistryUi();
|
||||
if (registryUi.type) {
|
||||
registryUi.type.addEventListener('change', renderInputRegistry);
|
||||
}
|
||||
if (registryUi.from) {
|
||||
registryUi.from.addEventListener('change', () => {
|
||||
enforceRegistryDateRange(true);
|
||||
renderInputRegistry();
|
||||
});
|
||||
}
|
||||
if (registryUi.to) {
|
||||
registryUi.to.addEventListener('change', () => {
|
||||
enforceRegistryDateRange(true);
|
||||
renderInputRegistry();
|
||||
});
|
||||
}
|
||||
const registryApplyFilterBtn = document.getElementById('registryApplyFilterBtn');
|
||||
if (registryApplyFilterBtn) {
|
||||
registryApplyFilterBtn.addEventListener('click', () => {
|
||||
enforceRegistryDateRange(true);
|
||||
renderInputRegistry();
|
||||
});
|
||||
}
|
||||
const registryResetFilterBtn = document.getElementById('registryResetFilterBtn');
|
||||
if (registryResetFilterBtn) {
|
||||
registryResetFilterBtn.addEventListener('click', () => {
|
||||
const ui = getRegistryUi();
|
||||
if (ui.search) ui.search.value = '';
|
||||
if (ui.type) ui.type.value = 'all';
|
||||
if (ui.from) ui.from.value = '';
|
||||
if (ui.to) ui.to.value = '';
|
||||
enforceRegistryDateRange(false);
|
||||
renderInputRegistry();
|
||||
});
|
||||
}
|
||||
if (registryUi.search) {
|
||||
registryUi.search.addEventListener('input', () => {
|
||||
renderInputRegistry();
|
||||
});
|
||||
registryUi.search.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
renderInputRegistry();
|
||||
}
|
||||
});
|
||||
}
|
||||
document.getElementById('featureModal').addEventListener('click', (event) => {
|
||||
if (event.target.id === 'featureModal') closeFeatureEditor();
|
||||
});
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 178 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 MiB |
Reference in New Issue
Block a user