fix report and add filter

This commit is contained in:
Vckynando12 2025-03-23 17:42:37 +07:00
parent 3381eb11b5
commit beb4d15d11
4 changed files with 617 additions and 259 deletions

View File

@ -56,6 +56,23 @@ void setup() {
Firebase.begin(&config, &auth); Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true); Firebase.reconnectWiFi(true);
// Konfigurasi NTP dan tunggu sinkronisasi
configTime(7 * 3600, 0, "pool.ntp.org"); // GMT+7
Serial.println("Menunggu sinkronisasi waktu");
while (time(nullptr) < 1000000000) {
Serial.print(".");
delay(100);
}
Serial.println("\nWaktu tersinkronisasi!");
// Setelah waktu tersinkronisasi, baru kirim timestamp pertama
unsigned long epochTime = time(nullptr);
if (Firebase.setInt(firebaseData, "/device/lastActiveWemos", epochTime)) {
Serial.println("Initial timestamp sent: " + String(epochTime));
} else {
Serial.println("Failed to send initial timestamp");
}
// Inisialisasi RFID // Inisialisasi RFID
SPI.begin(); SPI.begin();
mfrc522.PCD_Init(); mfrc522.PCD_Init();
@ -84,13 +101,6 @@ void setup() {
// Update path untuk restart control // Update path untuk restart control
Firebase.setBool(firebaseData, "/control/restartWemos", false); Firebase.setBool(firebaseData, "/control/restartWemos", false);
// Konfigurasi NTP
configTime(7 * 3600, 0, "pool.ntp.org"); // GMT+7
// Kirim timestamp pertama kali
unsigned long epochTime = time(nullptr);
Firebase.setInt(firebaseData, "/device/lastActiveWemos", epochTime);
// Update status device saat startup // Update status device saat startup
Firebase.setString(firebaseData, "/logs/systemWemos", "Device Online"); Firebase.setString(firebaseData, "/logs/systemWemos", "Device Online");
} }

View File

