Skip to content

Commit

Permalink
Feat(timesheet): Add TimesheetDetailModal for displaying pending (#3382)
Browse files Browse the repository at this point in the history
* feat(timesheet): add TimesheetDetailModal for displaying pending timesheet details

* fix: coderabbitai
  • Loading branch information
Innocent-Akim authored Nov 30, 2024
1 parent 0ae8dcb commit 169a2ea
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { EmployeeAvatar } from "./CompactTimesheetComponent";
import { formatDate } from "@/app/helpers";
import { ClockIcon } from "lucide-react";

export function CalendarView({ data }: { data?: GroupedTimesheet[] }) {
export function CalendarView({ data, loading }: { data?: GroupedTimesheet[], loading: boolean }) {
const t = useTranslations();
return (
<div className="grow h-full w-full bg-[#FFFFFF] dark:bg-dark--theme">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@

import { formatDate } from '@/app/helpers';
import { DisplayTimeForTimesheet, TaskNameInfoDisplay, TotalDurationByDate, TotalTimeDisplay } from '@/lib/features';
import { clsxm } from '@app/utils';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@components/ui/accordion';
import { Badge } from '@components/ui/badge';
import { ArrowRightIcon } from 'assets/svg';
import { Button, Card } from 'lib/components';
import { Button, Card, statusColor } from 'lib/components';
import { useTranslations } from 'next-intl';
import { ReactNode } from 'react';
import { EmployeeAvatar } from './CompactTimesheetComponent';
import { useTimesheet } from '@/app/hooks/features/useTimesheet';
import { useTimelogFilterOptions } from '@/app/hooks';
import { TimesheetLog, TimesheetStatus } from '@/app/interfaces';

interface ITimesheetCard {
title?: string;
Expand Down Expand Up @@ -64,3 +72,112 @@ export function TimesheetCard({ ...props }: ITimesheetCard) {
</Card>
)
}



export const TimesheetCardDetail = ({ data }: { data?: Record<TimesheetStatus, TimesheetLog[]> }) => {

const { getStatusTimesheet, groupByDate } = useTimesheet({});
const { timesheetGroupByDays } = useTimelogFilterOptions();
const timesheetGroupByDate = groupByDate(data?.PENDING || [])
const t = useTranslations();
return (
<div className="rounded-md">
{timesheetGroupByDate.map((plan, index) => {
return <div key={index}>
<div
className={clsxm(
'h-[48px] flex justify-between items-center w-full',
'bg-[#ffffffcc] dark:bg-dark--theme rounded-md border-1',
'border-gray-400 px-5 text-[#71717A] font-medium'
)}>
<div className='flex gap-x-3'>
{timesheetGroupByDays === 'Weekly' && (
<span>Week {index + 1}</span>
)}
<span>{formatDate(plan.date)}</span>
</div>
<TotalDurationByDate
timesheetLog={plan.tasks}
createdAt={formatDate(plan.date)}
/>
</div>
<Accordion type="single" collapsible>
{Object.entries(getStatusTimesheet(plan.tasks)).map(([status, rows]) => {
return rows.length > 0 && status && <AccordionItem
key={status}
value={status === 'DENIED' ? 'REJECTED' : status}
className={clsxm("p-1 rounded")}
>
<AccordionTrigger
style={{ backgroundColor: statusColor(status).bgOpacity }}
type="button"
className={clsxm(
'flex flex-row-reverse justify-end items-center w-full h-[50px] rounded-sm gap-x-2 hover:no-underline px-2',
statusColor(status).text
)}
>
<div className="flex items-center justify-between w-full space-x-1">
<div className="flex items-center space-x-1">
<div className={clsxm('p-2 rounded', statusColor(status).bg)}></div>
<div className="flex items-center gap-x-1">
<span className="text-base font-normal text-gray-400 uppercase">
{status === 'DENIED' ? 'REJECTED' : status}
</span>
<span className="text-gray-400 text-[14px]">({rows.length})</span>
</div>
<Badge
variant={'outline'}
className="flex items-center gap-x-2 h-[25px] rounded-md bg-[#E4E4E7] dark:bg-gray-800"
>
<span className="text-[#5f5f61]">{t('timer.TOTAL_HOURS')}</span>
<TotalTimeDisplay timesheetLog={rows} />
</Badge>
</div>
</div>
</AccordionTrigger>
<AccordionContent className="flex flex-col w-full">
{rows.map((task) => (
<div
key={task.id}
style={{
backgroundColor: statusColor(status).bgOpacity,
borderBottomColor: statusColor(status).bg
}}
className={clsxm(
'flex items-center border-b border-b-gray-200 dark:border-b-gray-600 space-x-4 p-1 h-[60px]'
)}>
<div className="flex-[2]">
<TaskNameInfoDisplay
task={task.task}
className={clsxm(
'shadow-[0px_0px_15px_0px_#e2e8f0] dark:shadow-transparent'
)}
taskTitleClassName={clsxm(
'text-sm text-ellipsis overflow-hidden text-sm'
)}
showSize={false}
dash
taskNumberClassName="text-sm"
/>
</div>
<div className="flex items-center flex-1 gap-x-2">
<EmployeeAvatar
imageUrl={task.employee.user.imageUrl!}
/>
<span className="flex-1 font-medium text-[14px] overflow-hidden">{task.employee.fullName}</span>
</div>
<DisplayTimeForTimesheet
duration={task.timesheet.duration}
/>
</div>
))}
</AccordionContent>
</AccordionItem>
})}
</Accordion>
</div>
})}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { TimesheetLog, TimesheetStatus } from '@/app/interfaces';
import { Modal } from '@/lib/components';
import React from 'react'
import { TimesheetCardDetail } from './TimesheetCard';
import { useTranslations } from 'next-intl';

export interface IAddTaskModalProps {
isOpen: boolean;
closeModal: () => void;
timesheet?: Record<TimesheetStatus, TimesheetLog[]>
}

function TimesheetDetailModal({ closeModal, isOpen, timesheet }: IAddTaskModalProps) {
const t = useTranslations()

return (
<Modal
isOpen={isOpen}
closeModal={closeModal}
title={'View Pending details'}
showCloseIcon
className="bg-light--theme-light dark:bg-dark--theme-light p-5 rounded w-full md:w-40 md:min-w-[35rem]"
titleClass="font-bold flex justify-start w-full text-2xl">
<div className=' py-4 w-full'>
<div className="flex flex-col w-full gap-4 h-[60vh] max-h-[60vh] overflow-y-auto ">
{
timesheet?.PENDING.length === 0 ? (
<div className="grow h-full w-full bg-[#FFFFFF] dark:bg-dark--theme flex flex-col items-center justify-center min-h-[280px]">
<p>{t('pages.timesheet.NO_ENTRIES_FOUND')}</p>
</div>
) : <TimesheetCardDetail data={timesheet} />
}
</div>
</div>

</Modal>

)
}

export default TimesheetDetailModal
27 changes: 24 additions & 3 deletions apps/web/app/[locale]/timesheet/[memberId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { GoSearch } from 'react-icons/go';
import { getGreeting } from '@/app/helpers';
import { useTimesheet } from '@/app/hooks/features/useTimesheet';
import { endOfDay, startOfDay } from 'date-fns';
import TimesheetDetailModal from './components/TimesheetDetailModal';

type TimesheetViewMode = 'ListView' | 'CalendarView';

Expand Down Expand Up @@ -73,6 +74,13 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
openModal: openManualTimeModal,
closeModal: closeManualTimeModal
} = useModal();

const {
isOpen: isTimesheetDetailOpen,
openModal: openTimesheetDetail,
closeModal: closeTimesheetDetail
} = useModal();

const username = user?.name || user?.firstName || user?.lastName || user?.username;

const [timesheetNavigator, setTimesheetNavigator] = useLocalStorageState<TimesheetViewMode>(
Expand All @@ -95,6 +103,13 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
);
return (
<>
{isTimesheetDetailOpen
&& <TimesheetDetailModal
closeModal={closeTimesheetDetail}
isOpen={isTimesheetDetailOpen}
timesheet={statusTimesheet}
/>}

<MainLayout
showTimer={isTrackingEnabled}
className="items-start pb-1 !overflow-hidden w-full"
Expand Down Expand Up @@ -124,6 +139,7 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
description="Tasks waiting for your approval"
icon={<GrTask className="font-bold" />}
classNameIcon="bg-[#FBB650] shadow-[#fbb75095]"
onClick={() => openTimesheetDetail()}
/>
<TimesheetCard
hours="63:00h"
Expand Down Expand Up @@ -195,10 +211,15 @@ const TimeSheet = React.memo(function TimeSheetPage({ params }: { params: { memb
{/* <DropdownMenuDemo /> */}
<div className="border border-gray-200 rounded-lg dark:border-gray-800">
{timesheetNavigator === 'ListView' ? (
<TimesheetView data={filterDataTimesheet}
loading={loadingTimesheet} />
<TimesheetView
data={filterDataTimesheet}
loading={loadingTimesheet}
/>
) : (
<CalendarView data={filterDataTimesheet} />
<CalendarView
data={filterDataTimesheet}
loading={loadingTimesheet}
/>
)}
</div>
</Container>
Expand Down
3 changes: 2 additions & 1 deletion apps/web/app/hooks/features/useTimesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export function useTimesheet({
createTimesheet,
loadingCreateTimesheet,
updateTimesheet,
loadingUpdateTimesheet
loadingUpdateTimesheet,
groupByDate
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ export const StatusTask = ({ timesheet }: { timesheet: TimesheetLog }) => {
);
};

const getBadgeColor = (timesheetStatus: TimesheetStatus | null) => {
export const getBadgeColor = (timesheetStatus: TimesheetStatus | null) => {
switch (timesheetStatus) {
case 'DRAFT':
return 'bg-gray-300';
Expand Down

0 comments on commit 169a2ea

Please sign in to comment.