Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat]: Add Timelog FilterOptions #3333

Merged
merged 5 commits into from
Nov 14, 2024
Merged

Conversation

Innocent-Akim
Copy link
Contributor

@Innocent-Akim Innocent-Akim commented Nov 13, 2024

#3044

Description

Please include a summary of the changes and the related issue.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

Previous screenshots

Please add here videos or images of previous status

Current screenshots

Please add here videos or images of previous status

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a custom hook for managing timesheet filter options, enhancing user control over displayed data.
    • Added new state management capabilities for employee, project, task, and status filters.
  • Improvements

    • Refactored the TimeSheetFilterPopover for better performance and user interface responsiveness.
    • Enhanced the getTaskTimesheetLogsApi function to accept additional filtering parameters for improved data retrieval.
    • Updated the DataTableTimeSheet component for improved styling flexibility.
    • Integrated local storage functionality in the MultiSelect component for better state persistence.
    • Modified the TaskBlockCard component to ensure the total worked tasks timer defaults to zero when no tasks are found.
  • Bug Fixes

    • Updated error handling and state management for timesheet data fetching to ensure reliability.
  • Documentation

    • Expanded available hooks and exports for better integration across the application.

Copy link
Contributor

coderabbitai bot commented Nov 13, 2024

Walkthrough

The pull request introduces significant updates to the TimeSheetFilterPopover component, including the implementation of a new custom hook, useTimelogFilterOptions, to enhance state management. The TimeSheetFilterPopover is now memoized with React.memo to optimize rendering. Various interfaces related to timesheet data have been updated to incorporate new properties, while the API function for fetching timesheet logs has been modified to accept additional filtering parameters. Additionally, new state atoms for managing filter options have been added, improving the overall functionality and flexibility of the timesheet features.

Changes

File Change Summary
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx Refactored TimeSheetFilterPopover to use React.memo, updated state management with useTimelogFilterOptions, and improved onValueChange handlers. Conditional class application for Clear labels added.
apps/web/app/hooks/features/useTimelogFilterOptions.ts Introduced useTimelogFilterOptions hook for managing filter options with jotai state management.
apps/web/app/hooks/features/useTimesheet.ts Updated TimesheetParams interface to make startDate and endDate optional. Integrated useTimelogFilterOptions for employee and project filtering.
apps/web/app/hooks/index.ts Added export for useTimelogFilterOptions.
apps/web/app/interfaces/timer/ITimerLog.ts Modified Task and ITimeSheet interfaces to add and update properties for better task representation.
apps/web/app/services/client/api/timer/timer-log.ts Expanded getTaskTimesheetLogsApi function to accept projectIds and employeeIds as optional parameters.
apps/web/app/stores/index.ts Added export for time-logs module.
apps/web/app/stores/time-logs.ts Updated imports and introduced new state atoms for filtering capabilities related to timesheets.
apps/web/lib/features/integrations/calendar/table-time-sheet.tsx Modified DataTableTimeSheet component for dynamic class assignment in styling.
apps/web/lib/features/task/task-issue.tsx Adjusted access of issueType from the task object to reflect a nested structure.

Possibly related PRs

Suggested reviewers

  • evereq
  • Cedric921

Poem

🐰 In the meadow where filters play,
The Popover hops, brightening the day.
With states that dance and options galore,
Timesheets now sing, who could ask for more?
So let’s cheer for changes, both big and small,
A joyful leap for the timesheet, after all! 🎉

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 eslint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/web/lib/components/custom-select/multi-select.tsx