@ -43,59 +43,135 @@ public function handle()
// Ambil data terakhir jika ada // Ambil data terakhir jika ada
$lastEntry = !empty($historyData) ? end($historyData) : null; $lastEntry = !empty($historyData) ? end($historyData) : null;
// Cek apakah ada perubahan pada security, smartcab, control, atau logs // Cek perubahan pada setiap kategori data
$securityChanged = false;
$smartcabChanged = false;
$controlChanged = false;
$logsChanged = false;
$dht11Changed = false;
$deviceChanged = false;
$hasChanges = false; $hasChanges = false;
if ($lastEntry === null) { if ($lastEntry === null) {
$hasChanges = true; // Jika belum ada data, anggap semua kategori berubah (kecuali yang dikecualikan)
$securityChanged = !empty($securityData);
$smartcabChanged = !empty($smartcabData);
$controlChanged = !empty($controlData);
$logsChanged = !empty($logsData);
// Untuk DHT11 dan Device, kita hanya anggap berubah jika ada data selain yang dikecualikan
$dht11Changed = !empty($dht11Data) && $this->hasNonTrivialDHT11Data($dht11Data);
$deviceChanged = !empty($deviceData) && $this->hasNonTrivialDeviceData($deviceData);
$hasChanges = $securityChanged || $smartcabChanged || $controlChanged ||
$logsChanged || $dht11Changed || $deviceChanged;
} else { } else {
// Cek perubahan pada setiap kategori
$securityChanged = $this->hasDataChanged($lastEntry['security'] ?? [], $securityData); $securityChanged = $this->hasDataChanged($lastEntry['security'] ?? [], $securityData);
$smartcabChanged = $this->hasDataChanged($lastEntry['smartcab'] ?? [], $smartcabData); $smartcabChanged = $this->hasDataChanged($lastEntry['smartcab'] ?? [], $smartcabData);
$controlChanged = $this->hasDataChanged($lastEntry['control'] ?? [], $controlData); $controlChanged = $this->hasDataChanged($lastEntry['control'] ?? [], $controlData);
$logsChanged = $this->hasDataChanged($lastEntry['logs'] ?? [], $logsData); $logsChanged = $this->hasDataChanged($lastEntry['logs'] ?? [], $logsData);
$hasChanges = $securityChanged || $smartcabChanged || $controlChanged || $logsChanged; // Untuk dht11 dan device kita periksa secara khusus, mengabaikan field yang sering berubah
$dht11Changed = $this->hasNonTrivialDHT11Changes($lastEntry['dht11'] ?? [], $dht11Data);
$deviceChanged = $this->hasNonTrivialDeviceChanges($lastEntry['device'] ?? [], $deviceData);
$hasChanges = $securityChanged || $smartcabChanged || $controlChanged ||
$logsChanged || $dht11Changed || $deviceChanged;
} }
// Hanya simpan jika ada perubahan // Proses setiap perubahan secara terpisah
if ($hasChanges) { $changesMade = false;
$newData = [
'id' => Str::uuid()->toString(), // Generate ID unik // Buat template data lengkap
'timestamp' => now()->toIso8601String(), $fullData = [
'security' => $securityData, 'security' => $securityData,
'smartcab' => $smartcabData, 'smartcab' => $smartcabData,
'control' => $controlData, 'control' => $controlData,
'logs' => $logsData 'logs' => $logsData,
]; 'dht11' => $dht11Data,
'device' => $deviceData
];
if (!empty($dht11Data)) { // Simpan setiap perubahan secara terpisah dengan data lengkap
$newData['dht11'] = $dht11Data; if ($securityChanged && !empty($securityData)) {
} $newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'security'; // Tambahkan informasi apa yang berubah
if (!empty($deviceData)) { $historyData[] = $newEntry;
$newData['device'] = $deviceData; event(new ReportUpdated($newEntry, 'security'));
} $this->info('Perubahan terdeteksi pada security, ID: ' . $newEntry['id']);
$changesMade = true;
}
$historyData[] = $newData; if ($smartcabChanged && !empty($smartcabData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'smartcab';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'smartcab'));
$this->info('Perubahan terdeteksi pada smartcab, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($controlChanged && !empty($controlData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'control';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'control'));
$this->info('Perubahan terdeteksi pada control, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($logsChanged && !empty($logsData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'logs';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'logs'));
$this->info('Perubahan terdeteksi pada logs, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($dht11Changed && !empty($dht11Data)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'dht11';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'dht11'));
$this->info('Perubahan signifikan terdeteksi pada dht11, ID: ' . $newEntry['id']);
$changesMade = true;
}
if ($deviceChanged && !empty($deviceData)) {
$newEntry = $fullData;
$newEntry['id'] = Str::uuid()->toString();
$newEntry['timestamp'] = now()->toIso8601String();
$newEntry['change_type'] = 'device';
$historyData[] = $newEntry;
event(new ReportUpdated($newEntry, 'device'));
$this->info('Perubahan signifikan terdeteksi pada device, ID: ' . $newEntry['id']);
$changesMade = true;
}
// Jika ada perubahan yang disimpan, update file
if ($changesMade) {
Storage::put('reports.json', json_encode($historyData, JSON_PRETTY_PRINT)); Storage::put('reports.json', json_encode($historyData, JSON_PRETTY_PRINT));
$this->info('Data baru tersimpan dengan ID: ' . $newData['id']); $this->info('Semua perubahan berhasil disimpan.');
// Broadcast event untuk realtime update
event(new ReportUpdated($newData));
if (isset($securityChanged) && $securityChanged) {
$this->info('Perubahan terdeteksi pada security');
}
if (isset($smartcabChanged) && $smartcabChanged) {
$this->info('Perubahan terdeteksi pada smartcab');
}
if (isset($controlChanged) && $controlChanged) {
$this->info('Perubahan terdeteksi pada control');
}
if (isset($logsChanged) && $logsChanged) {
$this->info('Perubahan terdeteksi pada logs');
}
} else { } else {
$this->info('Tidak ada perubahan pada data, data tidak disimpan'); $this->info('Tidak ada perubahan signifikan pada data, data tidak disimpan');
} }
} catch (\Exception $e) { } catch (\Exception $e) {
@ -110,4 +186,96 @@ private function hasDataChanged($oldData, $newData)
return $oldJson !== $newJson; return $oldJson !== $newJson;
} }
/**
* Memeriksa perubahan pada data dht11 selain humidity dan temperature
*/
private function hasNonTrivialDHT11Changes($oldData, $newData)
{
// Buat salinan data untuk perbandingan
$oldDataCompare = is_array($oldData) ? $oldData : [];
$newDataCompare = is_array($newData) ? $newData : [];
// Hapus field yang sering berubah
if (isset($oldDataCompare['humidity'])) {
unset($oldDataCompare['humidity']);
}
if (isset($oldDataCompare['temperature'])) {
unset($oldDataCompare['temperature']);
}
if (isset($newDataCompare['humidity'])) {
unset($newDataCompare['humidity']);
}
if (isset($newDataCompare['temperature'])) {
unset($newDataCompare['temperature']);
}
// Bandingkan data yang tersisa
return json_encode($oldDataCompare) !== json_encode($newDataCompare);
}
/**
* Memeriksa apakah DHT11 data memiliki field selain yang dikecualikan
*/
private function hasNonTrivialDHT11Data($data)
{
$dataCopy = is_array($data) ? $data : [];
// Hapus field yang sering berubah
if (isset($dataCopy['humidity'])) {
unset($dataCopy['humidity']);
}
if (isset($dataCopy['temperature'])) {
unset($dataCopy['temperature']);
}
// Periksa apakah masih ada data lain
return !empty($dataCopy);
}
/**
* Memeriksa perubahan pada data device selain lastActive dan lastActiveWemos
*/
private function hasNonTrivialDeviceChanges($oldData, $newData)
{
// Buat salinan data untuk perbandingan
$oldDataCompare = is_array($oldData) ? $oldData : [];
$newDataCompare = is_array($newData) ? $newData : [];
// Hapus field yang sering berubah
if (isset($oldDataCompare['lastActive'])) {
unset($oldDataCompare['lastActive']);
}
if (isset($oldDataCompare['lastActiveWemos'])) {
unset($oldDataCompare['lastActiveWemos']);
}
if (isset($newDataCompare['lastActive'])) {
unset($newDataCompare['lastActive']);
}
if (isset($newDataCompare['lastActiveWemos'])) {
unset($newDataCompare['lastActiveWemos']);
}
// Bandingkan data yang tersisa
return json_encode($oldDataCompare) !== json_encode($newDataCompare);
}
/**
* Memeriksa apakah Device data memiliki field selain yang dikecualikan
*/
private function hasNonTrivialDeviceData($data)
{
$dataCopy = is_array($data) ? $data : [];
// Hapus field yang sering berubah
if (isset($dataCopy['lastActive'])) {
unset($dataCopy['lastActive']);
}
if (isset($dataCopy['lastActiveWemos'])) {
unset($dataCopy['lastActiveWemos']);
}
// Periksa apakah masih ada data lain
return !empty($dataCopy);
}
} }

