From 921f9f69e21b98730767a123b5533ba2de266d26 Mon Sep 17 00:00:00 2001 From: Innocent-akim Date: Fri, 13 Dec 2024 22:27:10 +0200 Subject: [PATCH 1/4] feat: improve AddTaskModal with form handling and error check --- .../[memberId]/components/AddTaskModal.tsx | 100 ++++++---- apps/web/app/helpers/date.ts | 9 + .../hooks/features/useTimelogFilterOptions.ts | 2 +- apps/web/app/interfaces/timer/ITimerLog.ts | 4 +- .../calendar/table-time-sheet.tsx | 184 +----------------- .../lib/features/multiple-select/index.tsx | 4 +- 6 files changed, 81 insertions(+), 222 deletions(-) diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx index 3893337aa..31c241932 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx @@ -12,6 +12,7 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@c import { DatePickerFilter } from './TimesheetFilterDate'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@components/ui/select'; import { useTimesheet } from '@/app/hooks/features/useTimesheet'; +import { toUTC } from '@/app/helpers'; export interface IAddTaskModalProps { isOpen: boolean; closeModal: () => void; @@ -22,6 +23,18 @@ interface Shift { totalHours: string; dateFrom: Date | string, } +interface FormState { + isBillable: boolean; + notes: string; + projectId: string; + taskId: string; + employeeId: string; + shifts: { + dateFrom: Date; + startTime: string; + endTime: string; + }[]; +} export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) { const { tasks } = useTeamTasks(); @@ -32,6 +45,7 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) { const timeOptions = generateTimeOptions(5); const t = useTranslations(); + const [formState, setFormState] = React.useState({ notes: '', isBillable: true, @@ -77,37 +91,53 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) { }, ]; - const handleAddTimesheet = async () => { + const createUtcDate = (baseDate: Date, time: string): Date => { + const [hours, minutes] = time.split(':').map(Number); + return new Date(Date.UTC(baseDate.getFullYear(), baseDate.getMonth(), baseDate.getDate(), hours, minutes)); + }; + + const handleAddTimesheet = async (formState: FormState) => { const payload = { isBillable: formState.isBillable, description: formState.notes, projectId: formState.projectId, - logType: TimeLogType.MANUAL as any, - source: TimerSource.BROWSER as any, + logType: TimeLogType.MANUAL, + source: TimerSource.BROWSER, taskId: formState.taskId, - employeeId: formState.employeeId - } - const createUtcDate = (baseDate: Date, time: string): Date => { - const [hours, minutes] = time.split(':').map(Number); - return new Date(Date.UTC(baseDate.getFullYear(), baseDate.getMonth(), baseDate.getDate(), hours, minutes)); + employeeId: formState.employeeId, + organizationContactId: null || "", + organizationTeamId: null, }; - try { - await Promise.all(formState.shifts.map(async (shift) => { - const baseDate = shift.dateFrom instanceof Date ? shift.dateFrom : new Date(shift.dateFrom ?? new Date()); - const startedAt = createUtcDate(baseDate, shift.startTime.toString().slice(0, 5)); - const stoppedAt = createUtcDate(baseDate, shift.endTime.toString().slice(0, 5)); - await createTimesheet({ - ...payload, - startedAt, - stoppedAt, - }); - })); - closeModal(); + if (!formState.shifts || formState.shifts.length === 0) { + throw new Error('No shifts provided.'); + } + await Promise.all( + formState.shifts.map(async (shift) => { + if (!shift.dateFrom || !shift.startTime || !shift.endTime) { + throw new Error('Incomplete shift data.'); + } + + const baseDate = shift.dateFrom instanceof Date ? shift.dateFrom : new Date(shift.dateFrom); + const start = createUtcDate(baseDate, shift.startTime); + const end = createUtcDate(baseDate, shift.endTime); + const startedAt = toUTC(start).toISOString(); + const stoppedAt = toUTC(end).toISOString(); + if (stoppedAt <= startedAt) { + throw new Error('End time must be after start time.'); + } + await createTimesheet({ + ...payload, + startedAt, + stoppedAt, + }); + }) + ); + console.log('Timesheets successfully created.'); } catch (error) { console.error('Failed to create timesheet:', error); } - } + }; return ( *: updateFormState('employeeId', value.id)} renderOption={(option: any) => ( @@ -235,7 +265,7 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) { - ), - cell: ({ row }) => - }, - { - accessorKey: 'employee', - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- {row.original.employee} -
- ) - }, - { - accessorKey: 'status', - header: ({ column }) => ( - - ), - cell: ({ row }) => { - return ; - } - }, - { - accessorKey: 'time', - header: () =>
Time
, - cell: ({ row }) => ( -
- {row.original.time} -
- ) - } -]; export function DataTableTimeSheet({ data, user }: { data?: GroupedTimesheet[], user?: IUser | undefined }) { const modal = useModal(); @@ -183,28 +80,7 @@ export function DataTableTimeSheet({ data, user }: { data?: GroupedTimesheet[], }; const t = useTranslations(); - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState([]); - const [columnVisibility, setColumnVisibility] = React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - const table = useReactTable({ - data: dataSourceTimeSheet, - columns, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - onRowSelectionChange: setRowSelection, - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection - } - }); + const handleSort = (key: string, order: SortOrder) => { console.log(`Sorting ${key} in ${order} order`); }; @@ -277,6 +153,7 @@ export function DataTableTimeSheet({ data, user }: { data?: GroupedTimesheet[], className={clsxm("p-1 rounded")} > -
-
- {table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length}{' '} - row(s) selected. -
-
- - Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} - - - - - -
-
); } @@ -544,19 +386,7 @@ const TaskActionMenu = ({ dataTimesheet, isManage, user }: { dataTimesheet: Time }; -const TaskDetails = ({ description, name }: { description: string; name: string }) => { - return ( -
-
- ever -
- - {name} - -
{description}
-
- ); -}; + export const StatusTask = ({ timesheet }: { timesheet: TimesheetLog }) => { const t = useTranslations(); diff --git a/apps/web/lib/features/multiple-select/index.tsx b/apps/web/lib/features/multiple-select/index.tsx index 94149c225..5ec9af5da 100644 --- a/apps/web/lib/features/multiple-select/index.tsx +++ b/apps/web/lib/features/multiple-select/index.tsx @@ -158,8 +158,8 @@ export function CustomSelect({ - {options.map((value) => ( - + {options.map((value, index) => ( + {renderOption ? renderOption(value) : value.charAt(0).toUpperCase() + value.slice(1)} ))} From 3cdb5b0181d1dd98b939f3628387730d125eb032 Mon Sep 17 00:00:00 2001 From: Innocent-akim Date: Sat, 14 Dec 2024 12:29:18 +0200 Subject: [PATCH 2/4] feat: add image to employee selection --- .../[locale]/timesheet/[memberId]/components/AddTaskModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx index 31c241932..1bf66edc3 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx @@ -189,6 +189,7 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) { onChange={(value: any) => updateFormState('employeeId', value.id)} renderOption={(option: any) => (
+ {option.employee.fullName}
)} From 632ea9a27a4645b6d2a5ba1168f93b354ff91066 Mon Sep 17 00:00:00 2001 From: Innocent-akim Date: Sat, 14 Dec 2024 12:57:38 +0200 Subject: [PATCH 3/4] fix: coderabbitai --- .../timesheet/[memberId]/components/AddTaskModal.tsx | 6 +++--- apps/web/app/helpers/date.ts | 6 +++++- apps/web/app/interfaces/timer/ITimerLog.ts | 4 ++-- .../lib/features/integrations/calendar/table-time-sheet.tsx | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx index 1bf66edc3..7b9159c14 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx @@ -101,8 +101,8 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) { isBillable: formState.isBillable, description: formState.notes, projectId: formState.projectId, - logType: TimeLogType.MANUAL, - source: TimerSource.BROWSER, + logType: TimeLogType.MANUAL as any, + source: TimerSource.BROWSER as any, taskId: formState.taskId, employeeId: formState.employeeId, organizationContactId: null || "", @@ -337,7 +337,7 @@ const OptimizedAccordion = ({ setShifts, shifts, timeOptions, t }: { const calculateTotalHoursHour = React.useCallback( (start: string, end: string): string => { - if (!start || !end) return '00:00:00h'; + if (!start || !end) return '00:00h'; const startMinutes = convertToMinutesHour(start); const endMinutes = convertToMinutesHour(end); const totalMinutes = endMinutes >= startMinutes diff --git a/apps/web/app/helpers/date.ts b/apps/web/app/helpers/date.ts index 1ac3c5d71..c384f5fd9 100644 --- a/apps/web/app/helpers/date.ts +++ b/apps/web/app/helpers/date.ts @@ -228,7 +228,11 @@ export const formatDate = (dateStr: string | Date): string => { export function toLocal(date: string | Date | moment.Moment): moment.Moment { - return moment.utc(date).local(); + const localDate = moment(date); + if (!localDate.isValid()) { + throw new Error('Invalid date provided to toUTC'); + } + return localDate.utc(); } export function toUTC(date: string | Date | moment.Moment): moment.Moment { diff --git a/apps/web/app/interfaces/timer/ITimerLog.ts b/apps/web/app/interfaces/timer/ITimerLog.ts index 8ba2038fa..a5c932fc2 100644 --- a/apps/web/app/interfaces/timer/ITimerLog.ts +++ b/apps/web/app/interfaces/timer/ITimerLog.ts @@ -88,8 +88,8 @@ export interface TimesheetLog extends BaseEntity { startedAt: string | Date; stoppedAt: string | Date; editedAt: string | null; - logType: TimeLogType; - source: TimerSource; + logType: TimeLogType.MANUAL; + source: TimerSource.BROWSER; description: string; reason: string | null; isBillable: boolean; 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 5567083dd..cc5f876b1 100644 --- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx +++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx @@ -241,7 +241,7 @@ export function DataTableTimeSheet({ data, user }: { data?: GroupedTimesheet[], Date: Sat, 14 Dec 2024 13:00:39 +0200 Subject: [PATCH 4/4] fix: coderabbitai --- .../web/lib/features/integrations/calendar/table-time-sheet.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cc5f876b1..58a046f88 100644 --- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx +++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx @@ -48,7 +48,7 @@ import { useTranslations } from 'next-intl'; import { formatDate } from '@/app/helpers'; import { GroupedTimesheet, useTimesheet } from '@/app/hooks/features/useTimesheet'; import { DisplayTimeForTimesheet, TaskNameInfoDisplay, TotalDurationByDate, TotalTimeDisplay } from '../../task/task-displays'; -import { IUser, TimeLogType, TimesheetLog, TimesheetStatus } from '@/app/interfaces'; +import { IUser, TimesheetLog, TimesheetStatus } from '@/app/interfaces'; import { toast } from '@components/ui/use-toast'; import { ToastAction } from '@components/ui/toast';