-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: make team stats table responsive - Add horizontal scroll for small screens - Set minimum widths for each column - Add whitespace-nowrap to prevent content wrapping - Make pagination controls responsive - Improve dark mode styling - Fix avatar and text truncation * fix * fix: align table body columns with header widths - Replace min-w with fixed w for consistent column widths - Add overflow-hidden to prevent content overflow - Fix table cell width alignment across header and body - Use consistent width values for each column - Add proper key props for React lists * feat: enhance team stats table UI - Add chart icon column for visual indicators - Increase member column width for better readability - Improve text colors and spacing consistency - Update column alignment and styling - Fix table header font weights and colors * feat: add activity modal with detailed stats - Create ActivityModal component with activity breakdown - Add tabs for Activity and Tracked vs Manual time - Implement circular progress charts with SVG - Show detailed time breakdowns with percentages - Integrate modal with team stats table chart button * refactor: simplify activity modal - Remove tabs and keep only activity breakdown chart - Use custom Modal component with useModal hook - Update chart segments with fixed percentages - Improve modal styling and layout * refactor(activity-modal): optimize component structure and performance - Extract reusable Circle and LegendItem components - Add memoization for time calculations - Improve TypeScript types and interfaces - Clean up code formatting and remove unused variables - Enhance code maintainability and readability * feat(activity-modal): implement modal for tracking and managing activities * deep scan * Add accessibility attributes to tab buttons. * fix: coderabbitai
- Loading branch information
1 parent
2f8f946
commit 5650129
Showing
4 changed files
with
397 additions
and
135 deletions.
There are no files selected for viewing
193 changes: 193 additions & 0 deletions
193
apps/web/app/[locale]/dashboard/team-dashboard/[teamId]/components/activity-modal.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 |
---|---|---|
@@ -0,0 +1,193 @@ | ||
'use client'; | ||
|
||
import { Modal, Avatar } from '@/lib/components'; | ||
import { ITimerEmployeeLog } from '@/app/interfaces/timer/ITimerLog'; | ||
import { useState, useMemo } from 'react'; | ||
|
||
interface ActivityModalProps { | ||
employeeLog: ITimerEmployeeLog; | ||
isOpen: boolean; | ||
closeModal: () => void; | ||
} | ||
|
||
type TabType = 'tracked' | 'activity'; | ||
|
||
interface CircleProps { | ||
color: string; | ||
dashArray: string; | ||
dashOffset?: string; | ||
} | ||
|
||
const Circle = ({ color, dashArray, dashOffset = '0' }: CircleProps) => ( | ||
<circle | ||
cx="50" | ||
cy="50" | ||
r="40" | ||
fill="none" | ||
stroke={color} | ||
strokeWidth="20" | ||
strokeDasharray={`${dashArray} 251.327`} | ||
strokeDashoffset={dashOffset} | ||
className="transition-all duration-1000" | ||
/> | ||
); | ||
|
||
const LegendItem = ({ | ||
color, | ||
label, | ||
time, | ||
percentage | ||
}: { | ||
color: string; | ||
label: string; | ||
time: string; | ||
percentage: number; | ||
}) => ( | ||
<div className="flex justify-between items-center text-sm"> | ||
<div className="flex items-center space-x-2"> | ||
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: color }}></div> | ||
<span className="text-gray-700 dark:text-gray-300">{label}</span> | ||
</div> | ||
<span className="font-normal text-gray-900 dark:text-gray-100"> | ||
{time} ({percentage}%) | ||
</span> | ||
</div> | ||
); | ||
|
||
const formatTime = (minutes: number): string => { | ||
const hours = Math.floor(minutes / 60); | ||
const mins = Math.floor(minutes % 60); | ||
return `${hours}h ${mins.toString().padStart(2, '0')}min`; | ||
}; | ||
|
||
export const ActivityModal = ({ employeeLog, isOpen, closeModal }: ActivityModalProps) => { | ||
const [activeTab, setActiveTab] = useState<TabType>('activity'); | ||
|
||
const timeCalculations = useMemo(() => { | ||
const totalTime = employeeLog.sum || 0; | ||
return { | ||
totalTime, | ||
activeTime: totalTime * 0.57, | ||
idleTime: totalTime * 0.13, | ||
unknownTime: totalTime * 0.3, | ||
trackedTime: totalTime * 0.75, | ||
manualTime: totalTime * 0.25 | ||
}; | ||
}, [employeeLog.sum]); | ||
|
||
const { activeTime, idleTime, unknownTime, trackedTime, manualTime } = timeCalculations; | ||
|
||
const formattedTimes = useMemo( | ||
() => ({ | ||
active: formatTime(activeTime), | ||
idle: formatTime(idleTime), | ||
unknown: formatTime(unknownTime), | ||
tracked: formatTime(trackedTime), | ||
manual: formatTime(manualTime) | ||
}), | ||
[activeTime, idleTime, unknownTime, trackedTime, manualTime] | ||
); | ||
|
||
return ( | ||
<Modal | ||
isOpen={isOpen} | ||
closeModal={closeModal} | ||
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md:min-w-[32rem] justify-start h-[auto]" | ||
titleClass="font-bold flex justify-start w-full" | ||
> | ||
<div className="flex flex-col w-full gap-4 justify-start md:w-40 md:min-w-[32rem] p-4"> | ||
<div className="flex justify-between items-center mb-2"> | ||
<div className="flex items-center space-x-3"> | ||
<Avatar | ||
size={40} | ||
imageUrl={employeeLog.employee.user?.imageUrl} | ||
imageTitle={employeeLog.employee.fullName} | ||
className="relative" | ||
/> | ||
<h3 className="text-xl font-medium text-gray-900 dark:text-gray-100"> | ||
{employeeLog.employee.fullName} | ||
</h3> | ||
</div> | ||
</div> | ||
|
||
<div className="flex p-1 space-x-2 bg-gray-100 dark:bg-[#1B1D22] rounded-lg"> | ||
{(['tracked', 'activity'] as const).map((tab) => ( | ||
<button | ||
key={tab} | ||
role="tab" | ||
aria-selected={activeTab === tab} | ||
aria-label={tab === 'tracked' ? 'Show tracked vs manual time' : 'Show activity breakdown'} | ||
onClick={() => setActiveTab(tab)} | ||
className={`flex-1 px-4 py-2 rounded-md transition-all duration-200 ${ | ||
activeTab === tab | ||
? 'bg-white shadow-sm dark:bg-dark--theme-light text-primary dark:text-primary-light font-normal' | ||
: 'text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200' | ||
}`} | ||
> | ||
{tab === 'tracked' ? 'Tracked vs Manual' : 'Activity Breakdown'} | ||
</button> | ||
))} | ||
</div> | ||
|
||
<div className="relative mt-8"> | ||
{activeTab === 'tracked' ? ( | ||
<div className="transition-opacity duration-200 ease-in-out"> | ||
<div className="relative mx-auto mb-8 w-48 h-48"> | ||
<svg className="w-full h-full -rotate-90" viewBox="0 0 100 100"> | ||
<Circle color="#2563EB" dashArray="188.49556" /> | ||
<Circle color="#9333EA" dashArray="62.83185" dashOffset="-188.49556" /> | ||
</svg> | ||
</div> | ||
|
||
<div className="space-y-4"> | ||
<LegendItem | ||
color="#2563EB" | ||
label="Tracked Time" | ||
time={formattedTimes.tracked} | ||
percentage={75} | ||
/> | ||
<LegendItem | ||
color="#9333EA" | ||
label="Manual Time" | ||
time={formattedTimes.manual} | ||
percentage={25} | ||
/> | ||
</div> | ||
</div> | ||
) : ( | ||
<div className="transition-opacity duration-200 ease-in-out"> | ||
<div className="relative mx-auto mb-8 w-48 h-48"> | ||
<svg className="w-full h-full -rotate-90" viewBox="0 0 100 100"> | ||
<Circle color="#2563EB" dashArray="143.25639" /> | ||
<Circle color="#F59E0B" dashArray="32.67251" dashOffset="-143.25639" /> | ||
<Circle color="#EF4444" dashArray="75.3981" dashOffset="-175.9289" /> | ||
</svg> | ||
</div> | ||
|
||
<div className="space-y-4"> | ||
<LegendItem | ||
color="#2563EB" | ||
label="Active Time" | ||
time={formattedTimes.active} | ||
percentage={57} | ||
/> | ||
<LegendItem | ||
color="#F59E0B" | ||
label="Idle Time" | ||
time={formattedTimes.idle} | ||
percentage={13} | ||
/> | ||
<LegendItem | ||
color="#EF4444" | ||
label="Unknown Activity" | ||
time={formattedTimes.unknown} | ||
percentage={30} | ||
/> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
</Modal> | ||
); | ||
}; |
30 changes: 30 additions & 0 deletions
30
apps/web/app/[locale]/dashboard/team-dashboard/[teamId]/components/team-icon.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 |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export const ChartIcon = () => { | ||
return ( | ||
<svg | ||
width="16" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M2.67967 3.98016C1.83301 5.10016 1.33301 6.4935 1.33301 8.00016C1.33301 11.6802 4.31967 14.6668 7.99967 14.6668C11.6797 14.6668 14.6663 11.6802 14.6663 8.00016C14.6663 4.32016 11.6797 1.3335 7.99967 1.3335" | ||
stroke="#C4C4C4" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
<path | ||
d="M3.33301 8.00016C3.33301 10.5802 5.41967 12.6668 7.99967 12.6668C10.5797 12.6668 12.6663 10.5802 12.6663 8.00016C12.6663 5.42016 10.5797 3.3335 7.99967 3.3335" | ||
stroke="#C4C4C4" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
<path | ||
d="M8 10.6668C9.47333 10.6668 10.6667 9.4735 10.6667 8.00016C10.6667 6.52683 9.47333 5.3335 8 5.3335" | ||
stroke="#C4C4C4" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
); | ||
}; |
Oops, something went wrong.