From 1626c610b2583858762dc38121392460d555fa1f Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Mon, 19 Jan 2026 11:26:19 +0300 Subject: [PATCH 1/2] fix: add helper function to display activity results --- .../client/src/pages/dashboard.tsx | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/platforms/eReputation/client/src/pages/dashboard.tsx b/platforms/eReputation/client/src/pages/dashboard.tsx index 9abded9f..4666480b 100644 --- a/platforms/eReputation/client/src/pages/dashboard.tsx +++ b/platforms/eReputation/client/src/pages/dashboard.tsx @@ -172,6 +172,20 @@ export default function Dashboard() { } }; + // Helper function to get the display result from an activity + const getActivityResult = (activity: any): string => { + if (activity.result && activity.result !== 'Calculating...') { + return activity.result; + } + + // If status is complete and we have calculatedScore in data (including 0) + if (activity.status === 'complete' && activity.data?.calculatedScore !== undefined) { + return `Score: ${activity.data.calculatedScore}/5`; + } + + return 'Calculating...'; + }; + // Helper function to get score color based on percentage const getScoreColor = (result: string | undefined | null) => { if (!result) return 'text-gray-500 font-black'; // Default gray for undefined/null @@ -629,9 +643,8 @@ export default function Dashboard() { activity.result && activity.result.includes('fair') ? '#eab308' : '#22c55e', backgroundColor: activity.result && activity.result.includes('low') ? '#fef2f2' : activity.result && activity.result.includes('moderate') ? '#fff7ed' : - activity.result && activity.result.includes('fair') ? '#fefce8' : '#f0fdf4' - }}> - {activity.result ? activity.result.replace('Score: ', '') : 'Calculating...'} + activity.result && activity.result.includes('fair') ? '#fefce8' : '#f0fdf4' }}> + {getActivityResult(activity).replace('Score: ', '')} )} @@ -713,14 +726,14 @@ export default function Dashboard() {

Reputation Score

