1import { useEffect, useState } from 'react';
2import { useParams, useNavigate } from 'react-router-dom';
3import MainLayout from '../components/Layout/MainLayout';
4import { apiFetch } from '../lib/api';
5import './Customers.css';
7export default function TenantAudit() {
8 const { id } = useParams();
9 const navigate = useNavigate();
10 const [logs, setLogs] = useState([]);
11 const [tenant, setTenant] = useState(null);
12 const [total, setTotal] = useState(0);
13 const [page, setPage] = useState(1);
14 const [limit] = useState(50);
15 const [loading, setLoading] = useState(true);
16 const [error, setError] = useState('');
23 async function fetchTenant() {
25 const token = localStorage.getItem('token');
26 const res = await apiFetch(`/tenants/${id}`, {
28 Authorization: `Bearer ${token}`,
29 'X-Tenant-Subdomain': 'admin'
34 const json = await res.json();
35 setTenant(json.tenant);
38 console.error('Failed to load tenant:', err);
42 async function fetchAuditLogs() {
46 const token = localStorage.getItem('token');
47 const params = new URLSearchParams();
48 params.set('page', page);
49 params.set('limit', limit);
51 const res = await apiFetch(`/tenants/${id}/audit?${params.toString()}`, {
53 Authorization: `Bearer ${token}`,
54 'X-Tenant-Subdomain': 'admin'
59 const text = await res.text();
60 throw new Error(text || 'Failed to load audit logs');
63 const json = await res.json();
64 setLogs(json.logs || []);
65 setTotal(json.total || 0);
67 console.error('Audit log error:', err);
68 setError(err.message || 'Failed to load audit logs');
74 function formatDate(dateString) {
75 if (!dateString) return '-';
76 const date = new Date(dateString);
77 return date.toLocaleString('en-AU', {
86 function getActionBadgeClass(action) {
87 const actionLower = action?.toLowerCase() || '';
88 if (actionLower.includes('create')) return 'status-active';
89 if (actionLower.includes('update') || actionLower.includes('edit')) return 'status-pending';
90 if (actionLower.includes('delete')) return 'status-closed';
91 return 'status-draft';
94 const totalPages = Math.ceil(total / limit);
98 <div className="page-content">
99 <div className="page-header">
100 <div className="header-content">
102 {tenant ? `${tenant.name} - Audit Log` : 'Tenant Audit Log'}
105 <div className="header-actions">
106 <button className="btn btn-secondary" onClick={() => navigate(`/tenants/${id}`)}>
107 <span className="material-symbols-outlined">arrow_back</span>
113 {error && <div className="error-message">{error}</div>}
116 <div className="loading">Loading audit logs...</div>
117 ) : logs.length === 0 ? (
118 <div className="empty-state">
119 <span className="material-symbols-outlined">history</span>
120 <p>No audit logs found for this tenant</p>
124 <table className="data-table">
135 {logs.map((log, idx) => (
136 <tr key={log.audit_id || idx}>
137 <td style={{ whiteSpace: 'nowrap' }}>
138 {formatDate(log.created_at)}
141 {log.user_name || log.user_email || `User #${log.user_id}` || '-'}
144 <span className={`status-badge ${getActionBadgeClass(log.action)}`}>
149 {log.resource_type || '-'}
150 {log.resource_id && (
151 <span style={{ color: 'var(--text-muted)', fontSize: '0.9em' }}>
152 {' '}#{log.resource_id}
156 <td style={{ maxWidth: '400px', wordWrap: 'break-word' }}>
158 typeof log.details === 'string' ? log.details : JSON.stringify(log.details)
167 <div className="pagination">
171 onClick={() => setPage(p => Math.max(1, p - 1))}
173 <span className="material-symbols-outlined">navigate_before</span>
175 <span>Page {page} of {totalPages}</span>
178 disabled={page >= totalPages}
179 onClick={() => setPage(p => p + 1)}
181 <span className="material-symbols-outlined">navigate_next</span>