{task.employee.fullName}
@@ -160,9 +161,16 @@ const CalendarDataView = ({ data, t }: { data?: GroupedTimesheet[], t: Translati
dash
taskNumberClassName="text-sm"
/>
-
- {task.project &&
}
-
{task.project && task.project.name}
+
+ {task.project?.imageUrl && (
+
+ )}
+
+ {task.project?.name ?? 'No Project'}
+
))}
@@ -258,7 +266,7 @@ const BaseCalendarDataView = ({ data, daysLabels, t, CalendarComponent }: BaseCa
{task.project?.imageUrl && (
)}
diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx
index 1f90136cd..1cd5ddd81 100644
--- a/apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx
+++ b/apps/web/app/[locale]/timesheet/[memberId]/components/EditTaskModal.tsx
@@ -1,20 +1,20 @@
import { Modal, statusColor } from "@/lib/components";
-import { IoMdArrowDropdown } from "react-icons/io";
import { FaRegClock } from "react-icons/fa";
import { DatePickerFilter } from "./TimesheetFilterDate";
import { FormEvent, useCallback, useState } from "react";
import { useTranslations } from "next-intl";
import { clsxm } from "@/app/utils";
import { Item, ManageOrMemberComponent, getNestedValue } from "@/lib/features/manual-time/manage-member-component";
-import { useOrganizationProjects } from "@/app/hooks";
+import { useOrganizationProjects, useOrganizationTeams } from "@/app/hooks";
import { CustomSelect, TaskNameInfoDisplay } from "@/lib/features";
import { statusTable } from "./TimesheetAction";
import { TimesheetLog } from "@/app/interfaces";
-import { secondsToTime } from "@/app/helpers";
+import { differenceBetweenHours, formatTimeFromDate, secondsToTime, toDate } from "@/app/helpers";
import { useTimesheet } from "@/app/hooks/features/useTimesheet";
import { toast } from "@components/ui/use-toast";
import { ToastAction } from "@components/ui/toast";
import { ReloadIcon } from "@radix-ui/react-icons";
+import { addMinutes, format, parseISO } from "date-fns";
export interface IEditTaskModalProps {
isOpen: boolean;
@@ -23,24 +23,27 @@ export interface IEditTaskModalProps {
}
export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskModalProps) {
const { organizationProjects } = useOrganizationProjects();
+ const { activeTeam } = useOrganizationTeams();
const t = useTranslations();
const { updateTimesheet, loadingUpdateTimesheet } = useTimesheet({})
+ const initialTimeRange = {
+ startTime: formatTimeFromDate(dataTimesheet.startedAt),
+ endTime: formatTimeFromDate(dataTimesheet.stoppedAt),
+ };
const [dateRange, setDateRange] = useState<{ date: Date | null }>({
date: dataTimesheet.timesheet?.startedAt ? new Date(dataTimesheet.timesheet.startedAt) : new Date(),
});
+ const seconds = differenceBetweenHours(toDate(dataTimesheet.startedAt), toDate(dataTimesheet.stoppedAt));
+ const { h: hours, m: minutes } = secondsToTime(seconds);
- const { h: hours, m: minutes } = secondsToTime(dataTimesheet.timesheet.duration);
-
- const [timeRange, setTimeRange] = useState<{ startTime: string; endTime: string }>({
- startTime: dataTimesheet.timesheet?.startedAt
- ? dataTimesheet.timesheet.startedAt.toString().slice(0, 5)
- : '',
- endTime: dataTimesheet.timesheet?.stoppedAt
- ? dataTimesheet.timesheet.stoppedAt.toString().slice(0, 5)
- : '',
- });
+ const [timeRange, setTimeRange] = useState<{ startTime: string; endTime: string }>(initialTimeRange);
+ /**
+ * Updates the start or end time in the state based on the provided key and value.
+ * @param {string} key - The key of the time range to update. This can be either 'startTime' or 'endTime'.
+ * @param {string} value - The new value for the selected time range.
+ */
const updateTime = (key: 'startTime' | 'endTime', value: string) => {
setTimeRange(prevState => ({
...prevState,
@@ -51,10 +54,16 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
isBillable: dataTimesheet.isBillable ?? true,
projectId: dataTimesheet.project?.id || '',
notes: dataTimesheet.description || '',
+ employeeId: dataTimesheet.employeeId || '',
});
const memberItemsLists = {
Project: organizationProjects,
};
+ /**
+ * Updates the project id in the form state when a project is selected or deselected in the dropdown.
+ * @param {Object} values - An object with the selected values from the dropdown.
+ * @param {Item | null} values['Project'] - The selected project.
+ */
const handleSelectedValuesChange = (values: { [key: string]: Item | null }) => {
setTimesheetData((prev) => ({
...prev,
@@ -96,10 +105,11 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
...timeRange.endTime.split(':').map(Number)
)
);
+
const payload = {
id: dataTimesheet.id,
isBillable: timesheetData.isBillable,
- employeeId: dataTimesheet.employeeId,
+ employeeId: timesheetData.employeeId,
logType: dataTimesheet.logType,
source: dataTimesheet.source,
startedAt,
@@ -150,6 +160,12 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
const handleFromChange = (fromDate: Date | null) => {
setDateRange((prev) => ({ ...prev, date: fromDate }));
};
+ const getMinEndTime = (): string => {
+ if (!timeRange.startTime) return "00:00";
+ const startDate = parseISO(`2000-01-01T${timeRange.startTime}`);
+ return format(addMinutes(startDate, 5), 'HH:mm');
+ };
+
return (
for
-
{dataTimesheet.employee?.fullName ?? ""}
-
+
setTimesheetData({ ...timesheetData, employeeId: value.employeeId })}
+ renderOption={(option) => (
+
+
+
{option.employee.fullName}
+
+ )}
+ />
-
{t('dailyPlan.TASK_TIME')}
+
{t('dailyPlan.TASK_TIME')}
- {hours}:{minutes} h
+ {String(hours).padStart(2, '0')}:{String(minutes).padStart(2, '0')} h
@@ -189,13 +218,13 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
*
updateTime("startTime", e.target.value)}
className="w-full p-1 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md"
required
@@ -208,10 +237,11 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
*
updateTime('endTime', e.target.value)}
className="w-full p-1 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md"
required
diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetCard.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetCard.tsx
index 55bb42912..f0e82584e 100644
--- a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetCard.tsx
+++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetCard.tsx
@@ -130,10 +130,10 @@ export const TimesheetCardDetail = ({ data }: { data?: Record
- {t('timer.TOTAL_HOURS')}
-
+ {t('timer.TOTAL_HOURS').split(' ')[0]}{':'}
+
diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetDetailModal.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetDetailModal.tsx
index 139fdb610..33b3eeb05 100644
--- a/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetDetailModal.tsx
+++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetDetailModal.tsx
@@ -69,6 +69,7 @@ export default TimesheetDetailModal
+
const MembersWorkedCard = ({ element, t }: { element: TimesheetLog[], t: TranslationHooks }) => {
const memberWork = groupBy(element, (items) => items.employeeId);
const memberWorkItems = Object.entries(memberWork)
diff --git a/apps/web/app/[locale]/timesheet/[memberId]/page.tsx b/apps/web/app/[locale]/timesheet/[memberId]/page.tsx
index cf0f37493..9f6f9668a 100644
--- a/apps/web/app/[locale]/timesheet/[memberId]/page.tsx
+++ b/apps/web/app/[locale]/timesheet/[memberId]/page.tsx
@@ -271,7 +271,7 @@ const ViewToggleButton: React.FC
= ({ mode, active, icon,
);
+ViewToggleButton.displayName = 'ViewToggleButton';
diff --git a/apps/web/app/helpers/date.ts b/apps/web/app/helpers/date.ts
index 922f3a5b1..71d8fc6d3 100644
--- a/apps/web/app/helpers/date.ts
+++ b/apps/web/app/helpers/date.ts
@@ -79,6 +79,40 @@ export function differenceBetweenHours(startedAt: Date, stoppedAt: Date): number
}
+/**
+ * Converts a given date string to a time string in the format HH:mm.
+ *
+ * This function takes an optional date string as input. If the input is not
+ * provided, the function returns an empty string. If the input is a valid date
+ * string, the function converts the string to a Date object, formats the time
+ * in the format HH:mm, and returns the result as a string.
+ *
+ * @param {string | undefined} dateString - The date string to format
+ * @returns {string} The formatted time string
+ */
+export const formatTimeFromDate = (date: string | Date | undefined) => {
+ if (!date) return "";
+ const dateObject = date instanceof Date ? date : new Date(date);
+ const hours = dateObject.getHours().toString().padStart(2, '0');
+ const minutes = dateObject.getMinutes().toString().padStart(2, '0');
+
+ return `${hours}:${minutes}`;
+};
+
+/**
+ * Converts a given input to a Date object.
+ *
+ * This function accepts either a Date object or a string representation of a date.
+ * If the input is already a Date object, it returns the input as-is. If the input
+ * is a string, it converts the string to a Date object and returns the result.
+ *
+ * @param {Date | string} date - The date input, which can be either a Date object or a string.
+ * @returns {Date} The corresponding Date object.
+ */
+export const toDate = (date: Date | string) =>
+ (date instanceof Date ? date : new Date(date));
+
+
export function convertMsToTime(milliseconds: number) {
let seconds = Math.floor(milliseconds / 1000);
let minutes = Math.floor(seconds / 60);
@@ -142,6 +176,7 @@ export const tomorrowDate = moment().add(1, 'days').toDate();
export const yesterdayDate = moment().subtract(1, 'days').toDate();
+
export const formatDayPlanDate = (dateString: string | Date, format?: string) => {
if (dateString.toString().length > 10) {
dateString = dateString.toString().split('T')[0];
diff --git a/apps/web/app/hooks/features/useTimesheet.ts b/apps/web/app/hooks/features/useTimesheet.ts
index ecbcaacda..b96b8dc67 100644
--- a/apps/web/app/hooks/features/useTimesheet.ts
+++ b/apps/web/app/hooks/features/useTimesheet.ts
@@ -167,7 +167,7 @@ export function useTimesheet({
const response = await queryUpdateTimesheet(timesheet);
setTimesheet(prevTimesheet =>
prevTimesheet.map(item =>
- item.timesheet?.id === response.data.id
+ item.id === response.data.id
? response.data
: item
)
diff --git a/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx b/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx
index 37707b6ce..56aee673e 100644
--- a/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx
+++ b/apps/web/components/pages/task/details-section/blocks/task-secondary-info.tsx
@@ -358,7 +358,7 @@ export function ProjectDropDown(props: ITaskProjectDropdownProps) {
<>
{
return (
-
+
{' '}
{item.name}
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 03f66bac9..2c73b7deb 100644
--- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
+++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
@@ -224,7 +224,7 @@ export function DataTableTimeSheet({ data, user }: { data?: GroupedTimesheet[],