Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,26 @@ import type { ProjectRowProps } from "./types";
*
* @param {Array} props.dates - Array of date strings for the week.
* @param {TaskProps} props.tasks - TaskProps object containing task data for the week.
* @param {boolean} props.hideTime - Optional flag to hide time entries in the row.
* @param {React.ReactNode} props.children - Child components to be rendered inside the accordion content.
*/
export const ProjectRow = ({
dates,
tasks,
hideTime,
children,
...rest
}: ProjectRowProps) => {
const [collapsed, setCollapsed] = useState(false);

const projectData = useMemo(() => {
let total = 0;
const totalTimeEntries = [];
const totalTimeEntries: string[] = [];

if (hideTime) {
return { total, totalTimeEntries };
}

for (const date of dates) {
const currentTotal = calculateTotalHours(tasks, date);
totalTimeEntries.push(
Expand All @@ -39,7 +46,7 @@ export const ProjectRow = ({
total += currentTotal;
}
return { total, totalTimeEntries };
}, [dates, tasks]);
}, [dates, tasks, hideTime]);

return (
<Accordion.Root
Expand All @@ -53,7 +60,7 @@ export const ProjectRow = ({
<div {...props}>
<BaseProjectRow
{...rest}
totalHours={floatToTime(projectData.total, 2)}
totalHours={hideTime ? "" : floatToTime(projectData.total, 2)}
timeEntries={projectData.totalTimeEntries}
collapsed={collapsed}
totalHoursTheme="green"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useFrappePostCall } from "frappe-react-sdk";
*/
import TaskPopover from "@/components/taskPopover";
import { calculateTotalHours, parseFrappeErrorMsg } from "@/lib/utils";
import { usePersonalTimesheet } from "@/pages/timesheet/personal/context";
import type { TaskDataItemProps } from "@/types/timesheet";
import type { TaskRowProps } from "./types";
import { InlineTimeEntry } from "../inline-time-entry";
Expand All @@ -28,29 +29,28 @@ import { InlineTimeEntry } from "../inline-time-entry";
* @param {string} props.taskKey - Key of the task to be rendered.
* @param {TaskProps} props.tasks - TaskProps object containing task data for the week.
* @param {string} props.status - Status of the task.
* @param {Array} props.likedTaskData - Array of liked task data to determine if the task is liked or not.
* @param {boolean} props.disabled - Whether the task row is disabled.
* @param {number} props.dailyWorkingHours - Daily working hours for the task.
* @param {string} props.employee - Employee for the timesheet entry.
* @param {function} props.getLikedTaskData - Function to fetch liked task data after toggling like status.
* @param {boolean} props.hideStarButton - Whether to hide the star button for liking the task.
*/
export const TaskRow = ({
dates,
taskKey,
tasks,
status,
likedTaskData,
disabled,
dailyWorkingHours,
totalTimeEntriesInHours,
employee,
getLikedTaskData,
hideLikeButton,
setSelectedTask,
...rest
}: TaskRowProps) => {
const [taskLiked, setTaskLiked] = useState(false);
const likedTaskData = usePersonalTimesheet(
({ state }) => state.likedTaskData,
);
const { call: toggleLikeCall } = useFrappePostCall(
"frappe.desk.like.toggle_like",
);
Expand Down Expand Up @@ -112,7 +112,6 @@ export const TaskRow = ({
setTaskLiked((prev) => !prev);
try {
await toggleLikeCall(data);
getLikedTaskData?.();
} catch (err) {
const error = parseFrappeErrorMsg(
err as Parameters<typeof parseFrappeErrorMsg>[0],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@ import type {
* Internal dependencies
*/
import type { WorkingFrequency } from "@/types";
import type {
HolidayProp,
LeaveProps,
TaskDataProps,
TaskProps,
} from "@/types/timesheet";
import type { HolidayProp, LeaveProps, TaskProps } from "@/types/timesheet";

export interface HeaderRowProps extends Omit<BaseHeaderRowProps, "days"> {
dates: string[];
Expand Down Expand Up @@ -90,6 +85,7 @@ export interface ProjectRowProps extends Omit<
> {
dates: string[];
tasks: TaskProps;
hideTime?: boolean;
children?: React.ReactNode;
}

Expand All @@ -101,12 +97,10 @@ export interface TaskRowProps extends Omit<
taskKey: string;
tasks: TaskProps;
status: string;
likedTaskData?: TaskDataProps[];
disabled?: boolean;
dailyWorkingHours?: number;
totalTimeEntriesInHours?: number[];
employee?: string;
getLikedTaskData?: () => void;
hideLikeButton?: boolean;
setSelectedTask?: (taskKey: string) => void;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ import {
import { getHolidayList } from "@/lib/utils";
import { useTimesheetOutletContext } from "@/pages/timesheet/outletContext";
import { WorkingFrequency } from "@/types";
import type {
HolidayProp,
LeaveProps,
TaskDataProps,
TaskProps,
} from "@/types/timesheet";
import type { HolidayProp, LeaveProps, TaskProps } from "@/types/timesheet";
import { ProjectRow } from "./components/row/projectRow";
import { TaskRow } from "./components/row/taskRow";
import { TimeOffRow } from "./components/row/timeOffRow";
Expand All @@ -38,13 +33,11 @@ export type PersonalTimesheetRowProps = {
disabled?: boolean;
workingFrequency: WorkingFrequency;
importTasks?: boolean;
loadingLikedTasks?: boolean;
likedTaskData?: Array<object>;
getLikedTaskData?: () => void;
hideLikeButton?: boolean;
setSelectedTask?: (task: string) => void;
onButtonClick?: () => void;
status: ApprovalStatusLabelType;
hideTotalRow: boolean;
};

export const PersonalTimesheetRow = ({
Expand All @@ -58,11 +51,10 @@ export const PersonalTimesheetRow = ({
workingHour,
workingFrequency,
disabled,
likedTaskData,
onButtonClick,
status,
hideTotalRow,
setSelectedTask,
getLikedTaskData,
hideLikeButton,
}: PersonalTimesheetRowProps) => {
const { openAddTimeDialog } = useTimesheetOutletContext();
Expand Down Expand Up @@ -92,31 +84,34 @@ export const PersonalTimesheetRow = ({
totalHoursTheme,
}) => (
<>
<TotalRow
breadcrumbs={{
items: [
{ label: "Projects", interactive: false },
{ label: "Tasks", interactive: false },
],
size: "md",
highlightAllItems: true,
crumbClassName: "first:pl-0 last:pr-0",
}}
totalHours={totalHours}
totalHoursTheme={totalHoursTheme}
totalTimeEntries={totalTimeEntries}
className="pl-7.5"
starred={true}
disabled={disabled}
onCellClick={openAddTimeDialog}
/>
{!hideTotalRow ? (
<TotalRow
breadcrumbs={{
items: [
{ label: "Projects", interactive: false },
{ label: "Tasks", interactive: false },
],
size: "md",
highlightAllItems: true,
crumbClassName: "first:pl-0 last:pr-0",
}}
totalHours={totalHours}
totalHoursTheme={totalHoursTheme}
totalTimeEntries={totalTimeEntries}
className="pl-7.5"
starred={true}
disabled={disabled}
onCellClick={openAddTimeDialog}
/>
) : null}

{projects.map((project) => (
<ProjectRow
key={project.project}
dates={dates}
tasks={project.tasks}
label={project.project_name || project.project}
hideTime={hideTotalRow}
className="pl-7.5"
>
{Object.entries(project.tasks).map(([taskKey, task]) => (
Expand All @@ -127,14 +122,12 @@ export const PersonalTimesheetRow = ({
tasks={{ [taskKey]: task }}
label={task.subject || task.name}
status={task.status}
likedTaskData={likedTaskData as TaskDataProps[]}
className="pl-13.5"
disabled={disabled}
dailyWorkingHours={dailyWorkingHours}
totalTimeEntriesInHours={totalTimeEntriesInHours}
employee={employee}
setSelectedTask={setSelectedTask}
getLikedTaskData={getLikedTaskData}
hideLikeButton={hideLikeButton}
/>
))}
Expand Down
6 changes: 2 additions & 4 deletions frontend/packages/app/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ export const calculateLeaveHours = (
* @param compositeFilters Array of FilterCondition objects.
* @returns Array of Frappe filters in the format [[fieldCategory, field, operator, value]].
*/
const buildFrappeFilters = (compositeFilters: FilterCondition[]) => {
export const buildFrappeFilters = (compositeFilters: FilterCondition[]) => {
const f = compositeFilters.filter((filter) => filter.fieldCategory);

return f
Expand Down Expand Up @@ -508,7 +508,7 @@ const buildFrappeFilters = (compositeFilters: FilterCondition[]) => {
* @param compositeFilters Array of FilterCondition objects.
* @returns Object containing startDate, maxWeek, and frappeFilters derived from the composite filters.
*/
const buildCompositeFilters = (compositeFilters: FilterCondition[]) => {
export const buildCompositeFilters = (compositeFilters: FilterCondition[]) => {
if (compositeFilters.length === 0) {
return {
startDate: undefined,
Expand Down Expand Up @@ -574,5 +574,3 @@ const buildCompositeFilters = (compositeFilters: FilterCondition[]) => {
frappeFilters,
};
};

export default buildCompositeFilters;
71 changes: 71 additions & 0 deletions frontend/packages/app/src/pages/timesheet/personal/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* External dependencies.
*/
import type { ApprovalStatusType } from "@next-pms/design-system/components";
import type { FilterCondition } from "@rtcamp/frappe-ui-react";
import { createContext, useContextSelector } from "use-context-selector";

/**
* Internal dependencies.
*/
import type {
DataProp,
TaskDataProps,
TimesheetFilters,
} from "@/types/timesheet";

export interface PersonalTimesheetContextProps {
state: {
hasMoreWeeks: boolean;
isLoadingPersonalData: boolean;
isInitialLoad: boolean;
isFilterRequest: boolean;
timesheetData: DataProp;
filters: TimesheetFilters;
compositeFilters: FilterCondition[];
likedTaskData: TaskDataProps[];
};
actions: {
loadData: () => void;
handleSearchChange: (value: string) => void;
handleApprovalStatusChange: (value?: ApprovalStatusType | null) => void;
handleCompositeFilterChange: (value: FilterCondition[]) => void;
};
}

export const initialTimesheetData: DataProp = {
working_hour: 0,
working_frequency: "Per Day",
data: {},
leaves: [],
holidays: [],
};

export const PersonalTimesheetContext =
createContext<PersonalTimesheetContextProps>({
state: {
hasMoreWeeks: false,
isLoadingPersonalData: false,
isInitialLoad: true,
isFilterRequest: false,
timesheetData: initialTimesheetData,
filters: {
search: "",
approvalStatus: undefined,
},
compositeFilters: [],
likedTaskData: [],
},
actions: {
loadData: () => null,
handleSearchChange: () => null,
handleApprovalStatusChange: () => null,
handleCompositeFilterChange: () => null,
},
});

export const usePersonalTimesheet = <T>(
selector: (state: PersonalTimesheetContextProps) => T,
) => {
return useContextSelector(PersonalTimesheetContext, selector);
};
Loading
Loading