diff --git a/apps/edr-freight-web/backoffice/src/pages/ActivityLogPage.tsx b/apps/edr-freight-web/backoffice/src/pages/ActivityLogPage.tsx index 68eafe1fc..9e3f53da3 100644 --- a/apps/edr-freight-web/backoffice/src/pages/ActivityLogPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/ActivityLogPage.tsx @@ -1,268 +1,268 @@ -import { useState, useEffect, useCallback } from "react"; -import { useTranslation } from "react-i18next"; -import i18n from "i18next"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@/shared/common/ui/card"; -import { - Table, - TableHeader, - TableRow, - TableHead, - TableBody, - TableCell, -} from "@/shared/common/ui/table"; -import { Input } from "@/shared/common/ui/input"; -import { Button } from "@/shared/common/ui/button"; -import { Search, RefreshCw, ChevronLeft, ChevronRight } from "lucide-react"; -import { listAuditLogExtensions } from "@/shared/services/audit/audit.api"; - -interface ActivityLog { - id: string; - createdAt: string; - entityName: string; - queryMethod: "INSERT" | "UPDATE" | "DELETE" | string; - user: { - id: string; - name: { - am: string; - en: string; - }; - email: string; - username?: string; - }; -} - -const ITEMS_PER_PAGE = 10; - -export default function ActivityLogPage() { - const { t } = useTranslation(); - const [searchQuery, setSearchQuery] = useState(""); - const [allLogs, setAllLogs] = useState([]); - const [filteredLogs, setFilteredLogs] = useState([]); - const [loading, setLoading] = useState(false); - const [currentPage, setCurrentPage] = useState(1); - - const fetchAuditLogs = useCallback(async () => { - setLoading(true); - try { - // Fetch all 1000 records from super admin endpoint - const data = await listAuditLogExtensions( - "/audit-log-extensions/audit/superAdmin", - { - skip: 0, - take: 1000, - orderBy: "createdAt:DESC", - } - ); - - const logs = (data.items || []) as ActivityLog[]; - setAllLogs(logs); - setCurrentPage(1); - } catch (error: any) { - console.error("Error fetching audit logs:", error); - setAllLogs([]); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - fetchAuditLogs(); - // Refresh every 30 seconds - const interval = setInterval(fetchAuditLogs, 30000); - return () => clearInterval(interval); - }, [fetchAuditLogs]); - - // Paginate the logs whenever currentPage changes - useEffect(() => { - const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; - const endIndex = startIndex + ITEMS_PER_PAGE; - let filtered = allLogs.slice(startIndex, endIndex); - - // Apply search filter - if (searchQuery) { - filtered = filtered.filter( - (log) => - log.entityName.toLowerCase().includes(searchQuery.toLowerCase()) || - log.user.name.am.toLowerCase().includes(searchQuery.toLowerCase()) || - log.user.name.en.toLowerCase().includes(searchQuery.toLowerCase()) || - log.user.email.toLowerCase().includes(searchQuery.toLowerCase()), - ); - } - - setFilteredLogs(filtered); - }, [allLogs, currentPage, searchQuery]); - - const totalPages = Math.ceil(allLogs.length / ITEMS_PER_PAGE); - const hasNextPage = currentPage < totalPages; - const hasPrevPage = currentPage > 1; - - return ( -
- - -
- - {t("activityLogPage.title")} - - -
-
- -
-
- - setSearchQuery(e.target.value)} - className="pl-10" - /> -
-
- -
- - - - - {t("activityLogPage.table.time")} - - - {t("activityLogPage.table.description")} - - - {t("activityLogPage.table.performedBy")} - - - - - {loading ? ( - - -
- - {t("common.loading", "Loading...")} -
-
-
- ) : filteredLogs.length > 0 ? ( - filteredLogs.map((log) => { - const userName = - i18n.language === "am" ? log.user.name.am : log.user.name.en; - const entityLabel = log.entityName.replace(/_/g, " "); - const actionVerb = - log.queryMethod.toLowerCase() === "insert" - ? "created" - : log.queryMethod.toLowerCase() === "update" - ? "updated" - : log.queryMethod.toLowerCase() === "delete" - ? "deleted" - : log.queryMethod.toLowerCase(); - const timestamp = new Date(log.createdAt).toLocaleString( - i18n.language, - { - year: "numeric", - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit", - } - ); - - return ( - - {timestamp} - -

- {userName} - {" "} - - {actionVerb} - - {" "} - - {entityLabel} - - {" "} - at {timestamp} -

-
- -
-

{userName}

-

{log.user.email}

-
-
-
- ); - }) - ) : ( - - - {t("activityLogPage.table.noLogs")} - - - )} -
-
-
- - {/* Pagination Footer */} - {allLogs.length > 0 && ( -
-
- {t("common.showing", "Showing")} {(currentPage - 1) * ITEMS_PER_PAGE + 1}- - {Math.min(currentPage * ITEMS_PER_PAGE, allLogs.length)}{" "} - {t("common.of", "of")} {allLogs.length} -
- -
- - -
- - {currentPage} / {totalPages || 1} - -
- - -
-
- )} -
-
-
- ); -} +import { useState, useEffect, useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import i18n from "i18next"; +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from "@/shared/common/ui/card"; +import { + Table, + TableHeader, + TableRow, + TableHead, + TableBody, + TableCell, +} from "@/shared/common/ui/table"; +import { Input } from "@/shared/common/ui/input"; +import { Button } from "@/shared/common/ui/button"; +import { Search, RefreshCw, ChevronLeft, ChevronRight } from "lucide-react"; +import { listAuditLogExtensions } from "@/shared/services/audit/audit.api"; + +interface ActivityLog { + id: string; + createdAt: string; + entityName: string; + queryMethod: "INSERT" | "UPDATE" | "DELETE" | string; + user: { + id: string; + name: { + am: string; + en: string; + }; + email: string; + username?: string; + }; +} + +const ITEMS_PER_PAGE = 10; + +export default function ActivityLogPage() { + const { t } = useTranslation(); + const [searchQuery, setSearchQuery] = useState(""); + const [allLogs, setAllLogs] = useState([]); + const [filteredLogs, setFilteredLogs] = useState([]); + const [loading, setLoading] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + + const fetchAuditLogs = useCallback(async () => { + setLoading(true); + try { + // Fetch all 1000 records from super admin endpoint + const data = await listAuditLogExtensions( + "/audit-log-extensions/audit/superAdmin", + { + skip: 0, + take: 1000, + orderBy: "createdAt:DESC", + } + ); + + const logs = (data.items || []) as ActivityLog[]; + setAllLogs(logs); + setCurrentPage(1); + } catch (error: any) { + console.error("Error fetching audit logs:", error); + setAllLogs([]); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchAuditLogs(); + // Refresh every 30 seconds + const interval = setInterval(fetchAuditLogs, 30000); + return () => clearInterval(interval); + }, [fetchAuditLogs]); + + // Paginate the logs whenever currentPage changes + useEffect(() => { + const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; + const endIndex = startIndex + ITEMS_PER_PAGE; + let filtered = allLogs.slice(startIndex, endIndex); + + // Apply search filter + if (searchQuery) { + filtered = filtered.filter( + (log) => + log.entityName.toLowerCase().includes(searchQuery.toLowerCase()) || + log.user.name.am.toLowerCase().includes(searchQuery.toLowerCase()) || + log.user.name.en.toLowerCase().includes(searchQuery.toLowerCase()) || + log.user.email.toLowerCase().includes(searchQuery.toLowerCase()), + ); + } + + setFilteredLogs(filtered); + }, [allLogs, currentPage, searchQuery]); + + const totalPages = Math.ceil(allLogs.length / ITEMS_PER_PAGE); + const hasNextPage = currentPage < totalPages; + const hasPrevPage = currentPage > 1; + + return ( +
+ + +
+ + {t("activityLogPage.title")} + + +
+
+ +
+
+ + setSearchQuery(e.target.value)} + className="pl-10" + /> +
+
+ +
+ + + + + {t("activityLogPage.table.time")} + + + {t("activityLogPage.table.description")} + + + {t("activityLogPage.table.performedBy")} + + + + + {loading ? ( + + +
+ + {t("common.loading", "Loading...")} +
+
+
+ ) : filteredLogs.length > 0 ? ( + filteredLogs.map((log) => { + const userName = + i18n.language === "am" ? log.user.name.am : log.user.name.en; + const entityLabel = log.entityName.replace(/_/g, " "); + const actionVerb = + log.queryMethod.toLowerCase() === "insert" + ? "created" + : log.queryMethod.toLowerCase() === "update" + ? "updated" + : log.queryMethod.toLowerCase() === "delete" + ? "deleted" + : log.queryMethod.toLowerCase(); + const timestamp = new Date(log.createdAt).toLocaleString( + i18n.language, + { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + } + ); + + return ( + + {timestamp} + +

+ {userName} + {" "} + + {actionVerb} + + {" "} + + {entityLabel} + + {" "} + at {timestamp} +

+
+ +
+

{userName}

+

{log.user.email}

+
+
+
+ ); + }) + ) : ( + + + {t("activityLogPage.table.noLogs")} + + + )} +
+
+
+ + {/* Pagination Footer */} + {allLogs.length > 0 && ( +
+
+ {t("common.showing", "Showing")} {(currentPage - 1) * ITEMS_PER_PAGE + 1}- + {Math.min(currentPage * ITEMS_PER_PAGE, allLogs.length)}{" "} + {t("common.of", "of")} {allLogs.length} +
+ +
+ + +
+ + {currentPage} / {totalPages || 1} + +
+ + +
+
+ )} +
+
+
+ ); +} diff --git a/apps/edr-freight-web/backoffice/src/pages/AddSitePage.tsx b/apps/edr-freight-web/backoffice/src/pages/AddSitePage.tsx index 3f7b03cf4..fdd26eca4 100644 --- a/apps/edr-freight-web/backoffice/src/pages/AddSitePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/AddSitePage.tsx @@ -1,7 +1,7 @@ -import SitesPage from "@/super-admin/components/sites/SitesPage"; - -const AddSitePage = () => { - return ; -}; - -export default AddSitePage; +import SitesPage from "@/super-admin/components/sites/SitesPage"; + +const AddSitePage = () => { + return ; +}; + +export default AddSitePage; diff --git a/apps/edr-freight-web/backoffice/src/pages/AdvancedDashboardPage.tsx b/apps/edr-freight-web/backoffice/src/pages/AdvancedDashboardPage.tsx index 18328c954..429f931e6 100644 --- a/apps/edr-freight-web/backoffice/src/pages/AdvancedDashboardPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/AdvancedDashboardPage.tsx @@ -1,11 +1,141 @@ -// STUB — record-management/routes lazy-imports this page. The advanced dashboard -// is not migrated into this backoffice; render a placeholder instead of failing -// the build. Replace with the real page if it is brought over. -export default function AdvancedDashboardPage() { +import React, { useState } from "react"; +import { Button } from "@/shared/common/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/shared/common/ui/dropdown-menu"; +import { ChevronDown } from "lucide-react"; +import { + PermittedDashboardItem, + DashaboardParam, +} from "@/record-management/services/api/advancedDashboardService"; +import { useAdvancedDashboardHook } from "@/shared/hooks/useAdvancedDashboardHook"; +import useSettings from "@/record-management/components/hooks/useSettings"; +import { useLocalizedName } from "@/shared/common/localizedName"; +import { getFormattedDashboardLabel } from "@/performance-management/utils/dashboardUtils"; +import { PositionDto } from "@/record-management/dto/userRecords/usersDto"; + +const AdvancedDashboardPage = () => { + const [selectedDashboard, setSelectedDashboard] = + useState(null); + const [selectedParameters, setSelectedParameters] = useState< + Record + >({}); + const localizedName = useLocalizedName(); + + const { + permittedDashboards, + dashboardUrl, + isLoadingPermitted, + isLoadingUrl, + } = useAdvancedDashboardHook(selectedDashboard?.id); + + const { allScopedDepartments, allScopedEmployees } = useSettings(); + + const positionOptions = Array.isArray(allScopedDepartments) + ? allScopedDepartments.map((pos: PositionDto) => ({ + label: localizedName(pos.name) || "N/A", + value: pos.id, + })) + : []; + + const employeeOptions = Array.isArray(allScopedEmployees) + ? allScopedEmployees.map((emp: any) => ({ + label: localizedName(emp.name) || "N/A", + value: emp.id, + })) + : []; + + const handleParameterSelect = (key: string, value: string) => { + setSelectedParameters((prev) => ({ + ...prev, + [key]: value, + })); + }; + return ( -
-

Advanced Dashboard

-

This page has not been migrated into the backoffice yet.

+
+ {/* Dashboard + Position + Employee in a row */} +
+ {/* Dashboard Dropdown */} + + + + + + Available Dashboards + {permittedDashboards?.items.map((dashboard) => ( + { + setSelectedDashboard(dashboard); + setSelectedParameters({}); // reset parameters + }}> + {getFormattedDashboardLabel(dashboard.name, "am")} + + ))} + + + + {/* Position Dropdown */} + {selectedDashboard?.params?.includes(DashaboardParam.POSITION) && ( + + )} + + {/* Employee Dropdown */} + {selectedDashboard?.params?.includes(DashaboardParam.EMPLOYEE) && ( + + )} +
+ + {/* Dashboard iframe */} + {isLoadingUrl ? ( +

Loading dashboard...

+ ) : dashboardUrl ? ( +