Oops! Something went wrong! :(

ESLint: 8.46.0

ESLint couldn't find the config "next/core-web-vitals" to extend from. Please check that the name of the config is correct.

The config "next/core-web-vitals" was referenced from the config file in "/apps/web/.eslintrc.json".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3a2a258 and 40cafab.

📒 Files selected for processing (1)
  • apps/web/lib/components/custom-select/multi-select.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/lib/components/custom-select/multi-select.tsx

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Innocent-Akim Innocent-Akim self-assigned this Nov 13, 2024
@Innocent-Akim Innocent-Akim added feature WEB Web app Don't Merge Do not merge this PR until label not removed Ever Teams labels Nov 13, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (16)
apps/web/app/stores/index.ts (1)

30-30: LGTM! Consider grouping with related exports.

The export statement is correctly implemented. However, for better organization, consider grouping it with other time-related exports (like 'timer') or task-related exports since timesheets are closely related to these domains.

 export * from './timer';
+export * from './time-logs';
 export * from './invitations';
 export * from './team-tasks';
-
-export * from './languages';
-export * from './timezones';
-export * from './collaborative';
-
-export * from './task-status';
-export * from './task-version';
-export * from './task-priorities';
-export * from './task-sizes';
-export * from './task-labels';
-export * from './issue-type';
-export * from './task-related-issue-type';
-export * from './daily-plan';
-
-export * from './roles';
-export * from './role-permissions';
-export * from './request-to-join';
-export * from './public';
-
-export * from './integration-tenant';
-export * from './integration-github';
-export * from './integration-types';
-export * from './integration';
-export * from './time-logs'
apps/web/app/hooks/features/useTimelogFilterOptions.ts (2)

15-26: Improve return object organization and fix naming

  1. Remove the extra blank line at line 15
  2. Consider grouping related values with their setters for better maintainability

Apply this organization to improve readability:

 return {
-    statuState,
-    employee,
-    project,
-    task,
-    setEmployeeState,
-    setProjectState,
-    setTaskState,
-    setStatuState
+    employee,
+    setEmployeeState,
+    project,
+    setProjectState,
+    status: statusState,
+    setStatusState,
+    task,
+    setTaskState
 };
🧰 Tools
🪛 GitHub Check: Cspell

[warning] 17-17:
Unknown word (statu)


[warning] 24-24:
Unknown word (Statu)


5-26: Consider enhancing the hook with additional features

To improve the robustness and performance of this hook, consider:

  1. Memoizing the return object using useMemo to prevent unnecessary re-renders
  2. Adding validation for initial states
  3. Implementing error handling for invalid state values

Example implementation:

export function useTimelogFilterOptions() {
    const [employeeState, setEmployeeState] = useAtom(timesheetFilterEmployeeState);
    // ... other state declarations ...

    return useMemo(() => ({
        employee,
        setEmployeeState,
        // ... other values and setters ...
    }), [employee, project, statusState, task]);
}
🧰 Tools
🪛 GitHub Check: Cspell

[warning] 8-8:
Unknown word (statu)


[warning] 8-8:
Unknown word (Statu)


[warning] 17-17:
Unknown word (statu)


[warning] 24-24:
Unknown word (Statu)

apps/web/app/interfaces/timer/ITimerLog.ts (1)

14-14: Consider using enums or string literals for better type safety

While adding issueType and taskStatus as nullable strings provides flexibility, using more specific types would improve type safety and prevent potential inconsistencies.

Consider refactoring to use string literals or enums:

type TaskIssueType = 'bug' | 'feature' | 'improvement' | null;
type TaskStatus = 'todo' | 'in_progress' | 'done' | null;

interface Task {
  // ... other properties
  issueType: TaskIssueType;
  taskStatus: TaskStatus;
}

Also applies to: 16-16

apps/web/app/hooks/index.ts (1)

43-43: Consider reorganizing the export location.

While the export follows the correct naming convention, consider:

  1. Moving it to maintain alphabetical ordering with other feature hooks
  2. Grouping it with other time-related hooks like useTimer for better organization

Apply this diff to maintain consistent ordering:

export * from './features/useTeamTasks';
export * from './features/useTimer';
+export * from './features/useTimelogFilterOptions';
export * from './features/useUser';
export * from './features/useUserProfilePage';
-export * from './features/useTimelogFilterOptions';
export * from './useCollaborative';
apps/web/app/hooks/features/useTimesheet.ts (2)

63-64: Consider handling empty arrays explicitly

While filtering undefined values is good, consider whether empty arrays should be treated differently from undefined values in the API call. This might affect how the backend filters the results.

-employeeIds: employee?.map((member) => member.employee.id).filter((id) => id !== undefined),
-projectIds: project?.map((project) => project.id).filter((id) => id !== undefined)
+employeeIds: employee?.length ? employee.map((member) => member.employee.id).filter((id) => id !== undefined) : undefined,
+projectIds: project?.length ? project.map((project) => project.id).filter((id) => id !== undefined) : undefined

66-69: Enhance error handling for better user experience

The current error handling only logs to console. Consider adding proper error state management and user feedback.

 }).catch((error) => {
     console.error('Error fetching timesheet:', error);
+    // Assuming you have an error state atom
+    setTimesheetError(error.message);
+    // Consider showing a user-friendly notification
+    showErrorNotification('Failed to fetch timesheet data');
 });
apps/web/lib/components/custom-select/multi-select.tsx (1)

Line range hint 1-183: Consider performance and accessibility improvements

While the component functions correctly, there are several opportunities for optimization:

  1. Memoize callback functions to prevent unnecessary re-renders:
const onClick = useCallback((item: T) => {
    // ... existing onClick logic
}, [multiSelect, selectedItems, itemId, onValueChange]);

const removeItem = useCallback((item: T) => {
    // ... existing removeItem logic
}, [multiSelect, selectedItems, itemId, onValueChange]);
  1. Memoize derived state:
const selectedItemsCount = useMemo(() => selectedItems.length, [selectedItems]);
  1. Enhance accessibility:
 <PopoverContent
+    role="listbox"
+    aria-multiselectable={multiSelect}
     className={cn(
         'w-full max-w-full max-h-[80vh] border border-transparent dark:bg-dark--theme-light',
         popoverClassName
     )}
 >
  1. Consider extracting side effects into custom hooks:
function usePopoverWidth(triggerRef: RefObject<HTMLButtonElement>) {
    const [width, setWidth] = useState<number | null>(null);
    useEffect(() => {
        if (triggerRef.current) {
            setWidth(triggerRef.current.offsetWidth);
        }
    }, []);
    return width;
}

