Skip to content

Commit

Permalink
[Feat]: Improve AddManuel with form handling and error check (#3426)
Browse files Browse the repository at this point in the history
* feat: improve AddTaskModal with form handling and error check

* feat: add image to employee selection

* fix: coderabbitai

* fix: coderabbitai
  • Loading branch information
Innocent-Akim authored Dec 23, 2024
1 parent 34accd9 commit 7f69d43
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 215 deletions.
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,
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,13 +182,14 @@ 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) => (
<div className="flex items-center gap-x-2">
<img className='h-6 w-6 rounded-full' src={option.employee.user.imageUrl} />
<span>{option.employee.fullName}</span>
</div>
)}
Expand Down Expand Up @@ -235,7 +266,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 @@ -321,21 +352,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 +371,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 +466,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
13 changes: 13 additions & 0 deletions apps/web/app/helpers/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,16 @@ export const formatDate = (dateStr: string | Date): string => {
return '';
}
}


export function toLocal(date: string | Date | moment.Moment): moment.Moment {
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 {
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
Loading

0 comments on commit 7f69d43

Please sign in to comment.