diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 2357564..917694e 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react' import { useAuth, useProject } from '@/hooks' import { useNavigate } from 'react-router-dom' -import { groupService, Group } from '@/services' +import { groupService, requirementService, Group } from '@/services' +import type { Requirement } from '@/services/requirementService' /** * Helper function to convert hex color to RGB values @@ -61,11 +62,19 @@ function darkenColor(hex: string, percent: number): string { return `#${r}${g}${b}` } +// Interface for group statistics +interface GroupStats { + total: number + validated: number + notValidated: number +} + export default function DashboardPage() { const { user, logout } = useAuth() const { projects, currentProject, setCurrentProject, isLoading: projectsLoading, createProject } = useProject() const navigate = useNavigate() const [groups, setGroups] = useState([]) + const [requirements, setRequirements] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [hoveredGroup, setHoveredGroup] = useState(null) @@ -78,23 +87,56 @@ export default function DashboardPage() { const [createProjectLoading, setCreateProjectLoading] = useState(false) const [createProjectError, setCreateProjectError] = useState(null) + // Calculate stats for each group + const getGroupStats = (groupId: number): GroupStats => { + const groupRequirements = requirements.filter(req => + req.groups.some(g => g.id === groupId) + ) + const validated = groupRequirements.filter(req => + req.validation_status === 'Approved' + ).length + return { + total: groupRequirements.length, + validated, + notValidated: groupRequirements.length - validated + } + } + + // Get total stats + const getTotalStats = () => { + const total = requirements.length + const validated = requirements.filter(req => + req.validation_status === 'Approved' + ).length + return { total, validated, notValidated: total - validated } + } + useEffect(() => { - const fetchGroups = async () => { + const fetchData = async () => { try { setLoading(true) const fetchedGroups = await groupService.getGroups() setGroups(fetchedGroups) + + // Fetch requirements if project is selected + if (currentProject) { + const fetchedRequirements = await requirementService.getRequirements({ + project_id: currentProject.id + }) + setRequirements(fetchedRequirements) + } + setError(null) } catch (err) { - console.error('Failed to fetch groups:', err) - setError('Failed to load groups') + console.error('Failed to fetch data:', err) + setError('Failed to load data') } finally { setLoading(false) } } - fetchGroups() - }, []) + fetchData() + }, [currentProject]) const handleCategoryClick = (groupName: string) => { navigate(`/requirements?group=${encodeURIComponent(groupName)}`) @@ -104,6 +146,10 @@ export default function DashboardPage() { navigate('/requirements') } + const handleCreateRequirementClick = () => { + navigate('/requirements', { state: { openCreateModal: true } }) + } + const handleProjectSelect = (project: typeof currentProject) => { if (project) { setCurrentProject(project) @@ -144,207 +190,262 @@ export default function DashboardPage() { } return ( -
- {/* Header */} -
-

- Digital Twin Requirements Tool -

