-
Notifications
You must be signed in to change notification settings - Fork 74
[Feat]: Timesheet Filter Calendar #3305
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
Conversation
WalkthroughThe pull request introduces several enhancements across multiple components in the timesheet application. Key modifications include the addition of new state variables and props in the Changes
Possibly related PRs
Suggested reviewers
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
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsxOops! 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. 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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Outside diff range and nitpick comments (18)
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx (1)
Line range hint
6-24
: Consider enhancing accessibility for status messagesThe implementation looks good, but we could improve accessibility for screen readers.
Consider applying these changes:
- <div className="flex items-center justify-center h-full"> + <div className="flex items-center justify-center h-full" role="status" aria-live="polite"> <p>{t('pages.timesheet.NO_ENTRIES_FOUND')}</p> </div> - <div className="flex items-center justify-center h-full"> + <div className="flex items-center justify-center h-full" role="status" aria-live="polite"> <p>{t('pages.timesheet.LOADING')}</p> </div>apps/web/app/services/client/api/timer/timer-log.ts (1)
Line range hint
13-13
: Remove unnecessary comment.The comment
// todayStart, todayEnd;
appears to be unused and doesn't provide any meaningful context.-// todayStart, todayEnd;
apps/web/app/hooks/features/useTimesheet.ts (1)
20-33
: Consider adding error state handling.While the hook handles loading state, it could benefit from proper error state management to improve user experience when API calls fail.
Consider extending the hook's return value:
return { loadingTimesheet, timesheet: groupByDate(timesheet), getTaskTimesheet, + error: null, // Add error state + clearError: () => {}, // Add error clearing function };apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetCard.tsx (2)
42-42
: LGTM! Consider extracting hover styles for reusability.The dark mode hover state enhancement improves visual feedback. However, these styles could be reused across similar buttons.
Consider creating a reusable class in your global styles:
.btn-hover-effect { @apply hover:bg-gray-50 hover:dark:bg-primary-light/40 focus:ring-2 focus:ring-offset-2 focus:ring-gray-200; }
Line range hint
29-29
: Consider using design tokens for magic numbers.The fixed height value
h-[175px]
should be defined as a design token for better maintainability and consistency.Consider moving this to your theme configuration:
export const theme = { components: { cards: { timesheet: { height: '175px' } } } }Then use it as:
h-[theme.components.cards.timesheet.height]
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx (5)
18-18
: Consider simplifying the state reset logicThe current implementation uses a useEffect to reset the state, but this could be simplified by handling it directly in the clear filter click handler.
Consider this alternative approach:
-const [shouldRemoveItems, setShouldRemoveItems] = React.useState(false); +const clearFilters = () => { + setShouldRemoveItems(true); + // Reset on next tick to allow MultiSelect components to process the clear + setTimeout(() => setShouldRemoveItems(false), 0); +}; -useEffect(() => { - if (shouldRemoveItems) { - setShouldRemoveItems(false); - } -}, [shouldRemoveItems]);Also applies to: 23-27
Line range hint
52-57
: Implement proper state management for filter valuesThe MultiSelect components currently log selections to console without managing the state. This indicates incomplete implementation.
- Add state management for selected values:
interface FilterState { employees: typeof activeTeam.members[number][]; projects: typeof activeTeam.projects[number][]; tasks: typeof tasks[number][]; statuses: typeof statusOptions[number][]; } const [filters, setFilters] = useState<FilterState>({ employees: [], projects: [], tasks: [], statuses: [] });
- Update the onValueChange handlers:
-onValueChange={(selectedItems) => console.log(selectedItems)} +onValueChange={(selectedItems) => setFilters(prev => ({ + ...prev, + employees: selectedItems +}))}Also applies to: 67-73, 82-88, 97-103
Line range hint
108-116
: Implement filter application logicThe "Apply Filter" button lacks an onClick handler, and the clear filter implementation could be improved.
Add proper handlers:
-onClick={() => setShouldRemoveItems(true)} +onClick={clearFilters} <Button + onClick={() => { + // Implement filter application logic + onApplyFilters(filters); + }} 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>
49-49
: Improve the Clear label functionalityThe "Clear" text in labels appears to be styled with low opacity (primary/10) but lacks click handlers.
Consider making these individual clear buttons functional:
-<span className={clsxm("text-primary/10")}>Clear</span> +<button + onClick={() => { + setFilters(prev => ({ ...prev, employees: [] })); + setShouldRemoveItems(true); + setTimeout(() => setShouldRemoveItems(false), 0); + }} + className={clsxm( + "text-primary hover:text-primary/80 text-xs cursor-pointer transition-colors" + )} +> + Clear +</button>Also applies to: 64-64, 79-79, 94-94
Add ARIA attributes and keyboard interactions for better accessibility
The TimeSheetFilterPopover component needs accessibility improvements based on the codebase analysis:
- Add ARIA labels to the filter buttons and sections:
<Button variant="outline" aria-label={t('common.OPEN_FILTER')} aria-expanded={open} aria-controls="filter-popover-content" className='flex items-center justify-center h-[2.2rem] rounded-lg...' >
- Add keyboard navigation support for the filter sections:
<PopoverContent id="filter-popover-content" role="dialog" aria-label={t('common.FILTER_OPTIONS')} className="w-96" >
- The MultiSelect component already has accessibility features in the codebase (
apps/web/lib/components/custom-select/multi-select.tsx
), but needs proper ARIA labels for each filter section:<MultiSelect aria-label={t('manualTime.EMPLOYEE_FILTER')} removeItems={shouldRemoveItems} items={activeTeam?.members ?? []} ... />🔗 Analysis chain
Line range hint
15-122
: Verify accessibility and add component documentationThe component could benefit from improved accessibility and documentation.
Run accessibility checks:
Add JSDoc documentation:
/** * TimeSheetFilterPopover Component * * A popover component that provides filtering capabilities for timesheet data. * Allows filtering by employees, projects, tasks, and status. * * @component * @example * ```tsx * <TimeSheetFilterPopover /> * ``` */🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for aria-label attributes and role definitions rg -l 'aria-|role=' --type tsx # Check for keyboard navigation handlers rg -l 'onKeyDown|onKeyPress|onKeyUp' --type tsxLength of output: 138
Script:
#!/bin/bash # Search for aria-label attributes and role definitions in TypeScript/React files rg -l 'aria-|role=' -t ts -t tsx -t jsx # Check for keyboard navigation handlers in TypeScript/React files rg -l 'onKeyDown|onKeyPress|onKeyUp' -t ts -t tsx -t jsx # Look for existing documentation in the TimeSheetFilterPopover file rg -A 5 '\/\*\*' "apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx" # Check if MultiSelect component has accessibility features rg -A 10 'MultiSelect' -t ts -t tsx -t jsxLength of output: 402
Script:
#!/bin/bash # Search for aria-label attributes and role definitions rg 'aria-|role=' --type-add 'typescript:*.{ts,tsx}' --type typescript # Check for keyboard navigation handlers rg 'onKeyDown|onKeyPress|onKeyUp' --type-add 'typescript:*.{ts,tsx}' --type typescript # Look for existing documentation rg '/\*\*' "apps/web/app/\[locale\]/timesheet/\[memberId\]/components/TimeSheetFilterPopover.tsx" # Check MultiSelect component implementation rg -A 10 'export.*MultiSelect' --type-add 'typescript:*.{ts,tsx}' --type typescriptLength of output: 14753
apps/web/lib/features/task/task-issue.tsx (1)
217-217
: Add JSDoc documentation for consistency.The new
TaskIssueStatusTimesheet
component is missing JSDoc documentation. Consider adding documentation similar to other components in the file for better maintainability.Add documentation above the component:
/** * Renders a TaskStatus component for a timesheet entry * @param props - Object containing the timesheet entry and display options * @returns A TaskStatus component displaying the issue type of the timesheet */apps/web/lib/components/custom-select/multi-select.tsx (2)
Line range hint
57-71
: Consider adding type guard for onValueChange callback.The functions are well-implemented with proper handling of both single and multi-select cases. However, consider adding a type guard before calling
onValueChange
for additional safety.const removeAllItems = () => { setSelectedItems([]); - if (onValueChange) { + if (typeof onValueChange === 'function') { onValueChange(null); } };
Line range hint
19-77
: Consider debouncing rapid removeItems changes.The current implementation might cause unnecessary re-renders if the
removeItems
prop changes rapidly. Consider implementing debouncing for better performance.Example implementation using useCallback and debounce:
import { debounce } from 'lodash'; const debouncedRemoveAll = useCallback( debounce(() => { removeAllItems(); }, 250), [removeAllItems] ); useEffect(() => { if (removeItems) { debouncedRemoveAll(); } return () => { debouncedRemoveAll.cancel(); }; }, [removeItems, debouncedRemoveAll]);apps/web/app/[locale]/timesheet/[memberId]/page.tsx (2)
2-2
: Consider using date-fns consistently instead of momentThe codebase is already using
date-fns
(seestartOfDay
,endOfDay
imports). For consistency and bundle size optimization, consider usingdate-fns
for date formatting as well instead of introducingmoment
.-import moment from 'moment'; +import { format } from 'date-fns'; // Later in the code: -date={`${moment(dateRange.from).format('YYYY-MM-DD')} - ${moment(dateRange.to).format('YYYY-MM-DD')}`} +date={`${format(dateRange.from, 'yyyy-MM-dd')} - ${format(dateRange.to, 'yyyy-MM-dd')}`}Also applies to: 4-4, 40-41
150-157
: Add debounce to search inputFor better performance, especially with large datasets, consider debouncing the search input to prevent excessive re-renders.
+import { useMemo, useState, useCallback } from 'react'; +import debounce from 'lodash/debounce'; +const debouncedSetSearch = useCallback( + debounce((value: string) => setSearch(value), 300), + [] +); -onChange={(v) => setSearch(v.target.value)} +onChange={(v) => debouncedSetSearch(v.target.value)}apps/web/lib/features/integrations/calendar/table-time-sheet.tsx (1)
Line range hint
214-225
: Implement missing action handlersThe
handleButtonClick
function has TODO comments for approval and pending logic. This leaves the timesheet approval workflow incomplete.Would you like me to help implement these handlers or create GitHub issues to track their implementation? The implementation would need to:
- Handle approval logic for timesheet entries
- Handle pending status changes
- Integrate with any existing approval workflow systems
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx (2)
324-324
: Avoid unnecessary use ofmoment
inonSelect
The
date
parameter is already aDate
object. Wrapping it withmoment(date).toDate()
is redundant. You can directly usedate
.Apply this diff to simplify the code:
-onSelect={(date) => date && setSelectedPlan(moment(date).toDate())} +onSelect={(date) => date && setSelectedPlan(date)}
17-17
: Consider replacingmoment
withdate-fns
for consistency and performanceSince
date-fns
is already imported and used in the project, you might consider replacingmoment
withdate-fns
to reduce bundle size and improve performance.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (14)
apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx
(6 hunks)apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetCard.tsx
(1 hunks)apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilter.tsx
(3 hunks)apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx
(7 hunks)apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx
(1 hunks)apps/web/app/[locale]/timesheet/[memberId]/page.tsx
(8 hunks)apps/web/app/hooks/features/useTimesheet.ts
(1 hunks)apps/web/app/interfaces/IProject.ts
(1 hunks)apps/web/app/interfaces/ITask.ts
(1 hunks)apps/web/app/interfaces/timer/ITimerLog.ts
(2 hunks)apps/web/app/services/client/api/timer/timer-log.ts
(1 hunks)apps/web/lib/components/custom-select/multi-select.tsx
(5 hunks)apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
(3 hunks)apps/web/lib/features/task/task-issue.tsx
(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/web/app/interfaces/ITask.ts
🧰 Additional context used
🪛 GitHub Check: Cspell
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx
[warning] 286-286:
Unknown word (Caledar)
🔇 Additional comments (16)
apps/web/app/interfaces/IProject.ts (1)
27-27
: Consider clarifying the relationship between IProject.name and IProject.repository.name
The addition of an optional name
property to IProject
creates potential ambiguity since:
IProjectRepository
already has a requiredname
fieldIProject
has an optional reference toIProjectRepository
via therepository
propertyIProjectCreate
requiresname
butIProject
makes it optional
This could lead to confusion about which name should be used in different contexts.
Let's verify how these interfaces are being used:
Consider one of these alternatives:
- If
name
should always come from the repository:
interface IProject {
// ... other properties ...
repository: IProjectRepository; // Make required
// Remove duplicate name property
}
- If
name
is separate from repository name:
interface IProject {
// ... other properties ...
name: string; // Make required and document the difference
repository?: IProjectRepository;
}
✅ Verification successful
The optional name property in IProject is justified and correctly implemented
Based on the codebase analysis:
- The
name
property is used in UI components for project selection, specifically inTimeSheetFilterPopover.tsx
where it's accessed directly viaproject.name
- The interface hierarchy is correct:
IProjectCreate
requiresname
for creationIProject
makes it optional since it can be derived fromrepository.name
when available- This dual-source approach supports both repository-linked and standalone projects
The current implementation provides flexibility while maintaining type safety, allowing projects to have names either directly or through their repository association.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for usage patterns of IProject interface and its name properties
echo "Searching for IProject usage patterns..."
rg -t typescript "IProject" -A 5
echo "Searching specifically for project name access patterns..."
rg -t typescript "project\.name|project\.repository\.name" -A 2
Length of output: 386
Script:
#!/bin/bash
# Search for IProject interface usage patterns
echo "Searching for IProject usage..."
rg "IProject" -A 5
echo -e "\nSearching for project name access patterns..."
rg "project\.name|project\.repository\.name" -A 2
echo -e "\nSearching for files containing IProject..."
fd -e ts -e tsx . | xargs rg "IProject"
Length of output: 11363
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetView.tsx (2)
1-3
: LGTM! Imports are well-organized and necessary.
The imports correctly include the new GroupedTimesheet type and all required dependencies.
5-5
: Verify consistent usage of GroupedTimesheet type
The type change from IDailyPlan[]
to GroupedTimesheet[]
looks good, but let's verify its consistent usage across the codebase.
✅ Verification successful
The verification results show that the type change from IDailyPlan[]
to GroupedTimesheet[]
in TimesheetView
component is consistent with its usage across the codebase. The GroupedTimesheet
type is properly defined in useTimesheet.ts
and is used consistently in both TimesheetView
and DataTableTimeSheet
components. The IDailyPlan[]
type is still used extensively but in different, unrelated contexts (daily planning features) and doesn't conflict with the timesheet functionality.
Type change is correctly implemented and consistent
The GroupedTimesheet
interface is well-defined and its usage is consistent across timesheet-related components. The change from IDailyPlan[]
to GroupedTimesheet[]
is correct as these are separate concerns in the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for any remaining IDailyPlan[] type usage that might need updating
# and verify GroupedTimesheet type implementation
# Check for any remaining IDailyPlan[] usage that might need updating
rg "IDailyPlan\[\]" -A 3
# Check GroupedTimesheet type implementation and usage
ast-grep --pattern 'interface GroupedTimesheet {
$$$
}'
# Check for other components using GroupedTimesheet
rg "GroupedTimesheet(\[\])?" -A 3
Length of output: 12735
apps/web/app/interfaces/timer/ITimerLog.ts (2)
1-1
: LGTM! Import statement is correctly placed.
The import for ITaskIssue
is appropriately added at the top of the file.
74-74
: Verify the optional property usage across the codebase.
The addition of the optional issueType
property maintains backward compatibility while extending the interface for issue tracking functionality. This change appears to be well-structured.
Let's verify the usage of this new property:
✅ Verification successful
Based on the search results, I can now provide a conclusive response about the optional issueType
property usage:
The optional issueType
property is well-integrated across the codebase
The property is consistently used throughout the application with proper type checking and handling:
- Used in task creation/updates with proper optionality in
useTeamTasks.ts
- Properly typed in multiple interfaces (
ITask.ts
,ITimerLog.ts
) - Handled in UI components with proper null checks (e.g.,
task?.issueType
) - Used in filtering and display logic with fallbacks to default values
- Integrated with the task status system and issue type management
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check how the new issueType property is being used across the codebase
# Find components that might need updates to handle the new property
rg -l "ITimeSheet" --type ts --type tsx
# Check for any existing issue type handling in timesheet components
rg "issueType" --type ts --type tsx -C 3
# Look for TaskStatus components that might need updates
ast-grep --pattern 'TaskStatus' -l
Length of output: 277
Script:
#!/bin/bash
# Let's try with correct syntax and without tsx extension
# Find components that might need updates to handle the new property
rg -l "ITimeSheet" --type typescript
# Check for any existing issue type handling in timesheet components
rg "issueType" --type typescript -C 3
# Look for TaskStatus components that might need updates
ast-grep -l -p 'TaskStatus' --lang typescript
Length of output: 296
Script:
#!/bin/bash
# Let's try with simpler commands and without file type specifications
# Find files containing ITimeSheet
rg -l "ITimeSheet"
# Check for any existing issue type handling
rg "issueType" -C 3
# Look for files containing TaskStatus
rg -l "TaskStatus"
Length of output: 79630
apps/web/app/services/client/api/timer/timer-log.ts (1)
52-53
: LGTM! Verify backend API support.
The addition of the 'task.taskStatus' relation follows the existing pattern and aligns with the grouped timesheet feature. However, please ensure that the backend API supports this relation to avoid runtime errors.
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilter.tsx (2)
1-1
: LGTM: Well-structured interface changes
The interface updates and imports are well-organized, with proper TypeScript typing and backward compatibility through optional props.
Also applies to: 12-14
1-1
: Verify FilterWithStatus component interface
Let's ensure the FilterWithStatus component properly handles these prop types.
Also applies to: 30-31
✅ Verification successful
FilterWithStatus component interface is correctly implemented
The component's interface matches the expected usage:
activeStatus
prop is correctly typed asFilterStatus
enumonToggle
prop is properly typed as a callback function that takes aFilterStatus
parameter- Additional
className
prop is available for styling flexibility
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for FilterWithStatus component definition to verify prop types
ast-grep --pattern 'interface $interface {
$$$
activeStatus: $type
onToggle: $callback
$$$
}'
# Backup: Search using ripgrep if component is not using TypeScript interface
rg -A 10 'export.*FilterWithStatus.*='
Length of output: 144
Script:
#!/bin/bash
# Let's try a different approach to find the FilterWithStatus component and its props
# First, let's locate the file
fd "FilterWithStatus.tsx"
# Then let's examine its contents
rg -A 20 "export.*FilterWithStatus" $(fd "FilterWithStatus.tsx")
# Also search for any type/interface definitions
rg "type|interface.*FilterWithStatus" $(fd "FilterWithStatus.tsx")
# Let's also check for any Props type definition
rg "Props" $(fd "FilterWithStatus.tsx")
Length of output: 1475
apps/web/app/hooks/features/useTimesheet.ts (1)
15-18
: LGTM! Clear and well-structured interface definition.
The GroupedTimesheet
interface provides good type safety for the grouped timesheet data structure.
apps/web/lib/features/task/task-issue.tsx (1)
2-2
: LGTM!
The import of ITimeSheet
is appropriate for the new functionality being added.
apps/web/lib/components/custom-select/multi-select.tsx (2)
19-19
: LGTM! Interface extension is well-defined.
The new optional removeItems
prop follows TypeScript best practices and boolean naming conventions.
32-32
: LGTM! Props destructuring is correct.
The removeItems
prop is correctly destructured from the component props.
apps/web/app/[locale]/timesheet/[memberId]/page.tsx (1)
163-164
: LGTM! Filter status implementation looks good
The filter status implementation with proper prop passing and state management is well done.
apps/web/lib/features/integrations/calendar/table-time-sheet.tsx (2)
61-61
: LGTM: Import of new data type
The import of GroupedTimesheet
type aligns with the data structure changes mentioned in the summary.
297-298
: 🛠️ Refactor suggestion
Verify task property access safety
The code accesses new properties isActive
and employee.fullName
directly. These could cause runtime errors if the properties are undefined.
Consider adding null checks:
- <span className="flex-1">{task.isActive}</span>
- <span className="flex-1">{task.employee.fullName}</span>
+ <span className="flex-1">{task.isActive?.toString() ?? 'N/A'}</span>
+ <span className="flex-1">{task.employee?.fullName ?? 'Unknown'}</span>
✅ Verification successful
Property access is safe, but type coercion needed
The properties are guaranteed to be present based on the ITimeSheet
interface definition found in apps/web/app/interfaces/timer/ITimerLog.ts
:
isActive
is defined as a booleanemployee
is defined with typeEmployee
However, the display of boolean isActive
should be coerced to string:
- <span className="flex-1">{task.isActive}</span>
+ <span className="flex-1">{task.isActive.toString()}</span>
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for the GroupedTimesheet type definition and verify required properties
ast-grep --pattern 'type GroupedTimesheet = {
$$$
tasks: {
$$$
isActive: $_
employee: {
fullName: $_
$$$
}
$$$
}[]
$$$
}'
Length of output: 163
Script:
#!/bin/bash
# Let's try a broader search for type definitions related to tasks
ast-grep --pattern 'type $_ = {
$$$
tasks: $_
$$$
}'
# Also search for interface definitions
ast-grep --pattern 'interface $_ {
$$$
tasks: $_
$$$
}'
# Search for task type definitions
ast-grep --pattern 'type Task = {
$$$
}'
# Let's also check the actual usage context
rg -B5 -A5 "task\.(isActive|employee\.fullName)" apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
Length of output: 2556
Script:
#!/bin/bash
# Let's check the ITimeSheet interface definition since we found it's used in GroupedTimesheet
ast-grep --pattern 'interface ITimeSheet {
$$$
}'
# Also search for any imports of ITimeSheet to find its definition
rg "ITimeSheet" -A 2 apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
# Let's also check the actual data being passed to this component
rg -B10 "TableTimeSheet" apps/web/lib/features/integrations/calendar/table-time-sheet.tsx
Length of output: 2840
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx (1)
326-326
:
Fix the logical operator in the disabled
prop
The disabled
function currently uses &&
, which only disables dates that are both in the past and available for planning. To disable dates that are either in the past or unavailable, use ||
and adjust the condition accordingly.
Apply this diff to correct the logic:
-disabled={(date) => checkPastDate(date) && isDateAvailableForPlanning(date)}
+disabled={(date) => checkPastDate(date) || !isDateAvailableForPlanning(date)}
Likely invalid or redundant comment.
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilter.tsx
Show resolved
Hide resolved
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 (8)
apps/web/app/hooks/features/useTimesheet.ts (1)
Line range hint
42-85
: Enhance error handling and input validation.Consider the following improvements:
- Add error state handling instead of just console.log
- Validate startDate and endDate parameters
- Reset loading state on error
Here's a suggested implementation:
export function useTimesheet({ startDate, endDate, }: TimesheetParams) { const { user } = useAuthenticateUser(); const [timesheet, setTimesheet] = useAtom(timesheetRapportState); + const [error, setError] = useState<Error | null>(null); const { loading: loadingTimesheet, queryCall: queryTimesheet } = useQuery(getTaskTimesheetLogsApi); const getTaskTimesheet = useCallback( ({ startDate, endDate }: TimesheetParams) => { if (!user) return; + if (!startDate || !endDate) { + setError(new Error('Start date and end date are required')); + return; + } const from = moment(startDate).format('YYYY-MM-DD'); const to = moment(endDate).format('YYYY-MM-DD') + setError(null); queryTimesheet({ startDate: from, endDate: to, organizationId: user.employee?.organizationId, tenantId: user.tenantId ?? '', timeZone: user.timeZone?.split('(')[0].trim(), }).then((response) => { setTimesheet(response.data); }).catch((error) => { - console.error('Error fetching timesheet:', error); + setError(error); + setTimesheet([]); }); }, [user, queryTimesheet, setTimesheet] ); return { loadingTimesheet, timesheet: groupByDate(timesheet), getTaskTimesheet, + error, }; }apps/web/lib/components/custom-select/multi-select.tsx (1)
66-71
: Consider memoizing removeAllItems with useCallback.The function is used as a dependency in useEffect. Memoizing it would prevent unnecessary effect runs.
- const removeAllItems = () => { + const removeAllItems = useCallback(() => { setSelectedItems([]); if (onValueChange) { onValueChange(null); } - }; + }, [onValueChange]);apps/web/app/[locale]/timesheet/[memberId]/page.tsx (2)
120-120
: Enhance date formatting with localization supportReplace moment.js with date-fns and add localization support for date formatting:
-date={`${moment(dateRange.from).format('YYYY-MM-DD')} - ${moment(dateRange.to).format('YYYY-MM-DD')}`} +date={`${format(dateRange.from, 'P', { locale: userLocale })} - ${format(dateRange.to, 'P', { locale: userLocale })}`}
153-160
: Enhance search input with debounce and clear functionalityConsider adding:
- Debounce to prevent excessive filtering on each keystroke
- A clear button to reset the search
+const debouncedSetSearch = useMemo( + () => debounce((value: string) => setSearch(value), 300), + [] +); <input - onChange={(v) => setSearch(v.target.value)} + onChange={(v) => debouncedSetSearch(v.target.value)} + value={search} role="searchbox" aria-label="Search timesheet" type="search" name="timesheet-search" id="timesheet-search" className="!h-[2.2rem] w-full bg-transparent focus:border-transparent focus:ring-2 focus:ring-transparent placeholder-gray-500 placeholder:font-medium shadow-sm outline-none" placeholder={t('common.SEARCH')} /> +{search && ( + <button + onClick={() => setSearch('')} + className="p-1" + aria-label={t('common.CLEAR_SEARCH')} + > + <XIcon className="h-4 w-4 text-gray-500" /> + </button> +)}apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx (4)
Line range hint
46-51
: Potential stale state inonChange
callback withinhandleFromChange
.In the
handleFromChange
function, theonChange
callback usesdateRange
, which may not reflect the latest state due to the asynchronous nature ofsetState
. This can lead to inconsistencies in the date range updates.To ensure
onChange
receives the updated date range, compute the new date range before updating the state:const handleFromChange = (fromDate: Date | null) => { if (maxDate && fromDate && fromDate > maxDate) { return; } - setDateRange((prev) => ({ ...prev, from: fromDate })); - onChange?.({ ...dateRange, from: fromDate }); + const newDateRange = { ...dateRange, from: fromDate }; + setDateRange(newDateRange); + onChange?.(newDateRange); };
Line range hint
53-58
: MissingonChange
callback inhandleToChange
.The
handleToChange
function updatesdateRange
but does not invokeonChange
, which may prevent parent components from receiving updates.Consider updating
handleToChange
to callonChange
with the new date range:const handleToChange = (toDate: Date | null) => { if (dateRange.from && toDate && toDate < dateRange.from) { return; } - setDateRange((prev) => ({ ...prev, to: toDate })); + const newDateRange = { ...dateRange, to: toDate }; + setDateRange(newDateRange); + onChange?.(newDateRange); };
305-312
: Simplify date comparisons inisDateAvailableForPlanning
.The current date comparison logic involves multiple conversions and string manipulations, which can be simplified for better readability and performance.
Refactor the function using
moment
'sisSame
method:const isDateAvailableForPlanning = useCallback( (dateToCheck: Date) => { return !plans.some(plan => { - const formattedPlanDate = moment(plan.date.toString().split('T')[0]).toISOString().split('T')[0]; - const formattedDateToCheck = moment(dateToCheck).toISOString().split('T')[0]; - return formattedPlanDate === formattedDateToCheck; + return moment(plan.date).isSame(dateToCheck, 'day'); }); }, [plans] );
233-236
: Refactor repeated class names to enhance maintainability.Multiple class names are repeated across the component, which can be extracted into constants or utility functions to improve code readability and ease future modifications.
Extract repeated class names into constants:
+ const datePickerClassName = "dark:bg-dark--theme-light rounded-lg bg-white dark:text-gray-200"; + const datePickerButtonClassName = "decoration-transparent flex items-center w-full h-[2.2em] bg-white dark:text-gray-200 dark:bg-dark--theme-light border-gray-300 justify-start text-left font-normal text-black h-[2.2rem] border dark:border-slate-600 rounded-md hover:border-primary"; <DatePicker - className="dark:bg-dark--theme-light rounded-lg bg-white dark:text-gray-200 " + className={datePickerClassName} - buttonClassName={'decoration-transparent flex items-center w-full h-[2.2em] bg-white dark:text-gray-200 dark:bg-dark--theme-light border-gray-300 justify-start text-left font-normal text-black h-[2.2rem] border dark:border-slate-600 rounded-md hover:border-primary'} + buttonClassName={datePickerButtonClassName} ... />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilter.tsx
(3 hunks)apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx
(7 hunks)apps/web/app/[locale]/timesheet/[memberId]/page.tsx
(8 hunks)apps/web/app/hooks/features/useTimesheet.ts
(1 hunks)apps/web/lib/components/custom-select/multi-select.tsx
(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilter.tsx
🔇 Additional comments (7)
apps/web/app/hooks/features/useTimesheet.ts (1)
15-18
: LGTM! Clear and well-defined interface.
The GroupedTimesheet
interface is concise and properly typed for its purpose.
apps/web/lib/components/custom-select/multi-select.tsx (2)
19-19
: LGTM: Interface extension is clean and backward compatible.
The new optional removeItems
prop is well-named and follows boolean prop naming conventions.
32-32
: LGTM: Props destructuring matches interface changes.
apps/web/app/[locale]/timesheet/[memberId]/page.tsx (3)
40-41
: Well-implemented state management and filter logic
Good job on:
- Using localStorage for filter status persistence
- Implementing memoized filter logic with proper null checks
- Efficient search implementation with toLowerCase memoization
Also applies to: 53-61
166-167
: LGTM! Well-structured filter state management
Good implementation of filter status props with proper typing and state management.
181-181
: LGTM! Proper implementation of filtered data rendering
Good use of memoized filtered data with appropriate conditional rendering.
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx (1)
210-222
: Addition of maxDate
prop improves component flexibility.
The inclusion of the maxDate
prop in DatePickerFilter
allows for better control over the range of selectable dates.
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx
Show resolved
Hide resolved
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (3)
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx (3)
Line range hint
47-58
: Enhance date handling logic for better readability and maintainabilityThe date validation logic could be more concise and readable.
Consider this refactoring:
const handleFromChange = (fromDate: Date | null) => { - if (maxDate && fromDate && fromDate > maxDate) { + if (fromDate && maxDate && fromDate > maxDate) { return; } setDateRange((prev) => ({ ...prev, from: fromDate })); onChange?.({ ...dateRange, from: fromDate }); }; const handleToChange = (toDate: Date | null) => { - if (dateRange.from && toDate && toDate < dateRange.from) { + if (toDate && dateRange.from && toDate < dateRange.from) { return; } setDateRange((prev) => ({ ...prev, to: toDate })); + onChange?.({ ...dateRange, to: toDate }); };Also added the missing onChange call in handleToChange for consistency with handleFromChange.
249-256
: Extract modifier class definitions for better maintainabilityThe modifier classes could be extracted into constants to improve maintainability and reusability.
Consider this approach:
+const MODIFIER_CLASSES = { + booked: clsxm( + 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-primary after:rounded-full' + ), + selected: clsxm('bg-primary after:hidden text-white !rounded-full'), + pastDay: clsxm( + 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-yellow-600 after:rounded-full' + ), + today: clsxm('border-2 !border-yellow-700 rounded') +}; modifiersClassNames={{ - booked: clsxm( - 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-primary after:rounded-full' - ), - selected: clsxm('bg-primary after:hidden text-white !rounded-full'), - pastDay: clsxm( - 'relative after:absolute after:bottom-0 after:left-1/2 after:-translate-x-1/2 after:w-1.5 after:h-1.5 after:bg-yellow-600 after:rounded-full' - ), - today: clsxm('border-2 !border-yellow-700 rounded') + ...MODIFIER_CLASSES }}
270-340
: Consider replacing moment.js with modern alternativesmoment.js is considered legacy and is now in maintenance mode. Consider migrating to more modern alternatives like date-fns (already used in the codebase) or Temporal API.
Benefits of migration:
- Better tree-shaking support
- Smaller bundle size
- Better performance
- Immutable API design
The migration would involve replacing moment.js operations with date-fns equivalents throughout the component.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx
(7 hunks)
🔇 Additional comments (1)
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx (1)
1-2
: LGTM: Import additions are appropriate
The new imports and hooks are well-chosen for the component's needs, particularly for performance optimization with memo, useCallback, and useMemo.
Also applies to: 16-17
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (2)
apps/web/app/[locale]/timesheet/[memberId]/components/TimesheetFilterDate.tsx (2)
Line range hint
52-59
: Inconsistent onChange callback usage between handlersThe
handleToChange
function doesn't trigger theonChange
callback immediately likehandleFromChange
does. This inconsistency could lead to unexpected behavior when consuming components rely on immediate updates.Apply this diff to maintain consistency:
const handleToChange = (toDate: Date | null) => { if (dateRange.from && toDate && toDate < dateRange.from) { return; } setDateRange((prev) => ({ ...prev, to: toDate })); + onChange?.({ ...dateRange, to: toDate }); };
Line range hint
224-228
: Optimize isDateDisabled function with useMemoSince
isDateDisabled
returns a function that depends onminDate
andmaxDate
, usinguseMemo
would be more appropriate thanuseCallback
.Apply this diff to optimize the implementation:
- const isDateDisabled = React.useCallback((date: Date) => { + const isDateDisabled = React.useMemo(() => (date: Date) => { if (minDate && date < minDate) return true; if (maxDate && date > maxDate) return true; return false; }, [minDate, maxDate]);
#3044
#3040
#3046
Description
Please include a summary of the changes and the related issue.
Type of Change
Checklist
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
Summary by CodeRabbit
Release Notes
New Features
FilterCalendar
component for improved date selection and planning.TaskIssueStatusTimesheet
function for rendering task statuses related to timesheets.Improvements
Bug Fixes