feat: implement HTTP method filtering, parallelize initialization requests, and replace native alerts with toast notifications for audit log exports

This commit is contained in:
SibarchanNayak 2026-07-29 17:18:13 +05:30
parent 2a2798cd0f
commit fd8fd3a4e8
3 changed files with 68 additions and 13 deletions

3
package-lock.json generated
View File

@ -58,6 +58,9 @@
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.46.4", "typescript-eslint": "^8.46.4",
"vite": "^7.2.4" "vite": "^7.2.4"
},
"engines": {
"node": ">=22.0.0"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {

View File

@ -21,6 +21,7 @@ import { tenantService } from "@/services/tenant-service";
import type { AuditLog } from "@/types/audit-log"; import type { AuditLog } from "@/types/audit-log";
import type { Tenant } from "@/types/tenant"; import type { Tenant } from "@/types/tenant";
import { useAppTheme } from "@/hooks/useAppTheme"; import { useAppTheme } from "@/hooks/useAppTheme";
import { showToast } from "@/utils/toast";
// Helper function to format date // Helper function to format date
const formatDate = (dateString: string): string => { const formatDate = (dateString: string): string => {
@ -137,7 +138,7 @@ const AuditLogs = (): ReactElement => {
null, null,
); );
// Fetch tenants on mount for the selector // FE-1: Run all three independent fetches in parallel instead of sequentially
useEffect(() => { useEffect(() => {
const fetchTenants = async () => { const fetchTenants = async () => {
try { try {
@ -176,9 +177,11 @@ const AuditLogs = (): ReactElement => {
} }
}; };
fetchTenants(); Promise.all([
fetchResourceTypes(); fetchTenants(),
fetchModules(); fetchResourceTypes(),
fetchModules(),
]);
}, []); }, []);
@ -254,6 +257,15 @@ const AuditLogs = (): ReactElement => {
tenantId: tenantFilter || undefined, tenantId: tenantFilter || undefined,
}); });
if (response.success) { if (response.success) {
// FE-3 (BE-8 truncation warning): Notify when results were silently limited
if (response.data.truncated) {
showToast.warning(
"Export Truncated",
response.data.warning ||
`Only ${(response.data.exported ?? 10000).toLocaleString()} of ${response.data.total.toLocaleString()} records were exported. Use date filters to narrow the range.`
);
}
const blob = new Blob( const blob = new Blob(
[JSON.stringify(response.data.records, null, 2)], [JSON.stringify(response.data.records, null, 2)],
{ type: "application/json" }, { type: "application/json" },
@ -267,7 +279,11 @@ const AuditLogs = (): ReactElement => {
document.body.removeChild(link); document.body.removeChild(link);
} }
} catch (err: any) { } catch (err: any) {
alert("Export failed: " + err.message); // FE-3: Use app toast system instead of native alert()
showToast.error(
"Export Failed",
err?.response?.data?.error?.message || err.message || "An unexpected error occurred"
);
} }
}; };

View File

@ -20,6 +20,7 @@ import type { AuditLog } from "@/types/audit-log";
import { useAppTheme } from "@/hooks/useAppTheme"; import { useAppTheme } from "@/hooks/useAppTheme";
import { PrimaryButton } from "@/components/shared"; import { PrimaryButton } from "@/components/shared";
import { useAppSelector } from "@/hooks/redux-hooks"; import { useAppSelector } from "@/hooks/redux-hooks";
import { showToast } from "@/utils/toast";
export interface AuditLogsProps { export interface AuditLogsProps {
customTenantId?: string; customTenantId?: string;
@ -123,7 +124,8 @@ const AuditLogs = ({
}); });
// Filter state // Filter state
const methodFilter = null; // FE-2: methodFilter converted from a hardcoded null constant to real controllable state
const [methodFilter, setMethodFilter] = useState<string | null>(null);
const [actionFilter, setActionFilter] = useState<string | null>(null); const [actionFilter, setActionFilter] = useState<string | null>(null);
const [resourceTypeFilter, setResourceTypeFilter] = useState<string | null>( const [resourceTypeFilter, setResourceTypeFilter] = useState<string | null>(
null, null,
@ -226,10 +228,12 @@ const AuditLogs = ({
} }
}; };
// Fetch resource types and modules on mount // FE-1: Fetch resource types and modules in parallel (were sequential before)
useEffect(() => { useEffect(() => {
fetchResourceTypes(); Promise.all([
fetchModules(); fetchResourceTypes(),
fetchModules(),
]);
}, [tenantId]); }, [tenantId]);
// Debouncing for Search // Debouncing for Search
@ -275,9 +279,15 @@ const AuditLogs = ({
}); });
if (response.success) { if (response.success) {
// In a real app, we'd trigger a file download here. // FE-3 (export truncation warning from BE-8): Inform the user if results were truncated
// For now, we'll just log and show a message since the response is JSON. if (response.data.truncated) {
console.log("Export data:", response.data.records); showToast.warning(
"Export Truncated",
response.data.warning ||
`Only ${(response.data.exported ?? 10000).toLocaleString()} of ${response.data.total.toLocaleString()} records were exported. Use date filters to narrow the range.`
);
}
const blob = new Blob( const blob = new Blob(
[JSON.stringify(response.data.records, null, 2)], [JSON.stringify(response.data.records, null, 2)],
{ type: "application/json" }, { type: "application/json" },
@ -291,7 +301,11 @@ const AuditLogs = ({
document.body.removeChild(link); document.body.removeChild(link);
} }
} catch (err: any) { } catch (err: any) {
alert("Failed to export audit logs: " + (err.message || "Unknown error")); // FE-3: Use app toast system instead of native alert()
showToast.error(
"Export Failed",
err?.response?.data?.error?.message || err.message || "An unexpected error occurred"
);
} }
}; };
@ -564,6 +578,26 @@ const AuditLogs = ({
/> />
)} )}
{/* FE-2: HTTP Method filter — methodFilter is now real state */}
{isTenantAdmin && (
<FilterDropdown
label="Method"
options={[
{ value: "GET", label: "GET" },
{ value: "POST", label: "POST" },
{ value: "PUT", label: "PUT" },
{ value: "PATCH", label: "PATCH" },
{ value: "DELETE", label: "DELETE" },
]}
value={methodFilter}
onChange={(value) => {
setMethodFilter(value as string | null);
setCurrentPage(1);
}}
placeholder="All Methods"
/>
)}
{/* Module Filter */} {/* Module Filter */}
<FilterDropdown <FilterDropdown
label="Module" label="Module"
@ -659,6 +693,7 @@ const AuditLogs = ({
{(startDate || {(startDate ||
endDate || endDate ||
actionFilter || actionFilter ||
methodFilter ||
resourceTypeFilter || resourceTypeFilter ||
moduleIdFilter || moduleIdFilter ||
search || search ||
@ -668,6 +703,7 @@ const AuditLogs = ({
setStartDate(""); setStartDate("");
setEndDate(""); setEndDate("");
setActionFilter(null); setActionFilter(null);
setMethodFilter(null);
setResourceTypeFilter(null); setResourceTypeFilter(null);
setModuleIdFilter(null); setModuleIdFilter(null);
setOrderBy(null); setOrderBy(null);