feat: implement HTTP method filtering, parallelize initialization requests, and replace native alerts with toast notifications for audit log exports
This commit is contained in:
parent
2a2798cd0f
commit
fd8fd3a4e8
3
package-lock.json
generated
3
package-lock.json
generated
@ -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": {
|
||||||
|
|||||||
@ -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"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user