Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[Feat]: Add role-based access control for team dashboard #3601

Merged
merged 2 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
};
}