Skip to content

Commit

Permalink
[Feat]: Add role-based access control for team dashboard (#3601)
Browse files Browse the repository at this point in the history
* feat: add role-based access control for team dashboard

* feat: React Hook useMemo has a missing dependency: user.employee.id. Either include it or remove the dependency array.
  • Loading branch information
Innocent-Akim authored Feb 9, 2025
1 parent e71a27c commit 85a822d
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,25 @@ import { DateRangePicker } from './date-range-picker';
import { DateRange } from 'react-day-picker';
import { ITimeLogReportDailyChartProps } from '@/app/interfaces/timer/ITimerLog';
import { TeamDashboardFilter } from './team-dashboard-filter';

interface DashboardHeaderProps {
onUpdateDateRange: (startDate: Date, endDate: Date) => void;
onUpdateFilters: (filters: Partial<Omit<ITimeLogReportDailyChartProps, 'organizationId' | 'tenantId'>>) => void;
isManage?: boolean;
}

export function DashboardHeader({ onUpdateDateRange, onUpdateFilters }: DashboardHeaderProps) {
export function DashboardHeader({ onUpdateDateRange, onUpdateFilters, isManage }: DashboardHeaderProps) {
const handleDateRangeChange = (range: DateRange | undefined) => {
if (range?.from && range?.to) {
onUpdateDateRange(range.from, range.to);
}
};


return (
<div className="flex justify-between items-center">
<h1 className="text-2xl font-semibold">Team Dashboard</h1>
<div className="flex gap-4 items-center">
<DateRangePicker onDateRangeChange={handleDateRangeChange} />
<TeamDashboardFilter />
<TeamDashboardFilter isManage={isManage} />
<Select defaultValue="export">
<SelectTrigger className="w-[100px] border border-[#E4E4E7] dark:border-[#2D2D2D] dark:bg-dark--theme-light">
<SelectValue placeholder="Export" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { MultiSelect } from '@/lib/components/custom-select';
import { cn } from '@/lib/utils';
import { useOrganizationAndTeamManagers } from '@/app/hooks/features/useOrganizationTeamManagers';
import { useTimelogFilterOptions } from '@/app/hooks';

export const TeamDashboardFilter = React.memo(function TeamDashboardFilter() {
interface TeamDashboardFilterProps {
isManage?: boolean;
}
export const TeamDashboardFilter = React.memo(function TeamDashboardFilter({ isManage }: TeamDashboardFilterProps) {
const t = useTranslations();
const { userManagedTeams } = useOrganizationAndTeamManagers();
const { allteamsState, setAllTeamsState, alluserState, setAllUserState } = useTimelogFilterOptions();
Expand Down Expand Up @@ -61,7 +63,8 @@ export const TeamDashboardFilter = React.memo(function TeamDashboardFilter() {
className={cn(
'text-primary/10',
allteamsState.length > 0 && 'text-primary dark:text-primary-light'
)}>
)}
>
{t('common.CLEAR')} ({allteamsState.length})
</span>
</label>
Expand All @@ -77,35 +80,37 @@ export const TeamDashboardFilter = React.memo(function TeamDashboardFilter() {
/>
</div>

<div className="">
<label className="flex justify-between mb-1 text-sm text-gray-600">
<span className="text-[12px]">{t('manualTime.EMPLOYEE')}</span>
<span
className={cn(
'text-primary/10',
alluserState.length > 0 && 'text-primary dark:text-primary-light'
)}
>
{t('common.CLEAR')} ({alluserState.length})
</span>
</label>
<MultiSelect
localStorageKey="team-dashboard-select-filter-employee"
removeItems={shouldRemoveItems}
items={allteamsState.flatMap((team) => {
const members = team.members ?? [];
return members.filter((member) => member && member.employee);
})}
itemToString={(member) => {
if (!member?.employee) return '';
return member.employee.fullName || t('manualTime.EMPLOYEE');
}}
itemId={(item) => item.id}
onValueChange={(selectedItems) => setAllUserState(selectedItems as any)}
multiSelect={true}
triggerClassName="dark:border-gray-700"
/>
</div>
{isManage && (
<div className="">
<label className="flex justify-between mb-1 text-sm text-gray-600">
<span className="text-[12px]">{t('manualTime.EMPLOYEE')}</span>
<span
className={cn(
'text-primary/10',
alluserState.length > 0 && 'text-primary dark:text-primary-light'
)}
>
{t('common.CLEAR')} ({alluserState.length})
</span>
</label>
<MultiSelect
localStorageKey="team-dashboard-select-filter-employee"
removeItems={shouldRemoveItems}
items={allteamsState.flatMap((team) => {
const members = team.members ?? [];
return members.filter((member) => member && member.employee);
})}
itemToString={(member) => {
if (!member?.employee) return '';
return member.employee.fullName || t('manualTime.EMPLOYEE');
}}
itemId={(item) => item.id}
onValueChange={(selectedItems) => setAllUserState(selectedItems as any)}
multiSelect={true}
triggerClassName="dark:border-gray-700"
/>
</div>
)}

<div className="flex gap-x-4 justify-end items-center w-full">
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { useTranslations } from 'next-intl';
function TeamDashboard() {
const { activeTeam, isTrackingEnabled } = useOrganizationTeams();
const t=useTranslations();
const { rapportChartActivity, updateDateRange, updateFilters, loadingTimeLogReportDailyChart, rapportDailyActivity, loadingTimeLogReportDaily, statisticsCounts,loadingTimesheetStatisticsCounts} = useReportActivity();
const { rapportChartActivity, updateDateRange, updateFilters, loadingTimeLogReportDailyChart, rapportDailyActivity, loadingTimeLogReportDaily, statisticsCounts,loadingTimesheetStatisticsCounts, isManage} = useReportActivity();
const router = useRouter();
const fullWidth = useAtomValue(fullWidthState);
const paramsUrl = useParams<{ locale: string }>();
Expand Down Expand Up @@ -57,6 +57,7 @@ function TeamDashboard() {
<DashboardHeader
onUpdateDateRange={updateDateRange}
onUpdateFilters={updateFilters}
isManage={isManage}
/>
<TeamStatsGrid
statisticsCounts={statisticsCounts}
Expand Down
14 changes: 9 additions & 5 deletions apps/web/app/hooks/features/useReportActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function useReportActivity() {
const [rapportChartActivity, setRapportChartActivity] = useAtom(timeLogsRapportChartState);
const [rapportDailyActivity, setRapportDailyActivity] = useAtom(timeLogsRapportDailyState);
const [statisticsCounts, setStatisticsCounts] = useAtom(timesheetStatisticsCountsState);
const { allteamsState, alluserState } = useTimelogFilterOptions();
const { allteamsState, alluserState,isUserAllowedToAccess } = useTimelogFilterOptions();

const { loading: loadingTimeLogReportDailyChart, queryCall: queryTimeLogReportDailyChart } =
useQuery(getTimeLogReportDailyChart);
Expand All @@ -70,6 +70,7 @@ export function useReportActivity() {
useQuery(getTimesheetStatisticsCounts);

const [currentFilters, setCurrentFilters] = useState<Partial<UseReportActivityProps>>(defaultProps);
const isManage = user && isUserAllowedToAccess(user);

// Memoize the merged props to avoid recalculation
const getMergedProps = useMemo(() => {
Expand All @@ -92,8 +93,10 @@ export function useReportActivity() {
projectIds: (customProps?.projectIds ||
currentFilters.projectIds ||
defaultProps.projectIds) as string[],
employeeIds: alluserState?.map(({ employee: { id } }) => id).filter(Boolean),
teamIds:allteamsState?.map(({ id }) => id).filter(Boolean),
employeeIds: isManage
? alluserState?.map(({ employee: { id } }) => id).filter(Boolean)
: [user.employee.id],
teamIds: allteamsState?.map(({ id }) => id).filter(Boolean),
activityLevel: {
start:
customProps?.activityLevel?.start ??
Expand All @@ -109,7 +112,7 @@ export function useReportActivity() {
};
return merged as Required<UseReportActivityProps>;
};
}, [user?.employee.organizationId, user?.tenantId, currentFilters, alluserState, allteamsState]);
}, [user?.employee.organizationId, user?.employee.id, user?.tenantId, currentFilters, isManage, alluserState, allteamsState]);

// Generic fetch function to reduce code duplication
const fetchReport = useCallback(
Expand Down Expand Up @@ -233,6 +236,7 @@ export function useReportActivity() {
updateDateRange,
updateFilters,
currentFilters,
setStatisticsCounts
setStatisticsCounts,
isManage
};
}

0 comments on commit 85a822d

Please sign in to comment.