diff --git a/src/components/shared/MoreFilters.tsx b/src/components/shared/MoreFilters.tsx new file mode 100644 index 0000000..9f3a2be --- /dev/null +++ b/src/components/shared/MoreFilters.tsx @@ -0,0 +1,96 @@ +import type { ReactElement, ReactNode } from "react"; +import { SlidersHorizontal, X, ChevronDown } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { useAppTheme } from "@/hooks/useAppTheme"; +import { PrimaryButton } from "./PrimaryButton"; +import { SecondaryButton } from "./SecondaryButton"; + +interface MoreFiltersProps { + title?: string; + isOpen: boolean; + onOpenToggle: (isOpen: boolean) => void; + onApply: () => void; + onCancel: () => void; + hasActiveFilters?: boolean; + children: ReactNode; +} + +export const MoreFilters = ({ + title = "Filter Options", + isOpen, + onOpenToggle, + onApply, + onCancel, + hasActiveFilters = false, + children, +}: MoreFiltersProps): ReactElement => { + const { primaryColor } = useAppTheme(); + + return ( +
+ + + {isOpen && ( +
+ {/* Header */} +
+

+ {title} +

+ +
+ + {/* Children container */} +
+ {children} +
+ + {/* Footer Actions */} +
+ + Cancel + + + Apply filters + +
+
+ )} +
+ ); +}; diff --git a/src/components/shared/index.ts b/src/components/shared/index.ts index 562d590..b6029e6 100644 --- a/src/components/shared/index.ts +++ b/src/components/shared/index.ts @@ -45,3 +45,4 @@ export { SearchBox } from './SearchBox'; export { FormTagInput } from './FormTagInput'; export { MarkdownViewer } from './MarkdownViewer'; export { GradientStatCard } from './GradientStatCard'; +export { MoreFilters } from './MoreFilters'; diff --git a/src/pages/superadmin/AuditLogs.tsx b/src/pages/superadmin/AuditLogs.tsx index 7e56d5b..00f0330 100644 --- a/src/pages/superadmin/AuditLogs.tsx +++ b/src/pages/superadmin/AuditLogs.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect } from "react"; import type { ReactElement } from "react"; import { Layout } from "@/components/layout/Layout"; import { @@ -8,19 +8,18 @@ import { FilterDropdown, StatusBadge, type Column, + MoreFilters, + FormSelect, + FormField, } from "@/components/shared"; import { Download, - ArrowUpDown, Search, - ChevronDown, - SlidersHorizontal, } from "lucide-react"; import { auditLogService } from "@/services/audit-log-service"; import { tenantService } from "@/services/tenant-service"; import type { AuditLog } from "@/types/audit-log"; import type { Tenant } from "@/types/tenant"; -import { cn } from "@/lib/utils"; import { useAppTheme } from "@/hooks/useAppTheme"; // Helper function to format date @@ -82,7 +81,7 @@ const getStatusColor = (status: number | null): string => { }; const AuditLogs = (): ReactElement => { - const { primaryColor } = useAppTheme(); + useAppTheme(); const [expandedId, setExpandedId] = useState(null); const [auditLogs, setAuditLogs] = useState([]); const [tenants, setTenants] = useState([]); @@ -125,13 +124,12 @@ const AuditLogs = (): ReactElement => { const [orderBy, setOrderBy] = useState(null); const [search, setSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState(""); - const [showMoreFilters, setShowMoreFilters] = useState(false); - - const hasExtraFilters = useMemo( - () => - Boolean(moduleFilter || methodFilter || startDate || endDate || orderBy), - [moduleFilter, methodFilter, startDate, endDate, orderBy], - ); + const [isMoreFiltersOpen, setIsMoreFiltersOpen] = useState(false); + const [tempResourceType, setTempResourceType] = useState(null); + const [tempModule, setTempModule] = useState(null); + const [tempOrderBy, setTempOrderBy] = useState(null); + const [tempStartDate, setTempStartDate] = useState(""); + const [tempEndDate, setTempEndDate] = useState(""); // View modal const [viewModalOpen, setViewModalOpen] = useState(false); @@ -183,11 +181,7 @@ const AuditLogs = (): ReactElement => { fetchModules(); }, []); - useEffect(() => { - if (hasExtraFilters) { - setShowMoreFilters(true); - } - }, [hasExtraFilters]); + const fetchAuditLogs = async (): Promise => { try { @@ -574,43 +568,132 @@ const AuditLogs = (): ReactElement => { placeholder="All Actions" /> - {/* Resource Filter */} - { - setResourceTypeFilter(value as string | null); - setCurrentPage(1); + {/* More Filters Popover */} + { + if (open) { + setTempResourceType(resourceTypeFilter); + setTempModule(moduleFilter); + setTempOrderBy(orderBy); + setTempStartDate(startDate); + setTempEndDate(endDate); + } + setIsMoreFiltersOpen(open); }} - placeholder="All Resources" - isSearchable - /> - - + {/* Dropdowns row 1 */} +
+ {/* Resource Type */} +
+ setTempResourceType(val || null)} + isSearchable + /> +
+ + {/* Module */} +
+ ({ value: m.id, label: m.name }))} + value={tempModule || ""} + onValueChange={(val) => setTempModule(val || null)} + isSearchable + /> +
+
+ + {/* Dropdowns row 2 */} +
+ {/* Sort by */} +
+ setTempOrderBy(val ? val.split(",") : null)} + /> +
+ + {/* Empty/Placeholder space for balance */} +
+
+ + {/* Date Filters Row */} +
+ {/* Start Date */} +
+ setTempStartDate(e.target.value)} + /> +
+ + {/* End Date */} +
+ setTempEndDate(e.target.value)} + /> +
+
+ + + {/* Clear Filters */} + {(startDate || + endDate || + tenantFilter || + actionFilter || + resourceTypeFilter || + moduleFilter || + methodFilter || + search || + orderBy) && ( + + )}
{/* Actions */} @@ -625,118 +708,6 @@ const AuditLogs = (): ReactElement => { - - {showMoreFilters && ( -
-
- ({ value: m.id, label: m.name }))} - value={moduleFilter} - onChange={(value) => { - setModuleFilter(value as string | null); - setCurrentPage(1); - }} - placeholder="All Modules" - isSearchable - /> - - { - setMethodFilter(value as string | null); - setCurrentPage(1); - }} - placeholder="All Methods" - /> - - { - setOrderBy(value as string[] | null); - setCurrentPage(1); - }} - placeholder="Newest" - showIcon - icon={} - /> -
- -
-
- - Start Date: - - { - setStartDate(e.target.value); - setCurrentPage(1); - }} - className="text-xs px-2 py-1.5 border border-[rgba(0,0,0,0.08)] rounded focus:outline-none focus:ring-1 focus:ring-[#112868]/20" - /> -
-
- - End Date: - - { - setEndDate(e.target.value); - setCurrentPage(1); - }} - className="text-xs px-2 py-1.5 border border-[rgba(0,0,0,0.08)] rounded focus:outline-none focus:ring-1 focus:ring-[#112868]/20" - /> -
-
-
- )} - - {(startDate || - endDate || - tenantFilter || - actionFilter || - resourceTypeFilter || - moduleFilter || - methodFilter || - search || - orderBy) && ( -
- -
- )} {/* Table */} diff --git a/src/pages/tenant/AuditLogs.tsx b/src/pages/tenant/AuditLogs.tsx index a07b775..053634c 100644 --- a/src/pages/tenant/AuditLogs.tsx +++ b/src/pages/tenant/AuditLogs.tsx @@ -9,8 +9,11 @@ import { StatusBadge, SearchBox, type Column, + MoreFilters, + FormSelect, + FormField, } from "@/components/shared"; -import { Download, ArrowUpDown } from "lucide-react"; +import { Download } from "lucide-react"; import { auditLogService } from "@/services/audit-log-service"; import { moduleService } from "@/services/module-service"; import type { AuditLog } from "@/types/audit-log"; @@ -120,7 +123,7 @@ const AuditLogs = ({ }); // Filter state - const [methodFilter, setMethodFilter] = useState(null); + const methodFilter = null; const [actionFilter, setActionFilter] = useState(null); const [resourceTypeFilter, setResourceTypeFilter] = useState( null, @@ -131,8 +134,12 @@ const AuditLogs = ({ const [orderBy, setOrderBy] = useState(null); const [search, setSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState(""); - - // View modal + // More Filters popover state + const [isMoreFiltersOpen, setIsMoreFiltersOpen] = useState(false); + const [tempResourceType, setTempResourceType] = useState(null); + const [tempOrderBy, setTempOrderBy] = useState(null); + const [tempStartDate, setTempStartDate] = useState(""); + const [tempEndDate, setTempEndDate] = useState(""); const [viewModalOpen, setViewModalOpen] = useState(false); const [selectedAuditLogId, setSelectedAuditLogId] = useState( null, @@ -522,7 +529,7 @@ const AuditLogs = ({ {/* Table Container */}
{/* Table Header with Filters */} -
+
{/* Search and Filters */}
@@ -535,41 +542,26 @@ const AuditLogs = ({ /> {isTenantAdmin && ( - <> - {/* Action Filter */} - { - setActionFilter(value as string | null); - setCurrentPage(1); - }} - placeholder="All Actions" - /> - - {/* Resource Type Filter */} - { - setResourceTypeFilter(value as string | null); - setCurrentPage(1); - }} - placeholder="All Resources" - isSearchable - /> - + /* Action Filter */ + { + setActionFilter(value as string | null); + setCurrentPage(1); + }} + placeholder="All Actions" + /> )} {/* Module Filter */} @@ -584,27 +576,110 @@ const AuditLogs = ({ placeholder="All Modules" /> - {/* Sort Filter - Always show as it's useful for everyone */} - { - setOrderBy(value as string[] | null); - setCurrentPage(1); + {/* More Filters Popover */} + { + if (open) { + setTempResourceType(resourceTypeFilter); + setTempOrderBy(orderBy); + setTempStartDate(startDate); + setTempEndDate(endDate); + } + setIsMoreFiltersOpen(open); }} - placeholder="Newest" - showIcon - icon={} - /> + onApply={() => { + setResourceTypeFilter(tempResourceType); + setOrderBy(tempOrderBy); + setStartDate(tempStartDate); + setEndDate(tempEndDate); + setCurrentPage(1); + setIsMoreFiltersOpen(false); + }} + onCancel={() => setIsMoreFiltersOpen(false)} + hasActiveFilters={Boolean(resourceTypeFilter || orderBy || startDate || endDate)} + > + {/* Dropdowns row */} +
+ {/* Resource Type */} +
+ setTempResourceType(val || null)} + isSearchable + /> +
+ + {/* Sort by */} +
+ setTempOrderBy(val ? val.split(",") : null)} + /> +
+
+ + {/* Date Filters Row */} +
+ {/* From Date */} +
+ setTempStartDate(e.target.value)} + /> +
+ + {/* To Date */} +
+ setTempEndDate(e.target.value)} + /> +
+
+
+ + {/* Clear Filters */} + {(startDate || + endDate || + actionFilter || + resourceTypeFilter || + moduleIdFilter || + search || + orderBy) && ( + + )}
{/* Actions */} @@ -621,58 +696,6 @@ const AuditLogs = ({ )}
- - {/* Date Filters - Separated row for better spacing */} -
-
- From: - { - setStartDate(e.target.value); - setCurrentPage(1); - }} - className="text-xs px-2 py-1.5 border border-[rgba(0,0,0,0.08)] rounded focus:outline-none focus:ring-1 focus:ring-[#112868]/20" - /> -
-
- To: - { - setEndDate(e.target.value); - setCurrentPage(1); - }} - className="text-xs px-2 py-1.5 border border-[rgba(0,0,0,0.08)] rounded focus:outline-none focus:ring-1 focus:ring-[#112868]/20" - /> -
- {(startDate || - endDate || - actionFilter || - resourceTypeFilter || - methodFilter || - moduleIdFilter || - search) && ( - - )} -
{/* Table */}