From 483250227c059e4ba1c25990386c764d305452da Mon Sep 17 00:00:00 2001 From: Innocent-akim Date: Sun, 22 Dec 2024 16:56:01 +0200 Subject: [PATCH] fix: timesheet data overflow in Calendar View and View Details button functionality --- .../[memberId]/components/CalendarView.tsx | 23 +-- .../components/MonthlyTimesheetCalendar.tsx | 2 +- .../components/TimesheetDetailModal.tsx | 165 ++++++++++++++---- .../components/WeeklyTimesheetCalendar.tsx | 24 ++- apps/web/app/helpers/array-data.ts | 29 +++ .../calendar/table-time-sheet.tsx | 4 +- 6 files changed, 200 insertions(+), 47 deletions(-) diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/CalendarView.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/CalendarView.tsx index 94e615515..8fc437601 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/CalendarView.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/CalendarView.tsx @@ -112,7 +112,7 @@ const CalendarDataView = ({ data, t }: { data?: GroupedTimesheet[], t: Translati
- + {status === 'DENIED' ? 'REJECTED' : status} ({rows.length}) @@ -218,7 +218,7 @@ const BaseCalendarDataView = ({ data, daysLabels, t, CalendarComponent }: BaseCa
- + {rows.map((task) => (
-
+
- {task.employee.fullName} + {task.employee.fullName}
-
- {task.project && } - {task.project && task.project.name} +
+ {task.project && } + {task.project && task.project.name}
))} diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/MonthlyTimesheetCalendar.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/MonthlyTimesheetCalendar.tsx index f2dffcf18..7b7756bf1 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/MonthlyTimesheetCalendar.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/MonthlyTimesheetCalendar.tsx @@ -153,7 +153,7 @@ const MonthlyTimesheetCalendar: React.FC = ({ {format(date, "dd MMM yyyy")}
- Total{" : "} + {/* Total{" : "} */} {plan && ; case 'MenHours': - return
MenHours
; + return ; default: return null; } @@ -64,40 +65,16 @@ function TimesheetDetailModal({ closeModal, isOpen, timesheet, timesheetDetailMo ) } - export default TimesheetDetailModal -/** - * Returns an array of objects containing the employeeId and an array of TimesheetLog records. - * The TimesheetLog records are grouped by the employeeId. - * The array is sorted in descending order by employeeId. - * - * @param {TimesheetLog[]} timesheetDetail - an array of TimesheetLog records - * @returns {Array<{ employeeId: string; element: TimesheetLog[] }>} - an array of objects containing the employeeId and an array of TimesheetLog records. - */ -const membersWorked = ({ timesheetDetail }: { timesheetDetail: TimesheetLog[] }) => { - type GroupeMap = Record; - const MembersWorked = timesheetDetail.reduce((acc, cur) => { - if (!cur.employeeId) { - return acc; - } - const employeeId = cur.employeeId; - if (!acc[employeeId]) { - acc[employeeId] = []; - } - acc[employeeId].push(cur); - return acc; - }, {}); - return Object.entries(MembersWorked) +const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: TranslationHooks }) => { + const memberWork = groupBy(element, (items) => items.employeeId); + const memberWorkItems = Object.entries(memberWork) .map(([employeeId, element]) => ({ employeeId, element })) .sort((a, b) => b.employeeId.localeCompare(a.employeeId)); -} - -const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: TranslationHooks }) => { - const memberWorkItems = membersWorked({ timesheetDetail: element }); const { getStatusTimesheet } = useTimesheet({}); return (
@@ -122,7 +99,7 @@ const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: Transla
{t('timer.TOTAL_HOURS').split(' ')[0]}: @@ -154,7 +131,7 @@ const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: Transla
{t('timer.TOTAL_HOURS').split(' ')[0]}: @@ -208,3 +185,131 @@ const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: Transla
) } + + +interface MenHoursCardProps { + element: TimesheetLog[]; + t: TranslationHooks; +} + +/** + * @param {MenHoursCardProps} props - the component props + * @returns {JSX.Element} - the rendered component + */ +const MenHoursCard = ({ element, t }: MenHoursCardProps) => { + const menHours = groupBy(element, (items) => items.timesheet.status); + const menHoursItems = Object.entries(menHours) + .map(([status, element]) => ({ status, element })) + .sort((a, b) => b.status.localeCompare(a.status)); + + const { getStatusTimesheet } = useTimesheet({}); + + return ( +
+ {menHoursItems.map((timesheet, index) => { + return ( + + + +
+
+
+ {timesheet.element[0].timesheet.status === 'DENIED' ? 'REJECTED' : timesheet.element[0].timesheet.status} +
+ + {t('timer.TOTAL_HOURS').split(' ')[0]}: + + +
+
+ + + {Object.entries(getStatusTimesheet(timesheet.element)).map(([status, rows]) => { + return rows.length > 0 && status && ( + +
+
+
+
+ + {status === 'DENIED' ? 'REJECTED' : status} + + ({rows.length}) +
+ + {t('timer.TOTAL_HOURS').split(' ')[0]}: + + +
+ +
+
+ + {rows.map((items) => ( +
+
+ +
+
+ {items.project?.imageUrl && } + {items.project?.name} +
+
+ ))} +
+
+ ) + })} +
+
+
+
+ ) + }) + } +
+ ); +}; diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/WeeklyTimesheetCalendar.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/WeeklyTimesheetCalendar.tsx index 3fe53b726..4719c2c39 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/WeeklyTimesheetCalendar.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/WeeklyTimesheetCalendar.tsx @@ -7,7 +7,7 @@ import { TotalDurationByDate } from "@/lib/features"; import { formatDate } from "@/app/helpers"; import { TranslationHooks } from "next-intl"; -type WeeklyCalendarProps = { +export type WeeklyCalendarProps = { t: TranslationHooks data?: GroupedTimesheet[]; onDateClick?: (date: Date) => void; @@ -32,6 +32,24 @@ const generateWeek = (currentDate: Date) => { return eachDayOfInterval({ start: weekStart, end: weekEnd }); }; +/** + * A weekly calendar component for displaying timesheet data. + * + * The component is a grid of days in the week, with each day displaying the total duration of the tasks for that day. + * The component is also responsive and can be used in a variety of screen sizes. + * + * @param {WeeklyCalendarProps} props - The props for the component. + * @param {GroupedTimesheet[]} [props.data=[]] - The data to display in the calendar. + * @param {((date: Date) => void)} [props.onDateClick] - The function to call when a date is clicked. + * @param {((date: Date, plan?: GroupedTimesheet) => React.ReactNode)} [props.renderDayContent] - The function to call to render the content for each day. + * @param {Locale} [props.locale=enGB] - The locale to use for the dates. + * @param {string[]} [props.daysLabels=defaultDaysLabels] - The labels for the days of the week. + * @param {string} [props.noDataText="No Data"] - The text to display when there is no data for a day. + * @param {{ container?: string; header?: string; grid?: string; day?: string; noData?: string; }} [props.classNames={}] - The CSS class names to use for the component. + * @param {TranslationHooks} props.t - The translations to use for the component. + * + * @returns {React.ReactElement} The JSX element for the component. + */ const WeeklyTimesheetCalendar: React.FC = ({ data = [], onDateClick, @@ -117,12 +135,12 @@ const WeeklyTimesheetCalendar: React.FC = ({ {format(date, "dd MMM yyyy")}
- Total{" : "} + {/* Total{" : "} */} {plan && ( )}
diff --git a/apps/web/app/helpers/array-data.ts b/apps/web/app/helpers/array-data.ts index d96877bf5..70e79d917 100644 --- a/apps/web/app/helpers/array-data.ts +++ b/apps/web/app/helpers/array-data.ts @@ -74,3 +74,32 @@ const formatTime = (d: Date | string, addHour: boolean) => { return `${new Date(d).getHours() < 10 ? pad(new Date(d).getHours() + 1) : new Date(d).getHours() + 1}:00`; else return `${new Date(d).getHours() < 10 ? pad(new Date(d).getHours()) : new Date(d).getHours()}:00`; }; + + +/** + * Groups an array of items by the key returned by the `key` function. + * + * @example + * const items = [ + * { id: 1, name: 'John', group: 'A' }, + * { id: 2, name: 'Jane', group: 'A' }, + * { id: 3, name: 'Bob', group: 'B' }, + * ]; + * + * const groupedItems = groupBy(items, item => item.group); + * + * // groupedItems = { + * // A: [{ id: 1, name: 'Akim', group: 'A' }, { id: 2, name: 'Jane', group: 'A' }], + * // B: [{ id: 3, name: 'Bob', group: 'B' }] + * // } + * + * @param array The array of items to group. + * @param key A function that takes an item and returns a key to group by. + * @returns An object where the keys are the unique values of the key function and the values are arrays of items. + */ +export const groupBy = (array: T[], key: (item: T) => K): Record => + array.reduce((acc, item) => { + const groupKey = key(item); + (acc[groupKey] ||= []).push(item); + return acc; + }, {} as Record); diff --git a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx index 6da176f0e..397bcfd39 100644 --- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx +++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx @@ -286,7 +286,7 @@ export function DataTableTimeSheet({ data, user }: { data?: GroupedTimesheet[], >
-
+
{status === 'DENIED' ? 'REJECTED' : status} @@ -295,7 +295,7 @@ export function DataTableTimeSheet({ data, user }: { data?: GroupedTimesheet[],
{t('timer.TOTAL_HOURS').split(' ')[0]}: