-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: optimize team stats grid component
- Add StatItem interface for better type safety - Extract formatTime helper function - Use useMemo for stats array to prevent unnecessary recalculations - Reduce code repetition with shared timeValue - Remove unnecessary fragment - Improve code organization and readability
- Loading branch information
1 parent
ac8d53a
commit 67f9115
Showing
1 changed file
with
106 additions
and
88 deletions.
There are no files selected for viewing
194 changes: 106 additions & 88 deletions
194
apps/web/app/[locale]/dashboard/team-dashboard/[teamId]/components/team-stats-grid.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,99 +1,117 @@ | ||
"use client"; | ||
'use client'; | ||
|
||
import { secondsToTime } from "@/app/helpers"; | ||
import { ITimesheetStatisticsData } from "@/app/interfaces"; | ||
import { Card } from "@/components/ui/card"; | ||
import { Loader2 } from "lucide-react"; | ||
import { secondsToTime } from '@/app/helpers'; | ||
import { ITimesheetStatisticsData } from '@/app/interfaces'; | ||
import { Card } from '@/components/ui/card'; | ||
import { Loader2 } from 'lucide-react'; | ||
import { useMemo } from 'react'; | ||
|
||
function formatPercentage(value: number | undefined): number { | ||
if (!value) return 0; | ||
return Math.round(value); | ||
if (!value) return 0; | ||
return Math.round(value); | ||
} | ||
|
||
function formatTime(hours: number, minutes: number, seconds: number): string { | ||
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; | ||
} | ||
|
||
interface StatItem { | ||
title: string; | ||
value: string; | ||
type: 'number' | 'time'; | ||
color?: string; | ||
progress?: number; | ||
progressColor?: string; | ||
showProgress: boolean; | ||
} | ||
|
||
export function TeamStatsGrid({ | ||
statisticsCounts, | ||
loadingTimesheetStatisticsCounts | ||
statisticsCounts, | ||
loadingTimesheetStatisticsCounts | ||
}: { | ||
statisticsCounts: ITimesheetStatisticsData | null; | ||
loadingTimesheetStatisticsCounts: boolean; | ||
statisticsCounts: ITimesheetStatisticsData | null; | ||
loadingTimesheetStatisticsCounts: boolean; | ||
}) { | ||
const {h:hours, m:minutes, s:seconds} = secondsToTime(statisticsCounts?.weekDuration || 0); | ||
const { h: hours, m: minutes, s: seconds } = secondsToTime(statisticsCounts?.weekDuration || 0); | ||
const timeValue = formatTime(hours, minutes, seconds); | ||
const progress = formatPercentage(statisticsCounts?.weekActivities); | ||
|
||
const stats = [ | ||
{ | ||
title: "Members worked", | ||
value: statisticsCounts?.employeesCount?.toString() || "0", | ||
type: "number", | ||
showProgress: false | ||
}, | ||
{ | ||
title: "Tracked", | ||
value: `${hours}:${minutes}:${seconds}`, | ||
type: "time", | ||
color: "text-blue-500", | ||
progress: formatPercentage(statisticsCounts?.weekActivities), | ||
progressColor: "bg-blue-500", | ||
showProgress: true | ||
}, | ||
{ | ||
title: "Manual", | ||
value: `${hours}:${minutes}:${seconds}`, | ||
type: "time", | ||
color: "text-red-500", | ||
progress: formatPercentage(statisticsCounts?.weekActivities), | ||
progressColor: "bg-red-500", | ||
showProgress: true | ||
}, | ||
{ | ||
title: "Idle", | ||
value: `${hours}:${minutes}:${seconds}`, | ||
type: "time", | ||
color: "text-yellow-500", | ||
progress: formatPercentage(statisticsCounts?.weekActivities), | ||
progressColor: "bg-yellow-500", | ||
showProgress: true | ||
}, | ||
{ | ||
title: "Total Hours", | ||
value: `${hours}:${minutes}:${seconds}`, | ||
type: "time", | ||
color: "text-green-500", | ||
showProgress: false | ||
} | ||
]; | ||
const stats: StatItem[] = useMemo( | ||
() => [ | ||
{ | ||
title: 'Members worked', | ||
value: statisticsCounts?.employeesCount?.toString() || '0', | ||
type: 'number', | ||
showProgress: false | ||
}, | ||
{ | ||
title: 'Tracked', | ||
value: timeValue, | ||
type: 'time', | ||
color: 'text-blue-500', | ||
progress, | ||
progressColor: 'bg-blue-500', | ||
showProgress: true | ||
}, | ||
{ | ||
title: 'Manual', | ||
value: timeValue, | ||
type: 'time', | ||
color: 'text-red-500', | ||
progress, | ||
progressColor: 'bg-red-500', | ||
showProgress: true | ||
}, | ||
{ | ||
title: 'Idle', | ||
value: timeValue, | ||
type: 'time', | ||
color: 'text-yellow-500', | ||
progress, | ||
progressColor: 'bg-yellow-500', | ||
showProgress: true | ||
}, | ||
{ | ||
title: 'Total Hours', | ||
value: timeValue, | ||
type: 'time', | ||
color: 'text-green-500', | ||
showProgress: false | ||
} | ||
], | ||
[timeValue, progress, statisticsCounts?.employeesCount] | ||
); | ||
|
||
return ( | ||
<> | ||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5"> | ||
{stats.map((stat) => ( | ||
<Card key={stat.title} className="p-6"> | ||
<div className="flex flex-col"> | ||
<span className="text-sm font-medium text-gray-500"> | ||
{stat.title} | ||
</span> | ||
<div className="mt-2 h-9"> | ||
{loadingTimesheetStatisticsCounts ? ( | ||
<Loader2 className="w-6 h-6 text-gray-500 animate-spin" /> | ||
) : ( | ||
<span className={`text-2xl font-semibold ${stat.color || "text-gray-900 dark:text-white"}`}> | ||
{stat.value} | ||
</span> | ||
)} | ||
</div> | ||
{stat.showProgress && ( | ||
<div className="mt-4"> | ||
<div className="w-full h-2 bg-gray-100 rounded-full"> | ||
<div | ||
className={`h-full rounded-full ${stat.progressColor} transition-all duration-300`} | ||
style={{ width: `${loadingTimesheetStatisticsCounts ? 0 : stat.progress}%` }} | ||
/> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
</Card> | ||
))} | ||
</div> | ||
</> | ||
); | ||
return ( | ||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5"> | ||
{stats.map((stat) => ( | ||
<Card key={stat.title} className="p-6"> | ||
<div className="flex flex-col"> | ||
<span className="text-sm font-medium text-gray-500">{stat.title}</span> | ||
<div className="mt-2 h-9"> | ||
{loadingTimesheetStatisticsCounts ? ( | ||
<Loader2 className="w-6 h-6 text-gray-500 animate-spin" /> | ||
) : ( | ||
<span className={`text-2xl font-semibold ${stat.color || 'text-gray-900 dark:text-white'}`}> | ||
{stat.value} | ||
</span> | ||
)} | ||
</div> | ||
{stat.showProgress && ( | ||
<div className="mt-4"> | ||
<div className="w-full h-2 bg-gray-100 rounded-full"> | ||
<div | ||
className={`h-full rounded-full ${stat.progressColor} transition-all duration-300`} | ||
style={{ | ||
width: `${loadingTimesheetStatisticsCounts ? 0 : stat.progress}%` | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
</Card> | ||
))} | ||
</div> | ||
); | ||
} |