- {(selectedActivity as any).result || 'Calculating...'} + {getActivityResult(selectedActivity as any)}
{/* Animated Circle Progress with Dynamic Gradient */}
{(() => { - const resultStr = (selectedActivity as any).result || ''; + const resultStr = getActivityResult(selectedActivity as any); const score = parseFloat(resultStr.replace('Score: ', '')) || 0; const percentage = (score / 5) * 100; // Scores are out of 5, not 10 const circumference = 314; From 9834899ff483ded791af15346d886cd8618105c1 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Mon, 19 Jan 2026 11:43:10 +0300 Subject: [PATCH 2/2] refactor: simplify activityIconClass and scoreTheme --- .../client/src/pages/dashboard.tsx | 1703 +++++++++-------- 1 file changed, 852 insertions(+), 851 deletions(-) diff --git a/platforms/eReputation/client/src/pages/dashboard.tsx b/platforms/eReputation/client/src/pages/dashboard.tsx index 4666480b..cb7bfa49 100644 --- a/platforms/eReputation/client/src/pages/dashboard.tsx +++ b/platforms/eReputation/client/src/pages/dashboard.tsx @@ -2,175 +2,170 @@ import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@/hooks/useAuth"; import { clearAuth } from "@/lib/authUtils"; -import { useToast } from "@/hooks/use-toast"; -import { isUnauthorizedError } from "@/lib/authUtils"; import { apiClient } from "@/lib/apiClient"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, + Dialog, + DialogContent, + DialogHeader, + DialogTitle, } from "@/components/ui/dialog"; import OtherCalculationModal from "@/components/modals/other-calculation-modal"; import ReferenceModal from "@/components/modals/reference-modal"; import ReferenceViewModal from "@/components/modals/reference-view-modal"; export default function Dashboard() { - const { toast } = useToast(); - const { user, isAuthenticated, isLoading } = useAuth(); - const [otherModalOpen, setOtherModalOpen] = useState(false); - const [referenceModalOpen, setReferenceModalOpen] = useState(false); - const [viewModalOpen, setViewModalOpen] = useState(false); - const [selectedActivity, setSelectedActivity] = useState(null); - const [referenceViewModal, setReferenceViewModal] = useState(null); - const [activeFilter, setActiveFilter] = useState('all'); - const [currentPage, setCurrentPage] = useState(1); - - // This page is only rendered when authenticated, no need for redirect logic - - const { data: stats } = useQuery<{ totalReferences: string }>({ - queryKey: ["/api/dashboard/stats"], - queryFn: async () => { - const response = await apiClient.get("/api/dashboard/stats"); - return response.data; - }, - enabled: isAuthenticated, - }); - - const { data: activitiesResponse, refetch: refetchActivities } = useQuery<{ - activities: any[]; - pagination: { - page: number; - limit: number; - total: number; - totalPages: number; - hasNext: boolean; - hasPrev: boolean; + const { user, isAuthenticated, isLoading } = useAuth(); + const [otherModalOpen, setOtherModalOpen] = useState(false); + const [referenceModalOpen, setReferenceModalOpen] = useState(false); + const [viewModalOpen, setViewModalOpen] = useState(false); + const [selectedActivity, setSelectedActivity] = useState(null); + const [referenceViewModal, setReferenceViewModal] = useState(null); + const [activeFilter, setActiveFilter] = useState('all'); + const [currentPage, setCurrentPage] = useState(1); + + // This page is only rendered when authenticated, no need for redirect logic + + const { data: stats } = useQuery<{ totalReferences: string }>({ + queryKey: ["/api/dashboard/stats"], + queryFn: async () => { + const response = await apiClient.get("/api/dashboard/stats"); + return response.data; + }, + enabled: isAuthenticated, + }); + + const { data: activitiesResponse, refetch: refetchActivities } = useQuery<{ + activities: any[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + hasNext: boolean; + hasPrev: boolean; + }; + }>({ + queryKey: ["/api/dashboard/activities", currentPage, activeFilter], + queryFn: async () => { + const response = await apiClient.get(`/api/dashboard/activities?page=${currentPage}&filter=${activeFilter}`); + return response.data; + }, + enabled: isAuthenticated, + refetchOnWindowFocus: true, + refetchInterval: 5000, // Poll every 5 seconds for updates + staleTime: 0, // Always consider data stale + }); + + const activities = activitiesResponse?.activities || []; + const pagination = activitiesResponse?.pagination; + + const handleLogout = () => { + clearAuth(); + window.location.href = "/auth"; }; - }>({ - queryKey: ["/api/dashboard/activities", currentPage, activeFilter], - queryFn: async () => { - const response = await apiClient.get(`/api/dashboard/activities?page=${currentPage}&filter=${activeFilter}`); - return response.data; - }, - enabled: isAuthenticated, - refetchOnWindowFocus: true, - refetchInterval: 5000, // Poll every 5 seconds for updates - staleTime: 0, // Always consider data stale - }); - - const activities = activitiesResponse?.activities || []; - const pagination = activitiesResponse?.pagination; - - const handleLogout = () => { - clearAuth(); - window.location.href = "/auth"; - }; - - const handleViewActivity = (activity: any) => { - // For reference activities, show reference details modal - if (activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received') { - const referenceData = activity.data; // This contains the full reference object - setReferenceViewModal({ - id: activity.id, - type: activity.activity === 'Reference Received' ? 'Received' : 'Sent', - forFrom: activity.activity === 'Reference Received' - ? (activity.data?.author?.name || activity.data?.author?.ename || activity.data?.author?.handle || 'Unknown') - : activity.target, - date: new Date(activity.date).toLocaleDateString(), - status: activity.status || 'Signed', - referenceType: referenceData?.referenceType || 'general', - content: referenceData?.content || 'Reference content not available', - targetType: referenceData?.targetType || 'user' - }); - return; - } - // For calculation activities, show the original details modal - // Double-check this is not a reference (shouldn't happen, but safety check) - if (activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received') { - // This shouldn't happen, but if it does, show reference modal instead - const referenceData = activity.data; - setReferenceViewModal({ - id: activity.id, - type: activity.activity === 'Reference Received' ? 'Received' : 'Sent', - forFrom: activity.activity === 'Reference Received' - ? (activity.data?.author?.name || activity.data?.author?.ename || activity.data?.author?.handle || 'Unknown') - : activity.target, - date: new Date(activity.date).toLocaleDateString(), - status: activity.status || 'Signed', - referenceType: referenceData?.referenceType || 'general', - content: referenceData?.content || 'Reference content not available', - targetType: referenceData?.targetType || 'user' - }); - return; - } + const handleViewActivity = (activity: any) => { + // For reference activities, show reference details modal + if (activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received') { + const referenceData = activity.data; // This contains the full reference object + setReferenceViewModal({ + id: activity.id, + type: activity.activity === 'Reference Received' ? 'Received' : 'Sent', + forFrom: activity.activity === 'Reference Received' + ? (activity.data?.author?.name || activity.data?.author?.ename || activity.data?.author?.handle || 'Unknown') + : activity.target, + date: new Date(activity.date).toLocaleDateString(), + status: activity.status || 'Signed', + referenceType: referenceData?.referenceType || 'general', + content: referenceData?.content || 'Reference content not available', + targetType: referenceData?.targetType || 'user' + }); + return; + } - setSelectedActivity(activity); - setViewModalOpen(true); - }; + // For calculation activities, show the original details modal + // Double-check this is not a reference (shouldn't happen, but safety check) + if (activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received') { + // This shouldn't happen, but if it does, show reference modal instead + const referenceData = activity.data; + setReferenceViewModal({ + id: activity.id, + type: activity.activity === 'Reference Received' ? 'Received' : 'Sent', + forFrom: activity.activity === 'Reference Received' + ? (activity.data?.author?.name || activity.data?.author?.ename || activity.data?.author?.handle || 'Unknown') + : activity.target, + date: new Date(activity.date).toLocaleDateString(), + status: activity.status || 'Signed', + referenceType: referenceData?.referenceType || 'general', + content: referenceData?.content || 'Reference content not available', + targetType: referenceData?.targetType || 'user' + }); + return; + } + setSelectedActivity(activity); + setViewModalOpen(true); + }; - // Number counting animation hook - const useCountUp = (end: number, duration: number = 2000) => { - const [count, setCount] = useState(0); - useEffect(() => { - if (end === 0) return; + // Number counting animation hook + const useCountUp = (end: number, duration: number = 2000) => { + const [count, setCount] = useState(0); - let startTime: number; - let animationFrame: number; + useEffect(() => { + if (end === 0) return; - const animate = (currentTime: number) => { - if (!startTime) startTime = currentTime; - const progress = Math.min((currentTime - startTime) / duration, 1); + let startTime: number; + let animationFrame: number; - setCount(Math.floor(progress * end)); + const animate = (currentTime: number) => { + if (!startTime) startTime = currentTime; + const progress = Math.min((currentTime - startTime) / duration, 1); - if (progress < 1) { - animationFrame = requestAnimationFrame(animate); - } - }; + setCount(Math.floor(progress * end)); + + if (progress < 1) { + animationFrame = requestAnimationFrame(animate); + } + }; - animationFrame = requestAnimationFrame(animate); + animationFrame = requestAnimationFrame(animate); - return () => { - if (animationFrame) { - cancelAnimationFrame(animationFrame); + return () => { + if (animationFrame) { + cancelAnimationFrame(animationFrame); + } + }; + }, [end, duration]); + + return count; + }; + + const totalReferences = parseInt(stats?.totalReferences || "0"); + const animatedReferences = useCountUp(totalReferences, 600); + + const getStatusBadge = (status: string) => { + switch (status) { + case "complete": + return Complete; + case "processing": + return Processing; + case "submitted": + return Submitted; + case "error": + return Error; + default: + return {status}; } - }; - }, [end, duration]); - - return count; - }; - - const totalReferences = parseInt(stats?.totalReferences || "0"); - const animatedReferences = useCountUp(totalReferences, 600); - - const getStatusBadge = (status: string) => { - switch (status) { - case "complete": - return Complete; - case "processing": - return Processing; - case "submitted": - return Submitted; - case "error": - return Error; - default: - return {status}; - } - }; + }; // Helper function to get the display result from an activity const getActivityResult = (activity: any): string => { @@ -186,734 +181,740 @@ export default function Dashboard() { return 'Calculating...'; }; - // Helper function to get score color based on percentage - const getScoreColor = (result: string | undefined | null) => { - if (!result) return 'text-gray-500 font-black'; // Default gray for undefined/null - - const score = parseFloat(result.replace('Score: ', '')) || 0; - const percentage = (score / 5) * 100; // Scores are out of 5, not 10 - - if (percentage <= 25) return 'text-red-500 font-black'; // Red (0-25%) - if (percentage <= 50) return 'text-orange-500 font-black'; // Orange (25-50%) - if (percentage <= 75) return 'text-yellow-600 font-black'; // Yellow (50-75%) - return 'text-green-500 font-black'; // Green (75-100%) - }; - - const getActivityIcon = (activity: string) => { - switch (activity) { - case "Self Calculation": - case "Self eReputation": - case "Self Evaluation": - return ; - case "Other Evaluation": - case "User Evaluation": - return ; - case "Group Evaluation": - return ; - case "Platform Analysis": - case "Post-Platform Evaluation": - return ; - case "Reference Provided": - return ; - case "Reference Received": - return ; - default: - return ; - } - }; + // Helper function to get score color based on percentage + const getScoreTheme = (result: string | undefined | null) => { + // 1. Handle empty states early + if (!result) { + return { + text: 'text-gray-500', + border: '#6b7280', + bg: '#f9fafb', + label: 'Calculating...' + }; + } - if (isLoading) { - return ( -
-
-
-

Loading...

-
-
- ); - } - - return ( -
- {/* Navigation Header */} - - - {/* Main Content Area */} -
- - {/* Dashboard Header */} -
-
-

Dashboard

-

Manage and monitor your W3DS eReputation

-
- - {/* Quick Stats */} -
-
-
Total eReferences
-
- {animatedReferences} -
-
-
- - -
- received -
-
-
-
+ - {/* Action Buttons */} -
- - - -
+ {/* Main Content Area */} +
- {/* Activity History Table */} -
-
-
-
-

Recent Activity

-

Your latest eReputation activities and calculations

-
- - {/* Quick Filters */} -
- - - - - - -
-
-
- - {activities.length === 0 ? ( -
-
-
- - - -
-
-

- {activeFilter !== 'all' ? 'No activities match this filter' : 'No activities yet'} -

-

- {activeFilter !== 'all' - ? 'Try selecting a different filter or clear filters to see all activities' - : 'Start by calculating your reputation or providing a reference'} -

+ {/* Dashboard Header */} +
+
+

Dashboard

+

Manage and monitor your W3DS eReputation

+
+ + {/* Quick Stats */} +
+
+
Total eReferences
+
+ {animatedReferences} +
+
+
+ + + +
+ received +
+
+
- {activeFilter !== 'all' ? ( - - ) : ( -
+ + {/* Action Buttons */} +
-
- )} -
-
- ) : ( - <> - {/* Desktop Table View */} -
- - - - - - - - - - - - {activities.map((activity: any) => ( - - - - - - - - ))} - -
Evaluation TypeContextDateStatus
-
-
- {getActivityIcon(activity.activity)} + + +
+ + {/* Activity History Table */} +
+
+
-
{activity.activity}
+

Recent Activity

+

Your latest eReputation activities and calculations

+
+ + {/* Quick Filters */} +
+ + + + + +
-
-
- {activity.activity === 'Reference Received' - ? `From ${activity.target || 'Unknown'}` - : activity.activity === 'Reference Provided' - ? `To ${activity.target || 'Unknown'}` - : activity.target || 'Unknown'} - - {new Date(activity.date).toLocaleDateString()} - - {activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received' ? ( - - {activity.status === 'Revoked' ? 'revoked' : activity.status === 'Signed' ? 'signed' : 'signed'} - - ) : ( - - {activity.result || 'Calculating...'} - - )} - - - - - - - handleViewActivity(activity)}> - - - - - View Details - - - -
-
- - {/* Mobile Card View */} -
- {activities.map((activity: any) => ( -
-
-
-
- {getActivityIcon(activity.activity)}
-
-
{activity.activity}
-
- {activity.activity === 'Reference Received' - ? `From ${activity.target || 'Unknown'}` - : activity.activity === 'Reference Provided' - ? `To ${activity.target || 'Unknown'}` - : activity.target || 'Unknown'} -
+
+ + {activities.length === 0 ? ( +
+
+
+ + + +
+
+

+ {activeFilter !== 'all' ? 'No activities match this filter' : 'No activities yet'} +

+

+ {activeFilter !== 'all' + ? 'Try selecting a different filter or clear filters to see all activities' + : 'Start by calculating your reputation or providing a reference'} +

+
+ {activeFilter !== 'all' ? ( + + ) : ( +
+ +
+ )} +
-
-
- - - - - - handleViewActivity(activity)}> - - - - - View Details - - - -
-
{new Date(activity.date).toLocaleDateString()}
- {activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received' ? ( - - {activity.status === 'Revoked' ? 'revoked' : activity.status === 'Signed' ? 'signed' : 'signed'} - - ) : ( - + ) : ( + <> + {/* Desktop Table View */} +
+ + + + + + + + + + + + {activities.map((activity: any) => ( + + + + + + + + ))} + +
Evaluation TypeContextDateStatus
+
+
+ {getActivityIcon(activity.activity)} +
+
+
{activity.activity}
+
+
+
+ {activity.activity === 'Reference Received' + ? `From ${activity.target || 'Unknown'}` + : activity.activity === 'Reference Provided' + ? `To ${activity.target || 'Unknown'}` + : activity.target || 'Unknown'} + + {new Date(activity.date).toLocaleDateString()} + + {activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received' ? ( + + {activity.status === 'Revoked' ? 'revoked' : activity.status === 'Signed' ? 'signed' : 'signed'} + + ) : ( + + {getActivityResult(activity)} + + )} + + + + + + + handleViewActivity(activity)}> + + + + + View Details + + + +
+
+ + {/* Mobile Card View */} +
+ {activities.map((activity: any) => ( +
+
+
+
+ {getActivityIcon(activity.activity)} +
+
+
{activity.activity}
+
+ {activity.activity === 'Reference Received' + ? `From ${activity.target || 'Unknown'}` + : activity.activity === 'Reference Provided' + ? `To ${activity.target || 'Unknown'}` + : activity.target || 'Unknown'} +
+
+
+
+ + + + + + handleViewActivity(activity)}> + + + + + View Details + + + +
+
{new Date(activity.date).toLocaleDateString()}
+ {activity.type === 'reference' || activity.activity === 'Reference Provided' || activity.activity === 'Reference Received' ? ( + + {activity.status === 'Revoked' ? 'revoked' : activity.status === 'Signed' ? 'signed' : 'signed'} + + ) : ( + {getActivityResult(activity).replace('Score: ', '')} - - )} -
-
-
-
- ))} -
- - {pagination && pagination.totalPages > 1 && ( -
-
- Showing {((pagination.page - 1) * pagination.limit) + 1} to {Math.min(pagination.page * pagination.limit, pagination.total)} of {pagination.total} results -
-
- - - Page {pagination.page} of {pagination.totalPages} - - -
+ + )} +
+
+
+
+ ))} +
+ + {pagination && pagination.totalPages > 1 && ( +
+
+ Showing {((pagination.page - 1) * pagination.limit) + 1} to {Math.min(pagination.page * pagination.limit, pagination.total)} of {pagination.total} results +
+
+ + + Page {pagination.page} of {pagination.totalPages} + + +
+
+ )} + + )}
- )} - - )} -
-
- - {/* Modals */} - - - - {/* Reference View Modal */} - !open && setReferenceViewModal(null)} - reference={referenceViewModal} - /> - - {/* Activity Details Modal - Only for calculations, not references */} - - - - -
- - - -
- {selectedActivity - ? ((selectedActivity as any).target === 'Personal Profile' - ? "Your eReputation" - : `${(selectedActivity as any).target} eReputation`) - : "Activity Details"} -
-
- - {selectedActivity && (selectedActivity as any).type !== 'reference' && (selectedActivity as any).activity !== 'Reference Provided' && (selectedActivity as any).activity !== 'Reference Received' ? ( -
- - {/* Score Visualization */} -
-
-

Reputation Score

- +
+ + {/* Modals */} + + + + {/* Reference View Modal */} + !open && setReferenceViewModal(null)} + reference={referenceViewModal} + /> + + {/* Activity Details Modal - Only for calculations, not references */} + + + + +
+ + + +
+ {selectedActivity + ? ((selectedActivity as any).target === 'Personal Profile' + ? "Your eReputation" + : `${(selectedActivity as any).target} eReputation`) + : "Activity Details"} +
+
+ + {selectedActivity && (selectedActivity as any).type !== 'reference' && (selectedActivity as any).activity !== 'Reference Provided' && (selectedActivity as any).activity !== 'Reference Received' ? ( +
+ + {/* Score Visualization */} +
+
+

Reputation Score

+ {getActivityResult(selectedActivity as any)} - -
+ +
- {/* Animated Circle Progress with Dynamic Gradient */} -
- {(() => { + {/* Animated Circle Progress with Dynamic Gradient */} +
+ {(() => { const resultStr = getActivityResult(selectedActivity as any); - const score = parseFloat(resultStr.replace('Score: ', '')) || 0; - const percentage = (score / 5) * 100; // Scores are out of 5, not 10 - const circumference = 314; - const strokeDasharray = `${(percentage / 100) * circumference} ${circumference}`; - - // Create dynamic gradient ID based on score - const gradientId = `scoreGradient${Math.round(percentage)}`; - - // Determine gradient stops based on score percentage - ALWAYS starts with red - let gradientStops = []; - if (percentage <= 25) { - // 0-25%: Red only - gradientStops = [ - { offset: "0%", color: "#ef4444" }, - { offset: "100%", color: "#ef4444" } - ]; - } else if (percentage <= 50) { - // 25-50%: Red to orange - gradientStops = [ - { offset: "0%", color: "#ef4444" }, - { offset: "100%", color: "#f97316" } - ]; - } else if (percentage <= 75) { - // 50-75%: Red to orange to yellow - gradientStops = [ - { offset: "0%", color: "#ef4444" }, - { offset: "50%", color: "#f97316" }, - { offset: "100%", color: "#eab308" } - ]; - } else { - // 75-100%: Red to orange to yellow to green - gradientStops = [ - { offset: "0%", color: "#ef4444" }, - { offset: "33%", color: "#f97316" }, - { offset: "66%", color: "#eab308" }, - { offset: "100%", color: "#22c55e" } - ]; - } - - // Calculate the path coordinates for the arc - const radius = 50; - const centerX = 60; - const centerY = 60; - const startAngle = -90; // Start at top (12 o'clock) - const endAngle = startAngle + (percentage * 3.6); // 3.6 degrees per percent - - // Convert to radians - const startRad = (startAngle * Math.PI) / 180; - const endRad = (endAngle * Math.PI) / 180; - - // Calculate arc path - const startX = centerX + radius * Math.cos(startRad); - const startY = centerY + radius * Math.sin(startRad); - const endX = centerX + radius * Math.cos(endRad); - const endY = centerY + radius * Math.sin(endRad); - - const largeArcFlag = percentage > 50 ? 1 : 0; - - return ( - - {/* Background circle */} - - - - {/* Path-following gradient using coordinates */} - - {percentage <= 25 ? ( - // Red only - <> - - - - ) : percentage <= 50 ? ( - // Red to orange - <> - - - - ) : percentage <= 75 ? ( - // Red to orange to yellow - <> - - - - - ) : ( - // Full gradient - red to orange to yellow to green - <> - - - - - + const score = parseFloat(resultStr.replace('Score: ', '')) || 0; + const percentage = (score / 5) * 100; // Scores are out of 5, not 10 + const circumference = 314; + const strokeDasharray = `${(percentage / 100) * circumference} ${circumference}`; + + // Create dynamic gradient ID based on score + const gradientId = `scoreGradient${Math.round(percentage)}`; + + // Determine gradient stops based on score percentage - ALWAYS starts with red + let gradientStops = []; + if (percentage <= 25) { + // 0-25%: Red only + gradientStops = [ + { offset: "0%", color: "#ef4444" }, + { offset: "100%", color: "#ef4444" } + ]; + } else if (percentage <= 50) { + // 25-50%: Red to orange + gradientStops = [ + { offset: "0%", color: "#ef4444" }, + { offset: "100%", color: "#f97316" } + ]; + } else if (percentage <= 75) { + // 50-75%: Red to orange to yellow + gradientStops = [ + { offset: "0%", color: "#ef4444" }, + { offset: "50%", color: "#f97316" }, + { offset: "100%", color: "#eab308" } + ]; + } else { + // 75-100%: Red to orange to yellow to green + gradientStops = [ + { offset: "0%", color: "#ef4444" }, + { offset: "33%", color: "#f97316" }, + { offset: "66%", color: "#eab308" }, + { offset: "100%", color: "#22c55e" } + ]; + } + + // Calculate the path coordinates for the arc + const radius = 50; + const centerX = 60; + const centerY = 60; + const startAngle = -90; // Start at top (12 o'clock) + const endAngle = startAngle + (percentage * 3.6); // 3.6 degrees per percent + + // Convert to radians + const startRad = (startAngle * Math.PI) / 180; + const endRad = (endAngle * Math.PI) / 180; + + // Calculate arc path + const startX = centerX + radius * Math.cos(startRad); + const startY = centerY + radius * Math.sin(startRad); + const endX = centerX + radius * Math.cos(endRad); + const endY = centerY + radius * Math.sin(endRad); + + const largeArcFlag = percentage > 50 ? 1 : 0; + + return ( + + {/* Background circle */} + + + + {/* Path-following gradient using coordinates */} + + {percentage <= 25 ? ( + // Red only + <> + + + + ) : percentage <= 50 ? ( + // Red to orange + <> + + + + ) : percentage <= 75 ? ( + // Red to orange to yellow + <> + + + + + ) : ( + // Full gradient - red to orange to yellow to green + <> + + + + + + )} + + + + {/* Progress arc with gradient following the path */} + + + ); + })()} + +
+ + {Math.round(((parseFloat(((selectedActivity as any).result || '').replace('Score: ', '')) || 0) / 5) * 100)}% + + SCORE +
+
+
+ + {/* AI Explanation/Justification */} + {(selectedActivity as any).explanation && ( +
+
+
+ + + +
+ +
+

+ {(selectedActivity as any).explanation} +

+
)} - - - - {/* Progress arc with gradient following the path */} - - - ); - })()} - -
- - {Math.round(((parseFloat(((selectedActivity as any).result || '').replace('Score: ', '')) || 0) / 5) * 100)}% - - SCORE -
-
-
- - {/* AI Explanation/Justification */} - {(selectedActivity as any).explanation && ( -
-
-
- - - -
- -
-

- {(selectedActivity as any).explanation} -

-
- )} - -
- -
-
- ) : selectedActivity ? ( -
-

This activity is a reference, not a calculation.

- -
- ) : null} -
-
-
- ); + +
+ +
+
+ ) : selectedActivity ? ( +
+

This activity is a reference, not a calculation.

+ +
+ ) : null} + + +
+ ); }