update
This commit is contained in:
+1
-1
@@ -36,7 +36,7 @@ services:
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
- /mnt/simrs/logs:/app/logs
|
||||
- /mnt/simrs-dev/logs:/app/logs
|
||||
|
||||
# ─── Production ────────────────────────────
|
||||
prod:
|
||||
|
||||
+1
-1
@@ -307,7 +307,7 @@ export function App() {
|
||||
<FilterToolbar
|
||||
filters={filters}
|
||||
backups={backups}
|
||||
onChange={(updated) => setFilters((prev) => ({ ...prev, ...updated }))}
|
||||
onApply={(applied) => setFilters(applied)}
|
||||
onReset={() =>
|
||||
setFilters({
|
||||
source: 'auto',
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Database, Calendar, Clock, Layers, AlertCircle, Search, Filter, RotateCcw } from 'lucide-react';
|
||||
import { FilterState, BackupFile } from '../types';
|
||||
|
||||
interface FilterToolbarProps {
|
||||
filters: FilterState;
|
||||
backups: BackupFile[];
|
||||
onChange: (updated: Partial<FilterState>) => void;
|
||||
onApply: (applied: FilterState) => void;
|
||||
onReset: () => void;
|
||||
}
|
||||
|
||||
export const FilterToolbar: React.FC<FilterToolbarProps> = ({ filters, backups, onChange, onReset }) => {
|
||||
export const FilterToolbar: React.FC<FilterToolbarProps> = ({ filters, backups, onApply, onReset }) => {
|
||||
const [localFilters, setLocalFilters] = useState<FilterState>(filters);
|
||||
|
||||
// Sync local filters if parent filters change externally (e.g., Reset or selecting backup ZIP file)
|
||||
useEffect(() => {
|
||||
setLocalFilters(filters);
|
||||
}, [filters]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onApply({ ...localFilters, page: 1 });
|
||||
};
|
||||
|
||||
const handleLocalChange = (updated: Partial<FilterState>) => {
|
||||
setLocalFilters((prev) => ({ ...prev, ...updated }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="glass-card p-4 rounded-xl mb-6">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
onChange({ page: 1 });
|
||||
}}
|
||||
className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-7 gap-3 items-end"
|
||||
>
|
||||
<form onSubmit={handleSubmit} className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-7 gap-3 items-end">
|
||||
{/* Sumber Log */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-semibold text-slate-400 mb-1 flex items-center gap-1">
|
||||
<Database className="w-3.5 h-3.5 text-cyan-400" /> Sumber Log
|
||||
</label>
|
||||
<select
|
||||
value={filters.source}
|
||||
onChange={(e) => onChange({ source: e.target.value, page: 1 })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white focus:border-cyan-500 outline-none"
|
||||
value={localFilters.source}
|
||||
onChange={(e) => handleLocalChange({ source: e.target.value })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white focus:border-cyan-500 outline-none cursor-pointer"
|
||||
>
|
||||
<option value="auto">⚡ Auto (Aktif + ZIP)</option>
|
||||
<option value="active">📁 Log Aktif</option>
|
||||
@@ -51,9 +61,9 @@ export const FilterToolbar: React.FC<FilterToolbarProps> = ({ filters, backups,
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={filters.date}
|
||||
onChange={(e) => onChange({ date: e.target.value, page: 1 })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white focus:border-cyan-500 outline-none"
|
||||
value={localFilters.date}
|
||||
onChange={(e) => handleLocalChange({ date: e.target.value })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white focus:border-cyan-500 outline-none cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -65,15 +75,15 @@ export const FilterToolbar: React.FC<FilterToolbarProps> = ({ filters, backups,
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
type="time"
|
||||
value={filters.time_start}
|
||||
onChange={(e) => onChange({ time_start: e.target.value, page: 1 })}
|
||||
value={localFilters.time_start}
|
||||
onChange={(e) => handleLocalChange({ time_start: e.target.value })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-1.5 py-1.5 text-[11px] text-white focus:border-cyan-500 outline-none"
|
||||
/>
|
||||
<span className="text-slate-500 text-xs">-</span>
|
||||
<input
|
||||
type="time"
|
||||
value={filters.time_end}
|
||||
onChange={(e) => onChange({ time_end: e.target.value, page: 1 })}
|
||||
value={localFilters.time_end}
|
||||
onChange={(e) => handleLocalChange({ time_end: e.target.value })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-1.5 py-1.5 text-[11px] text-white focus:border-cyan-500 outline-none"
|
||||
/>
|
||||
</div>
|
||||
@@ -85,9 +95,9 @@ export const FilterToolbar: React.FC<FilterToolbarProps> = ({ filters, backups,
|
||||
<Layers className="w-3.5 h-3.5 text-cyan-400" /> Channel
|
||||
</label>
|
||||
<select
|
||||
value={filters.channel}
|
||||
onChange={(e) => onChange({ channel: e.target.value, page: 1 })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white focus:border-cyan-500 outline-none"
|
||||
value={localFilters.channel}
|
||||
onChange={(e) => handleLocalChange({ channel: e.target.value })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white focus:border-cyan-500 outline-none cursor-pointer"
|
||||
>
|
||||
<option value="all">-- Semua Channel --</option>
|
||||
<option value="session">Session Lifecycle</option>
|
||||
@@ -107,9 +117,9 @@ export const FilterToolbar: React.FC<FilterToolbarProps> = ({ filters, backups,
|
||||
<AlertCircle className="w-3.5 h-3.5 text-cyan-400" /> Level
|
||||
</label>
|
||||
<select
|
||||
value={filters.level}
|
||||
onChange={(e) => onChange({ level: e.target.value, page: 1 })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2 py-1.5 text-xs text-white focus:border-cyan-500 outline-none"
|
||||
value={localFilters.level}
|
||||
onChange={(e) => handleLocalChange({ level: e.target.value })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2 py-1.5 text-xs text-white focus:border-cyan-500 outline-none cursor-pointer"
|
||||
>
|
||||
<option value="all">Semua</option>
|
||||
<option value="DEBUG">DEBUG</option>
|
||||
@@ -128,8 +138,8 @@ export const FilterToolbar: React.FC<FilterToolbarProps> = ({ filters, backups,
|
||||
<input
|
||||
type="text"
|
||||
placeholder="NIP, URI, IP, Pesan..."
|
||||
value={filters.search}
|
||||
onChange={(e) => onChange({ search: e.target.value })}
|
||||
value={localFilters.search}
|
||||
onChange={(e) => handleLocalChange({ search: e.target.value })}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white focus:border-cyan-500 outline-none"
|
||||
/>
|
||||
</div>
|
||||
@@ -138,7 +148,7 @@ export const FilterToolbar: React.FC<FilterToolbarProps> = ({ filters, backups,
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
type="submit"
|
||||
className="flex-1 py-1.5 px-3 rounded-lg bg-teal-600 hover:bg-teal-500 text-white font-semibold text-xs flex items-center justify-center gap-1 transition-colors cursor-pointer"
|
||||
className="flex-1 py-1.5 px-3 rounded-lg bg-teal-600 hover:bg-teal-500 text-white font-semibold text-xs flex items-center justify-center gap-1 transition-colors cursor-pointer shadow-lg shadow-teal-900/30"
|
||||
>
|
||||
<Filter className="w-3.5 h-3.5" />
|
||||
Filter
|
||||
|
||||
Reference in New Issue
Block a user