From 193b26f6f142d2c8ea244e32309a8acc0ac5f85b Mon Sep 17 00:00:00 2001 From: Adrian Flores Cortes Date: Tue, 3 Feb 2026 19:58:31 -0600 Subject: [PATCH] [SPRINT-1] feat: Complete RBAC, Audit and Notifications frontend modules ## ST-1.1 RBAC Frontend (5 SP) - Add RoleDetailPage for create/edit roles - Add PermissionsPage to list all permissions - Add RoleCard, RoleForm, PermissionsMatrix components - Add routes: /rbac/roles/new, /rbac/roles/:id, /rbac/permissions - Add sidebar navigation item ## ST-1.2 Audit Complete (3 SP) - Enhance AuditFilters with user filter and search - Add Recharts graphs to AuditStatsCard (activity trend, action distribution) - Add period selector (7/14/30 days) - Add most active users section ## ST-1.3 Notifications Complete (3 SP) - Add TemplatesPage for CRUD notification templates - Add TemplateCard, TemplateForm, TemplatePreview components - Add ChannelConfig component for channel toggles - Extend notifications API with template endpoints - Extend useNotifications hook with template queries Co-Authored-By: Claude Opus 4.5 --- src/components/audit/AuditFilters.tsx | 142 ++++-- src/components/audit/AuditStatsCard.tsx | 195 +++++++- .../notifications/ChannelConfig.tsx | 186 ++++++++ src/components/notifications/TemplateCard.tsx | 155 ++++++ src/components/notifications/TemplateForm.tsx | 442 ++++++++++++++++++ .../notifications/TemplatePreview.tsx | 314 +++++++++++++ src/components/notifications/index.ts | 4 + src/components/rbac/PermissionsMatrix.tsx | 192 ++++++++ src/components/rbac/RoleCard.tsx | 121 +++++ src/components/rbac/RoleForm.tsx | 208 +++++++++ src/components/rbac/index.ts | 3 + src/hooks/index.ts | 23 + src/hooks/useNotifications.ts | 81 +++- src/layouts/DashboardLayout.tsx | 2 + src/pages/dashboard/AuditLogsPage.tsx | 60 ++- .../notifications/NotificationsPage.tsx | 32 +- .../dashboard/notifications/TemplatesPage.tsx | 308 ++++++++++++ src/pages/dashboard/notifications/index.ts | 1 + src/pages/dashboard/rbac/PermissionsPage.tsx | 212 +++++++++ src/pages/dashboard/rbac/RoleDetailPage.tsx | 100 ++++ src/pages/dashboard/rbac/index.ts | 2 + src/router/index.tsx | 8 + src/services/api.ts | 106 +++++ 23 files changed, 2826 insertions(+), 71 deletions(-) create mode 100644 src/components/notifications/ChannelConfig.tsx create mode 100644 src/components/notifications/TemplateCard.tsx create mode 100644 src/components/notifications/TemplateForm.tsx create mode 100644 src/components/notifications/TemplatePreview.tsx create mode 100644 src/components/rbac/PermissionsMatrix.tsx create mode 100644 src/components/rbac/RoleCard.tsx create mode 100644 src/components/rbac/RoleForm.tsx create mode 100644 src/components/rbac/index.ts create mode 100644 src/pages/dashboard/notifications/TemplatesPage.tsx create mode 100644 src/pages/dashboard/rbac/PermissionsPage.tsx create mode 100644 src/pages/dashboard/rbac/RoleDetailPage.tsx diff --git a/src/components/audit/AuditFilters.tsx b/src/components/audit/AuditFilters.tsx index c6cf985..9171c70 100644 --- a/src/components/audit/AuditFilters.tsx +++ b/src/components/audit/AuditFilters.tsx @@ -1,13 +1,16 @@ import { useState } from 'react'; -import { AuditAction, QueryAuditLogsParams } from '@/services/api'; +import { AuditAction, QueryAuditLogsParams, User } from '@/services/api'; import { getAuditActionLabel } from '@/hooks/useAudit'; -import { Filter, X } from 'lucide-react'; +import { Filter, X, Search } from 'lucide-react'; import clsx from 'clsx'; interface AuditFiltersProps { filters: QueryAuditLogsParams; onFiltersChange: (filters: QueryAuditLogsParams) => void; entityTypes?: string[]; + users?: Pick[]; + onSearchChange?: (search: string) => void; + searchValue?: string; } const AUDIT_ACTIONS: AuditAction[] = [ @@ -21,8 +24,16 @@ const AUDIT_ACTIONS: AuditAction[] = [ 'import', ]; -export function AuditFilters({ filters, onFiltersChange, entityTypes = [] }: AuditFiltersProps) { +export function AuditFilters({ + filters, + onFiltersChange, + entityTypes = [], + users = [], + onSearchChange, + searchValue = '', +}: AuditFiltersProps) { const [isOpen, setIsOpen] = useState(false); + const [localSearch, setLocalSearch] = useState(searchValue); const activeFiltersCount = [ filters.action, @@ -32,6 +43,21 @@ export function AuditFilters({ filters, onFiltersChange, entityTypes = [] }: Aud filters.to_date, ].filter(Boolean).length; + const getUserDisplayName = (user: Pick) => { + const fullName = `${user.first_name || ''} ${user.last_name || ''}`.trim(); + return fullName || user.email; + }; + + const handleSearchSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSearchChange?.(localSearch); + }; + + const handleSearchClear = () => { + setLocalSearch(''); + onSearchChange?.(''); + }; + const handleChange = (key: keyof QueryAuditLogsParams, value: string | undefined) => { onFiltersChange({ ...filters, @@ -46,41 +72,66 @@ export function AuditFilters({ filters, onFiltersChange, entityTypes = [] }: Aud return (
- {/* Filter toggle button */} -
- - - {activeFiltersCount > 0 && ( - + {/* Search and Filter toggle */} +
+ {/* Search input */} + {onSearchChange && ( +
+ + setLocalSearch(e.target.value)} + placeholder="Search logs by description, entity ID..." + className="w-full pl-10 pr-10 py-2 rounded-lg border border-secondary-300 dark:border-secondary-600 bg-white dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 text-sm" + /> + {localSearch && ( + + )} + )} + +
+ + + {activeFiltersCount > 0 && ( + + )} +
{/* Filter panel */} {isOpen && (
-
+
{/* Action filter */}
+ {/* User filter */} + {users.length > 0 && ( +
+ + +
+ )} + {/* From date filter */}