Skip to content

Commit

Permalink
Merge pull request #3339 from ever-co/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
evereq authored Nov 14, 2024
2 parents 3adeff3 + aace163 commit f8aef98
Show file tree
Hide file tree
Showing 50 changed files with 943 additions and 379 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import React from "react";
import { useOrganizationTeams, useTeamTasks } from "@app/hooks";
import { Button } from "@components/ui/button";
import { statusOptions } from "@app/constants";
import { MultiSelect } from "lib/components/custom-select/multi-select";
import { MultiSelect } from "lib/components/custom-select";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@components/ui/popover";
import { SettingFilterIcon } from "@/assets/svg";
import { useTranslations } from "next-intl";
import { clsxm } from "@/app/utils";
import { useTimelogFilterOptions } from "@/app/hooks";



export function TimeSheetFilterPopover() {
export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover() {
const [shouldRemoveItems, setShouldRemoveItems] = React.useState(false);
const { activeTeam } = useOrganizationTeams();
const { tasks } = useTeamTasks();
const t = useTranslations();
const { setEmployeeState, setProjectState, setStatusState, setTaskState, employee, project, statusState, task } = useTimelogFilterOptions();

React.useEffect(() => {
if (shouldRemoveItems) {
setShouldRemoveItems(false);
}
}, [shouldRemoveItems]);

return (
<>
<Popover>
Expand All @@ -36,39 +47,45 @@ export function TimeSheetFilterPopover() {
<div className="">
<label className="flex justify-between text-gray-600 mb-1 text-sm">
<span className="text-[12px]">{t('manualTime.EMPLOYEE')}</span>
<span className="text-primary/10">Clear</span>
<span className={clsxm("text-primary/10", employee?.length > 0 && "text-primary dark:text-primary-light")}>{t('common.CLEAR')}</span>
</label>
<MultiSelect
localStorageKey="timesheet-select-filter-employee"
removeItems={shouldRemoveItems}
items={activeTeam?.members ?? []}
itemToString={(members) => (members ? members.employee.fullName : '')}
itemId={(item) => item.id}
onValueChange={(selectedItems) => console.log(selectedItems)}
onValueChange={(selectedItems) => setEmployeeState(selectedItems as any)}
multiSelect={true}
triggerClassName="dark:border-gray-700"
/>
</div>
<div className="">
<label className="flex justify-between text-gray-600 mb-1 text-sm">
<span className="text-[12px]">{t('sidebar.PROJECTS')}</span>
<span className="text-primary/10">Clear</span>
<span className={clsxm("text-primary/10", project?.length > 0 && "text-primary dark:text-primary-light")}>{t('common.CLEAR')}</span>
</label>
<MultiSelect
items={activeTeam?.members ?? []}
itemToString={(members) => (members ? members.employee.fullName : '')}
localStorageKey="timesheet-select-filter-projects"
removeItems={shouldRemoveItems}
items={activeTeam?.projects ?? []}
itemToString={(project) => (activeTeam?.projects ? project.name! : '')}
itemId={(item) => item.id}
onValueChange={(selectedItems) => console.log(selectedItems)}
onValueChange={(selectedItems) => setProjectState(selectedItems as any)}
multiSelect={true}
triggerClassName="dark:border-gray-700"
/>
</div>
<div className="">
<label className="flex justify-between text-gray-600 mb-1 text-sm">
<span className="text-[12px]">{t('hotkeys.TASK')}</span>
<span className="text-primary/10">Clear</span>
<span className={clsxm("text-primary/10", task?.length > 0 && "text-primary dark:text-primary-light")}>{t('common.CLEAR')}</span>
</label>
<MultiSelect
localStorageKey="timesheet-select-filter-task"
removeItems={shouldRemoveItems}
items={tasks}
onValueChange={(task) => task}
onValueChange={(selectedItems) => setTaskState(selectedItems as any)}
itemId={(task) => (task ? task.id : '')}
itemToString={(task) => (task ? task.title : '')}
multiSelect={true}
Expand All @@ -78,26 +95,29 @@ export function TimeSheetFilterPopover() {
<div className="">
<label className="flex justify-between text-gray-600 mb-1 text-sm">
<span className="text-[12px]">{t('common.STATUS')}</span>
<span className="text-primary/10">Clear</span>
<span className={clsxm("text-primary/10", statusState && "text-primary dark:text-primary-light")}>{t('common.CLEAR')}</span>
</label>
<MultiSelect
localStorageKey="timesheet-select-filter-status"
removeItems={shouldRemoveItems}
items={statusOptions}
itemToString={(status) => (status ? status.value : '')}
itemId={(item) => item.value}
onValueChange={(selectedItems) => console.log(selectedItems)}
onValueChange={(selectedItems) => setStatusState(selectedItems)}
multiSelect={true}
triggerClassName="dark:border-gray-700"
/>
</div>
<div className="flex items-center justify-end gap-x-4 w-full">
<Button
onClick={() => setShouldRemoveItems(true)}
variant={'outline'}
className='flex items-center text-sm justify-center h-10 rounded-lg dark:text-gray-300' >
<span className="text-sm">Clear Filter</span>
<span className="text-sm">{t('common.CLEAR_FILTER')}</span>
</Button>
<Button
className='flex items-center text-sm justify-center h-10 rounded-lg bg-primary dark:bg-primary-light dark:text-gray-300' >
<span className="text-sm">Apply Filter</span>
<span className="text-sm">{t('common.APPLY_FILTER')}</span>
</Button>
</div>
</div>
Expand All @@ -106,4 +126,4 @@ export function TimeSheetFilterPopover() {
</Popover>
</>
)
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function TimesheetCard({ ...props }: ITimesheetCard) {
'border border-gray-200 ',
'text-[#282048] text-sm',
'flex items-center',
'hover:bg-gray-50 focus:ring-2 focus:ring-offset-2 focus:ring-gray-200 dark:border-gray-600'
'hover:bg-gray-50 hover:dark:bg-primary-light/40 focus:ring-2 focus:ring-offset-2 focus:ring-gray-200 dark:border-gray-600'
)}
aria-label="View timesheet details"
onClick={onClick}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FilterWithStatus } from './FilterWithStatus';
import { FilterStatus, FilterWithStatus } from './FilterWithStatus';
import { FrequencySelect, TimeSheetFilterPopover, TimesheetFilterDate, TimesheetFilterDateProps } from '.';
import { Button } from 'lib/components';
import { AddManualTimeModal } from '@/lib/features/manual-time/add-manual-time-modal';
Expand All @@ -9,10 +9,12 @@ interface ITimesheetFilter {
openModal: () => void,
closeModal: () => void,
t: TranslationHooks,
initDate?: Pick<TimesheetFilterDateProps, 'initialRange' | 'onChange' | 'maxDate' | 'minDate'>
initDate?: Pick<TimesheetFilterDateProps, 'initialRange' | 'onChange' | 'maxDate' | 'minDate'>,
onChangeStatus?: (status: FilterStatus) => void;
filterStatus?: FilterStatus
}

export function TimesheetFilter({ closeModal, isOpen, openModal, t, initDate }: ITimesheetFilter,) {
export function TimesheetFilter({ closeModal, isOpen, openModal, t, initDate, filterStatus, onChangeStatus }: ITimesheetFilter,) {
return (
<>
{
Expand All @@ -25,10 +27,8 @@ export function TimesheetFilter({ closeModal, isOpen, openModal, t, initDate }:
<div className="flex w-full justify-between items-center">
<div>
<FilterWithStatus
activeStatus="Rejected"
onToggle={(label) => {
// If logging is needed, use proper logging service
}}
activeStatus={filterStatus || "All Tasks"}
onToggle={(label) => onChangeStatus?.(label)}
/>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { cn } from "@/lib/utils"
import { clsxm } from "@/app/utils"
import { checkPastDate, cn } from "@/lib/utils"
import { DatePicker } from "@components/ui/DatePicker"
import { Button } from "@components/ui/button"
import {
Expand All @@ -12,6 +13,8 @@ import { TranslationHooks } from "next-intl"
import React, { useEffect, useState } from "react"
import { MdKeyboardArrowRight } from "react-icons/md"
import { PiCalendarDotsThin } from "react-icons/pi"
import { Dispatch, SetStateAction, useCallback, useMemo, memo } from 'react';
import moment from 'moment';

interface DatePickerInputProps {
date: Date | null;
Expand All @@ -37,9 +40,9 @@ export function TimesheetFilterDate({
from: initialRange?.from ?? new Date(),
to: initialRange?.to ?? new Date(),
});

const [isVisible, setIsVisible] = useState(false);

const handleFromChange = (fromDate: Date | null) => {
if (maxDate && fromDate && fromDate > maxDate) {
return;
Expand Down Expand Up @@ -86,7 +89,7 @@ export function TimesheetFilterDate({
break;
}
};

useEffect(() => {
if (dateRange.from && dateRange.to) {
onChange?.(dateRange);
Expand Down Expand Up @@ -172,7 +175,7 @@ export function TimesheetFilterDate({
<Button
key={index}
variant="outline"
className="h-7 flex items-center justify-between border-none text-[12px] text-gray-700 dark:dark:bg-dark--theme-light"
className={clsxm("h-6 flex items-center justify-between border-none text-[12px] text-gray-700 dark:bg-dark--theme-light hover:bg-primary hover:text-white hover:dark:bg-primary-light")}
onClick={() => {
label === t('common.FILTER_CUSTOM_RANGE') && setIsVisible((prev) => !prev)
handlePresetClick(label)
Expand Down Expand Up @@ -204,19 +207,19 @@ const DatePickerInput: React.FC<DatePickerInputProps> = ({ date, label }) => (
</>
);


export function DatePickerFilter({
label,
date,
setDate,
minDate,
maxDate
maxDate,
}: {
label: string;
date: Date | null;
setDate: (date: Date | null) => void;
minDate?: Date | null;
maxDate?: Date | null

maxDate?: Date | null;
}) {
const isDateDisabled = React.useCallback((date: Date) => {
if (minDate && date < minDate) return true;
Expand All @@ -227,9 +230,10 @@ export function DatePickerFilter({
return (
<div>
<DatePicker
captionLayout="dropdown"
buttonVariant={'link'}
className="dark:bg-dark--theme-light rounded-lg bg-white dark:text-gray-200"
buttonClassName={'decoration-transparent flex items-center w-full h-[2.2em] bg-white dark:text-gray-200 dark:bg-dark--theme-light border-gray-300 justify-start text-left font-normal text-black h-[2.2rem] border dark:border-slate-600 rounded-md'}
className="dark:bg-dark--theme-light rounded-lg bg-white dark:text-gray-200 "
buttonClassName={'decoration-transparent flex items-center w-full h-[2.2em] bg-white dark:text-gray-200 dark:bg-dark--theme-light border-gray-300 justify-start text-left font-normal text-black h-[2.2rem] border dark:border-slate-600 rounded-md hover:border-primary'}
customInput={<DatePickerInput date={date} label={label} />}
mode="single"
numberOfMonths={1}
Expand All @@ -242,15 +246,94 @@ export function DatePickerFilter({
}
}}
modifiersClassNames={{
disabled: 'text-[#6989AA] cursor-not-allowed',
selected: '!rounded-full bg-primary text-white',
today: '!rounded-full bg-[#BCCAD9] text-white'
booked: clsxm(
'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-primary after:rounded-full'
),
selected: clsxm('bg-primary after:hidden text-white !rounded-full'),
pastDay: clsxm(
'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-yellow-600 after:rounded-full'
),
today: clsxm('border-2 !border-yellow-700 rounded')
}}
disabled={[
...(minDate ? [{ before: minDate }] : []),
...(maxDate ? [{ after: maxDate }] : [])
]}

/>
</div>
);
}



interface ICalendarProps<T extends { date: string | Date }> {
setSelectedPlan: Dispatch<SetStateAction<Date>>;
selectedPlan: Date | undefined;
plans: T[];
pastPlans: T[];
handleCalendarSelect: () => void;
createEmptyPlan: () => Promise<void>;
bookedDayClassName?: string;
selectedDayClassName?: string;
pastDayClassName?: string;
currentDayClassName?: string;
clickDebounceInterval?: number;
startYear?: number;
endYear?: number;
}

export const FilterCalendar = memo(function FuturePlansCalendar<T extends { date: string | Date }>({
setSelectedPlan,
selectedPlan,
plans,
pastPlans,
bookedDayClassName = 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-primary after:rounded-full',
selectedDayClassName = 'bg-primary after:hidden text-white !rounded-full',
pastDayClassName = 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-yellow-600 after:rounded-full',
currentDayClassName = 'border-2 !border-yellow-700 rounded',
startYear,
endYear
}: ICalendarProps<T>) {

const sortedPlansByDateDesc = useMemo(
() => [...plans].sort((plan1, plan2) => new Date(plan1.date).getTime() < new Date(plan2.date).getTime() ? 1 : -1),
[plans]);
const createDateKey = (date: string | Date) =>
moment(date.toString().split('T')[0]).toISOString().split('T')[0];
const isDateAvailableForPlanning = useCallback(
(dateToCheck: Date) => {
const dateKey = createDateKey(dateToCheck);
const planDates = new Set(plans.map(plan => createDateKey(plan.date)));
return !planDates.has(dateKey);
},
[plans]
);
return (
<DatePicker
mode="single"
captionLayout="dropdown"
buttonVariant={'link'}
className={"dark:bg-dark--theme-light rounded-lg bg-white dark:text-gray-200"}
buttonClassName={'decoration-transparent flex items-center w-full h-[2.2em] bg-white dark:text-gray-200 dark:bg-dark--theme-light border-gray-300 justify-start text-left font-normal text-black h-[2.2rem] border dark:border-slate-600 rounded-md'}
numberOfMonths={1}
initialFocus
customInput={<DatePickerInput date={new Date()} label={''} />}
selected={selectedPlan || undefined}
onSelect={(date) => date && setSelectedPlan(moment(date).toDate())}
disabled={(date) => checkPastDate(date) || !isDateAvailableForPlanning(date)}
modifiers={{
booked: sortedPlansByDateDesc.map(plan => moment.utc(plan.date.toString().split('T')[0]).toDate()),
pastDay: pastPlans.map(plan => moment.utc(plan.date.toString().split('T')[0]).toDate())
}}
modifiersClassNames={{
booked: clsxm(bookedDayClassName),
selected: clsxm(selectedDayClassName),
pastDay: clsxm(pastDayClassName),
today: clsxm(currentDayClassName)
}}
fromYear={startYear || new Date(sortedPlansByDateDesc?.[0]?.date ?? Date.now()).getFullYear()}
toYear={endYear || new Date(sortedPlansByDateDesc?.[sortedPlansByDateDesc?.length - 1]?.date ?? Date.now()).getFullYear() + 10}
/>
);
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { IDailyPlan } from '@/app/interfaces';
import { GroupedTimesheet } from '@/app/hooks/features/useTimesheet';
import { DataTableTimeSheet } from 'lib/features/integrations/calendar';
import { useTranslations } from 'next-intl';

export function TimesheetView({ data }: { data?: IDailyPlan[] }) {
export function TimesheetView({ data }: { data?: GroupedTimesheet[] }) {
const t = useTranslations();
return (
<div className='grow h-full w-full bg-[#FFFFFF] dark:bg-dark--theme'>
Expand Down
Loading

0 comments on commit f8aef98

Please sign in to comment.