Would you like me to provide more detailed examples for any of these improvements?

apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (4)

17-17: Consider adding prop types for memoization effectiveness

While memoizing the component is good for performance, it would be more effective to:

  1. Define proper prop types for the component
  2. Implement a custom comparison function as the second argument to React.memo if needed

Example implementation:

type TimeSheetFilterPopoverProps = {
  // Add your prop types here
}

export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover(props: TimeSheetFilterPopoverProps) {
  // ... component implementation
}, (prevProps, nextProps) => {
  // Add custom comparison if needed
  return true;
});

Line range hint 24-28: Simplify the useEffect implementation

The current useEffect implementation can be simplified by directly setting the state in the clear filter button click handler instead.

Consider replacing the useEffect with a direct state update:

- React.useEffect(() => {
-     if (shouldRemoveItems) {
-         setShouldRemoveItems(false);
-     }
- }, [shouldRemoveItems]);

  // In the clear filter button handler
- onClick={() => setShouldRemoveItems(true)}
+ onClick={() => {
+     // Clear the filters directly
+     setEmployeeState([]);
+     setProjectState([]);
+     setStatusState([]);
+     setTaskState([]);
+ }}
🧰 Tools
🪛 GitHub Check: Cspell

[warning] 22-22:
Unknown word (Statu)


[warning] 22-22:
Unknown word (statu)


Line range hint 49-104: Enhance accessibility and reduce code duplication

The filter sections have several areas for improvement:

  1. Missing accessibility attributes for interactive elements
  2. Repeated MultiSelect pattern could be abstracted
  3. Hardcoded text sizes could use design tokens

Consider creating a reusable FilterSection component:

type FilterSectionProps<T> = {
  label: string;
  items: T[];
  value: T[];
  onChange: (items: T[]) => void;
  itemToString: (item: T) => string;
  itemId: (item: T) => string;
};

function FilterSection<T>({ label, items, value, onChange, itemToString, itemId }: FilterSectionProps<T>) {
  return (
    <div role="region" aria-label={label}>
      <label className="flex justify-between text-gray-600 mb-1 text-sm">
        <span className="text-xs">{label}</span>
        <span 
          role="button"
          tabIndex={0}
          className={clsxm("text-primary/10", value?.length > 0 && "text-primary dark:text-primary-light")}
        >
          Clear
        </span>
      </label>
      <MultiSelect
        items={items}
        onValueChange={onChange}
        itemToString={itemToString}
        itemId={itemId}
        multiSelect={true}
        triggerClassName="dark:border-gray-700"
        aria-label={`Select ${label}`}
      />
    </div>
  );
}

Line range hint 106-120: Add proper button types and loading states

The filter action buttons are missing proper type attributes and loading states.

  <Button
    onClick={() => setShouldRemoveItems(true)}
    variant={'outline'}
+   type="button"
    className='flex items-center text-sm justify-center h-10 rounded-lg dark:text-gray-300'
  >
    <span className="text-sm">Clear Filter</span>
  </Button>
  <Button
+   type="submit"
+   disabled={isLoading}
    className='flex items-center text-sm justify-center h-10 rounded-lg bg-primary dark:bg-primary-light dark:text-gray-300'
  >
-   <span className="text-sm">Apply Filter</span>
+   <span className="text-sm">{isLoading ? 'Applying...' : 'Apply Filter'}</span>
  </Button>
apps/web/lib/features/integrations/calendar/table-time-sheet.tsx (4)

Line range hint 248-257: Implement missing status handlers

The handleButtonClick function has incomplete implementations for 'Approved' and 'Pending' states. This could lead to unexpected behavior when users interact with these buttons.

Consider implementing proper handlers for all states:

 const handleButtonClick = (action: StatusType) => {
     switch (action) {
         case 'Approved':
-            // TODO: Implement approval logic
+            // Add proper approval logic here
+            openModal();
             break;
         case 'Rejected':
             openModal()
             break;
         case 'Pending':
-            // TODO: Implement pending logic
+            // Add proper pending logic here
+            openModal();
             break;
         default:
             console.error(`Unsupported action: ${action}`);
     }
 };

Line range hint 155-156: Use provided data prop instead of hardcoded dataSourceTimeSheet

The table is using a hardcoded dataSourceTimeSheet instead of the data prop passed to the component. This makes the component inflexible and harder to test.

-        data: dataSourceTimeSheet,
+        data: data?.flatMap(group => group.tasks) ?? [],

Line range hint 283-292: Remove or implement commented code

There's commented-out code for TaskNameInfoDisplay component. Either implement this component or remove the commented code to maintain code cleanliness.

Consider implementing the task name display or removing the commented section entirely.


Line range hint 238-246: Add error handling for modal actions

The RejectSelectedModal has an empty implementation for the reject action. This could lead to a poor user experience when rejections fail.

 <RejectSelectedModal
     onReject={() => {
-        // Pending implementation
+        try {
+            // Implement rejection logic
+            closeModal();
+        } catch (error) {
+            // Handle error appropriately
+        }
     }}
     maxReasonLength={120}
     minReasonLength={0}
     closeModal={closeModal}
     isOpen={isOpen}
 />
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1a1d0c6 and 612985b.

