Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Feat]: Functionalize TeamDashboardFilter component with optimized state management #3576

Merged
merged 5 commits into from
Feb 3, 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
2 changes: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"adipiscing",
"aliqua",
"alldays",
"alluser",
"allteams",
"amet",
"Andross",
"animatable",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
"use client";
'use client';

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { DateRangePicker } from "./date-range-picker";
import { DateRange } from "react-day-picker";
import { ITimeLogReportDailyChartProps } from "@/app/interfaces/timer/ITimerLog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
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;
Expand All @@ -23,44 +18,13 @@ export function DashboardHeader({ onUpdateDateRange, onUpdateFilters }: Dashboar
}
};

const handleFilterChange = (value: string) => {
const today = new Date();
let startDate = new Date();
const endDate = today;

switch (value) {
case 'today':
startDate = today;
break;
case 'week':
startDate.setDate(today.getDate() - 7);
break;
case 'month':
startDate.setMonth(today.getMonth() - 1);
break;
default:
return;
}

onUpdateDateRange(startDate, endDate);
};

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} />
<Select defaultValue="filter" onValueChange={handleFilterChange}>
<SelectTrigger className="w-[100px] border border-[#E4E4E7] dark:border-[#2D2D2D] dark:bg-dark--theme-light">
<SelectValue placeholder="Filter" />
</SelectTrigger>
<SelectContent className="dark:bg-dark--theme-light">
<SelectItem value="filter">Filter</SelectItem>
<SelectItem value="today">Today</SelectItem>
<SelectItem value="week">This Week</SelectItem>
<SelectItem value="month">This Month</SelectItem>
</SelectContent>
</Select>
<TeamDashboardFilter />
<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
@@ -0,0 +1,127 @@
import { Popover, PopoverContent, PopoverTrigger } from '@components/ui/popover';
import React from 'react';
import { useTranslations } from 'next-intl';
import { Button } from '@components/ui/button';
import { SettingFilterIcon } from '@/assets/svg';
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() {
const t = useTranslations();
const { userManagedTeams } = useOrganizationAndTeamManagers();
const { allteamsState, setAllTeamsState, alluserState, setAllUserState } = useTimelogFilterOptions();
const [shouldRemoveItems, setShouldRemoveItems] = React.useState(false);
React.useEffect(() => {
if (shouldRemoveItems) {
setShouldRemoveItems(false);
}
}, [shouldRemoveItems]);

const totalFilteredItems = React.useMemo(() => {
let total = 0;
if (allteamsState?.length) total += allteamsState.length;
total += allteamsState?.reduce((acc, team) => acc + (team.members?.length || 0), 0) || 0;
return total;
}, [allteamsState]);

return (
<div>
<Popover modal>
<PopoverTrigger asChild>
<Button
variant="outline"
className="flex items-center justify-center h-[2.2rem] rounded-lg bg-white dark:bg-dark--theme-light border dark:border-gray-700 hover:bg-white p-3 gap-2"
>
<SettingFilterIcon className="text-gray-700 dark:text-white w-3.5" strokeWidth="1.8" />
<span className="text-gray-700 dark:text-white">{t('common.FILTER')}</span>
{totalFilteredItems > 0 && (
<span
role="status"
aria-label={`${totalFilteredItems} items filtered`}
className="rounded-full bg-primary dark:bg-primary-light h-7 w-7 flex items-center justify-center text-white text-center text-[12px]"
>
{totalFilteredItems > 100 ? '100+' : totalFilteredItems}
</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-96">
<div className="flex flex-col w-full">
<div className="flex gap-2 mb-3 text-xl font-bold">
<SettingFilterIcon className="w-4 text-gray-700 dark:text-white" strokeWidth="1.8" />
<span className="text-gray-700 dark:text-white">{t('common.FILTER')}</span>
</div>
<div className="grid gap-5">
<div className="">
<label className="flex justify-between mb-1 text-sm text-gray-600">
<span className="text-[12px]">{t('manualTime.TEAM')}</span>
<span
className={cn(
'text-primary/10',
allteamsState.length > 0 && 'text-primary dark:text-primary-light'
)}
>
{t('common.CLEAR')}
</span>
</label>
<MultiSelect
localStorageKey="timesheet-select-filter-employee"
removeItems={shouldRemoveItems}
items={userManagedTeams ?? []}
itemToString={(team) => team?.name ?? ''}
itemId={(item) => item.id}
onValueChange={(selectedItems) => setAllTeamsState(selectedItems as any)}
multiSelect={true}
triggerClassName="dark:border-gray-700"
/>
</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')}
</span>
</label>
<MultiSelect
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
onClick={() => setShouldRemoveItems(true)}
variant={'outline'}
className="flex justify-center items-center h-10 text-sm rounded-lg dark:text-gray-300"
>
<span className="text-sm">{t('common.CLEAR_FILTER')}</span>
</Button>
<Button className="flex justify-center items-center h-10 text-sm rounded-lg bg-primary dark:bg-primary-light dark:text-gray-300">
<span className="text-sm">{t('common.APPLY_FILTER')}</span>
</Button>
</div>
</div>
</div>
</PopoverContent>
</Popover>
</div>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover
aria-label={`${totalItems} items filtered`}
className="rounded-full bg-primary dark:bg-primary-light h-7 w-7 flex items-center justify-center text-white text-center text-[12px]"
>
{totalItems > 100 ? "100+" : totalItems}
{totalItems > 100 ? '100+' : totalItems}
</span>
)}
</Button>
Expand Down Expand Up @@ -193,15 +193,15 @@ export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover
triggerClassName="dark:border-gray-700"
/>
</div>
<div className="flex items-center justify-end w-full gap-x-4">
<div className="flex gap-x-4 justify-end items-center w-full">
<Button
onClick={() => setShouldRemoveItems(true)}
variant={'outline'}
className="flex items-center justify-center h-10 text-sm rounded-lg dark:text-gray-300"
className="flex justify-center items-center h-10 text-sm rounded-lg dark:text-gray-300"
>
<span className="text-sm">{t('common.CLEAR_FILTER')}</span>
</Button>
<Button className="flex items-center justify-center h-10 text-sm rounded-lg bg-primary dark:bg-primary-light dark:text-gray-300">
<Button className="flex justify-center items-center h-10 text-sm rounded-lg bg-primary dark:bg-primary-light dark:text-gray-300">
<span className="text-sm">{t('common.APPLY_FILTER')}</span>
</Button>
</div>
Expand Down
10 changes: 8 additions & 2 deletions apps/web/app/hooks/features/useTimelogFilterOptions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { IUser, RoleNameEnum, TimesheetLog } from '@/app/interfaces';
import { timesheetDeleteState, timesheetGroupByDayState, timesheetFilterEmployeeState, timesheetFilterProjectState, timesheetFilterStatusState, timesheetFilterTaskState, timesheetUpdateStatus, selectTimesheetIdState } from '@/app/stores';
import { timesheetDeleteState, timesheetGroupByDayState, timesheetFilterEmployeeState, timesheetFilterProjectState, timesheetFilterStatusState, timesheetFilterTaskState, timesheetUpdateStatus, selectTimesheetIdState, allTeamsState, allUserState } from '@/app/stores';
import { useAtom } from 'jotai';
import React from 'react';

export function useTimelogFilterOptions() {

const [employeeState, setEmployeeState] = useAtom(timesheetFilterEmployeeState);
const [alluserState, setAllUserState] = useAtom(allUserState)
const [allteamsState, setAllTeamsState] = useAtom(allTeamsState)
const [projectState, setProjectState] = useAtom(timesheetFilterProjectState);
const [statusState, setStatusState] = useAtom(timesheetFilterStatusState);
const [taskState, setTaskState] = useAtom(timesheetFilterTaskState);
Expand Down Expand Up @@ -94,6 +96,10 @@ export function useTimelogFilterOptions() {
setPuTimesheetStatus,
puTimesheetStatus,
isUserAllowedToAccess,
normalizeText
normalizeText,
allteamsState,
setAllTeamsState,
alluserState,
setAllUserState
};
}
4 changes: 3 additions & 1 deletion apps/web/app/stores/time-logs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ITimerLogsDailyReport } from '@app/interfaces/timer/ITimerLogs';
import { atom } from 'jotai';
import { IProject, ITeamTask, ITimerDailyLog, ITimerLogGrouped, ITimesheetStatisticsData, OT_Member, TimesheetFilterByDays, TimesheetLog, UpdateTimesheetStatus } from '../interfaces';
import { IOrganizationTeamList, IProject, ITeamTask, ITimerDailyLog, ITimerLogGrouped, ITimesheetStatisticsData, OT_Member, TimesheetFilterByDays, TimesheetLog, UpdateTimesheetStatus } from '../interfaces';

interface IFilterOption {
value: string;
Expand All @@ -25,3 +25,5 @@ export const selectTimesheetIdState = atom<TimesheetLog[]>([])
export const timeLogsRapportChartState = atom<ITimerDailyLog[]>([]);
export const timeLogsRapportDailyState = atom<ITimerLogGrouped[]>([])
export const timesheetStatisticsCountsState = atom<ITimesheetStatisticsData | null>(null)
export const allTeamsState=atom<IOrganizationTeamList[]>([]);
export const allUserState=atom<OT_Member[]>([]);
8 changes: 4 additions & 4 deletions apps/web/lib/features/team/teams-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ export const TeamsDropDown = ({ publicTeam }: { publicTeam?: boolean }) => {
if (
timerStatus &&
timerStatus?.running &&
timerStatus.lastLog &&
timerStatus.lastLog.organizationTeamId &&
timerStatus.lastLog.source === 'TEAMS' &&
timerStatus?.lastLog &&
timerStatus?.lastLog?.organizationTeamId &&
timerStatus?.lastLog?.source === 'TEAMS' &&
activeTeam &&
activeTeam?.id &&
timerStatus.lastLog.organizationTeamId === activeTeam?.id
timerStatus?.lastLog?.organizationTeamId === activeTeam?.id
) {
toast({
variant: 'default',
Expand Down