-
- - {/* Top Bar */} -
-
- {/* Breadcrumb with Project Dropdown */} -
- Projects - ยป - - {/* Project Dropdown */} -
- - - {/* Dropdown Menu */} - {showProjectDropdown && ( -
-
- {projects.length === 0 ? ( -
- No projects available -
- ) : ( - projects.map((project) => ( - - )) - )} -
- -
-
- )} -
-
- - {/* Language Toggle */} -
- English -
- -
- Portuguese -
- - {/* Admin Panel Button - Only visible for admins (role_id=3) */} - {user?.role_id === 3 && ( - - )} - - {/* User Info */} -
-
- - - - - {user?.full_name || user?.preferred_username || 'User'}{' '} - ({user?.role || 'user'}) - -
- -
+
+ {/* Sidebar */} +
- {/* Main Content */} -
- {/* No Project Selected Warning */} - {!projectsLoading && !currentProject && ( -
-
- - - -
-

No Project Selected

-

- Please select a project from the dropdown above or{' '} - - {' '}to get started. -

+ {/* Navigation */} + + + {/* Project Stats Summary at bottom */} + {currentProject && !loading && ( +
+

Project Summary

+
+
+

{getTotalStats().total}

+

Total

+
+
+

{getTotalStats().validated}

+

Validated

)} + -
- {/* Left Sidebar */} -
- {/* Requirements Search */} -
-

Requirements Search

-

- Search for specific elements by name or symbol. -

+ {/* Main Content */} +
+ {/* Top Bar */} +
+
+ {/* Breadcrumb with Project Dropdown */} +
+ Projects + + + + + {/* Project Dropdown */} +
+ + + {/* Dropdown Menu */} + {showProjectDropdown && ( +
+
+ {projects.length === 0 ? ( +
+ No projects available +
+ ) : ( + projects.map((project) => ( + + )) + )} +
+ +
+
+ )} +
- {/* Create a Requirement */} -
-

Create a Requirement

-

- Register a new Requirement. -

-
+ {/* Right side utilities - grouped tighter */} +
+ {/* Language Toggle */} +
+ EN +
+ +
+ PT +
- {/* Last Viewed Requirement */} -
-

Last Viewed Requirement:

-

No requirement accessed yet

- - View More - -
+ {/* Divider */} +
- {/* My Requirements */} -
-

My Requirements

-

- View your requirements and their properties. -

-
+ {/* Admin Panel Button - Only visible for admins (role_id=3) */} + {user?.role_id === 3 && ( + + )} - {/* Requirement Report */} -
-

Requirement Report

-

- Generate the current status of this projects requirements in PDF format -

+ {/* User Info */} +
+
+
+ + + +
+
+

+ {user?.full_name || user?.preferred_username || 'User'} +

+

{user?.role || 'user'}

+
+
+ +
+
- {/* Right Content - Quick Search Filters */} -
-

- Quick Search Filters -

+ {/* Page Content */} +
+ {/* No Project Selected Warning */} + {!projectsLoading && !currentProject && ( +
+
+ + + +
+

No Project Selected

+

+ Please select a project from the dropdown above or{' '} + + {' '}to get started. +

+
+
+
+ )} + + {/* Quick Search Filters Section */} +
+
+

+ Quick Search Filters +

+

+ Click a category to filter requirements +

+
{/* Loading State */} {loading && (
-
Loading groups...
+
+
+

Loading...

+
)} @@ -355,16 +456,18 @@ export default function DashboardPage() {
)} - {/* Groups Grid */} + {/* Groups Grid with Stats */} {!loading && !error && groups.length > 0 && ( -
+
{groups.map((group) => { const isHovered = hoveredGroup === group.id + const stats = getGroupStats(group.id) const bgColor = isHovered - ? lightenColor(group.hex_color, 0.2) + ? lightenColor(group.hex_color, 0.15) : group.hex_color const borderColor = darkenColor(group.hex_color, 0.2) const textColor = getContrastTextColor(group.hex_color) + const isLightText = textColor === '#ffffff' return (
handleCategoryClick(group.group_name)} onMouseEnter={() => setHoveredGroup(group.id)} onMouseLeave={() => setHoveredGroup(null)} - className="flex flex-col items-center justify-center p-6 cursor-pointer transition-colors min-h-[120px] rounded-lg" + className="flex flex-col p-4 cursor-pointer transition-all duration-200 rounded-xl shadow-sm hover:shadow-md" style={{ backgroundColor: bgColor, borderWidth: '2px', @@ -380,12 +483,44 @@ export default function DashboardPage() { borderColor: borderColor, }} > + {/* Group Name */} {group.group_name} + + {/* Stats Row */} + {currentProject && ( +
+
+ + + + {stats.validated} +
+
+ + + + {stats.notValidated} +
+ + {stats.total} total + +
+ )}
) })} @@ -399,7 +534,7 @@ export default function DashboardPage() {
)}
-
+
{/* Click outside to close dropdown */} diff --git a/frontend/src/pages/RequirementsPage.tsx b/frontend/src/pages/RequirementsPage.tsx index a6d309f..dcd26c1 100644 --- a/frontend/src/pages/RequirementsPage.tsx +++ b/frontend/src/pages/RequirementsPage.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react' import { useAuth, useProject } from '@/hooks' -import { useSearchParams, Link, useNavigate } from 'react-router-dom' +import { useSearchParams, Link, useNavigate, useLocation } from 'react-router-dom' import { groupService, tagService, requirementService, priorityService } from '@/services' import type { Group } from '@/services/groupService' import type { Tag } from '@/services/tagService' @@ -33,6 +33,7 @@ export default function RequirementsPage() { const { currentProject, isLoading: projectLoading } = useProject() const [searchParams, setSearchParams] = useSearchParams() const navigate = useNavigate() + const location = useLocation() // Data state const [groups, setGroups] = useState([]) @@ -126,6 +127,15 @@ export default function RequirementsPage() { } }, [searchParams, groups]) + // Open create modal if navigated from dashboard with state + useEffect(() => { + if (location.state?.openCreateModal && !isAuditor && !projectLoading && currentProject) { + setShowCreateModal(true) + // Clear the state so it doesn't reopen on refresh + navigate(location.pathname, { replace: true, state: {} }) + } + }, [location.state, isAuditor, projectLoading, currentProject, navigate, location.pathname]) + // Fetch deleted requirements when panel is opened const fetchDeletedRequirements = async () => { if (!currentProject) return