📒 Files selected for processing (10)
  • apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (5 hunks)
  • apps/web/app/hooks/features/useTimelogFilterOptions.ts (1 hunks)
  • apps/web/app/hooks/features/useTimesheet.ts (3 hunks)
  • apps/web/app/hooks/index.ts (1 hunks)
  • apps/web/app/interfaces/timer/ITimerLog.ts (1 hunks)
  • apps/web/app/services/client/api/timer/timer-log.ts (2 hunks)
  • apps/web/app/stores/index.ts (1 hunks)
  • apps/web/app/stores/time-logs.ts (1 hunks)
  • apps/web/lib/components/custom-select/multi-select.tsx (1 hunks)
  • apps/web/lib/features/integrations/calendar/table-time-sheet.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: Cspell
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx

[warning] 22-22:
Unknown word (Statu)


[warning] 22-22:
Unknown word (statu)


[warning] 95-95:
Unknown word (statu)


[warning] 102-102:
Unknown word (Statu)

apps/web/app/hooks/features/useTimelogFilterOptions.ts

[warning] 8-8:
Unknown word (statu)


[warning] 8-8:
Unknown word (Statu)


[warning] 17-17:
Unknown word (statu)


[warning] 24-24:
Unknown word (Statu)

🔇 Additional comments (11)
apps/web/app/stores/time-logs.ts (2)

3-3: LGTM: Import statement is well-structured

The new type imports are properly organized and necessary for the filter state atoms.


9-18: Consider improving type definitions and adding documentation

While the implementation is functional, there are a few suggestions for improvement:

  1. The type pattern T | T[] | null initialized with [] could lead to confusion. Consider using either:
    • Just array types with empty array default
    • Just nullable single items
  2. The status filter type could be simplified using a type alias

Here's a suggested refactor:

+// Type alias for status option
+type StatusOption = { value: string; label: string };
+
+/**
+ * Filter state for employee selection in timesheet
+ * @default []
+ */
-export const timesheetFilterEmployeeState = atom<OT_Member | OT_Member[] | null>([]);
+export const timesheetFilterEmployeeState = atom<OT_Member[]>([]);
+
+/**
+ * Filter state for project selection in timesheet
+ * @default []
+ */
-export const timesheetFilterProjectState = atom<IProject | IProject[] | null>([]);
+export const timesheetFilterProjectState = atom<IProject[]>([]);
+
+/**
+ * Filter state for task selection in timesheet
+ * @default []
+ */
-export const timesheetFilterTaskState = atom<ITeamTask | ITeamTask[] | null>([]);
+export const timesheetFilterTaskState = atom<ITeamTask[]>([]);
+
+/**
+ * Filter state for status selection in timesheet
+ * @default []
+ */
-export const timesheetFilterStatusState = atom<{
-    value: string;
-    label: string;
-} | {
-    value: string;
-    label: string;
-}[] | null>([]);
+export const timesheetFilterStatusState = atom<StatusOption[]>([]);

Let's verify if these atoms are used consistently across the codebase:

✅ Verification successful

Refactor is safe to implement

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of these atoms to ensure the refactor won't break existing code
rg -A 3 "timesheetFilter(Employee|Project|Task|Status)State"

Length of output: 1883

apps/web/app/hooks/features/useTimelogFilterOptions.ts (1)

1-3: LGTM! Clean and well-organized imports

The imports are properly structured, with clear separation between types, state atoms, and external dependencies.

apps/web/app/interfaces/timer/ITimerLog.ts (1)

Line range hint 1-1: Verify ITaskIssue import and usage

Let's verify the ITaskIssue interface definition and its usage patterns.

apps/web/app/services/client/api/timer/timer-log.ts (2)

23-25: LGTM! Function signature changes are well-structured.

The addition of optional projectIds and employeeIds parameters with default empty arrays is a clean approach to implementing the new filtering capabilities.

Also applies to: 31-33


59-65: Consider adding validation and optimizing parameter handling.

While the implementation works, consider these improvements for better robustness and efficiency:

  1. Add input validation for project and employee IDs
  2. Handle edge cases (empty strings, invalid IDs)
  3. Consider using a more efficient approach for multiple parameters

Here's a suggested improvement:

-	projectIds.forEach((id, index) => {
-		params.append(`projectIds[${index}]`, id);
-	});
-
-	employeeIds.forEach((id, index) => {
-		params.append(`employeeIds[${index}]`, id);
-	});
+	// Filter out invalid IDs and handle multiple parameters more efficiently
+	const validProjectIds = projectIds.filter(id => id && id.trim());
+	const validEmployeeIds = employeeIds.filter(id => id && id.trim());
+	
+	if (validProjectIds.length) {
+		validProjectIds.forEach((id, index) => {
+			params.append(`projectIds[${index}]`, id.trim());
+		});
+	}
+	
+	if (validEmployeeIds.length) {
+		validEmployeeIds.forEach((id, index) => {
+			params.append(`employeeIds[${index}]`, id.trim());
+		});
+	}