View File

@ -15,15 +15,19 @@ class ReportUpdated implements ShouldBroadcast
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $report; public $report;
public $changeType;
/** /**
* Create a new event instance. * Create a new event instance.
* *
* @param array $report Data report
* @param string|null $changeType Tipe perubahan (security, smartcab, dll)
* @return void * @return void
*/ */
public function __construct($report) public function __construct($report, $changeType = null)
{ {
$this->report = $report; $this->report = $report;
$this->changeType = $changeType ?? ($report['change_type'] ?? null);
} }
/** /**
@ -35,4 +39,18 @@ public function broadcastOn()
{ {
return new Channel('reports'); return new Channel('reports');
} }
/**
* Get the data to broadcast.
*
* @return array
*/
public function broadcastWith()
{
return [
'report' => $this->report,
'change_type' => $this->changeType,
'timestamp' => now()->toIso8601String()
];
}
} }

View File

@ -29,6 +29,27 @@
50% { background-color: #d1ecf1; } 50% { background-color: #d1ecf1; }
100% { background-color: #fff; } 100% { background-color: #fff; }
} }
/* Styling untuk multiple select */
select[multiple] {
max-height: 200px;
overflow-y: auto;
}
select[multiple] optgroup {
font-weight: 600;
color: #374151;
padding: 0.25rem 0;
}
select[multiple] option {
padding: 0.25rem 0.5rem;
margin: 0.125rem 0;
}
select[multiple] option:checked {
background-color: #2563eb;
color: white;
}
</style> </style>
</head> </head>
<body class="bg-gray-50"> <body class="bg-gray-50">
@ -57,43 +78,59 @@
<ul class="space-y-2 text-sm"> <ul class="space-y-2 text-sm">
<li class="flex items-center"> <li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-green-500 mr-2"></span> <span class="w-3 h-3 rounded-full bg-green-500 mr-2"></span>
<span><strong>Keamanan Normal:</strong> Sistem keamanan berjalan dengan baik</span> <span><strong>Fan:</strong> Perubahan pada status kipas</span>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-red-500 mr-2"></span> <span class="w-3 h-3 rounded-full bg-red-500 mr-2"></span>
<span><strong>Status Bahaya:</strong> Sistem keamanan mendeteksi potensi bahaya</span> <span><strong>Status:</strong> Perubahan status keamanan (aman/bahaya)</span>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-yellow-500 mr-2"></span> <span class="w-3 h-3 rounded-full bg-yellow-500 mr-2"></span>
<span><strong>Gerakan Terdeteksi:</strong> Sensor gerakan mendeteksi aktivitas</span> <span><strong>Motion:</strong> Perubahan status gerakan</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-lime-500 mr-2"></span>
<span><strong>Tidak Ada Gerakan:</strong> Tidak ada gerakan yang terdeteksi</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-blue-500 mr-2"></span>
<span><strong>Servo Terkunci:</strong> Servo dalam keadaan terkunci</span>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-cyan-500 mr-2"></span> <span class="w-3 h-3 rounded-full bg-cyan-500 mr-2"></span>
<span><strong>Servo Terbuka:</strong> Servo dalam keadaan terbuka</span> <span><strong>Servo Status:</strong> Perubahan status servo (terbuka/terkunci)</span>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-violet-400 mr-2"></span> <span class="w-3 h-3 rounded-full bg-violet-400 mr-2"></span>
<span><strong>Akses Terakhir:</strong> Perubahan pada akses terakhir</span> <span><strong>Last Access:</strong> Perubahan pada akses terakhir</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-purple-500 mr-2"></span>
<span><strong>Kontrol Diubah:</strong> Terjadi perubahan pada kontrol perangkat</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-pink-500 mr-2"></span>
<span><strong>Status Perangkat:</strong> Perubahan pada status perangkat</span>
</li> </li>
<li class="flex items-center"> <li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-orange-500 mr-2"></span> <span class="w-3 h-3 rounded-full bg-orange-500 mr-2"></span>
<span><strong>Error/Warning:</strong> Terdapat kesalahan atau peringatan dalam sistem</span> <span><strong>Restart ESP:</strong> Perangkat ESP direstart</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-amber-500 mr-2"></span>
<span><strong>Restart Wemos:</strong> Perangkat Wemos direstart</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-indigo-500 mr-2"></span>
<span><strong>RFID:</strong> Perubahan pada status RFID</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-lime-500 mr-2"></span>
<span><strong>DHT:</strong> Perubahan pada sensor DHT</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-teal-500 mr-2"></span>
<span><strong>MPU:</strong> Perubahan pada sensor MPU</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-sky-500 mr-2"></span>
<span><strong>Servo Log:</strong> Perubahan pada log servo</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-rose-500 mr-2"></span>
<span><strong>System ESP:</strong> Perubahan status sistem ESP</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-pink-500 mr-2"></span>
<span><strong>System Wemos:</strong> Perubahan status sistem Wemos</span>
</li>
<li class="flex items-center">
<span class="w-3 h-3 rounded-full bg-gray-500 mr-2"></span>
<span><strong>Device Status:</strong> Perubahan status perangkat lainnya</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -169,6 +206,38 @@
</div> </div>
</div> </div>
<!-- Tambahkan ini di dalam form filter, setelah Time Range Picker -->
<div class="md:col-span-2">
<label for="categories" class="block mb-2 text-sm font-medium text-gray-700">Filter Kategori</label>
<select id="categories" data-te-select-init multiple
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
<optgroup label="Keamanan">
<option value="motion">Motion</option>
<option value="status">Status Keamanan</option>
<option value="fan">Fan</option>
</optgroup>
<optgroup label="Perangkat">
<option value="servo-status">Servo Status</option>
<option value="last-access">Last Access</option>
<option value="device">Device Status</option>
</optgroup>
<optgroup label="Kontrol">
<option value="restart-esp">Restart ESP</option>
<option value="restart-wemos">Restart Wemos</option>
</optgroup>
<optgroup label="Sensor">
<option value="rfid">RFID</option>
<option value="dht">DHT</option>
<option value="mpu">MPU</option>
</optgroup>
<optgroup label="Log">
<option value="servo-log">Servo Log</option>
<option value="system-esp">System ESP</option>
<option value="system-wemos">System Wemos</option>
</optgroup>
</select>
</div>
<div class="md:col-span-2 flex justify-end space-x-2"> <div class="md:col-span-2 flex justify-end space-x-2">
<button type="button" id="applyFilter" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5"> <button type="button" id="applyFilter" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5">
Terapkan Filter Terapkan Filter
@ -287,6 +356,7 @@
const pageSize = 10; // Jumlah item per halaman const pageSize = 10; // Jumlah item per halaman
let filteredReports = []; // Menyimpan hasil filter let filteredReports = []; // Menyimpan hasil filter
let isFilterActive = false; // Flag untuk menandai apakah filter aktif let isFilterActive = false; // Flag untuk menandai apakah filter aktif
let selectedCategories = [];
// Variabel untuk menyimpan instance chart // Variabel untuk menyimpan instance chart
let systemOverviewChart = null; let systemOverviewChart = null;
@ -367,89 +437,84 @@ function goToNextPage() {
function applyFilters() { function applyFilters() {
const filterDate = document.getElementById('filterDate').value; const filterDate = document.getElementById('filterDate').value;
const timeFilterEnabled = document.getElementById('filterTimeToggle').checked; const timeFilterEnabled = document.getElementById('filterTimeToggle').checked;
const categories = Array.from(document.getElementById('categories').selectedOptions).map(opt => opt.value);
// Simpan halaman sebelumnya
const prevPage = currentPage;
// Set flag filter aktif // Set flag filter aktif
isFilterActive = filterDate || timeFilterEnabled; isFilterActive = filterDate || timeFilterEnabled || categories.length > 0;
selectedCategories = categories;
let startTime = null; let filteredData = allReports;
let endTime = null;
if (timeFilterEnabled) { // Filter berdasarkan tanggal dan waktu
startTime = document.getElementById('startTime').value; if (filterDate || timeFilterEnabled) {
endTime = document.getElementById('endTime').value; filteredData = filteredData.filter(report => {
// Convert to minutes for easier comparison
const [startHour, startMinute] = startTime.split(':').map(Number);
const [endHour, endMinute] = endTime.split(':').map(Number);
const startTotalMinutes = startHour * 60 + startMinute;
const endTotalMinutes = endHour * 60 + endMinute;
// Filter data berdasarkan kriteria
filteredReports = allReports.filter(report => {
const reportDate = new Date(report.timestamp); const reportDate = new Date(report.timestamp);
// Filter berdasarkan tanggal // Filter tanggal
if (filterDate) { if (filterDate) {
const dateStr = reportDate.toISOString().split('T')[0]; const dateStr = reportDate.toISOString().split('T')[0];
if (dateStr !== filterDate) return false; if (dateStr !== filterDate) return false;
} }
// Filter berdasarkan rentang waktu // Filter waktu
if (timeFilterEnabled) { if (timeFilterEnabled) {
const startTime = document.getElementById('startTime').value;
const endTime = document.getElementById('endTime').value;
const [startHour, startMinute] = startTime.split(':').map(Number);
const [endHour, endMinute] = endTime.split(':').map(Number);
const startTotalMinutes = startHour * 60 + startMinute;
const endTotalMinutes = endHour * 60 + endMinute;
const hour = reportDate.getHours(); const hour = reportDate.getHours();
const minute = reportDate.getMinutes(); const minute = reportDate.getMinutes();
const totalMinutes = hour * 60 + minute; const totalMinutes = hour * 60 + minute;
// Handle case where time range crosses midnight
if (startTotalMinutes <= endTotalMinutes) { if (startTotalMinutes <= endTotalMinutes) {
// Normal case (e.g., 08:00 to 17:00)
if (totalMinutes < startTotalMinutes || totalMinutes > endTotalMinutes) return false; if (totalMinutes < startTotalMinutes || totalMinutes > endTotalMinutes) return false;
} else { } else {
// Overnight case (e.g., 22:00 to 06:00)
if (totalMinutes < startTotalMinutes && totalMinutes > endTotalMinutes) return false; if (totalMinutes < startTotalMinutes && totalMinutes > endTotalMinutes) return false;
} }
} }
return true; return true;
}); });
} else { }
// Jika filter waktu tidak aktif, hanya filter berdasarkan tanggal
filteredReports = allReports.filter(report => {
const reportDate = new Date(report.timestamp);
// Filter berdasarkan tanggal // Filter berdasarkan kategori
if (filterDate) { if (categories.length > 0) {
const dateStr = reportDate.toISOString().split('T')[0]; filteredData = filteredData.filter(report => {
if (dateStr !== filterDate) return false; // Deteksi perubahan untuk report ini
} const changes = detectChanges(report, allReports, allReports.indexOf(report));
// Cek apakah ada perubahan yang masuk dalam kategori yang dipilih
return true; return changes.some(change => categories.includes(change.badge));
}); });
} }
// Reset halaman ke 1 HANYA ketika menerapkan filter baru (karena ini eksplisit pengguna memfilter) // Update data terfilter
filteredReports = filteredData;
// Reset ke halaman pertama
currentPage = 1; currentPage = 1;
// PENTING: Update chart dengan data yang sudah difilter // Update chart
renderSystemOverviewChart(filteredReports); renderSystemOverviewChart(filteredReports);
// Render data yang telah difilter // Render data
renderPaginatedReports(true); renderPaginatedReports(true);
// Tampilkan notifikasi hasil filter // Tampilkan notifikasi hasil filter
const filterCount = filteredReports.length; const filterCount = filteredReports.length;
const totalCount = allReports.length; const totalCount = allReports.length;
if (filterCount === 0) {
showFilterNotification(`Tidak ada data yang cocok dengan filter`); let filterMessage = `Ditemukan ${filterCount} dari ${totalCount} data`;
} else { if (categories.length > 0) {
showFilterNotification(`Ditemukan ${filterCount} dari ${totalCount} data`); filterMessage += ` dengan kategori: ${categories.join(', ')}`;
} }
// Tambahkan badge untuk menampilkan status filter pada chart showFilterNotification(filterCount === 0 ? 'Tidak ada data yang cocok dengan filter' : filterMessage);
// Update status filter pada chart
updateChartFilterStatus(); updateChartFilterStatus();
} }
@ -481,6 +546,8 @@ function resetFilters() {
document.getElementById('filterTimeToggle').checked = false; document.getElementById('filterTimeToggle').checked = false;
document.getElementById('timeRangePicker').classList.add('hidden'); document.getElementById('timeRangePicker').classList.add('hidden');
document.getElementById('timeToggleStatus').textContent = 'Semua Waktu'; document.getElementById('timeToggleStatus').textContent = 'Semua Waktu';
document.getElementById('categories').selectedIndex = -1; // Reset multiple select
selectedCategories = [];
// Reset flag filter // Reset flag filter
isFilterActive = false; isFilterActive = false;
@ -489,7 +556,7 @@ function resetFilters() {
filteredReports = allReports; filteredReports = allReports;
currentPage = 1; currentPage = 1;
// PENTING: Update chart dengan semua data // Update chart dengan semua data
renderSystemOverviewChart(allReports); renderSystemOverviewChart(allReports);
// Update chart filter status // Update chart filter status
@ -745,15 +812,25 @@ function renderReports(reports) {
const changes = detectChanges(report, allReports, allReports.indexOf(report)); const changes = detectChanges(report, allReports, allReports.indexOf(report));
changes.forEach(change => { changes.forEach(change => {
const badge = document.createElement('span'); const badge = document.createElement('span');
// Flowbite badge styles
// Flowbite badge styles dengan warna khusus untuk setiap jenis
const badgeClasses = { const badgeClasses = {
'primary': 'bg-blue-100 text-blue-800', 'primary': 'bg-blue-100 text-blue-800',
'secondary': 'bg-gray-100 text-gray-800', 'motion': 'bg-yellow-100 text-yellow-800',
'danger': 'bg-red-100 text-red-800', 'status': 'bg-red-100 text-red-800',
'warning': 'bg-yellow-100 text-yellow-800', 'fan': 'bg-green-100 text-green-800',
'info': 'bg-indigo-100 text-indigo-800', 'last-access': 'bg-violet-100 text-violet-800',
'light': 'bg-gray-100 text-gray-800', 'servo-status': 'bg-cyan-100 text-cyan-800',
'dark': 'bg-gray-700 text-gray-300' 'restart-esp': 'bg-orange-100 text-orange-800',
'restart-wemos': 'bg-amber-100 text-amber-800',
'rfid': 'bg-indigo-100 text-indigo-800',
'dht': 'bg-lime-100 text-lime-800',
'mpu': 'bg-teal-100 text-teal-800',
'servo-log': 'bg-sky-100 text-sky-800',
'system-esp': 'bg-rose-100 text-rose-800',
'system-wemos': 'bg-pink-100 text-pink-800',
'device': 'bg-gray-100 text-gray-800',
'light': 'bg-gray-100 text-gray-400'
}; };
badge.className = `px-2 py-0.5 rounded text-xs font-medium me-2 ${badgeClasses[change.badge] || badgeClasses['light']}`; badge.className = `px-2 py-0.5 rounded text-xs font-medium me-2 ${badgeClasses[change.badge] || badgeClasses['light']}`;
@ -810,41 +887,81 @@ function detectChanges(report, reports, index) {
// Security changes // Security changes
if (report.security && prevReport.security) { if (report.security && prevReport.security) {
if (report.security.motion !== prevReport.security.motion) { if (report.security.motion !== prevReport.security.motion) {
changes.push({ type: 'Gerakan', badge: 'danger' }); changes.push({ type: 'Motion', badge: 'motion' });
} }
if (report.security.status !== prevReport.security.status) { if (report.security.status !== prevReport.security.status) {
changes.push({ type: 'Status Keamanan', badge: 'danger' }); changes.push({ type: 'Status', badge: 'status' });
}
if (report.security.fan !== prevReport.security.fan) {
changes.push({ type: 'Fan', badge: 'fan' });
} }
} }
// Smartcab changes // Smartcab changes
if (report.smartcab && prevReport.smartcab) { if (report.smartcab && prevReport.smartcab) {
if (report.smartcab.last_access !== prevReport.smartcab.last_access) { if (report.smartcab.last_access !== prevReport.smartcab.last_access) {
changes.push({ type: 'Akses Terakhir', badge: 'info' }); changes.push({ type: 'Last Access', badge: 'last-access' });
} }
if (report.smartcab.servo_status !== prevReport.smartcab.servo_status) { if (report.smartcab.servo_status !== prevReport.smartcab.servo_status) {
changes.push({ type: 'Status Servo', badge: 'warning' }); changes.push({ type: 'Servo Status', badge: 'servo-status' });
} }
} }
// Control changes // Control changes
if (report.control && prevReport.control && if (report.control && prevReport.control) {
JSON.stringify(report.control) !== JSON.stringify(prevReport.control)) { if (report.control.restartESP !== prevReport.control.restartESP) {
changes.push({ type: 'Kontrol Perangkat', badge: 'secondary' }); changes.push({ type: 'Restart ESP', badge: 'restart-esp' });
} }
// Device changes if (report.control.restartWemos !== prevReport.control.restartWemos) {
if (report.device && prevReport.device && changes.push({ type: 'Restart Wemos', badge: 'restart-wemos' });
JSON.stringify(report.device) !== JSON.stringify(prevReport.device)) { }
changes.push({ type: 'Status Perangkat', badge: 'secondary' });
} }
// Logs changes // Logs changes
if (report.logs && prevReport.logs && if (report.logs && prevReport.logs) {
JSON.stringify(report.logs) !== JSON.stringify(prevReport.logs)) { // RFID logs
changes.push({ type: 'Log Sistem', badge: 'dark' }); if (report.logs.RFID && prevReport.logs.RFID &&
JSON.stringify(report.logs.RFID) !== JSON.stringify(prevReport.logs.RFID)) {
changes.push({ type: 'RFID', badge: 'rfid' });
}
// DHT logs
if (report.logs.dht && prevReport.logs.dht &&
JSON.stringify(report.logs.dht) !== JSON.stringify(prevReport.logs.dht)) {
changes.push({ type: 'DHT', badge: 'dht' });
}
// MPU logs
if (report.logs.mpu && prevReport.logs.mpu &&
JSON.stringify(report.logs.mpu) !== JSON.stringify(prevReport.logs.mpu)) {
changes.push({ type: 'MPU', badge: 'mpu' });
}
// Servo logs
if (report.logs.servo && prevReport.logs.servo &&
JSON.stringify(report.logs.servo) !== JSON.stringify(prevReport.logs.servo)) {
changes.push({ type: 'Servo Log', badge: 'servo-log' });
}
// System ESP logs
if (report.logs.systemESP !== prevReport.logs.systemESP) {
changes.push({ type: 'System ESP', badge: 'system-esp' });
}
// System Wemos logs
if (report.logs.systemWemos !== prevReport.logs.systemWemos) {
changes.push({ type: 'System Wemos', badge: 'system-wemos' });
}
}
// Device changes - general fallback if needed
if (report.device && prevReport.device &&
JSON.stringify(report.device) !== JSON.stringify(prevReport.device)) {
changes.push({ type: 'Device Status', badge: 'device' });
} }
if (changes.length === 0) { if (changes.length === 0) {
@ -1039,145 +1156,186 @@ function showDetail(report) {
function analyzeSystemOverview(reports) { function analyzeSystemOverview(reports) {
// Siapkan variabel untuk menyimpan hitungan status // Siapkan variabel untuk menyimpan hitungan status
const statusCounts = { const statusCounts = {
'securityNormal': 0, // Status keamanan normal 'motion': 0, // Motion status
'securityDanger': 0, // Status keamanan bahaya 'status': 0, // Security status
'motionDetected': 0, // Gerakan terdeteksi 'fan': 0, // Fan status
'motionNotDetected': 0, // Gerakan tidak terdeteksi 'servoStatus': 0, // Servo status
'servoLocked': 0, // Servo terkunci 'lastAccess': 0, // Last access
'servoUnlocked': 0, // Servo terbuka 'restartEsp': 0, // Restart ESP
'accessChanged': 0, // Perubahan akses terakhir 'restartWemos': 0, // Restart Wemos
'controlChanged': 0, // Perubahan kontrol perangkat 'rfid': 0, // RFID
'deviceChanged': 0, // Perubahan status perangkat 'dht': 0, // DHT sensor
'logError': 0 // Log error 'mpu': 0, // MPU sensor
'servoLog': 0, // Servo log
'systemEsp': 0, // System ESP
'systemWemos': 0, // System Wemos
'deviceStatus': 0 // Device status
}; };
// Total untuk persentase dan statusCounts // Total untuk persentase
let totalCounts = 0; let totalCounts = 0;
// Fungsi untuk menghitung perubahan antar report
function countChanges(report, index) {
if (index === 0 || index >= reports.length - 1) return [];
const changes = [];
const prevReport = reports[index + 1]; // Data baris sebelumnya (karena data sorted terbaru dulu)
// Security changes
if (report.security && prevReport.security) {
if (report.security.motion !== prevReport.security.motion) {
changes.push('motion');
}
if (report.security.status !== prevReport.security.status) {
changes.push('status');
}
if (report.security.fan !== prevReport.security.fan) {
changes.push('fan');
}
}
// Smartcab changes
if (report.smartcab && prevReport.smartcab) {
if (report.smartcab.last_access !== prevReport.smartcab.last_access) {
changes.push('lastAccess');
}
if (report.smartcab.servo_status !== prevReport.smartcab.servo_status) {
changes.push('servoStatus');
}
}
// Control changes
if (report.control && prevReport.control) {
if (report.control.restartESP !== prevReport.control.restartESP) {
changes.push('restartEsp');
}
if (report.control.restartWemos !== prevReport.control.restartWemos) {
changes.push('restartWemos');
}
}
// Logs changes
if (report.logs && prevReport.logs) {
// RFID logs
if (report.logs.RFID && prevReport.logs.RFID &&
JSON.stringify(report.logs.RFID) !== JSON.stringify(prevReport.logs.RFID)) {
changes.push('rfid');
}
// DHT logs
if (report.logs.dht && prevReport.logs.dht &&
JSON.stringify(report.logs.dht) !== JSON.stringify(prevReport.logs.dht)) {
changes.push('dht');
}
// MPU logs
if (report.logs.mpu && prevReport.logs.mpu &&
JSON.stringify(report.logs.mpu) !== JSON.stringify(prevReport.logs.mpu)) {
changes.push('mpu');
}
// Servo logs
if (report.logs.servo && prevReport.logs.servo &&
JSON.stringify(report.logs.servo) !== JSON.stringify(prevReport.logs.servo)) {
changes.push('servoLog');
}
// System ESP logs
if (report.logs.systemESP !== prevReport.logs.systemESP) {
changes.push('systemEsp');
}
// System Wemos logs
if (report.logs.systemWemos !== prevReport.logs.systemWemos) {
changes.push('systemWemos');
}
}
// Device changes - general fallback if needed
if (report.device && prevReport.device &&
JSON.stringify(report.device) !== JSON.stringify(prevReport.device)) {
changes.push('deviceStatus');
}
return changes;
}
// Periksa setiap laporan untuk mengisi kategori // Periksa setiap laporan untuk mengisi kategori
reports.forEach(report => { reports.forEach((report, index) => {
let categoriesAdded = 0; const changes = countChanges(report, index);
// Cek status keamanan changes.forEach(change => {
if (report.security) { statusCounts[change]++;
if (report.security.status) { totalCounts++;
if (report.security.status.toLowerCase() === 'bahaya') { });
statusCounts['securityDanger']++;
categoriesAdded++;
} else if (report.security.status.toLowerCase() === 'aman') {
statusCounts['securityNormal']++;
categoriesAdded++;
}
}
if (report.security.motion) { // Jika tidak ada perubahan, tambahkan satu ke total untuk report ini
if (report.security.motion.toLowerCase() === 'detected') { if (changes.length === 0 && index > 0 && index < reports.length - 1) {
statusCounts['motionDetected']++; totalCounts++;
categoriesAdded++;
} else if (report.security.motion.toLowerCase() === 'not detected') {
statusCounts['motionNotDetected']++;
categoriesAdded++;
}
}
} }
// Cek status smartcab
if (report.smartcab) {
if (report.smartcab.servo_status) {
if (report.smartcab.servo_status.toLowerCase() === 'locked') {
statusCounts['servoLocked']++;
categoriesAdded++;
} else if (report.smartcab.servo_status.toLowerCase() === 'unlocked') {
statusCounts['servoUnlocked']++;
categoriesAdded++;
}
}
if (report.smartcab.last_access) {
statusCounts['accessChanged']++;
categoriesAdded++;
}
}
// Cek perubahan kontrol
if (report.control && Object.keys(report.control).length > 0) {
statusCounts['controlChanged']++;
categoriesAdded++;
}
// Cek perubahan device
if (report.device && Object.keys(report.device).length > 0) {
statusCounts['deviceChanged']++;
categoriesAdded++;
}
// Cek logs untuk error
if (report.logs) {
let hasError = false;
for (const logKey in report.logs) {
if (report.logs[logKey] && report.logs[logKey].status) {
const status = report.logs[logKey].status.toLowerCase();
if (status.includes('error') || status.includes('warning')) {
hasError = true;
break;
}
}
}
if (hasError) {
statusCounts['logError']++;
categoriesAdded++;
}
}
// Tambahkan jumlah kategori ke total
totalCounts += categoriesAdded > 0 ? categoriesAdded : 1;
}); });
// Update counter // Update counter
document.getElementById('system-chart-count').textContent = document.getElementById('system-chart-count').textContent =
`Total: ${reports.length} laporan dengan ${totalCounts} status`; `Total: ${reports.length} laporan dengan ${totalCounts} perubahan status`;
// Kembalikan array untuk chart dengan hanya nilai yang bukan nol // Kembalikan array untuk chart dengan hanya nilai yang bukan nol
const resultData = [ const resultData = [
statusCounts['securityNormal'], statusCounts['fan'],
statusCounts['securityDanger'], statusCounts['status'],
statusCounts['motionDetected'], statusCounts['motion'],
statusCounts['motionNotDetected'], statusCounts['servoStatus'],
statusCounts['servoLocked'], statusCounts['lastAccess'],
statusCounts['servoUnlocked'], statusCounts['restartEsp'],
statusCounts['accessChanged'], statusCounts['restartWemos'],
statusCounts['controlChanged'], statusCounts['rfid'],
statusCounts['deviceChanged'], statusCounts['dht'],
statusCounts['logError'] statusCounts['mpu'],
statusCounts['servoLog'],
statusCounts['systemEsp'],
statusCounts['systemWemos'],
statusCounts['deviceStatus']
]; ];
// Label yang sesuai dengan resultData // Label yang sesuai dengan resultData
const resultLabels = [ const resultLabels = [
'Keamanan Normal', 'Fan',
'Status Bahaya', 'Status Keamanan',
'Gerakan Terdeteksi', 'Motion',
'Tidak Ada Gerakan', 'Servo Status',
'Servo Terkunci', 'Last Access',
'Servo Terbuka', 'Restart ESP',
'Akses Terakhir', 'Restart Wemos',
'Kontrol Diubah', 'RFID',
'Status Perangkat', 'DHT',
'Error/Warning' 'MPU',
'Servo Log',
'System ESP',
'System Wemos',
'Device Status'
]; ];
// Warna yang sesuai dengan resultData // Warna yang sesuai dengan resultData (sesuai dengan badge colors)
const resultColors = [ const resultColors = [
'#22c55e', // Keamanan Normal (Hijau) '#22c55e', // Fan (Hijau)
'#ef4444', // Status Bahaya (Merah) '#ef4444', // Status (Merah)
'#eab308', // Gerakan Terdeteksi (Kuning) '#eab308', // Motion (Kuning)
'#84cc16', // Tidak Ada Gerakan (Hijau Muda) '#06b6d4', // Servo Status (Cyan)
'#3b82f6', // Servo Terkunci (Biru) '#8b5cf6', // Last Access (Ungu Muda)
'#06b6d4', // Servo Terbuka (Cyan) '#f97316', // Restart ESP (Oranye)
'#8b5cf6', // Akses Terakhir (Ungu Muda) '#f59e0b', // Restart Wemos (Amber)
'#a855f7', // Kontrol Diubah (Ungu) '#6366f1', // RFID (Indigo)
'#ec4899', // Status Perangkat (Pink) '#84cc16', // DHT (Lime)
'#f97316' // Error/Warning (Oranye) '#14b8a6', // MPU (Teal)
'#0ea5e9', // Servo Log (Sky)
'#e11d48', // System ESP (Rose)
'#ec4899', // System Wemos (Pink)
'#6b7280' // Device Status (Gray)
]; ];
// Filter untuk menghilangkan kategori dengan nilai nol // Filter untuk menghilangkan kategori dengan nilai nol
@ -1298,32 +1456,36 @@ function updateChartFilterStatus() {
const filterDate = document.getElementById('filterDate').value; const filterDate = document.getElementById('filterDate').value;
const timeFilterEnabled = document.getElementById('filterTimeToggle').checked; const timeFilterEnabled = document.getElementById('filterTimeToggle').checked;
let filterInfo = ''; let filterInfo = [];
if (filterDate) { if (filterDate) {
const formattedDate = new Date(filterDate).toLocaleDateString('id-ID', { const formattedDate = new Date(filterDate).toLocaleDateString('id-ID', {
day: '2-digit', day: '2-digit',
month: '2-digit', month: '2-digit',
year: 'numeric' year: 'numeric'
}); });
filterInfo += `Tanggal: ${formattedDate}`; filterInfo.push(`Tanggal: ${formattedDate}`);
} }
if (timeFilterEnabled) { if (timeFilterEnabled) {
const startTime = document.getElementById('startTime').value; const startTime = document.getElementById('startTime').value;
const endTime = document.getElementById('endTime').value; const endTime = document.getElementById('endTime').value;
if (filterInfo) filterInfo += ' | '; filterInfo.push(`Waktu: ${startTime} - ${endTime}`);
filterInfo += `Waktu: ${startTime} - ${endTime}`; }
if (selectedCategories.length > 0) {
filterInfo.push(`Kategori: ${selectedCategories.join(', ')}`);
} }
// Update text pada chart count // Update text pada chart count
if (filterInfo) { if (filterInfo.length > 0) {
chartCountElement.innerHTML = ` chartCountElement.innerHTML = `
<span class="font-medium">Data Terfilter:</span> ${filteredReports.length} dari ${allReports.length} <span class="font-medium">Data Terfilter:</span> ${filteredReports.length} dari ${allReports.length}
`; `;
// Tampilkan badge filter // Tampilkan badge filter
chartFilterBadge.classList.remove('hidden'); chartFilterBadge.classList.remove('hidden');
chartFilterText.textContent = filterInfo; chartFilterText.textContent = filterInfo.join(' | ');
} else { } else {
chartCountElement.textContent = `Total: ${allReports.length} laporan dengan ${analyzeSystemOverview(allReports).series.reduce((a, b) => a + b, 0)} status`; chartCountElement.textContent = `Total: ${allReports.length} laporan dengan ${analyzeSystemOverview(allReports).series.reduce((a, b) => a + b, 0)} status`;