diff --git a/src/app/auditLogs/components/AuditLogsList.tsx b/src/app/auditLogs/components/AuditLogsList.tsx
index 2022615..e83e7fe 100644
--- a/src/app/auditLogs/components/AuditLogsList.tsx
+++ b/src/app/auditLogs/components/AuditLogsList.tsx
@@ -3,7 +3,7 @@ import {
MagnifyingGlassIcon,
CalendarBlank,
Export,
- CaretRight
+ CaretRight,
} from "@phosphor-icons/react";
import {
CustomTable,
@@ -11,14 +11,11 @@ import {
CustomStatus,
Skeleton,
CustomButton,
- CustomDropdown
+ CustomDropdown,
} from "../../../components/custom";
import type { Column } from "../../../components/custom/CustomTable";
import type { AuditLog, AuditLogFilters } from "../AuditLogsTypes";
-import {
- AUDIT_MODULE_LABELS,
- AUDIT_ACTION_VARIANTS,
-} from "../AuditLogsTypes";
+import { AUDIT_MODULE_LABELS, AUDIT_ACTION_VARIANTS } from "../AuditLogsTypes";
import { getAuditLogs } from "../AuditLogsApi";
import AuditLogDetail from "./AuditLogDetail";
@@ -32,9 +29,17 @@ function HeaderLabel({ text }: { text: string }) {
);
}
-function PrimaryText({ text, className = "" }: { text: string, className?: string }) {
+function PrimaryText({
+ text,
+ className = "",
+}: {
+ text: string;
+ className?: string;
+}) {
return (
-
+
{text}
);
@@ -53,9 +58,11 @@ function ModuleBadge({ module }: { module: string }) {
function getLogDescription(row: AuditLog) {
const entity = row.entityLabel || row.entityId || "record";
const mod = AUDIT_MODULE_LABELS[row.module] || row.module;
- if (row.action === "CREATE") return `New record created in ${mod}: ${entity}.`;
+ if (row.action === "CREATE")
+ return `New record created in ${mod}: ${entity}.`;
if (row.action === "UPDATE") return `${mod} record updated: ${entity}.`;
- if (row.action === "STATUS_CHANGE") return `Status changed for ${entity} in ${mod}.`;
+ if (row.action === "STATUS_CHANGE")
+ return `Status changed for ${entity} in ${mod}.`;
if (row.action === "DELETE") return `${mod} record deleted: ${entity}.`;
return `Action ${row.action} performed on ${entity}.`;
}
@@ -88,33 +95,22 @@ export default function AuditLogsList() {
if (daysFilter !== "all") {
const now = new Date();
const past = new Date();
-
+
if (daysFilter === "today") {
past.setHours(0, 0, 0, 0);
} else {
past.setDate(now.getDate() - parseInt(daysFilter, 10));
}
-
+
filters.dateFrom = past.toISOString();
filters.dateTo = now.toISOString();
}
const result = await getAuditLogs(filters);
- // Client-side search on entityLabel / entityId
- const filtered = search
- ? result.data.filter(
- (l) =>
- l.entityLabel?.toLowerCase().includes(search.toLowerCase()) ||
- l.entityId?.toLowerCase().includes(search.toLowerCase()) ||
- l.module?.toLowerCase().includes(search.toLowerCase()) ||
- l.performedBy?.toLowerCase().includes(search.toLowerCase())
- )
- : result.data;
-
- setLogs(filtered);
- setTotalItems(result.total);
- setTotalPages(result.totalPages);
+ setLogs(result.data || []);
+ setTotalItems(result.total || 0);
+ setTotalPages(result.totalPages || 1);
} catch (err) {
console.error("Failed to fetch audit logs", err);
setLogs([]);
@@ -122,7 +118,7 @@ export default function AuditLogsList() {
setLoading(false);
}
},
- [search, daysFilter]
+ [daysFilter],
);
useEffect(() => {
@@ -137,20 +133,29 @@ export default function AuditLogsList() {
const handleExportCSV = () => {
if (logs.length === 0) return;
- const headers = ["Timestamp", "User", "Action", "Module", "Description", "Audit ID"];
- const rows = logs.map(row => {
+ const headers = [
+ "Timestamp",
+ "User",
+ "Action",
+ "Module",
+ "Description",
+ "Audit ID",
+ ];
+ const rows = logs.map((row) => {
const date = new Date(row.createdAt).toLocaleString();
const user = row.performedBy ?? "System";
const action = row.action;
const module = AUDIT_MODULE_LABELS[row.module] ?? row.module;
-
+
const entity = row.entityLabel || row.entityId || "record";
let desc = `Action ${action} performed on ${entity}.`;
- if (action === "CREATE") desc = `New record created in ${module}: ${entity}.`;
+ if (action === "CREATE")
+ desc = `New record created in ${module}: ${entity}.`;
if (action === "UPDATE") desc = `${module} record updated: ${entity}.`;
- if (action === "STATUS_CHANGE") desc = `Status changed for ${entity} in ${module}.`;
+ if (action === "STATUS_CHANGE")
+ desc = `Status changed for ${entity} in ${module}.`;
if (action === "DELETE") desc = `${module} record deleted: ${entity}.`;
-
+
const auditId = row.id;
// Escape quotes and wrap in quotes to handle commas in values
@@ -160,14 +165,14 @@ export default function AuditLogsList() {
`"${action.replace(/"/g, '""')}"`,
`"${module.replace(/"/g, '""')}"`,
`"${desc.replace(/"/g, '""')}"`,
- `"${auditId.replace(/"/g, '""')}"`
+ `"${auditId.replace(/"/g, '""')}"`,
].join(",");
});
const csvContent = [headers.join(","), ...rows].join("\n");
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
-
+
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", `audit_logs_${new Date().getTime()}.csv`);
@@ -183,11 +188,14 @@ export default function AuditLogsList() {
header:
,
accessor: (row) => {
const d = new Date(row.createdAt);
- const time = d.toLocaleTimeString(undefined, {
- hour: "numeric",
- minute: "2-digit",
- hour12: true,
- }).toLowerCase().replace(' ', '');
+ const time = d
+ .toLocaleTimeString(undefined, {
+ hour: "numeric",
+ minute: "2-digit",
+ hour12: true,
+ })
+ .toLowerCase()
+ .replace(" ", "");
const date = d.toLocaleDateString(undefined, {
day: "numeric",
month: "short",
@@ -195,8 +203,12 @@ export default function AuditLogsList() {
});
return (
- {time}
- {date}
+
+ {time}
+
+
+ {date}
+
);
},
@@ -210,7 +222,8 @@ export default function AuditLogsList() {
{
header:
,
accessor: (row) => {
- const displayStatus = row.action === 'STATUS_CHANGE' ? 'Status Change' : row.action;
+ const displayStatus =
+ row.action === "STATUS_CHANGE" ? "Status Change" : row.action;
return (
,
accessor: (row) => {
- const hash = row.id.split('-')[0].substring(0, 4).toUpperCase();
+ const hash = row.id.split("-")[0].substring(0, 4).toUpperCase();
return (
LOG-{hash}
@@ -266,6 +279,15 @@ export default function AuditLogsList() {
// ─── Render ─────────────────────────────────────────────────────────────────
+ const displayedLogs = search
+ ? logs.filter((l) =>
+ l.entityLabel?.toLowerCase().includes(search.toLowerCase()) ||
+ l.entityId?.toLowerCase().includes(search.toLowerCase()) ||
+ l.module?.toLowerCase().includes(search.toLowerCase()) ||
+ l.performedBy?.toLowerCase().includes(search.toLowerCase())
+ )
+ : logs;
+
const startIndex = totalItems > 0 ? (currentPage - 1) * PAGE_SIZE + 1 : 0;
const endIndex = Math.min(currentPage * PAGE_SIZE, totalItems);
@@ -273,14 +295,16 @@ export default function AuditLogsList() {
columns={columns}
- data={logs}
+ data={displayedLogs}
leftHeaderActions={
setSearch(e.target.value)}
- leftIcon={}
+ leftIcon={
+
+ }
className="!bg-[#F8F9FA] !rounded-[8px] !h-[40px] !border-none !text-[13px]"
containerClassName="!gap-0 border-none"
/>
@@ -292,7 +316,13 @@ export default function AuditLogsList() {
}
+ leftIcon={
+
+ }
searchable={false}
options={[
{ label: "Today", value: "today" },
@@ -304,7 +334,7 @@ export default function AuditLogsList() {
className="!border-[#1E8E3E] !bg-[#E6F4EA] !h-[40px] hover:!bg-[#E6F4EA]"
/>
-
- setSelectedLog(null)}
- />
+ setSelectedLog(null)} />
);
}
diff --git a/src/app/cohartManage/components/cohartList.tsx b/src/app/cohartManage/components/cohartList.tsx
index 4ac232a..6acb2a5 100644
--- a/src/app/cohartManage/components/cohartList.tsx
+++ b/src/app/cohartManage/components/cohartList.tsx
@@ -54,6 +54,7 @@ export default function CohortList() {
const [cohorts, setCohorts] = useState
([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
+ const [successMsg, setSuccessMsg] = useState(null);
// Server-side pagination
const [currentPage, setCurrentPage] = useState(1);
@@ -113,8 +114,11 @@ export default function CohortList() {
const handleDelete = async () => {
if (!deleteTarget) return;
setDeleteLoading(true);
+ setError(null);
+ setSuccessMsg(null);
try {
await deleteCohart(deleteTarget.id);
+ setSuccessMsg(`Cohort "${deleteTarget.name}" deleted successfully.`);
setDeleteTarget(null);
fetchCohorts(currentPage);
} catch {
@@ -127,9 +131,12 @@ export default function CohortList() {
const handleToggleStatus = async () => {
if (!deactivateTarget) return;
setStatusLoading(true);
+ setError(null);
+ setSuccessMsg(null);
try {
const newStatus = deactivateTarget.status === 'Active' ? 'Inactive' : 'Active';
await updateCohartStatus(deactivateTarget.id, { status: newStatus });
+ setSuccessMsg(`Cohort "${deactivateTarget.name}" is now ${newStatus}.`);
setDeactivateTarget(null);
fetchCohorts(currentPage);
} catch {
@@ -239,6 +246,13 @@ export default function CohortList() {
onClose={() => setError(null)}
/>
)}
+ {successMsg && (
+ setSuccessMsg(null)}
+ />
+ )}
columns={columns}
diff --git a/src/app/configuration/actionBuilder/components/ActionTypeFormModal.tsx b/src/app/configuration/actionBuilder/components/ActionTypeFormModal.tsx
index 2541f4a..a62285f 100644
--- a/src/app/configuration/actionBuilder/components/ActionTypeFormModal.tsx
+++ b/src/app/configuration/actionBuilder/components/ActionTypeFormModal.tsx
@@ -1,4 +1,4 @@
-import { XCircleIcon } from '@phosphor-icons/react';
+import { useEffect } from 'react';
import {
CustomModal,
@@ -7,6 +7,7 @@ import {
CustomTextArea,
CustomSwitch,
CustomButton,
+ CustomAlertBanner,
} from '../../../../components/custom';
import type { ActionCategory, ActionType, ActionTypeFormData } from '../ActionBuilderTypes';
@@ -17,6 +18,7 @@ interface ActionTypeFormModalProps {
formData: ActionTypeFormData;
setFormData: React.Dispatch>;
error: string | null;
+ setError?: (err: string | null) => void;
onClose: () => void;
onSubmit: (e: React.FormEvent) => void;
}
@@ -28,9 +30,21 @@ export function ActionTypeFormModal({
formData,
setFormData,
error,
+ setError,
onClose,
onSubmit,
}: ActionTypeFormModalProps) {
+ useEffect(() => {
+ if (!error || !setError) return;
+ if (error === 'Please select a Category.' && formData.categoryId) {
+ setError(null);
+ } else if (error === 'Action Type Name is required.' && formData.name.trim()) {
+ setError(null);
+ } else if (error === 'Action Type Code is required.' && formData.code.trim()) {
+ setError(null);
+ }
+ }, [formData.categoryId, formData.name, formData.code, error, setError]);
+
return (
{error && (
-
-
- {error}
-
+ setError?.(null)}
+ autoClose={false}
+ />
)}
-