Let's verify the API endpoint format expectations:

apps/web/app/hooks/features/useTimesheet.ts (3)

9-9: LGTM! Clean integration of filter options

The integration of useTimelogFilterOptions hook is well-structured and follows React best practices.

Also applies to: 49-49


71-71: LGTM! Proper dependency management

The dependencies array is correctly updated to include the new filter options, ensuring proper memoization behavior.


12-13: Verify the impact of optional date parameters

Making startDate and endDate optional could lead to undefined behavior in the timesheet filtering. The code should handle cases where these parameters are undefined.

✅ Verification successful

Optional date parameters are properly handled

The codebase includes necessary checks for startDate and endDate, ensuring undefined values are managed correctly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of TimesheetParams to verify proper handling of undefined dates
ast-grep --pattern 'function $_(params: TimesheetParams) {
  $$$
}'

# Search for direct usage of startDate/endDate
rg -A 3 'startDate|endDate' 

Length of output: 24660

apps/web/lib/components/custom-select/multi-select.tsx (1)

Line range hint 9-21: Verify unique item IDs to prevent unexpected behavior

The component relies on itemId for item identification, but doesn't validate ID uniqueness. Duplicate IDs could lead to incorrect selection/deselection behavior.

Consider adding runtime validation:

function validateUniqueIds<T>(items: T[], itemId: (item: T) => string): void {
    const ids = new Set<string>();
    items.forEach(item => {
        const id = itemId(item);
        if (ids.has(id)) {
            console.warn(`Duplicate item ID found: ${id}. This may cause unexpected behavior.`);
        }
        ids.add(id);
    });
}

// Add to component:
useEffect(() => {
    if (items) {
        validateUniqueIds(items, itemId);
    }
}, [items, itemId]);
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (1)

Line range hint 1-125: Implementation aligns well with PR objectives

The changes successfully implement the useTimelogFilterOptions hook and enhance the TimeSheetFilterPopover component with proper filter functionality. The code is generally well-structured, though there are some opportunities for improvement as noted in the previous comments.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (15)
apps/web/app/stores/time-logs.ts (1)

9-11: Consider using null as initial value for nullable states

The atoms are initialized with empty arrays but their types include null. For better type safety and consistency, consider using null as the initial value if no selection is intended.

-export const timesheetFilterEmployeeState = atom<OT_Member | OT_Member[] | null>([]);
-export const timesheetFilterProjectState = atom<IProject | IProject[] | null>([]);
-export const timesheetFilterTaskState = atom<ITeamTask | ITeamTask[] | null>([])
+export const timesheetFilterEmployeeState = atom<OT_Member | OT_Member[] | null>(null);
+export const timesheetFilterProjectState = atom<IProject | IProject[] | null>(null);
+export const timesheetFilterTaskState = atom<ITeamTask | ITeamTask[] | null>(null);
apps/web/app/stores/index.ts (1)

30-30: Consider grouping related exports together.

For better maintainability, consider grouping this export with other time/task-related exports (around line 13-17).

 export * from './task-related-issue-type';
 export * from './daily-plan';
+export * from './time-logs';
 
 export * from './roles';
 export * from './role-permissions';
 export * from './request-to-join';
 export * from './public';
 
 export * from './integration-tenant';
 export * from './integration-github';
 export * from './integration-types';
 export * from './integration';
-export * from './time-logs'
apps/web/app/hooks/features/useTimelogFilterOptions.ts (2)

16-25: Improve return object structure

The return object can be simplified using object shorthand notation, and the status naming should be consistent.

Apply this diff to improve the code:

     return {
-        statuState,
-        employee,
-        project,
-        task,
-        setEmployeeState,
-        setProjectState,
-        setTaskState,
-        setStatuState
+        statusState,
+        employee,
+        project,
+        task,
+        setStatusState,
+        setEmployeeState,
+        setProjectState,
+        setTaskState
     };
🧰 Tools
🪛 GitHub Check: Cspell

[warning] 17-17:
Unknown word (statu)


[warning] 24-24:
Unknown word (Statu)


5-5: Add JSDoc documentation

Consider adding JSDoc documentation to describe the hook's purpose, parameters, and return value. This would improve maintainability and developer experience.

Example documentation:

+/**
+ * Hook for managing timesheet filter options state
+ * @returns {Object} Object containing filter states and their setters
+ * @property {OT_Member[]} employee - Selected employee filters
+ * @property {IProject[]} project - Selected project filters
+ * @property {ITeamTask[]} task - Selected task filters
+ * @property {unknown} statusState - Selected status filters
+ */
 export function useTimelogFilterOptions() {
apps/web/app/interfaces/timer/ITimerLog.ts (1)

14-14: Consider aligning issueType types across interfaces

While the additions enhance filtering capabilities, there's an architectural inconsistency: Task.issueType is typed as string | null while ITimeSheet.issueType uses ITaskIssue. This might lead to type conversion complexity or runtime issues.

Consider one of these approaches:

  1. Use ITaskIssue | null consistently across both interfaces
  2. Document why different types are necessary
  3. Create a separate type for task-specific issue types if they truly need to be different

Also applies to: 16-16

apps/web/app/services/client/api/timer/timer-log.ts (2)

59-65: Consider adding input validation and optimizing array handling

While the implementation is functional, consider these improvements:

  1. Add validation for project and employee IDs to prevent potential injection
  2. For large arrays, consider using a more efficient approach to construct the query parameters

Here's a suggested improvement:

-	projectIds.forEach((id, index) => {
-		params.append(`projectIds[${index}]`, id);
-	});
-
-	employeeIds.forEach((id, index) => {
-		params.append(`employeeIds[${index}]`, id);
-	});
+	// Validate and sanitize IDs
+	const sanitizedProjectIds = projectIds.filter(id => /^[a-zA-Z0-9-]+$/.test(id));
+	const sanitizedEmployeeIds = employeeIds.filter(id => /^[a-zA-Z0-9-]+$/.test(id));
+	
+	// More efficient array handling
+	if (sanitizedProjectIds.length) {
+		params.append('projectIds', JSON.stringify(sanitizedProjectIds));
+	}
+	if (sanitizedEmployeeIds.length) {
+		params.append('employeeIds', JSON.stringify(sanitizedEmployeeIds));
+	}

Line range hint 13-13: Remove unnecessary comment

The comment // todayStart, todayEnd; appears to be a leftover note and should be removed.

apps/web/app/hooks/index.ts (1)

43-43: Consider reorganizing time-related hook exports.

For better code organization, consider:

  1. Moving this export next to other time-related hooks like useTimer
  2. Creating a dedicated section for time/timesheet-related hooks if there are multiple such hooks
export * from './features/useTeamTasks';
export * from './features/useTimer';
+export * from './features/useTimelogFilterOptions';
export * from './features/useUser';
export * from './features/useUserProfilePage';
-export * from './features/useTimelogFilterOptions';
export * from './useCollaborative';
apps/web/app/hooks/features/useTimesheet.ts (2)

63-64: Consider handling empty filter cases explicitly

While filtering undefined values is good, consider these improvements:

  1. Empty arrays might behave differently from undefined in the API
  2. The mapping could be more concise
-                employeeIds: employee?.map((member) => member.employee.id).filter((id) => id !== undefined),
-                projectIds: project?.map((project) => project.id).filter((id) => id !== undefined)
+                employeeIds: employee?.length ? employee.map((member) => member.employee?.id).filter(Boolean) : undefined,
+                projectIds: project?.length ? project.map((project) => project?.id).filter(Boolean) : undefined

66-69: Enhance error handling for better user experience

The current error handling only logs to console. Consider adding proper error state management and user feedback.

             }).catch((error) => {
                 console.error('Error fetching timesheet:', error);
+                // Suggest implementing proper error state management
+                // setError(error);
+                // notifyUser('Failed to fetch timesheet data');
             });
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (2)

Line range hint 49-104: Consider extracting repeated MultiSelect pattern into a reusable component

The MultiSelect sections share similar structure and behavior, leading to code duplication. This could be abstracted into a reusable component for better maintainability.

Consider creating a component like this:

type FilterSelectProps = {
  label: string;
  items: any[];
  value: any[];
  onValueChange: (items: any[]) => void;
  itemToString: (item: any) => string;
  itemId: (item: any) => string;
  removeItems: boolean;
}

const FilterSelect: React.FC<FilterSelectProps> = ({
  label,
  items,
  value,
  onValueChange,
  itemToString,
  itemId,
  removeItems
}) => (
  <div>
    <label className="flex justify-between text-gray-600 mb-1 text-sm">
      <span className="text-[12px]">{label}</span>
      <span className={clsxm("text-primary/10", value?.length > 0 && "text-primary dark:text-primary-light")}>
        Clear
      </span>
    </label>
    <MultiSelect
      removeItems={removeItems}
      items={items}
      itemToString={itemToString}
      itemId={itemId}
      onValueChange={onValueChange}
      multiSelect={true}
      triggerClassName="dark:border-gray-700"
    />
  </div>
);

Line range hint 106-120: Add missing onClick handler for Apply Filter button

The Apply Filter button doesn't have an onClick handler, making it non-functional. Consider adding the handler and loading states for better user feedback.

Would you like me to help implement the Apply Filter functionality?

apps/web/lib/features/integrations/calendar/table-time-sheet.tsx (3)

274-274: Consider using a more semantic disabled state styling

The addition of disabled:bg-black seems too aggressive for a disabled state. Consider using more subtle styling that aligns with the application's design system.

-<div className={clsxm("flex items-center gap-2 p-x-1", 'disabled:bg-black')}>
+<div className={clsxm("flex items-center gap-2 p-x-1", 'disabled:opacity-50 disabled:cursor-not-allowed')}>

Line range hint 152-164: Implement pending TODO items in handleButtonClick

The function contains TODO comments for 'Approved' and 'Pending' actions that need implementation. This could lead to unexpected behavior when users interact with these actions.

