-
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.
[Feat]: Timesheet Statistics api types (#3557)
* refactor: improve timesheet statistics types and interfaces - Move ITimesheetStatisticsCounts interface to ITimerLog.ts - Use existing TimeLogType instead of custom LogType - Update type references across timesheet API endpoints - Improve type validation for log types Technical Details: - Replace custom LogType with existing TimeLogType from ITimer - Relocate statistics interface to proper timer interfaces file - Update return types in statistics request function - Refactor type guard to use TimeLogType Related files: - app/interfaces/timer/ITimerLog.ts - app/services/server/requests/timesheet.ts - app/api/timesheet/statistics/counts/route.ts * feat: add timesheet statistics API with formatting utilities * feat: improve team stats grid UI - Add loading spinner instead of dots - Enhance stats display with default values - Add smooth transitions for progress bars - Fix layout and styling issues - Improve error handling for null values * feat: refine team stats grid display - Remove progress bars from Total Hours and Members worked - Use secondsToTime helper for duration formatting - Add showProgress property to control progress bar visibility - Clean up and organize stats properties * 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 * fix: style team chart
- Loading branch information
1 parent
39b5eda
commit 99b69a8
Showing
11 changed files
with
574 additions
and
323 deletions.
There are no files selected for viewing
171 changes: 109 additions & 62 deletions
171
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,70 +1,117 @@ | ||
"use client"; | ||
'use client'; | ||
|
||
import { Card } from "@/components/ui/card"; | ||
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'; | ||
|
||
const stats = [ | ||
{ | ||
title: "Members worked", | ||
value: "17", | ||
type: "number" | ||
}, | ||
{ | ||
title: "Tracked", | ||
value: "47:23", | ||
type: "time", | ||
color: "text-blue-500", | ||
progress: 70, | ||
progressColor: "bg-blue-500" | ||
}, | ||
{ | ||
title: "Manual", | ||
value: "18:33", | ||
type: "time", | ||
color: "text-red-500", | ||
progress: 30, | ||
progressColor: "bg-red-500" | ||
}, | ||
{ | ||
title: "Idle", | ||
value: "05:10", | ||
type: "time", | ||
color: "text-yellow-500", | ||
progress: 10, | ||
progressColor: "bg-yellow-500" | ||
}, | ||
{ | ||
title: "Total Hours", | ||
value: "70:66", | ||
type: "time" | ||
} | ||
]; | ||
function formatPercentage(value: number | undefined): number { | ||
if (!value) return 0; | ||
return Math.round(value); | ||
} | ||
|
||
export function TeamStatsGrid() { | ||
return ( | ||
<> | ||
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')}`; | ||
} | ||
|
||
<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 dark:bg-dark--theme-light"> | ||
<div className="flex flex-col"> | ||
<span className="text-sm font-medium text-gray-500">{stat.title}</span> | ||
<span className={`text-2xl font-semibold mt-2 ${stat.color || "text-gray-900 dark:text-white"}`}> | ||
{stat.value} | ||
</span> | ||
{stat.progress && ( | ||
<div className="mt-4"> | ||
<div className="w-full h-2 bg-gray-100 rounded-full"> | ||
<div | ||
className={`h-full rounded-full ${stat.progressColor}`} | ||
style={{ width: `${stat.progress}%` }} | ||
/> | ||
</div> | ||
</div> | ||
interface StatItem { | ||
title: string; | ||
value: string; | ||
type: 'number' | 'time'; | ||
color?: string; | ||
progress?: number; | ||
progressColor?: string; | ||
showProgress: boolean; | ||
} | ||
|
||
export function TeamStatsGrid({ | ||
statisticsCounts, | ||
loadingTimesheetStatisticsCounts | ||
}: { | ||
statisticsCounts: ITimesheetStatisticsData | null; | ||
loadingTimesheetStatisticsCounts: boolean; | ||
}) { | ||
const { h: hours, m: minutes, s: seconds } = secondsToTime(statisticsCounts?.weekDuration || 0); | ||
const timeValue = formatTime(hours, minutes, seconds); | ||
const progress = formatPercentage(statisticsCounts?.weekActivities); | ||
|
||
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 dark:bg-dark--theme-light"> | ||
<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> | ||
</Card> | ||
))} | ||
</div> | ||
</> | ||
{stat.showProgress && ( | ||
<div className="mt-4"> | ||
<div className="w-full h-2 bg-gray-100 rounded-full dark:bg-gray-700"> | ||
<div | ||
className={`h-full rounded-full ${stat.progressColor} transition-all duration-300`} | ||
style={{ | ||
width: `${loadingTimesheetStatisticsCounts ? 0 : stat.progress}%` | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
</Card> | ||
))} | ||
</div> | ||
); | ||
} |
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
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
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
File renamed without changes.
Oops, something went wrong.