task}
+ onValueChange={(selectedItems) => setTaskState(selectedItems as any)}
itemId={(task) => (task ? task.id : '')}
itemToString={(task) => (task ? task.title : '')}
multiSelect={true}
@@ -91,14 +95,15 @@ export function TimeSheetFilterPopover() {
(status ? status.value : '')}
itemId={(item) => item.value}
- onValueChange={(selectedItems) => console.log(selectedItems)}
+ onValueChange={(selectedItems) => setStatusState(selectedItems)}
multiSelect={true}
triggerClassName="dark:border-gray-700"
/>
@@ -121,4 +126,4 @@ export function TimeSheetFilterPopover() {
>
)
-}
+})
diff --git a/apps/web/app/hooks/features/useTimelogFilterOptions.ts b/apps/web/app/hooks/features/useTimelogFilterOptions.ts
new file mode 100644
index 000000000..60733481f
--- /dev/null
+++ b/apps/web/app/hooks/features/useTimelogFilterOptions.ts
@@ -0,0 +1,25 @@
+import { timesheetFilterEmployeeState, timesheetFilterProjectState, timesheetFilterStatusState, timesheetFilterTaskState } from '@/app/stores';
+import { useAtom } from 'jotai';
+
+export function useTimelogFilterOptions() {
+ const [employeeState, setEmployeeState] = useAtom(timesheetFilterEmployeeState);
+ const [projectState, setProjectState] = useAtom(timesheetFilterProjectState);
+ const [statusState, setStatusState] = useAtom(timesheetFilterStatusState);
+ const [taskState, setTaskState] = useAtom(timesheetFilterTaskState);
+
+ const employee = employeeState;
+ const project = projectState;
+ const task = taskState
+
+
+ return {
+ statusState,
+ employee,
+ project,
+ task,
+ setEmployeeState,
+ setProjectState,
+ setTaskState,
+ setStatusState
+ };
+}
diff --git a/apps/web/app/hooks/features/useTimesheet.ts b/apps/web/app/hooks/features/useTimesheet.ts
index d2e398ade..c11e196ff 100644
--- a/apps/web/app/hooks/features/useTimesheet.ts
+++ b/apps/web/app/hooks/features/useTimesheet.ts
@@ -6,10 +6,11 @@ import { useCallback, useEffect } from 'react';
import { getTaskTimesheetLogsApi } from '@/app/services/client/api/timer/timer-log';
import moment from 'moment';
import { ITimeSheet } from '@/app/interfaces';
+import { useTimelogFilterOptions } from './useTimelogFilterOptions';
interface TimesheetParams {
- startDate: Date | string;
- endDate: Date | string;
+ startDate?: Date | string;
+ endDate?: Date | string;
}
export interface GroupedTimesheet {
@@ -45,7 +46,7 @@ export function useTimesheet({
}: TimesheetParams) {
const { user } = useAuthenticateUser();
const [timesheet, setTimesheet] = useAtom(timesheetRapportState);
-
+ const { employee, project } = useTimelogFilterOptions();
const { loading: loadingTimesheet, queryCall: queryTimesheet } = useQuery(getTaskTimesheetLogsApi);
const getTaskTimesheet = useCallback(
@@ -59,13 +60,21 @@ export function useTimesheet({
organizationId: user.employee?.organizationId,
tenantId: user.tenantId ?? '',
timeZone: user.timeZone?.split('(')[0].trim(),
+ employeeIds: employee?.map((member) => member.employee.id).filter((id) => id !== undefined),
+ projectIds: project?.map((project) => project.id).filter((id) => id !== undefined)
}).then((response) => {
setTimesheet(response.data);
}).catch((error) => {
console.error('Error fetching timesheet:', error);
});
},
- [user, queryTimesheet, setTimesheet]
+ [
+ user,
+ queryTimesheet,
+ setTimesheet,
+ employee,
+ project
+ ]
);
useEffect(() => {
getTaskTimesheet({ startDate, endDate });
diff --git a/apps/web/app/hooks/index.ts b/apps/web/app/hooks/index.ts
index 93610f55d..5f4642a6a 100644
--- a/apps/web/app/hooks/index.ts
+++ b/apps/web/app/hooks/index.ts
@@ -40,6 +40,7 @@ export * from './features/useTeamTasks';
export * from './features/useTimer';
export * from './features/useUser';
export * from './features/useUserProfilePage';
+export * from './features/useTimelogFilterOptions';
export * from './useCollaborative';
//export user personal setting
diff --git a/apps/web/app/interfaces/timer/ITimerLog.ts b/apps/web/app/interfaces/timer/ITimerLog.ts
index f7131863a..3e30da712 100644
--- a/apps/web/app/interfaces/timer/ITimerLog.ts
+++ b/apps/web/app/interfaces/timer/ITimerLog.ts
@@ -11,7 +11,9 @@ interface Project {
interface Task {
id: string;
title: string;
+ issueType?: ITaskIssue | null;
estimate: number | null;
+ taskStatus: string | null;
taskNumber: string;
}
@@ -71,5 +73,4 @@ export interface ITimeSheet {
employee: Employee;
duration: number;
isEdited: boolean;
- issueType?: ITaskIssue;
}
diff --git a/apps/web/app/services/client/api/timer/timer-log.ts b/apps/web/app/services/client/api/timer/timer-log.ts
index c7b86175b..f3a5b13b4 100644
--- a/apps/web/app/services/client/api/timer/timer-log.ts
+++ b/apps/web/app/services/client/api/timer/timer-log.ts
@@ -20,13 +20,17 @@ export async function getTaskTimesheetLogsApi({
tenantId,
startDate,
endDate,
- timeZone
+ timeZone,
+ projectIds = [],
+ employeeIds = []
}: {
organizationId: string,
tenantId: string,
startDate: string | Date,
endDate: string | Date,
- timeZone?: string
+ timeZone?: string,
+ projectIds?: string[],
+ employeeIds?: string[]
}) {
if (!organizationId || !tenantId || !startDate || !endDate) {
@@ -37,7 +41,6 @@ export async function getTaskTimesheetLogsApi({
if (isNaN(new Date(start).getTime()) || isNaN(new Date(end).getTime())) {
throw new Error('Invalid date format provided');
}
-
const params = new URLSearchParams({
'activityLevel[start]': '0',
'activityLevel[end]': '100',
@@ -53,6 +56,13 @@ export async function getTaskTimesheetLogsApi({
'relations[4]': 'task.taskStatus'
});
+ projectIds.forEach((id, index) => {
+ params.append(`projectIds[${index}]`, id);
+ });
+
+ employeeIds.forEach((id, index) => {
+ params.append(`employeeIds[${index}]`, id);
+ });
const endpoint = `/timesheet/time-log?${params.toString()}`;
return get(endpoint, { tenantId });
}
diff --git a/apps/web/app/stores/index.ts b/apps/web/app/stores/index.ts
index 35f126481..5ad723561 100644
--- a/apps/web/app/stores/index.ts
+++ b/apps/web/app/stores/index.ts
@@ -27,3 +27,4 @@ export * from './integration-tenant';
export * from './integration-github';
export * from './integration-types';
export * from './integration';
+export * from './time-logs'
diff --git a/apps/web/app/stores/time-logs.ts b/apps/web/app/stores/time-logs.ts
index 3cf5395ea..9f607b4ff 100644
--- a/apps/web/app/stores/time-logs.ts
+++ b/apps/web/app/stores/time-logs.ts
@@ -1,7 +1,18 @@
import { ITimerLogsDailyReport } from '@app/interfaces/timer/ITimerLogs';
import { atom } from 'jotai';
-import { ITimeSheet } from '../interfaces';
+import { IProject, ITeamTask, ITimeSheet, OT_Member } from '../interfaces';
+
+interface IFilterOption {
+ value: string;
+ label: string;
+}
export const timerLogsDailyReportState = atom([]);
export const timesheetRapportState = atom([])
+
+export const timesheetFilterEmployeeState = atom([]);
+export const timesheetFilterProjectState = atom([]);
+export const timesheetFilterTaskState = atom([]);
+
+export const timesheetFilterStatusState = atom([]);
diff --git a/apps/web/lib/components/custom-select/multi-select.tsx b/apps/web/lib/components/custom-select/multi-select.tsx
index 585758a13..670f79508 100644
--- a/apps/web/lib/components/custom-select/multi-select.tsx
+++ b/apps/web/lib/components/custom-select/multi-select.tsx
@@ -16,7 +16,8 @@ interface MultiSelectProps {
renderItem?: (item: T, onClick: () => void, isSelected: boolean) => JSX.Element;
defaultValue?: T | T[];
multiSelect?: boolean;
- removeItems?: boolean
+ removeItems?: boolean;
+ localStorageKey?: string;
}
export function MultiSelect({
@@ -29,13 +30,49 @@ export function MultiSelect({
renderItem,
defaultValue,
multiSelect = false,
- removeItems
+ removeItems,
+ localStorageKey = "select-items-selected",
}: MultiSelectProps) {
- const [selectedItems, setSelectedItems] = useState(Array.isArray(defaultValue) ? defaultValue : defaultValue ? [defaultValue] : []);
+ const [selectedItems, setSelectedItems] = useState(() => {
+ if (typeof window === 'undefined') return [];
+ try {
+ const saved = localStorage.getItem(localStorageKey);
+ return saved ? JSON.parse(saved) : [];
+ } catch {
+ return [];
+ }
+ });
const [isPopoverOpen, setPopoverOpen] = useState(false);
const [popoverWidth, setPopoverWidth] = useState(null);
const triggerRef = useRef(null);
+ // Load selected items from localStorage on component mount
+ useEffect(() => {
+ if (defaultValue) {
+ const initialItems = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
+ setSelectedItems(initialItems);
+ }
+ }, [defaultValue, setSelectedItems]);
+
+ useEffect(() => {
+ if (onValueChange) {
+ onValueChange(multiSelect ? selectedItems : selectedItems[0] || null);
+ }
+ }, [selectedItems, multiSelect, onValueChange]);
+
+ // Save selected items to localStorage whenever they change
+ // Handle persistence
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ try {
+ localStorage.setItem(localStorageKey, JSON.stringify(selectedItems));
+ } catch (error) {
+ console.error('Failed to save to localStorage:', error);
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [selectedItems, localStorageKey]);
+
const onClick = (item: T) => {
let newSelectedItems: T[];
if (multiSelect) {
@@ -49,25 +86,15 @@ export function MultiSelect({
setPopoverOpen(false);
}
setSelectedItems(newSelectedItems);
- if (onValueChange) {
- onValueChange(multiSelect ? newSelectedItems : newSelectedItems[0]);
- }
};
-
const removeItem = (item: T) => {
const newSelectedItems = selectedItems.filter((selectedItem) => itemId(selectedItem) !== itemId(item));
setSelectedItems(newSelectedItems);
- if (onValueChange) {
- onValueChange(multiSelect ? newSelectedItems : newSelectedItems.length > 0 ? newSelectedItems[0] : null);
- }
};
const removeAllItems = () => {
setSelectedItems([]);
- if (onValueChange) {
- onValueChange(null);
- }
};
useEffect(() => {
@@ -83,16 +110,6 @@ export function MultiSelect({
}, [removeItems, removeAllItems]) // deepscan-disable-line
- useEffect(() => {
- const initialItems = Array.isArray(defaultValue) ? defaultValue : defaultValue ? [defaultValue] : [];
- setSelectedItems(initialItems);
- if (onValueChange) {
- onValueChange(multiSelect ? initialItems : initialItems[0] || null);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [defaultValue]);
-
-
useEffect(() => {
if (triggerRef.current) {
setPopoverWidth(triggerRef.current.offsetWidth);
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 c530fbfd5..83468b5bb 100644
--- a/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
+++ b/apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
@@ -271,7 +271,7 @@ export function DataTableTimeSheet({ data }: { data?: GroupedTimesheet[] }) {
24:30h
-
+
{getTimesheetButtons(status as StatusType, t, handleButtonClick)}
diff --git a/apps/web/lib/features/task/task-block-card.tsx b/apps/web/lib/features/task/task-block-card.tsx
index d84d6985d..c008e6ab6 100644
--- a/apps/web/lib/features/task/task-block-card.tsx
+++ b/apps/web/lib/features/task/task-block-card.tsx
@@ -76,7 +76,7 @@ export default function TaskBlockCard(props: TaskItemProps) {
previousValue + currentValue.duration,
0
)) ||
- 0
+ 0
);
return (
diff --git a/apps/web/lib/features/task/task-issue.tsx b/apps/web/lib/features/task/task-issue.tsx
index 4632a8cf3..1b494a2f8 100644
--- a/apps/web/lib/features/task/task-issue.tsx
+++ b/apps/web/lib/features/task/task-issue.tsx
@@ -221,7 +221,7 @@ export function TaskIssueStatusTimesheet({
}: { task: Nullable; showIssueLabels?: boolean } & IClassName) {
return (