Would you like help implementing these actions? I can help generate the implementation code or create GitHub issues to track these tasks.


Line range hint 152-164: Add error handling for action state changes

The switch statement's default case only logs to console. Consider adding proper error handling and user feedback.

 const handleButtonClick = (action: StatusType) => {
     switch (action) {
         case 'Approved':
             // TODO: Implement approval logic
             break;
         case 'Rejected':
             openModal()
             break;
         case 'Pending':
             // TODO: Implement pending logic
             break;
         default:
-            console.error(`Unsupported action: ${action}`);
+            console.error(`Unsupported action: ${action}`);
+            // Show user feedback
+            toast.error(`Invalid action: ${action}`);
+            // Log to error tracking service
+            captureException(new Error(`Unsupported timesheet action: ${action}`));
     }
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1a1d0c6 and 612985b.

📒 Files selected for processing (10)
  • apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (5 hunks)
  • apps/web/app/hooks/features/useTimelogFilterOptions.ts (1 hunks)
  • apps/web/app/hooks/features/useTimesheet.ts (3 hunks)
  • apps/web/app/hooks/index.ts (1 hunks)
  • apps/web/app/interfaces/timer/ITimerLog.ts (1 hunks)
  • apps/web/app/services/client/api/timer/timer-log.ts (2 hunks)
  • apps/web/app/stores/index.ts (1 hunks)
  • apps/web/app/stores/time-logs.ts (1 hunks)
  • apps/web/lib/components/custom-select/multi-select.tsx (1 hunks)
  • apps/web/lib/features/integrations/calendar/table-time-sheet.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: Cspell
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx

[warning] 22-22:
Unknown word (Statu)


[warning] 22-22:
Unknown word (statu)


[warning] 95-95:
Unknown word (statu)


[warning] 102-102:
Unknown word (Statu)

apps/web/app/hooks/features/useTimelogFilterOptions.ts

[warning] 8-8:
Unknown word (statu)


[warning] 8-8:
Unknown word (Statu)


[warning] 17-17:
Unknown word (statu)


[warning] 24-24:
Unknown word (Statu)

🔇 Additional comments (14)
apps/web/app/stores/time-logs.ts (2)

3-3: LGTM: Import statement properly includes required types

The new imports are correctly added and necessary for typing the new filter states.


9-18: Verify completeness of filter states

Please verify if date/time period filters are needed for the timesheet filtering feature. If they are handled elsewhere, this is fine. If not, consider adding them to complete the filtering functionality.

apps/web/app/stores/index.ts (1)

30-30: LGTM! Export addition looks good.

The new export for time-logs module aligns with the PR's objective of enhancing timesheet functionality.

apps/web/app/hooks/features/useTimelogFilterOptions.ts (1)

1-3: LGTM! Well-organized imports

The imports are properly structured, following a good pattern of separating external libraries from internal imports.

apps/web/app/interfaces/timer/ITimerLog.ts (2)

Line range hint 77-77: LGTM: Optional issueType aligns with interface design

Making issueType optional is consistent with the interface's pattern for nullable fields and maintains backward compatibility.


14-16: Verify usage of new optional fields

Let's ensure existing code handles these new optional fields appropriately.

Also applies to: 77-77

✅ Verification successful

All usages of issueType and taskStatus handle null values appropriately.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct usage of these fields to verify proper null checking
echo "Checking usage of Task.issueType and Task.taskStatus:"
rg -A 2 "\.issueType\b|\.taskStatus\b" --type ts

echo "Checking for potential type assertions or non-null assumptions:"
ast-grep --pattern 'task.issueType!' --lang ts
ast-grep --pattern 'task.taskStatus!' --lang ts

Length of output: 28414

apps/web/app/services/client/api/timer/timer-log.ts (1)

23-25: LGTM! Well-structured parameter additions

The new optional parameters are properly typed and include sensible defaults, maintaining backward compatibility.

Also applies to: 31-33

apps/web/app/hooks/index.ts (1)

43-43: Verify the new hook implementation and integration.

Let's verify the hook's implementation and its integration with the TimeSheetFilterPopover component.

✅ Verification successful

Verified the new hook implementation and its integration with the TimeSheetFilterPopover component.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the hook implementation and integration

# Check if the hook file exists
echo "Checking hook file existence..."
fd "useTimelogFilterOptions.ts" apps/web/app/hooks/features

# Check hook implementation patterns
echo -e "\nChecking hook implementation..."
rg -A 10 "export function useTimelogFilterOptions" apps/web/app/hooks/features

# Check integration with TimeSheetFilterPopover
echo -e "\nChecking component integration..."
rg "useTimelogFilterOptions" apps/web/app/\[locale\]/timesheet --type ts

Length of output: 1973

apps/web/app/hooks/features/useTimesheet.ts (2)

9-9: Clean integration of filter options

The integration of useTimelogFilterOptions hook follows React best practices and properly separates concerns for filter state management.

Also applies to: 49-49


71-71: Proper dependency management

The dependencies array has been correctly updated to include the new filter states, ensuring proper memoization of the callback.

apps/web/lib/components/custom-select/multi-select.tsx (1)

137-137: LGTM! Defensive programming approach added.

The optional chaining operator (?.) is a good defensive programming practice to prevent runtime errors when items is undefined during initial render or loading states.

apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (2)

Line range hint 1-17: Good use of React.memo for performance optimization!

The memoization will prevent unnecessary re-renders of this component when parent components update with props that haven't changed.


Line range hint 24-28: LGTM: Clean effect implementation

The effect has a clear purpose and correctly handles the reset of shouldRemoveItems.

🧰 Tools
🪛 GitHub Check: Cspell

[warning] 22-22:
Unknown word (Statu)


[warning] 22-22:
Unknown word (statu)

apps/web/lib/features/integrations/calendar/table-time-sheet.tsx (1)

274-276: Verify the integration with useTimelogFilterOptions

The PR introduces useTimelogFilterOptions hook but it's not utilized in this component. Consider if this component should integrate with the new filtering functionality.

apps/web/app/stores/time-logs.ts Outdated Show resolved Hide resolved
apps/web/app/hooks/features/useTimelogFilterOptions.ts Outdated Show resolved Hide resolved
apps/web/app/hooks/features/useTimesheet.ts Show resolved Hide resolved
apps/web/lib/components/custom-select/multi-select.tsx Outdated Show resolved Hide resolved
@Innocent-Akim Innocent-Akim changed the title [Feat]: Add useTimelogFilterOptions [Feat]: Add Timelog FilterOptions Nov 13, 2024
@Innocent-Akim Innocent-Akim removed the Don't Merge Do not merge this PR until label not removed label Nov 13, 2024
@Innocent-Akim Innocent-Akim requested a review from evereq November 13, 2024 06:29
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (5)
apps/web/lib/features/task/task-block-card.tsx (1)

79-79: LGTM! Consider adding a comment to explain the fallback.

The change to default to 0 when no tasks exist is a good defensive programming practice. This prevents potential undefined/null values from causing display issues in the time formatting.

Consider adding a comment to explain the fallback logic:

    (currentMember?.totalWorkedTasks &&
      currentMember?.totalWorkedTasks?.length &&
      currentMember?.totalWorkedTasks
        .filter((t) => t.id === task?.id)
        .reduce(
          (previousValue, currentValue) =>
            previousValue + currentValue.duration,
          0
        )) ||
-    0
+    0 // Default to 0 seconds when no tasks exist or no duration is recorded
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (3)

22-22: Maintain consistency in state variable naming

The state variables follow different naming patterns: employee, project, statusState, task. Consider using consistent naming across all filter states.

- const { setEmployeeState, setProjectState, setStatusState, setTaskState, employee, project, statusState, task } = useTimelogFilterOptions();
+ const { setEmployeeState, setProjectState, setStatusState, setTaskState, employeeState, projectState, statusState, taskState } = useTimelogFilterOptions();

Line range hint 119-122: Implement onClick handler for Apply Filter button

The Apply Filter button is missing an onClick handler, making it non-functional. Consider implementing the filter application logic or removing the button if filters are applied automatically.

 <Button
+    onClick={() => {/* Implement filter application logic */}}
     className='flex items-center text-sm justify-center h-10  rounded-lg bg-primary dark:bg-primary-light dark:text-gray-300' >
     <span className="text-sm">Apply Filter</span>
 </Button>

Line range hint 31-39: Enhance accessibility for filter trigger button

Consider adding ARIA attributes to improve accessibility for screen readers.

 <Button variant="outline"
+    aria-label="Open filter options"
     className='flex items-center justify-center  h-[2.2rem] rounded-lg bg-white dark:bg-dark--theme-light border dark:border-gray-700 hover:bg-white p-3 gap-2'>
apps/web/lib/components/custom-select/multi-select.tsx (1)

19-20: Add prop documentation and improve type safety.

Consider adding JSDoc comments and improving type safety:

interface MultiSelectProps<T> {
+    /** Flag to clear all selected items */
     removeItems?: boolean;
+    /** Key used for localStorage persistence. Should be unique per instance */
-    localStorageKey?: string;
+    localStorageKey?: `multiselect-${string}`;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6a5120d and 3a2a258.

📒 Files selected for processing (3)
  • apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (5 hunks)
  • apps/web/lib/components/custom-select/multi-select.tsx (2 hunks)
  • apps/web/lib/features/task/task-block-card.tsx (1 hunks)
🔇 Additional comments (3)
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (3)

17-17: LGTM: Good use of React.memo for performance optimization

The memoization will prevent unnecessary re-renders of this component when parent components update.


Line range hint 24-28: LGTM: Effect hook implementation is clean and focused

The effect hook correctly handles the reset of shouldRemoveItems with proper dependency tracking.


53-53: LGTM: Good use of localStorageKey for filter persistence

The addition of localStorageKey for each filter ensures that user preferences are preserved across sessions.

Also applies to: 69-69, 85-85, 101-101

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants