From 9a0f0fa76b44d0c1181adef309a9f90d5839a3ae Mon Sep 17 00:00:00 2001 From: Mehara Rothila Ranawaka Date: Fri, 7 Nov 2025 00:41:58 +0530 Subject: [PATCH 01/38] Update admin dashboard pages --- src/app/dashboard/admin/audit-logs/page.tsx | 53 +++- src/app/dashboard/admin/config/page.tsx | 30 +- src/app/dashboard/admin/page.tsx | 30 +- src/app/dashboard/admin/reports/page.tsx | 81 ++++- .../dashboard/admin/service-types/page.tsx | 30 +- src/app/dashboard/admin/users/page.tsx | 298 +++++++++++++++++- 6 files changed, 490 insertions(+), 32 deletions(-) diff --git a/src/app/dashboard/admin/audit-logs/page.tsx b/src/app/dashboard/admin/audit-logs/page.tsx index a0359a7..699f8f9 100644 --- a/src/app/dashboard/admin/audit-logs/page.tsx +++ b/src/app/dashboard/admin/audit-logs/page.tsx @@ -3,10 +3,13 @@ import { useState, useEffect } from 'react'; import { adminService } from '@/services/adminService'; import { AuditLogResponse } from '@/types/admin'; +import { useDashboard } from '@/app/contexts/DashboardContext'; export default function AuditLogsPage() { + const { roles, loading: rolesLoading } = useDashboard(); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); useEffect(() => { loadLogs(); @@ -15,11 +18,15 @@ export default function AuditLogsPage() { const loadLogs = async () => { try { setLoading(true); + setError(null); const data = await adminService.getAuditLogs(); // Ensure data is an array setLogs(Array.isArray(data) ? data : []); } catch (err) { console.error('Failed to load audit logs:', err); + const errorMessage = (err as { response?: { data?: { message?: string } } })?.response?.data?.message || + 'Failed to load audit logs. The service may be unavailable.'; + setError(errorMessage); setLogs([]); // Set empty array on error } finally { setLoading(false); @@ -36,7 +43,11 @@ export default function AuditLogsPage() { }); }; - if (loading) { + // Check if user has permission to access this page + const hasRole = (role: string) => roles?.includes(role); + const hasAccess = hasRole('ADMIN') || hasRole('SUPER_ADMIN'); + + if (rolesLoading || loading) { return (
@@ -48,6 +59,28 @@ export default function AuditLogsPage() { ); } + // Block access for customers and employees + if (!hasAccess) { + return ( +
+
+ + + +

Access Denied

+

+ This page is only accessible to admins and super admins. +

+
+
+ ); + } + return (
@@ -55,6 +88,12 @@ export default function AuditLogsPage() {

System activity and audit trail

+ {error && ( +
+ {error} +
+ )} +
@@ -70,8 +109,16 @@ export default function AuditLogsPage() { {logs.length === 0 ? ( - ) : ( diff --git a/src/app/dashboard/admin/config/page.tsx b/src/app/dashboard/admin/config/page.tsx index a950951..d2116ea 100644 --- a/src/app/dashboard/admin/config/page.tsx +++ b/src/app/dashboard/admin/config/page.tsx @@ -3,8 +3,10 @@ import { useState, useEffect } from 'react'; import { adminService } from '@/services/adminService'; import { SystemConfigurationResponse } from '@/types/admin'; +import { useDashboard } from '@/app/contexts/DashboardContext'; export default function ConfigPage() { + const { roles, loading: rolesLoading } = useDashboard(); const [configs, setConfigs] = useState([]); const [loading, setLoading] = useState(true); @@ -43,7 +45,11 @@ export default function ConfigPage() { return acc; }, {} as Record); - if (loading) { + // Check if user has permission to access this page + const hasRole = (role: string) => roles?.includes(role); + const hasAccess = hasRole('ADMIN') || hasRole('SUPER_ADMIN'); + + if (rolesLoading || loading) { return (
@@ -55,6 +61,28 @@ export default function ConfigPage() { ); } + // Block access for customers and employees + if (!hasAccess) { + return ( +
+
+ + + +

Access Denied

+

+ This page is only accessible to admins and super admins. +

+
+
+ ); + } + return (
diff --git a/src/app/dashboard/admin/page.tsx b/src/app/dashboard/admin/page.tsx index 89a2ef4..83f1ade 100644 --- a/src/app/dashboard/admin/page.tsx +++ b/src/app/dashboard/admin/page.tsx @@ -4,8 +4,10 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; import { adminService } from '@/services/adminService'; import { AnalyticsDashboardResponse } from '@/types/admin'; +import { useDashboard } from '@/app/contexts/DashboardContext'; export default function AdminPage() { + const { roles, loading: rolesLoading } = useDashboard(); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); @@ -25,7 +27,11 @@ export default function AdminPage() { } }; - if (loading) { + // Check if user has permission to access this page + const hasRole = (role: string) => roles?.includes(role); + const hasAccess = hasRole('ADMIN') || hasRole('SUPER_ADMIN'); + + if (rolesLoading || loading) { return (
@@ -40,6 +46,28 @@ export default function AdminPage() { ); } + // Block access for customers and employees + if (!hasAccess) { + return ( +
+
+ + + +

Access Denied

+

+ This page is only accessible to admins and super admins. +

+
+
+ ); + } + return (
diff --git a/src/app/dashboard/admin/reports/page.tsx b/src/app/dashboard/admin/reports/page.tsx index 1d9a6b6..4aab25f 100644 --- a/src/app/dashboard/admin/reports/page.tsx +++ b/src/app/dashboard/admin/reports/page.tsx @@ -3,20 +3,24 @@ import { useState } from 'react'; import { adminService } from '@/services/adminService'; import { ReportRequest } from '@/types/admin'; +import { useDashboard } from '@/app/contexts/DashboardContext'; export default function ReportsPage() { + const { roles, loading: rolesLoading } = useDashboard(); const [generating, setGenerating] = useState(false); - const [reportType, setReportType] = useState<'REVENUE' | 'SERVICES' | 'CUSTOMERS' | 'EMPLOYEES'>('REVENUE'); + const [reportType, setReportType] = useState<'SERVICE_PERFORMANCE' | 'REVENUE' | 'EMPLOYEE_PRODUCTIVITY' | 'CUSTOMER_SATISFACTION' | 'INVENTORY' | 'APPOINTMENT_SUMMARY'>('REVENUE'); + const [format, setFormat] = useState<'JSON' | 'PDF' | 'EXCEL' | 'CSV'>('PDF'); const [dateRange, setDateRange] = useState({ - from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], - to: new Date().toISOString().split('T')[0], + fromDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], + toDate: new Date().toISOString().split('T')[0], }); const handleGenerateReport = async () => { try { setGenerating(true); const request: ReportRequest = { - reportType, + type: reportType, + format, ...dateRange, }; const report = await adminService.generateReport(request); @@ -31,6 +35,43 @@ export default function ReportsPage() { } }; + // Check if user has permission to access this page + const hasRole = (role: string) => roles?.includes(role); + const hasAccess = hasRole('ADMIN') || hasRole('SUPER_ADMIN'); + + if (rolesLoading) { + return ( +
+
+
+
+
+
+ ); + } + + // Block access for customers and employees + if (!hasAccess) { + return ( +
+
+ + + +

Access Denied

+

+ This page is only accessible to admins and super admins. +

+
+
+ ); + } + return (
@@ -46,13 +87,29 @@ export default function ReportsPage() { +
+ +
+ +
@@ -61,8 +118,8 @@ export default function ReportsPage() { setDateRange({ ...dateRange, from: e.target.value })} + value={dateRange.fromDate} + onChange={(e) => setDateRange({ ...dateRange, fromDate: e.target.value })} className="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 theme-text-primary focus:ring-2 focus:ring-blue-500" />
@@ -70,8 +127,8 @@ export default function ReportsPage() { setDateRange({ ...dateRange, to: e.target.value })} + value={dateRange.toDate} + onChange={(e) => setDateRange({ ...dateRange, toDate: e.target.value })} className="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 theme-text-primary focus:ring-2 focus:ring-blue-500" />
diff --git a/src/app/dashboard/admin/service-types/page.tsx b/src/app/dashboard/admin/service-types/page.tsx index 8e481ad..fc5953c 100644 --- a/src/app/dashboard/admin/service-types/page.tsx +++ b/src/app/dashboard/admin/service-types/page.tsx @@ -3,8 +3,10 @@ import { useState, useEffect } from 'react'; import { adminService } from '@/services/adminService'; import { ServiceTypeResponse } from '@/types/admin'; +import { useDashboard } from '@/app/contexts/DashboardContext'; export default function ServiceTypesPage() { + const { roles, loading: rolesLoading } = useDashboard(); const [serviceTypes, setServiceTypes] = useState([]); const [loading, setLoading] = useState(true); @@ -26,7 +28,11 @@ export default function ServiceTypesPage() { } }; - if (loading) { + // Check if user has permission to access this page + const hasRole = (role: string) => roles?.includes(role); + const hasAccess = hasRole('ADMIN') || hasRole('SUPER_ADMIN'); + + if (rolesLoading || loading) { return (
@@ -38,6 +44,28 @@ export default function ServiceTypesPage() { ); } + // Block access for customers and employees + if (!hasAccess) { + return ( +
+
+ + + +

Access Denied

+

+ This page is only accessible to admins and super admins. +

+
+
+ ); + } + return (
diff --git a/src/app/dashboard/admin/users/page.tsx b/src/app/dashboard/admin/users/page.tsx index 3b826c9..3757492 100644 --- a/src/app/dashboard/admin/users/page.tsx +++ b/src/app/dashboard/admin/users/page.tsx @@ -2,12 +2,24 @@ import { useState, useEffect } from 'react'; import { adminService } from '@/services/adminService'; +import { authService } from '@/services/authService'; import { UserResponse } from '@/types/admin'; +import { CreateEmployeeRequest, CreateAdminRequest } from '@/types/api'; +import { useDashboard } from '@/app/contexts/DashboardContext'; export default function AdminUsersPage() { + const { roles: currentUserRoles } = useDashboard(); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [roleFilter, setRoleFilter] = useState('ALL'); + const [showCreateModal, setShowCreateModal] = useState(false); + const [createUserType, setCreateUserType] = useState<'employee' | 'admin'>('employee'); + const [editingUserId, setEditingUserId] = useState(null); + const [editingRoles, setEditingRoles] = useState([]); + + // Check current user permissions + const isSuperAdmin = currentUserRoles.includes('SUPER_ADMIN'); + const isAdmin = currentUserRoles.includes('ADMIN') || isSuperAdmin; useEffect(() => { loadUsers(); @@ -36,6 +48,81 @@ export default function AdminUsersPage() { }); }; + const handleCreateUser = async (e: React.FormEvent) => { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + + try { + if (createUserType === 'employee') { + const payload: CreateEmployeeRequest = { + username: formData.get('username') as string, + email: formData.get('email') as string, + password: formData.get('password') as string, + firstName: formData.get('firstName') as string, + lastName: formData.get('lastName') as string, + department: formData.get('department') as string, + }; + await authService.createEmployee(payload); + } else { + const payload: CreateAdminRequest = { + username: formData.get('username') as string, + email: formData.get('email') as string, + password: formData.get('password') as string, + firstName: formData.get('firstName') as string, + lastName: formData.get('lastName') as string, + }; + await authService.createAdmin(payload); + } + + setShowCreateModal(false); + await loadUsers(); + alert(`${createUserType === 'employee' ? 'Employee' : 'Admin'} created successfully!`); + } catch (err: any) { + console.error('Failed to create user:', err); + const errorMessage = err?.response?.status === 403 + ? 'Permission denied. You do not have the required permissions to create this user type.' + : err?.response?.data?.message || 'Failed to create user. Please try again.'; + alert(errorMessage); + } + }; + + const handleStartEditRoles = (user: UserResponse) => { + setEditingUserId(user.userId); + setEditingRoles(user.roles || []); + }; + + const handleCancelEditRoles = () => { + setEditingUserId(null); + setEditingRoles([]); + }; + + const handleSaveRoles = async (userId: string) => { + try { + await adminService.updateUser(userId, { roles: editingRoles }); + setEditingUserId(null); + setEditingRoles([]); + await loadUsers(); + alert('Roles updated successfully!'); + } catch (err) { + console.error('Failed to update roles:', err); + alert('Failed to update roles. Please try again.'); + } + }; + + const handleToggleRole = (role: string) => { + if (editingRoles.includes(role)) { + setEditingRoles(editingRoles.filter(r => r !== role)); + } else { + setEditingRoles([...editingRoles, role]); + } + }; + + // Check if user can be edited (not customer) + const canEditUser = (user: UserResponse): boolean => { + const isCustomer = user.roles.includes('CUSTOMER'); + return !isCustomer; + }; + const roleFilters = ['ALL', 'CUSTOMER', 'EMPLOYEE', 'ADMIN', 'SUPER_ADMIN']; if (loading) { @@ -49,11 +136,57 @@ export default function AdminUsersPage() { ); } + // Block access for customers and employees + if (!isAdmin) { + return ( +
+
+ + + +

Access Denied

+

+ This page is only accessible to admins and super admins. +

+
+
+ ); + } + return (
-
-

User Management

-

Manage system users and their roles

+
+
+

User Management

+

Manage system users and their roles

+
+ {isSuperAdmin && ( +
+ + +
+ )}
{/* Stats */} @@ -110,6 +243,7 @@ export default function AdminUsersPage() {
+ {isAdmin && } @@ -122,17 +256,37 @@ export default function AdminUsersPage() { + {isAdmin && ( + + )} ))}
- No audit logs found + + + + +

