Skip to content

Commit

Permalink
feat: improve AddTaskModal with form handling and error check
Browse files Browse the repository at this point in the history
  • Loading branch information
Innocent-Akim committed Dec 13, 2024
1 parent 3a15c2f commit 921f9f6
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 222 deletions.
100 changes: 60 additions & 40 deletions apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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,
Expand Down Expand Up @@ -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 (
<Modal
Expand Down Expand Up @@ -152,9 +182,9 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
<span className="text-[#de5505e1] ml-1">*</span>:
</label>
<CustomSelect
classNameGroup='max-h-[40vh] '
classNameGroup='max-h-[40vh] !text-white '
ariaLabel='Task issues'
className='w-full font-medium'
className='w-full font-medium text-white'
options={activeTeam?.members as any}
onChange={(value: any) => updateFormState('employeeId', value.id)}
renderOption={(option: any) => (
Expand Down Expand Up @@ -235,7 +265,7 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
</button>
<button
disabled={loadingCreateTimesheet}
onClick={handleAddTimesheet}
onClick={() => handleAddTimesheet(formState as any)}
type="submit"
className={clsxm(
'bg-primary dark:bg-primary-light h-[2.3rem] w-[5.5rem] justify-center font-normal flex items-center text-white px-2 rounded-lg',
Expand Down Expand Up @@ -306,7 +336,7 @@ const OptimizedAccordion = ({ setShifts, shifts, timeOptions, t }: {

const calculateTotalHoursHour = React.useCallback(
(start: string, end: string): string => {
if (!start || !end) return '00:00h';
if (!start || !end) return '00:00:00h';
const startMinutes = convertToMinutesHour(start);
const endMinutes = convertToMinutesHour(end);
const totalMinutes = endMinutes >= startMinutes
Expand All @@ -321,21 +351,15 @@ const OptimizedAccordion = ({ setShifts, shifts, timeOptions, t }: {

const handleAddShift = () => {
setShifts([...shifts,
{ startTime: '', endTime: '', totalHours: '00:00h', dateFrom: new Date() },]);
{ startTime: '', endTime: '', totalHours: '00:00:00 h', dateFrom: new Date() },]);
};

const handleRemoveShift = (index: number) => {
const updatedShifts = shifts.filter((_, i) => i !== index);
setShifts(updatedShifts);
};

const convertMinutesToTime = (minutes: number): string => {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
const period = hours >= 12 ? 'PM' : 'AM';
const formattedHours = hours % 12 || 12;
return `${String(formattedHours).padStart(2, '0')}:${String(mins).padStart(2, '0')} ${period}`;
};


const handleShiftChange = (index: number, field: keyof Shift, value: string) => {
const updatedShifts = [...shifts];
Expand All @@ -346,11 +370,7 @@ const OptimizedAccordion = ({ setShifts, shifts, timeOptions, t }: {

if (!startTime || !endTime) return;

if (startTime === endTime) {
const startMinutes = convertToMinutesHour(startTime);
updatedShifts[index].endTime = convertMinutesToTime(startMinutes + 5);
return;
}

if (convertToMinutesHour(startTime) >= convertToMinutesHour(endTime)) {
return;
}
Expand Down Expand Up @@ -445,15 +465,15 @@ const ShiftManagement = (
<ShiftTimingSelect
label="Start"
timeOptions={timeOptions}
placeholder="00:00"
placeholder="00:00:00"
className="bg-[#30B3661A]"
value={value.startTime}
onChange={(value) => onChange(index, 'startTime', value)}
/>
<ShiftTimingSelect
label="End"
timeOptions={timeOptions}
placeholder="00:00"
placeholder="00:00:00"
className="bg-[#DA27271A]"
value={value.endTime}
onChange={(value) => onChange(index, 'endTime', value)}
Expand Down
9 changes: 9 additions & 0 deletions apps/web/app/helpers/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,12 @@ export const formatDate = (dateStr: string | Date): string => {
return '';
}
}


export function toLocal(date: string | Date | moment.Moment): moment.Moment {
return moment.utc(date).local();
}

export function toUTC(date: string | Date | moment.Moment): moment.Moment {
return moment(date).utc();
}
2 changes: 1 addition & 1 deletion apps/web/app/hooks/features/useTimelogFilterOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function useTimelogFilterOptions() {
const hour12 = hour24 % 12 || 12; // Convert to 12-hour format
const minutes = (totalMinutes % 60).toString().padStart(2, '0');
const period = hour24 < 12 ? 'AM' : 'PM'; // Determine AM/PM
return `${hour12.toString().padStart(2, '0')}:${minutes} ${period}`;
return `${hour12.toString().padStart(2, '0')}:${minutes}:00 ${period}`;
});
};

Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/interfaces/timer/ITimerLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ export interface TimesheetLog extends BaseEntity {
startedAt: string | Date;
stoppedAt: string | Date;
editedAt: string | null;
logType: TimeLogType.MANUAL;
source: TimerSource.BROWSER;
logType: TimeLogType;
source: TimerSource;
description: string;
reason: string | null;
isBillable: boolean;
Expand Down
Loading

0 comments on commit 921f9f6

Please sign in to comment.