[PULSE-13] Org Audit Log UI with advanced filtering #10
@@ -86,6 +86,7 @@ export default function OrganizationSettings() {
|
|||||||
const [auditPage, setAuditPage] = useState(0)
|
const [auditPage, setAuditPage] = useState(0)
|
||||||
const auditPageSize = 20
|
const auditPageSize = 20
|
||||||
const [auditActionFilter, setAuditActionFilter] = useState('')
|
const [auditActionFilter, setAuditActionFilter] = useState('')
|
||||||
|
const [auditLogIdFilter, setAuditLogIdFilter] = useState('')
|
||||||
const [auditStartDate, setAuditStartDate] = useState('')
|
const [auditStartDate, setAuditStartDate] = useState('')
|
||||||
const [auditEndDate, setAuditEndDate] = useState('')
|
const [auditEndDate, setAuditEndDate] = useState('')
|
||||||
|
|
||||||
@@ -174,6 +175,7 @@ export default function OrganizationSettings() {
|
|||||||
offset: auditPage * auditPageSize,
|
offset: auditPage * auditPageSize,
|
||||||
}
|
}
|
||||||
if (auditActionFilter) params.action = auditActionFilter
|
if (auditActionFilter) params.action = auditActionFilter
|
||||||
|
if (auditLogIdFilter) params.log_id = auditLogIdFilter
|
||||||
if (auditStartDate) params.start_date = auditStartDate
|
if (auditStartDate) params.start_date = auditStartDate
|
||||||
if (auditEndDate) params.end_date = auditEndDate
|
if (auditEndDate) params.end_date = auditEndDate
|
||||||
const { entries, total } = await getAuditLog(params)
|
const { entries, total } = await getAuditLog(params)
|
||||||
@@ -185,7 +187,7 @@ export default function OrganizationSettings() {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoadingAudit(false)
|
setIsLoadingAudit(false)
|
||||||
}
|
}
|
||||||
}, [currentOrgId, auditPage, auditActionFilter, auditStartDate, auditEndDate])
|
}, [currentOrgId, auditPage, auditActionFilter, auditLogIdFilter, auditStartDate, auditEndDate])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeTab === 'audit' && currentOrgId) {
|
if (activeTab === 'audit' && currentOrgId) {
|
||||||
@@ -793,46 +795,63 @@ export default function OrganizationSettings() {
|
|||||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">Who did what and when for this organization.</p>
|
<p className="text-sm text-neutral-500 dark:text-neutral-400">Who did what and when for this organization.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filters */}
|
{/* Advanced Filters */}
|
||||||
<div className="flex flex-wrap gap-4 items-end">
|
<div className="bg-neutral-50 dark:bg-neutral-900/50 border border-neutral-200 dark:border-neutral-800 rounded-xl p-4 mb-6">
|
||||||
<div className="space-y-1">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<label className="block text-xs font-medium text-neutral-500 uppercase">Action</label>
|
<div className="space-y-1">
|
||||||
<input
|
<label className="block text-xs font-medium text-neutral-500 uppercase">Log ID</label>
|
||||||
type="text"
|
<input
|
||||||
placeholder="e.g. site_created"
|
type="text"
|
||||||
value={auditActionFilter}
|
placeholder="e.g. 9629cf39..."
|
||||||
onChange={(e) => setAuditActionFilter(e.target.value)}
|
value={auditLogIdFilter}
|
||||||
className="w-40 px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white"
|
onChange={(e) => setAuditLogIdFilter(e.target.value)}
|
||||||
/>
|
className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="block text-xs font-medium text-neutral-500 uppercase">Action</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="e.g. site_created"
|
||||||
|
value={auditActionFilter}
|
||||||
|
onChange={(e) => setAuditActionFilter(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="block text-xs font-medium text-neutral-500 uppercase">From date</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={auditStartDate}
|
||||||
|
onChange={(e) => setAuditStartDate(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="block text-xs font-medium text-neutral-500 uppercase">To date</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={auditEndDate}
|
||||||
|
onChange={(e) => setAuditEndDate(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white focus:ring-2 focus:ring-brand-orange outline-none transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="mt-4 flex justify-end gap-2">
|
||||||
<label className="block text-xs font-medium text-neutral-500 uppercase">From date</label>
|
<Button
|
||||||
<input
|
variant="ghost"
|
||||||
type="date"
|
onClick={() => {
|
||||||
value={auditStartDate}
|
setAuditLogIdFilter('')
|
||||||
onChange={(e) => setAuditStartDate(e.target.value)}
|
setAuditActionFilter('')
|
||||||
className="px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white"
|
setAuditStartDate('')
|
||||||
/>
|
setAuditEndDate('')
|
||||||
|
setAuditPage(0)
|
||||||
|
}}
|
||||||
|
disabled={isLoadingAudit}
|
||||||
|
>
|
||||||
|
Clear Filters
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="block text-xs font-medium text-neutral-500 uppercase">To date</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={auditEndDate}
|
|
||||||
onChange={(e) => setAuditEndDate(e.target.value)}
|
|
||||||
className="px-3 py-2 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-sm text-neutral-900 dark:text-white"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => {
|
|
||||||
setAuditPage(0)
|
|
||||||
loadAudit()
|
|
||||||
}}
|
|
||||||
disabled={isLoadingAudit}
|
|
||||||
>
|
|
||||||
Apply
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
@@ -854,7 +873,6 @@ export default function OrganizationSettings() {
|
|||||||
<th className="text-left px-4 py-3 font-medium text-neutral-700 dark:text-neutral-300">Actor</th>
|
<th className="text-left px-4 py-3 font-medium text-neutral-700 dark:text-neutral-300">Actor</th>
|
||||||
<th className="text-left px-4 py-3 font-medium text-neutral-700 dark:text-neutral-300">Action</th>
|
<th className="text-left px-4 py-3 font-medium text-neutral-700 dark:text-neutral-300">Action</th>
|
||||||
<th className="text-left px-4 py-3 font-medium text-neutral-700 dark:text-neutral-300">Resource</th>
|
<th className="text-left px-4 py-3 font-medium text-neutral-700 dark:text-neutral-300">Resource</th>
|
||||||
<th className="text-left px-4 py-3 font-medium text-neutral-700 dark:text-neutral-300">Resource ID</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -871,7 +889,6 @@ export default function OrganizationSettings() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 font-medium text-neutral-900 dark:text-white">{entry.action}</td>
|
<td className="px-4 py-3 font-medium text-neutral-900 dark:text-white">{entry.action}</td>
|
||||||
<td className="px-4 py-3 text-neutral-600 dark:text-neutral-400">{entry.resource_type}</td>
|
<td className="px-4 py-3 text-neutral-600 dark:text-neutral-400">{entry.resource_type}</td>
|
||||||
<td className="px-4 py-3 text-neutral-500 dark:text-neutral-500 font-mono text-xs">{entry.resource_id || '—'}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface GetAuditLogParams {
|
|||||||
limit?: number
|
limit?: number
|
||||||
offset?: number
|
offset?: number
|
||||||
action?: string
|
action?: string
|
||||||
|
log_id?: string
|
||||||
start_date?: string
|
start_date?: string
|
||||||
end_date?: string
|
end_date?: string
|
||||||
}
|
}
|
||||||
@@ -63,6 +64,7 @@ export async function getAuditLog(params: GetAuditLogParams = {}): Promise<GetAu
|
|||||||
if (params.limit != null) search.set('limit', String(params.limit))
|
if (params.limit != null) search.set('limit', String(params.limit))
|
||||||
if (params.offset != null) search.set('offset', String(params.offset))
|
if (params.offset != null) search.set('offset', String(params.offset))
|
||||||
if (params.action) search.set('action', params.action)
|
if (params.action) search.set('action', params.action)
|
||||||
|
if (params.log_id) search.set('log_id', params.log_id)
|
||||||
if (params.start_date) search.set('start_date', params.start_date)
|
if (params.start_date) search.set('start_date', params.start_date)
|
||||||
if (params.end_date) search.set('end_date', params.end_date)
|
if (params.end_date) search.set('end_date', params.end_date)
|
||||||
const qs = search.toString()
|
const qs = search.toString()
|
||||||
|
|||||||
Reference in New Issue
Block a user