+ {error ? 'Unable to load audit logs' : 'No audit logs found'} +

+

+ Audit logs will appear here as system activities are tracked +

Status Last Login CreatedActions
-
- {Array.isArray(user.roles) && user.roles.length > 0 ? ( - user.roles.map((role) => ( - - {role} - - )) - ) : ( - No roles - )} -
+ {editingUserId === user.userId ? ( +
+
+ {['EMPLOYEE', 'ADMIN'].map((role) => ( + + ))} +
+
+ ) : ( +
+ {Array.isArray(user.roles) && user.roles.length > 0 ? ( + user.roles.map((role) => ( + + {role} + + )) + ) : ( + No roles + )} +
+ )}
@@ -158,12 +312,128 @@ export default function AdminUsersPage() {
{formatDate(user.createdAt)} + {canEditUser(user) ? ( + editingUserId === user.userId ? ( +
+ + +
+ ) : ( + + ) + ) : ( + Customer (Read-only) + )} +
+ + {/* Create User Modal */} + {showCreateModal && ( +
+
+

+ Create {createUserType === 'employee' ? 'Employee' : 'Admin'} +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ {createUserType === 'employee' && ( +
+ + +
+ )} +
+ + +
+
+
+
+ )}
); } From a67c8edfe3f2f3943c3306ad5f7860a0bd50ce89 Mon Sep 17 00:00:00 2001 From: Mehara Rothila Ranawaka Date: Fri, 7 Nov 2025 00:42:06 +0530 Subject: [PATCH 02/38] Update user dashboard pages and layout --- .../appointments/[appointmentId]/page.tsx | 116 ++++++----- src/app/dashboard/appointments/book/page.tsx | 4 +- src/app/dashboard/appointments/page.tsx | 46 +++-- src/app/dashboard/invoices/page.tsx | 14 +- src/app/dashboard/layout.tsx | 8 +- src/app/dashboard/payments/page.tsx | 43 +++- src/app/dashboard/projects/page.tsx | 11 +- src/app/dashboard/projects/request/page.tsx | 26 ++- src/app/dashboard/schedule/page.tsx | 43 +++- src/app/dashboard/time-logs/page.tsx | 189 +++++++++++++----- 10 files changed, 353 insertions(+), 147 deletions(-) diff --git a/src/app/dashboard/appointments/[appointmentId]/page.tsx b/src/app/dashboard/appointments/[appointmentId]/page.tsx index 3483d0e..7d07d7d 100644 --- a/src/app/dashboard/appointments/[appointmentId]/page.tsx +++ b/src/app/dashboard/appointments/[appointmentId]/page.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import { appointmentService } from '@/services/appointmentService' import type { AppointmentResponseDto, AppointmentStatus } from '@/types/appointment' +import { useDashboard } from '@/app/contexts/DashboardContext' interface StatusOption { value: AppointmentStatus @@ -22,6 +23,7 @@ export default function AppointmentDetailPage() { const router = useRouter() const params = useParams<{ appointmentId: string }>() const appointmentId = params.appointmentId + const { roles } = useDashboard() const [appointment, setAppointment] = useState(null) const [loading, setLoading] = useState(true) @@ -203,53 +205,55 @@ export default function AppointmentDetailPage() {
-
-

Reschedule appointment

-
-
- + {roles?.includes('CUSTOMER') && ( +
+

Reschedule appointment

+ +
+ + +
+ +
+