-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Calendar Report #3828
base: master
Are you sure you want to change the base?
Calendar Report #3828
Conversation
✅ Deploy Preview for actualbudget ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Bundle Stats — desktop-clientHey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle. As this PR is updated, I'll keep you updated on how the bundle size is impacted. Total
Changeset
View detailed bundle breakdownAdded
Removed
Bigger
Smaller
Unchanged
|
Bundle Stats — loot-coreHey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle. As this PR is updated, I'll keep you updated on how the bundle size is impacted. Total
Changeset
View detailed bundle breakdownAdded No assets were added Removed No assets were removed Bigger
Smaller No assets were smaller Unchanged No assets were unchanged |
df17843
to
ad6ac02
Compare
WalkthroughThe pull request introduces several enhancements to the reporting functionality within the desktop client application. Key modifications include the addition of a new Possibly related PRs
Suggested labels
Suggested reviewers
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: 14
🧹 Outside diff range and nitpick comments (22)
packages/loot-core/src/types/models/dashboard.d.ts (1)
93-101
: Consider moving type definition before its usageWhile the CalendarWidget type definition is well-structured and follows the established patterns, it's currently defined after its usage in the SpecializedWidget union type. Consider moving this type definition to be grouped with other widget types (before line 68) to maintain better code organization and prevent potential TypeScript resolution issues.
- // Current position (after SpecializedWidget) - export type CalendarWidget = AbstractWidget< - 'calendar-card', - { - name?: string; - conditions?: RuleConditionEntity[]; - conditionsOp?: 'and' | 'or'; - timeFrame?: TimeFrame; - } | null - >; + // Suggested position (grouped with other widget types, before SpecializedWidget) + export type CalendarWidget = AbstractWidget< + 'calendar-card', + { + name?: string; + conditions?: RuleConditionEntity[]; + conditionsOp?: 'and' | 'or'; + timeFrame?: TimeFrame; + } | null + >; type SpecializedWidget = | NetWorthWidget | CashFlowWidget | SpendingWidget | MarkdownWidget | CalendarWidget;packages/desktop-client/src/components/reports/reportRanges.ts (2)
164-167
: Document the special case behavior.The special case where
offset === 1
results instart
equalingend
should be documented to prevent confusion.Add a comment explaining this behavior:
export function getLatestRange(offset: number) { const end = monthUtils.currentMonth(); + // When offset is 1, start and end will be the same month let start = end; if (offset !== 1) { start = monthUtils.subMonths(end, offset); } return [start, end, 'sliding-window'] as const; }
164-167
: Consider handling negative offsets.The function should validate the
offset
parameter to handle edge cases.Consider adding validation:
export function getLatestRange(offset: number) { + if (offset <= 0) { + throw new Error('Offset must be a positive number'); + } const end = monthUtils.currentMonth(); let start = end; if (offset !== 1) { start = monthUtils.subMonths(end, offset); } return [start, end, 'sliding-window'] as const; }packages/desktop-client/src/components/transactions/TransactionList.jsx (1)
91-92
: Document prop types for better maintainability.Consider adding PropTypes or TypeScript type definitions for the new props to improve code maintainability and prevent potential runtime issues.
+ import PropTypes from 'prop-types'; export function TransactionList({ // ... other props showSelection = true, allowSplitTransaction = true, }) { // ... component implementation } + TransactionList.propTypes = { + // ... other prop types + showSelection: PropTypes.bool, + allowSplitTransaction: PropTypes.bool, + };packages/desktop-client/src/components/reports/Overview.tsx (1)
562-570
: Consider enhancing robustness and user experienceGood implementation, but consider these improvements:
- Add unit tests for the calendar widget integration
- Implement a loading state for the calendar data to improve user experience
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)
885-886
: Consider adding PropTypes and default values for the new props.The new props
showSelection
andallowSplitTransaction
are propagated correctly through the component hierarchy, but they lack type definitions and default values. This could lead to runtime issues if the props are not provided.Consider adding:
+TransactionTable.propTypes = { + showSelection: PropTypes.bool, + allowSplitTransaction: PropTypes.bool, + // ... other prop types +}; + +TransactionTable.defaultProps = { + showSelection: true, + allowSplitTransaction: true, + // ... other default props +};Also applies to: 1790-1791, 1994-1995, 2615-2616
209-234
: Enhance keyboard navigation accessibility for selection cells.While the selection UI is implemented correctly, it could benefit from improved keyboard navigation feedback.
Consider adding:
<SelectCell exposed={true} focused={false} selected={hasSelected} width={20} style={{ borderTopWidth: 0, borderBottomWidth: 0, + outline: 'none', + '&:focus-visible': { + boxShadow: `0 0 0 2px ${theme.focusColor}`, + }, }} onSelect={e => dispatchSelected({ type: 'select-all', isRangeSelect: e.shiftKey, }) } + aria-label="Select all transactions" />packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (4)
130-136
: Simplify total value calculations by initializing to zeroCurrently,
totalExpenseValue
andtotalIncomeValue
are set tonull
if there are no values, which requires additional null checks later. Initializing them to zero simplifies the logic and avoids unnecessary null checks.Apply this diff to initialize totals to zero:
const totalExpenseValue = expenseValues.length ? expenseValues.reduce((acc, val) => acc + val, 0) - : null; + : 0; const totalIncomeValue = incomeValues.length ? incomeValues.reduce((acc, val) => acc + val, 0) - : null; + : 0;Then, you can simplify the
getBarLength
function by removing the null checks:const getBarLength = (value: number) => { - if (value < 0 && totalExpenseValue !== null && totalExpenseValue !== 0) { + if (value < 0 && totalExpenseValue !== 0) { return (Math.abs(value) / totalExpenseValue) * 100; - } else if ( - value > 0 && - totalIncomeValue !== null && - totalIncomeValue !== 0 - ) { + } else if (value > 0 && totalIncomeValue !== 0) { return (value / totalIncomeValue) * 100; } else { return 0; } };
93-102
: Improve clarity by renaming the 'recalculate' functionThe function name
recalculate
might not clearly indicate its purpose. Consider renaming it to better reflect its functionality, such asgenerateCalendarData
orcomputeCalendarMetrics
, to enhance code readability and maintainability.
37-67
: Combine income and expense queries to reduce database callsCurrently, two separate queries are executed to fetch income and expense data. Consider combining them into a single query to reduce database calls and improve performance.
You can modify the root query to group both positive and negative amounts and then separate them in the processing logic:
const data = await runQuery( makeRootQuery() - .filter({ - $and: { amount: { $lt: 0 } }, - }), + // No additional filter here ); // Process the data into incomeData and expenseData + const incomeData = data.data.filter(item => item.amount > 0); + const expenseData = data.data.filter(item => item.amount < 0);This approach reduces the number of queries from two to one.
106-113
: Define a shared type for income and expense data itemsThe types for
incomeData
andexpenseData
are identical. Consider defining a shared type to eliminate duplication and improve code maintainability.Apply this diff to define a shared type:
+ type TransactionDataItem = { + date: string; + amount: number; + }; function recalculate( - incomeData: Array<{ - date: string; - amount: number; - }>, - expenseData: Array<{ - date: string; - amount: number; - }>, + incomeData: TransactionDataItem[], + expenseData: TransactionDataItem[], months: Date[], start: string, firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'], ) {packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (3)
34-43
: Remove unused commented-out code for cleaner codebaseThe commented-out code in lines 34-43 appears to be obsolete or no longer needed. Removing unnecessary code can improve readability and maintainability.
Apply this diff to remove the commented code:
type CalendarGraphProps = { data: { date: Date; incomeValue: number; expenseValue: number; incomeSize: number; expenseSize: number; }[]; start: Date; firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx']; onDayClick: (date: Date) => void; - // onFilter: ( - // conditionsOrSavedFilter: - // | null - // | { - // conditions: RuleConditionEntity[]; - // conditionsOp: 'and' | 'or'; - // id: RuleConditionEntity[]; - // } - // | RuleConditionEntity, - // ) => void; };
49-49
: Clean up commented-out parameter in function declarationLine 49 contains a commented-out parameter
//onFilter,
. If this parameter is no longer required, consider removing it to tidy up the code.Apply this diff:
export function CalendarGraph({ data, start, firstDayOfWeekIdx, - //onFilter, onDayClick, }: CalendarGraphProps) {
207-211
: Eliminate unnecessary state inDayButton
componentThe
currentFontSize
state mirrors thefontSize
prop without additional computation. You can simplify the component by usingfontSize
directly.Apply this diff to remove the redundant state:
function DayButton({ day, onPress, fontSize, resizeRef }: DayButtonProps) { - const [currentFontSize, setCurrentFontSize] = useState(fontSize); - - useEffect(() => { - setCurrentFontSize(fontSize); - }, [fontSize]); return ( <Button ref={resizeRef} /* ... */ > {/* ... */} <span style={{ - fontSize: `${currentFontSize}px`, + fontSize: `${fontSize}px`, fontWeight: 500, position: 'relative', }} > {getDate(day.date)} </span> </Button> ); }packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (1)
487-517
: Avoid assignments within expressions for clearer codeUsing assignments within expressions, such as in the
ref
callbacks, can make the code harder to read and maintain. Consider separating the assignment from the expression by using dedicated functions for each ref assignment.Apply this diff to refactor the
ref
assignments:- ref={rel => (monthFormatSizeContainers.current[0] = rel)} + const setMonthFormatSizeRef0 = (rel: HTMLSpanElement | null) => { + monthFormatSizeContainers.current[0] = rel; + }; + // ... + ref={setMonthFormatSizeRef0}Repeat this pattern for the other refs at lines 494, 501, 508, and 515:
// For index 1 - ref={rel => (monthFormatSizeContainers.current[1] = rel)} + const setMonthFormatSizeRef1 = (rel: HTMLSpanElement | null) => { + monthFormatSizeContainers.current[1] = rel; + }; + ref={setMonthFormatSizeRef1} // For index 2 - ref={rel => (monthFormatSizeContainers.current[2] = rel)} + const setMonthFormatSizeRef2 = (rel: HTMLSpanElement | null) => { + monthFormatSizeContainers.current[2] = rel; + }; + ref={setMonthFormatSizeRef2} // For index 3 - ref={rel => (monthFormatSizeContainers.current[3] = rel)} + const setMonthFormatSizeRef3 = (rel: HTMLSpanElement | null) => { + monthFormatSizeContainers.current[3] = rel; + }; + ref={setMonthFormatSizeRef3} // For index 4 - ref={rel => (monthFormatSizeContainers.current[4] = rel)} + const setMonthFormatSizeRef4 = (rel: HTMLSpanElement | null) => { + monthFormatSizeContainers.current[4] = rel; + }; + ref={setMonthFormatSizeRef4}🧰 Tools
🪛 Biome
[error] 487-487: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.(lint/suspicious/noAssignInExpressions)
[error] 494-494: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.(lint/suspicious/noAssignInExpressions)
[error] 501-501: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.(lint/suspicious/noAssignInExpressions)
[error] 508-508: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.(lint/suspicious/noAssignInExpressions)
[error] 515-515: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.(lint/suspicious/noAssignInExpressions)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
278-278
: Use Consistent Default Widget NameIn the
onSaveWidgetName
function, the default widget name is set to'Net Worth'
. Since this component represents a calendar report, consider using'Calendar'
as the default name for consistency.Apply this diff to update the default name:
- const name = newName || t('Net Worth'); + const name = newName || t('Calendar');
765-788
: Avoid Rendering Empty Strings in Conditional RenderingCurrently, when
totalIncome
ortotalExpense
is falsy, an empty string''
is rendered. This can lead to unnecessary empty nodes in the DOM.Apply this diff to render
null
instead:{totalIncome ? ( // Existing rendering logic ) : ( - '' + null )} {totalExpense ? ( // Existing rendering logic ) : ( - '' + null )}
184-210
: Ensure Consistency in Error LoggingIn the added error handling, errors are logged using
console.error
. For consistency and better debugging, consider using a centralized logging mechanism if available.If your application uses a logging service or utility, replace
console.error
with that method.packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (4)
Line range hint
32-34
: Consolidate duplicate mocks for'../../hooks/useFeatureFlag'
There are two
vi.mock
statements for'../../hooks/useFeatureFlag'
, which might lead to unexpected behavior or conflicts in the tests. It's advisable to combine them into a single mock to maintain clarity and avoid potential issues.Consider merging them as follows:
-vi.mock('../../hooks/useFeatureFlag', () => ({ - default: vi.fn().mockReturnValue(false), -})); -vi.mock('../../hooks/useFeatureFlag', () => ({ - useFeatureFlag: () => false, +vi.mock('../../hooks/useFeatureFlag', () => ({ + default: vi.fn().mockReturnValue(false), + useFeatureFlag: () => false, }));Also applies to: 38-40
Line range hint
279-282
: Address the TODO: Fix flakiness and re-enable the testThere's a commented-out section with a TODO note to fix flakiness and re-enable the test for navigating to the top of the transaction list. Resolving this will ensure that all keybindings are thoroughly tested, improving the robustness of the navigation functionality.
Would you like assistance in diagnosing the cause of the flakiness and implementing a fix? I can help by providing suggestions or code changes to resolve the issue.
Line range hint
359-384
: Re-enable the skipped test:'dropdown invalid value resets correctly'
The test
'dropdown invalid value resets correctly'
is currently skipped usingtest.skip
. It's important to address any underlying issues causing the test to fail so that input validation for invalid values is thoroughly tested.Can I assist in debugging this test to identify and fix the issues? Re-enabling it will help ensure that invalid inputs are handled correctly.
171-172
: Add test cases for'showSelection'
and'allowSplitTransaction'
propsTo ensure the new props
showSelection={true}
andallowSplitTransaction={true}
function as intended, consider adding specific test cases that verify their behavior. This will help confirm that selection visibility and split transaction capabilities are working correctly in theTransactionTable
.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
upcoming-release-notes/3828.md
is excluded by!**/*.md
📒 Files selected for processing (11)
packages/desktop-client/src/components/reports/Overview.tsx
(5 hunks)packages/desktop-client/src/components/reports/ReportRouter.tsx
(2 hunks)packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
(1 hunks)packages/desktop-client/src/components/reports/reportRanges.ts
(1 hunks)packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
(1 hunks)packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
(1 hunks)packages/desktop-client/src/components/transactions/TransactionList.jsx
(2 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.jsx
(10 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx
(1 hunks)packages/loot-core/src/types/models/dashboard.d.ts
(2 hunks)
🧰 Additional context used
🪛 Biome
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
[error] 487-487: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 494-494: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 501-501: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 508-508: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 515-515: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
🔇 Additional comments (13)
packages/desktop-client/src/components/reports/ReportRouter.tsx (2)
5-5
: LGTM! Import follows established pattern
The Calendar import follows the consistent pattern used for other report components.
23-24
: LGTM! Routes follow established pattern
The new Calendar routes maintain consistency with other report routes by:
- Following the same base path + :id parameter pattern
- Using the same component structure
Let's verify the Calendar component implements the expected route parameter handling:
packages/loot-core/src/types/models/dashboard.d.ts (1)
68-69
: LGTM: Clean addition to SpecializedWidget union type
The CalendarWidget is properly integrated into the SpecializedWidget union type, following the established pattern.
packages/desktop-client/src/components/reports/reportRanges.ts (1)
164-167
: Verify the impact of modified date range logic.
The change introduces a special case where start
equals end
when offset
is 1. While this might be intentional for the new calendar feature, we should verify it doesn't affect existing reports.
Let's check for existing usages:
packages/desktop-client/src/components/transactions/TransactionList.jsx (1)
256-257
: LGTM!
The props are correctly passed down to the TransactionTable
component, maintaining a clean pass-through implementation.
packages/desktop-client/src/components/reports/Overview.tsx (3)
25-25
: LGTM: Clean implementation of user preference sync
The implementation properly handles the first day of week preference with a sensible default value.
Also applies to: 56-57
38-38
: LGTM: Consistent import organization
The CalendarCard import follows the established pattern for report components.
562-570
: Verify CalendarCard component interface
The integration looks good, but please ensure:
- The CalendarCard component has proper TypeScript interfaces for all props
- The component is documented with JSDoc comments
#!/bin/bash
# Search for CalendarCard component definition and its interface
ast-grep --pattern 'interface $name {
$$$
widgetId: $_
isEditing: $_
meta: $_
firstDayOfWeekIdx: $_
$$$
}'
# Search for JSDoc comments
rg -B 2 "export (type|interface) .*CalendarCard.*|export function CalendarCard"
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (1)
159-162
:
Review the calculation logic for 'totalDays'
The calculation for totalDays
may not correctly account for the number of days to display, especially when the month doesn't start on the first day of the week. d.differenceInDays(firstDay, beginDay)
can return a negative value if beginDay
is before firstDay
, which is expected, but adding d.getDaysInMonth(firstDay)
may not produce the intended result. Consider revising the logic to ensure that totalDays
accurately represents the days needed to fill the calendar grid.
Please verify the correctness of the totalDays
calculation. You can test this with different months and firstDayOfWeekIdx
values to ensure the calendar displays properly.
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (1)
183-183
: Verify the logic for assigning resizeRef
based on index
Assigning resizeRef
to DayButton
only when index === 15
may not work consistently with varying data lengths. Ensure that this logic is intentional and functions correctly for all data sets.
Consider applying resizeRef
to a consistent element, such as the first day of the month, or refactoring the approach to dynamically select an appropriate DayButton
.
packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
356-360
: Include Dependencies in useEffect
Dependency Array
The useEffect
hook uses refContainer.current
, but refContainer
is not included in the dependency array. Although refs generally don't need to be in dependencies, if refContainer.current
can change, consider adding it to ensure the effect runs as expected.
Please verify if refContainer.current
can change over time. If so, include it in the dependencies:
useEffect(() => {
if (refContainer.current) {
setTotalHeight(refContainer.current.clientHeight - 115);
}
- }, [query]);
+ }, [query, refContainer.current]);
399-415
: Ensure Proper Handling of Negative Offset Values
In the useDrag
hook, the condition oy < 0
triggers cancel()
and opens the transactions. Ensure that negative offset values are correctly handled and that this logic matches the intended user interaction.
Please test the drag behavior to confirm that upward swipes open the transaction list as intended, and that the gestures function correctly on different devices.
121-123
: Confirm Default Value for firstDayOfWeekIdx
The default value for firstDayOfWeekIdx
is set to '0'
if _firstDayOfWeekIdx
is undefined. Ensure that this default aligns with the expected first day of the week in your application (e.g., Sunday).
Consider parsing the value to an integer:
- const firstDayOfWeekIdx = _firstDayOfWeekIdx || '0';
+ const firstDayOfWeekIdx = parseInt(_firstDayOfWeekIdx ?? '0', 10);
packages/desktop-client/src/components/transactions/TransactionsTable.jsx
Show resolved
Hide resolved
packages/desktop-client/src/components/transactions/TransactionsTable.jsx
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
b0037de
to
ab75214
Compare
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: 6
🧹 Outside diff range and nitpick comments (12)
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (2)
18-36
: Consider extracting the callback type definition.The
setData
callback type is complex and could be reused elsewhere. Consider extracting it into a separate type definition.+type CalendarSetDataType = { + calendarData: { + start: Date; + end: Date; + data: CalendarDataType[]; + totalExpense: number; + totalIncome: number; + }[]; +}; export function calendarSpreadsheet( start: string, end: string, conditions: RuleConditionEntity[] = [], conditionsOp: 'and' | 'or' = 'and', firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'], ) { return async ( spreadsheet: ReturnType<typeof useSpreadsheet>, - setData: (data: { - calendarData: { - start: Date; - end: Date; - data: CalendarDataType[]; - totalExpense: number; - totalIncome: number; - }[]; - }) => void, + setData: (data: CalendarSetDataType) => void, ) => {
103-114
: Simplify month range calculation using date-fns.The
getOneDatePerMonth
function could be simplified usingeachMonthOfInterval
from date-fns.- const getOneDatePerMonth = (start: Date, end: Date) => { - const months = []; - let currentDate = d.startOfMonth(start); - - while (!d.isSameMonth(currentDate, end)) { - months.push(currentDate); - currentDate = d.addMonths(currentDate, 1); - } - months.push(end); - - return months; - }; + const getOneDatePerMonth = (start: Date, end: Date) => { + return d.eachMonthOfInterval({ start, end }); + };packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (4)
34-43
: Remove or implement the commented outonFilter
propThe commented out
onFilter
prop and its type definition suggest incomplete functionality. Either implement this feature or remove the commented code to maintain clean and maintainable code.
78-81
: Extract grid layout constantsConsider extracting the grid layout values into named constants for better maintainability and reusability.
+const CALENDAR_GRID = { + COLUMNS: 7, + GAP: 2, +} as const; + <View style={{ color: theme.pageTextSubdued, display: 'grid', - gridTemplateColumns: 'repeat(7, 1fr)', + gridTemplateColumns: `repeat(${CALENDAR_GRID.COLUMNS}, 1fr)`, gridAutoRows: '1fr', - gap: 2, + gap: CALENDAR_GRID.GAP, }} >
119-176
: Extract tooltip content into a separate componentThe tooltip content is complex and could benefit from being extracted into a separate component for better maintainability and reusability.
Consider creating a
DayTooltip
component to encapsulate this logic:type DayTooltipProps = { day: { date: Date; incomeValue: number; expenseValue: number; incomeSize: number; expenseSize: number; }; }; function DayTooltip({ day }: DayTooltipProps) { const { t } = useTranslation(); return ( <View> <View style={{ marginBottom: 10 }}> <strong> {t('Day:') + ' '} {format(day.date, 'dd')} </strong> </View> {/* ... rest of the tooltip content ... */} </View> ); }
232-282
: Reduce style duplication in bar componentsThe bar styles for income and expense are very similar. Consider extracting common styles into a shared object or utility function.
const getBarStyles = (side: 'left' | 'right', color: string, size: number) => ({ position: 'absolute', [side]: 0, bottom: 0, opacity: 0.9, height: `${Math.ceil(size)}%`, backgroundColor: color, width: '50%', transition: 'height 0.5s ease-out', } as const); // Usage: <View className="bar positive-bar" style={getBarStyles('left', chartTheme.colors.blue, day.incomeSize)} /> <View className="bar" style={getBarStyles('right', chartTheme.colors.red, day.expenseSize)} />packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (3)
44-51
: Consider enhancing type safety for meta propThe
meta
prop's type could be more strictly defined to prevent potential runtime errors.Consider this improvement:
type CalendarProps = { widgetId: string; isEditing?: boolean; - meta?: CalendarWidget['meta']; + meta?: { + name?: string; + timeFrame?: string; + conditions?: unknown; + conditionsOp?: string; + }; onMetaChange: (newMeta: CalendarWidget['meta']) => void; onRemove: () => void; firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx']; };
97-111
: Optimize total calculations with single reduceThe current implementation uses two separate reduce operations to calculate totals. This can be optimized to a single pass.
Consider this optimization:
const { totalIncome, totalExpense } = useMemo(() => { if (!data) { return { totalIncome: 0, totalExpense: 0 }; } - return { - totalIncome: data.calendarData.reduce( - (prev, cur) => prev + cur.totalIncome, - 0, - ), - totalExpense: data.calendarData.reduce( - (prev, cur) => prev + cur.totalExpense, - 0, - ), - }; + return data.calendarData.reduce( + (acc, cur) => ({ + totalIncome: acc.totalIncome + cur.totalIncome, + totalExpense: acc.totalExpense + cur.totalExpense, + }), + { totalIncome: 0, totalExpense: 0 }, + ); }, [data]);
431-473
: Enhance accessibility for financial indicatorsWhile basic aria-labels are present, the financial information could be more accessible.
Consider these improvements:
<View style={{ color: chartTheme.colors.blue, flexDirection: 'row', fontSize: '10px', marginRight: 10, }} - aria-label="Income" + aria-label={`Income ${calendar.totalIncome !== 0 ? amountToCurrency(calendar.totalIncome) : 'none'}`} + role="text" > {/* ... */} </View> <View style={{ color: chartTheme.colors.red, flexDirection: 'row', fontSize: '10px', }} - aria-label="Expenses" + aria-label={`Expenses ${calendar.totalExpense !== 0 ? amountToCurrency(calendar.totalExpense) : 'none'}`} + role="text" >packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
93-131
: Consider consolidating related state managementThe component manages several related pieces of state (start, end, mode) separately. Consider using useReducer to manage these related states together, which would make the state transitions more predictable and easier to maintain.
- const [start, setStart] = useState(initialStart); - const [end, setEnd] = useState(initialEnd); - const [mode, setMode] = useState(initialMode); + const [dateRange, dispatch] = useReducer(dateRangeReducer, { + start: initialStart, + end: initialEnd, + mode: initialMode + });
702-830
: Consider extracting styles for better maintainabilityThe component contains multiple inline styles that could be extracted into a separate styles object or styled-components for better organization and reuse.
+const calendarStyles = { + container: { + minWidth: '300px', + maxWidth: '300px', + padding: 10, + borderRadius: 4, + backgroundColor: theme.tableBackground, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + flexWrap: 'wrap', + marginBottom: 16, + }, + // ... other styles +}; function CalendarWithHeader({ ... }) { return ( <View - style={{ - minWidth: '300px', - maxWidth: '300px', - padding: 10, - borderRadius: 4, - backgroundColor: theme.tableBackground, - }} + style={calendarStyles.container} >
913-932
: Simplify field mapping with object literalThe switch statement could be replaced with an object literal for better maintainability and performance.
+const FIELD_MAPPINGS = { + account: 'account.name', + payee: 'payee.name', + category: 'category.name', + payment: 'amount', + deposit: 'amount', +}; function getField(field?: string) { - if (!field) { - return 'date'; - } - - switch (field) { - case 'account': - return 'account.name'; - case 'payee': - return 'payee.name'; - case 'category': - return 'category.name'; - case 'payment': - return 'amount'; - case 'deposit': - return 'amount'; - default: - return field; - } + if (!field) return 'date'; + return FIELD_MAPPINGS[field] || field; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (4)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
(1 hunks)packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
(1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (1)
Learnt from: lelemm
PR: actualbudget/actual#3828
File: packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts:154-157
Timestamp: 2024-11-12T16:44:22.913Z
Learning: In `packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts`, the variable `firstDayOfWeekIdx` comes from a system parameter and is guaranteed to be a number between 0 and 6, so additional validation is not necessary when using it in `startOfWeek`.
🪛 Biome
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
[error] 56-56: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
[error] 490-490: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 497-497: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 504-504: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 511-511: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 518-518: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
🔇 Additional comments (6)
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (3)
1-17
: LGTM! Well-structured imports and type definitions.
The imports are properly organized and the CalendarDataType
interface is well-defined with clear type annotations.
79-101
: LGTM! Robust error handling for data fetching.
The code properly handles potential errors during data fetching and provides appropriate fallbacks.
187-190
: LGTM! Proper handling of firstDayOfWeekIdx.
Based on the learnings from previous reviews, firstDayOfWeekIdx
is guaranteed to be a number between 0 and 6, so the current implementation is correct.
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (1)
1-294
: Overall implementation looks good!
The calendar graph implementation effectively visualizes financial data with good separation of concerns, proper type definitions, and responsive design. The suggested improvements are mostly about code organization and reducing duplication.
🧰 Tools
🪛 Biome
[error] 56-56: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (1)
1-525
: Overall implementation looks good!
The calendar card implementation is well-structured with proper separation of concerns, good use of hooks, and responsive design considerations. The suggested improvements are non-blocking and can be addressed in follow-up iterations.
🧰 Tools
🪛 Biome
[error] 490-490: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 497-497: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 504-504: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 511-511: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
[error] 518-518: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
73-86
: LGTM! Clean component structure with proper loading handling.
The Calendar component effectively manages the loading state and cleanly delegates the main functionality to CalendarInner.
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.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: 7
🧹 Outside diff range and nitpick comments (20)
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (2)
206-211
: Simplify firstDayOfWeekIdx validationThe current validation is verbose and performs multiple
parseInt
operations. Consider simplifying it using a single parse operation.- weekStartsOn: - firstDayOfWeekIdx !== undefined && - !Number.isNaN(parseInt(firstDayOfWeekIdx)) && - parseInt(firstDayOfWeekIdx) >= 0 && - parseInt(firstDayOfWeekIdx) <= 6 - ? (parseInt(firstDayOfWeekIdx) as 0 | 1 | 2 | 3 | 4 | 5 | 6) - : 0, + weekStartsOn: (() => { + const idx = typeof firstDayOfWeekIdx === 'string' ? parseInt(firstDayOfWeekIdx) : firstDayOfWeekIdx; + return idx !== undefined && idx >= 0 && idx <= 6 ? (idx as 0 | 1 | 2 | 3 | 4 | 5 | 6) : 0; + })(),
61-62
: Enhance error messages for date parsing failuresThe current error messages could be more specific to help with debugging.
- throw new Error('Invalid start date format'); + throw new Error(`Invalid start date format: "${monthUtils.firstDayOfMonth(start)}". Expected format: yyyy-MM-dd`);- throw new Error('Invalid end date format'); + throw new Error(`Invalid end date format: "${monthUtils.lastDayOfMonth(end)}". Expected format: yyyy-MM-dd`);Also applies to: 73-74
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (6)
34-43
: Remove or implement the commented filter functionalityThe commented out
onFilter
prop and its type definition suggest an incomplete feature. Either implement the filtering functionality or remove the commented code to maintain code cleanliness.
54-60
: Optimize firstDayOfWeekIdx parsingThe
parseInt
function is called multiple times on the same value. Consider parsing it once and storing the result.- weekStartsOn: - firstDayOfWeekIdx !== undefined && - !Number.isNaN(parseInt(firstDayOfWeekIdx)) && - parseInt(firstDayOfWeekIdx) >= 0 && - parseInt(firstDayOfWeekIdx) <= 6 - ? (parseInt(firstDayOfWeekIdx) as 0 | 1 | 2 | 3 | 4 | 5 | 6) - : 0, + weekStartsOn: (() => { + const parsed = parseInt(firstDayOfWeekIdx); + return firstDayOfWeekIdx !== undefined && + !Number.isNaN(parsed) && + parsed >= 0 && + parsed <= 6 + ? (parsed as 0 | 1 | 2 | 3 | 4 | 5 | 6) + : 0; + })(),
64-71
: Extract magic numbers into named constantsThe font size calculation uses magic numbers (14). Consider extracting these into named constants for better maintainability.
+const MAX_FONT_SIZE = 14; + const buttonRef = useResizeObserver(rect => { const newValue = Math.floor(rect.height / 2); - if (newValue > 14) { - setFontSize(14); + if (newValue > MAX_FONT_SIZE) { + setFontSize(MAX_FONT_SIZE); } else { setFontSize(newValue); } });
143-143
: Use translation keys for tooltip labelsThe strings "Income:" and "Expenses:" are hardcoded. Since you're using i18n, these should use translation keys for consistency.
- Income: + {t('income.label')} // ... - Expenses: + {t('expenses.label')}Also applies to: 163-163
214-216
: Clean up effect dependenciesThe
useEffect
hook doesn't clean up the state when the component unmounts. While this might not cause issues in the current implementation, it's a good practice to include cleanup.useEffect(() => { setCurrentFontSize(fontSize); + return () => { + // Clean up any pending state updates + setCurrentFontSize(prevSize => prevSize); + }; }, [fontSize]);
267-267
: Extract transition styles into themeThe transition properties are duplicated. Consider extracting them into the theme for consistency and reusability.
// In theme.ts +export const transitions = { + barHeight: 'height 0.5s ease-out', +}; // In this file - transition: 'height 0.5s ease-out', + transition: theme.transitions.barHeight, // ... - transition: 'height 0.5s ease-out', + transition: theme.transitions.barHeight,Also applies to: 281-281
packages/desktop-client/src/style/themes/development.ts (1)
217-217
: Group calendar-related constants together.Consider moving this constant to be grouped with other calendar-related constants around line 134 for better maintainability and readability.
export const calendarText = colorPalette.navy50; export const calendarBackground = colorPalette.navy900; export const calendarItemText = colorPalette.navy150; export const calendarItemBackground = colorPalette.navy800; export const calendarSelectedBackground = colorPalette.navy500; +export const calendarCellBackground = colorPalette.navy900;
packages/desktop-client/src/style/themes/dark.ts (1)
217-217
: Group calendar-related constants together.Consider moving this constant to be grouped with other calendar-related constants around line 144 for better code organization and maintainability.
export const calendarText = colorPalette.navy50; export const calendarBackground = colorPalette.navy900; export const calendarItemText = colorPalette.navy150; export const calendarItemBackground = colorPalette.navy800; export const calendarSelectedBackground = buttonNormalSelectedBackground; +export const calendarCellBackground = colorPalette.navy900;
packages/desktop-client/src/style/themes/light.ts (1)
219-219
: Consider grouping with existing calendar constants.While the new
calendarCellBackground
constant is valid, consider moving it near the other calendar-related constants (around line 144) to maintain better code organization and make it easier to manage calendar theming.export const calendarSelectedBackground = colorPalette.navy500; +export const calendarCellBackground = colorPalette.navy100; export const buttonBareText = buttonNormalText;
packages/desktop-client/src/style/themes/midnight.ts (1)
219-219
: Group calendar-related constants together.Consider moving
calendarCellBackground
to be grouped with other calendar-related constants (around line 150) for better code organization and maintainability.export const calendarSelectedBackground = buttonNormalSelectedBackground; +export const calendarCellBackground = colorPalette.navy900; export const buttonBareText = buttonNormalText; - -export const calendarCellBackground = colorPalette.navy900;packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (4)
159-169
: Consider using TypeScript discriminated unions for menu itemsThe switch statement uses string literals, which could be made type-safe using a discriminated union type.
+type MenuItem = { name: 'rename'; text: string } | { name: 'remove'; text: string }; -onMenuSelect={item => { +onMenuSelect={(item: MenuItem['name']) => { switch (item) {
207-223
: Add aria-label to income and expense sections in tooltipThe tooltip content should have proper aria-labels for better accessibility.
-<View style={{ lineHeight: 1.5 }}> +<View style={{ lineHeight: 1.5 }} aria-label="Financial summary"> <View style={{ display: 'grid', gridTemplateColumns: '70px 1fr', gridAutoRows: '1fr', }} + role="list" >Also applies to: 224-240
398-423
: Enhance keyboard navigation for the month buttonThe month button should have proper keyboard focus management and ARIA attributes.
<Button variant="bare" + role="link" + aria-label={`View calendar for ${format(calendar.start, selectedMonthNameFormat)}`} style={{ visibility: monthNameVisible ? 'visible' : 'hidden', overflow: 'visible',
489-532
: Refactor month format measurement elementsThe repeated span elements for month format measurements could be generated programmatically to reduce code duplication.
+const MONTH_FORMATS = [ + 'MMMM yyyy', + 'MMM yyyy', + 'MMM yy', + 'MMM', + '' +] as const; <View style={{ fontWeight: 'bold', fontSize: '12px' }}> - <span - ref={node => { - if (node) monthFormatSizeContainers.current[0] = node; - }} - style={{ position: 'fixed', top: -9999, left: -9999 }} - data-format="MMMM yyyy" - > - {format(calendar.start, 'MMMM yyyy')}: - </span> + {MONTH_FORMATS.map((formatString, idx) => ( + <span + key={formatString} + ref={node => { + if (node) monthFormatSizeContainers.current[idx] = node; + }} + style={{ position: 'fixed', top: -9999, left: -9999 }} + data-format={formatString} + > + {formatString ? `${format(calendar.start, formatString)}:` : ''} + </span> + ))}packages/desktop-client/src/components/reports/reports/Calendar.tsx (4)
121-122
: Consider using a type-safe default for firstDayOfWeekIdxThe current fallback to '0' could be more explicit and type-safe.
-const [_firstDayOfWeekIdx] = useSyncedPref('firstDayOfWeekIdx'); -const firstDayOfWeekIdx = _firstDayOfWeekIdx || '0'; +const DEFAULT_FIRST_DAY_OF_WEEK = '0'; +const [_firstDayOfWeekIdx] = useSyncedPref('firstDayOfWeekIdx'); +const firstDayOfWeekIdx = _firstDayOfWeekIdx ?? DEFAULT_FIRST_DAY_OF_WEEK;
724-730
: Extract hardcoded dimensions as constantsConsider extracting the hardcoded dimensions for better maintainability.
+const CALENDAR_DIMENSIONS = { + MIN_WIDTH: '300px', + MAX_WIDTH: '300px', + PADDING: 10, + BORDER_RADIUS: 4, +}; + <View style={{ - minWidth: '300px', - maxWidth: '300px', - padding: 10, - borderRadius: 4, + minWidth: CALENDAR_DIMENSIONS.MIN_WIDTH, + maxWidth: CALENDAR_DIMENSIONS.MAX_WIDTH, + padding: CALENDAR_DIMENSIONS.PADDING, + borderRadius: CALENDAR_DIMENSIONS.BORDER_RADIUS, backgroundColor: theme.tableBackground, }} >
889-918
: Simplify conditional rendering logicThe conditions for showing income and expense can be simplified to avoid repetition.
- {totalIncome !== 0 && ( + {[ + { label: 'Income:', value: totalIncome, color: chartTheme.colors.blue }, + { label: 'Expenses:', value: totalExpense, color: chartTheme.colors.red } + ].map(({ label, value, color }) => value !== 0 && ( <> <View style={{ textAlign: 'right', marginRight: 4, }} > - Income: + {label} </View> - <View style={{ color: chartTheme.colors.blue }}> - {totalIncome !== 0 ? amountToCurrency(totalIncome) : ''} + <View style={{ color }}> + {amountToCurrency(value)} </View> </> - )} - {totalExpense !== 0 && ( - <> - <View - style={{ - textAlign: 'right', - marginRight: 4, - }} - > - Expenses: - </View> - <View style={{ color: chartTheme.colors.red }}> - {totalExpense !== 0 ? amountToCurrency(totalExpense) : ''} - </View> - </> - )} + ))}
926-945
: Enhance type safety of field mappingConsider using TypeScript enums or constants for field names to improve type safety and maintainability.
+enum FieldMapping { + Account = 'account', + Payee = 'payee', + Category = 'category', + Payment = 'payment', + Deposit = 'deposit', +} + +const FIELD_NAME_MAPPING: Record<FieldMapping, string> = { + [FieldMapping.Account]: 'account.name', + [FieldMapping.Payee]: 'payee.name', + [FieldMapping.Category]: 'category.name', + [FieldMapping.Payment]: 'amount', + [FieldMapping.Deposit]: 'amount', +}; + function getField(field?: string) { if (!field) { return 'date'; } - switch (field) { - case 'account': - return 'account.name'; - case 'payee': - return 'payee.name'; - case 'category': - return 'category.name'; - case 'payment': - return 'amount'; - case 'deposit': - return 'amount'; - default: - return field; - } + return FIELD_NAME_MAPPING[field as FieldMapping] || field; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (8)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
(1 hunks)packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
(1 hunks)packages/desktop-client/src/style/themes/dark.ts
(1 hunks)packages/desktop-client/src/style/themes/development.ts
(1 hunks)packages/desktop-client/src/style/themes/light.ts
(1 hunks)packages/desktop-client/src/style/themes/midnight.ts
(1 hunks)
🔇 Additional comments (6)
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (1)
1-17
: LGTM! Well-organized imports and clear type definitions.
The imports are properly organized and the CalendarDataType
interface is well-structured with clear field names.
packages/desktop-client/src/style/themes/dark.ts (1)
217-217
: Verify color contrast ratios for accessibility.
The calendarCellBackground
uses navy900, which should provide sufficient contrast with text colors. However, let's verify the contrast ratios meet WCAG guidelines.
packages/desktop-client/src/style/themes/light.ts (1)
219-219
: Verify color contrast with existing calendar colors.
The new calendarCellBackground
uses navy100
which needs to work well with existing calendar colors:
- Text: navy50
- Background: navy900
- Item text: navy150
- Item background: navy800
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (2)
1-51
: LGTM! Well-structured imports and type definitions.
The imports are properly organized, and the type definitions are clear and comprehensive.
68-78
: Consider adding error handling for invalid date ranges
The useMemo
hook calculates params without validating that start
is before end
. While the calculateTimeRange
function likely handles this, explicit validation would improve robustness.
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
73-86
: LGTM! Well-structured component with proper loading state handling.
The component follows good practices by:
- Using TypeScript for type safety
- Showing a loading indicator during data fetching
- Having a single responsibility
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
b8e0cdb
to
a2cebb2
Compare
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 (17)
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (3)
18-24
: Consider adding input validation for date parameters.The function accepts date strings but doesn't validate their format at the parameter level. While there's error handling during parsing, early validation could prevent unnecessary processing.
Consider adding validation:
export function calendarSpreadsheet( start: string, end: string, conditions: RuleConditionEntity[] = [], conditionsOp: 'and' | 'or' = 'and', firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'], ) { + const isValidDateFormat = (date: string) => /^\d{4}-\d{2}-\d{2}$/.test(date); + if (!isValidDateFormat(start) || !isValidDateFormat(end)) { + throw new Error('Invalid date format. Expected: YYYY-MM-DD'); + } return async (
219-223
: Consider extracting and documenting the calendar grid calculation logic.The logic for calculating
totalDays
and padding to ensure complete weeks is important but could be more self-documenting.Consider extracting this logic:
- let totalDays = - d.differenceInDays(firstDay, beginDay) + d.getDaysInMonth(firstDay); - if (totalDays % 7 !== 0) { - totalDays += 7 - (totalDays % 7); - } + const calculateCalendarGridDays = (firstDay: Date, beginDay: Date) => { + // Calculate days needed to display the full month + const daysBeforeMonth = d.differenceInDays(firstDay, beginDay); + const daysInMonth = d.getDaysInMonth(firstDay); + const totalDays = daysBeforeMonth + daysInMonth; + + // Ensure we have complete weeks by padding if necessary + const remainingDays = totalDays % 7; + return remainingDays === 0 ? totalDays : totalDays + (7 - remainingDays); + }; + + const totalDays = calculateCalendarGridDays(firstDay, beginDay);
244-244
: Document currency conversion assumptions.The code divides monetary values by 100, suggesting a conversion from cents to dollars/euros, but this assumption isn't documented.
Consider adding comments:
return { data: daysArray as CalendarDataType[], + // Convert from cents to dollars/euros totalExpense: (totalExpenseValue ?? 0) / 100, totalIncome: (totalIncomeValue ?? 0) / 100, };
Also applies to: 246-246, 253-254
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (6)
34-43
: Remove or implement the commented filter functionalityThe commented out
onFilter
prop suggests incomplete functionality. Either implement the filtering feature or remove the commented code to maintain clean types.- // onFilter: ( - // conditionsOrSavedFilter: - // | null - // | { - // conditions: RuleConditionEntity[]; - // conditionsOp: 'and' | 'or'; - // id: RuleConditionEntity[]; - // } - // | RuleConditionEntity, - // ) => void;
23-34
: Add JSDoc comments to type definitionsConsider adding documentation to describe the purpose and usage of each prop in the
CalendarGraphProps
type.+/** + * Props for the CalendarGraph component + * @property data - Array of daily financial data + * @property start - Starting date for the calendar + * @property firstDayOfWeekIdx - User preference for first day of week + * @property onDayClick - Callback when a day is clicked + */ type CalendarGraphProps = { data: { date: Date; incomeValue: number; expenseValue: number; incomeSize: number; expenseSize: number; }[]; start: Date; firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx']; onDayClick: (date: Date) => void; };
110-110
: Remove or implement the commented grid calculationThere's a commented out grid calculation that should either be implemented or removed.
- //gridTemplateRows: `repeat(${Math.trunc(data.length) <= data.length / 7 ? Math.trunc(data.length) : Math.trunc(data.length) + 1},1fr)`,
119-178
: Extract tooltip content into a separate componentThe tooltip content is complex enough to warrant its own component. This would improve maintainability and reusability.
Consider creating a
DayTooltip
component:type DayTooltipProps = { day: { date: Date; incomeValue: number; expenseValue: number; incomeSize: number; expenseSize: number; }; }; function DayTooltip({ day }: DayTooltipProps) { const { t } = useTranslation(); return ( <View> <View style={{ marginBottom: 10 }}> <strong> {t('Day:') + ' '} {format(day.date, 'dd')} </strong> </View> {/* ... rest of the tooltip content ... */} </View> ); }
211-296
: Optimize DayButton component performanceThe component could benefit from several performance optimizations:
- Memoize the component to prevent unnecessary re-renders
- Extract static styles
- Use CSS classes instead of inline styles for common properties
+ const dayButtonStyles = { + button: { + borderColor: 'transparent', + backgroundColor: theme.calendarCellBackground, + position: 'relative' as const, + padding: 'unset', + height: '100%', + minWidth: 0, + minHeight: 0, + margin: 0, + }, + // ... other static styles + }; - function DayButton({ day, onPress, fontSize, resizeRef }: DayButtonProps) { + const DayButton = React.memo(function DayButton({ + day, + onPress, + fontSize, + resizeRef + }: DayButtonProps) { // ... component implementation - } + });
234-284
: Consider using CSS Grid for bar layoutThe current implementation uses absolute positioning for the bars. Consider using CSS Grid for better maintainability and performance.
+ const barContainerStyle = { + display: 'grid', + gridTemplateColumns: '1fr 1fr', + height: '100%', + position: 'absolute', + width: '100%', + }; - {day.expenseSize !== 0 && ( - <View - style={{ - position: 'absolute', - width: '50%', - height: '100%', - // ... - }} - /> - )} + <View style={barContainerStyle}> + <View + style={{ + height: `${Math.ceil(day.incomeSize)}%`, + backgroundColor: chartTheme.colors.blue, + opacity: 0.9, + alignSelf: 'end', + transition: 'height 0.5s ease-out', + }} + /> + <View + style={{ + height: `${Math.ceil(day.expenseSize)}%`, + backgroundColor: chartTheme.colors.red, + opacity: 0.9, + alignSelf: 'end', + transition: 'height 0.5s ease-out', + }} + /> + </View>packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (4)
200-244
: Add i18n translations for tooltip contentThe tooltip content contains hardcoded English strings that should be translated.
Consider using the translation hook:
<View style={{ lineHeight: 1.5 }}> <View style={{ display: 'grid', gridTemplateColumns: '70px 1fr', gridAutoRows: '1fr', }}> {totalIncome !== 0 && ( <> <View style={{ textAlign: 'right', marginRight: 4, }}> - Income: + {t('Income')}: </View> <View style={{ color: chartTheme.colors.blue }}> {totalIncome !== 0 ? amountToCurrency(totalIncome) : ''} </View> </> )} {totalExpense !== 0 && ( <> <View style={{ textAlign: 'right', marginRight: 4, }}> - Expenses: + {t('Expenses')}: </View> <View style={{ color: chartTheme.colors.red }}> {totalExpense !== 0 ? amountToCurrency(totalExpense) : ''} </View> </> )} </View> </View>
160-170
: Improve menu selection error handlingThe error handling for unrecognized menu selections could be more graceful.
Consider using a more descriptive error message and logging:
switch (item) { case 'rename': setNameMenuOpen(true); break; case 'remove': onRemove(); break; default: - throw new Error(`Unrecognized selection: ${item}`); + console.error(`Unrecognized menu selection: ${item}`); + // Handle gracefully instead of throwing + break; }
327-333
: Memoize month format measurementsThe
measureMonthFormats
function could benefit from memoization to prevent unnecessary recalculations.Consider using
useMemo
:- const measureMonthFormats = useCallback(() => { + const measureMonthFormats = useMemo(() => () => { const measurements = monthFormatSizeContainers.current.map(container => ({ width: container?.clientWidth ?? 0, format: container?.getAttribute('data-format') ?? '', })); return measurements; }, []);
487-493
: Add error boundary for calendar click handlerThe calendar day click handler should be wrapped in a try-catch block to handle potential navigation errors gracefully.
Consider adding error handling:
onDayClick={date => { + try { navigate( isDashboardsFeatureEnabled ? `/reports/calendar/${widgetId}?day=${format(date, 'yyyy-MM-dd')}` : '/reports/calendar', ); + } catch (error) { + console.error('Failed to navigate:', error); + // Handle the error gracefully, perhaps show a notification + } }}packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
93-691
: Consider breaking down the CalendarInner component.The component is handling multiple responsibilities including state management, filtering, mobile UI, and transaction list rendering. Consider extracting some of these into separate components for better maintainability.
Potential extractions:
- Filter management logic
- Mobile transaction drawer
- Transaction list wrapper
740-771
: Add aria-label to month filter button.The button shows the month and year but lacks an aria-label to indicate its filtering action.
<Button variant="bare" + aria-label={`Filter transactions for ${format(calendar.start, 'MMMM yyyy')}`} style={{ color: theme.pageTextSubdued, fontWeight: 'bold',
926-945
: Improve type safety of getField function.Consider using a union type for the field parameter to catch invalid field names at compile time.
+type AllowedFields = 'account' | 'payee' | 'category' | 'payment' | 'deposit' | 'date'; + -function getField(field?: string) { +function getField(field?: AllowedFields) { if (!field) { return 'date'; }packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)
1918-1919
: Consider using React Context for prop propagationWhile the current implementation correctly propagates the selection and split transaction props through the component hierarchy, consider using React Context to avoid prop drilling, especially since these props are used by multiple nested components.
Example implementation:
+const TransactionContext = React.createContext({ + showSelection: true, + allowSplitTransaction: true +}); + +export function TransactionProvider({ children, showSelection, allowSplitTransaction }) { + return ( + <TransactionContext.Provider value={{ showSelection, allowSplitTransaction }}> + {children} + </TransactionContext.Provider> + ); +}This would simplify prop passing and make the code more maintainable.
Also applies to: 2002-2003, 2028-2028, 2649-2650
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
upcoming-release-notes/3828.md
is excluded by!**/*.md
📒 Files selected for processing (15)
packages/desktop-client/src/components/reports/Overview.tsx
(5 hunks)packages/desktop-client/src/components/reports/ReportRouter.tsx
(2 hunks)packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
(1 hunks)packages/desktop-client/src/components/reports/reportRanges.ts
(1 hunks)packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
(1 hunks)packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts
(1 hunks)packages/desktop-client/src/components/transactions/TransactionList.jsx
(2 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.jsx
(10 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx
(1 hunks)packages/desktop-client/src/style/themes/dark.ts
(1 hunks)packages/desktop-client/src/style/themes/development.ts
(1 hunks)packages/desktop-client/src/style/themes/light.ts
(1 hunks)packages/desktop-client/src/style/themes/midnight.ts
(1 hunks)packages/loot-core/src/types/models/dashboard.d.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
- packages/desktop-client/src/components/reports/Overview.tsx
- packages/desktop-client/src/components/reports/ReportRouter.tsx
- packages/desktop-client/src/components/reports/reportRanges.ts
- packages/desktop-client/src/components/transactions/TransactionList.jsx
- packages/desktop-client/src/style/themes/dark.ts
- packages/desktop-client/src/style/themes/development.ts
- packages/desktop-client/src/style/themes/light.ts
- packages/desktop-client/src/style/themes/midnight.ts
- packages/loot-core/src/types/models/dashboard.d.ts
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
Learnt from: lelemm
PR: actualbudget/actual#3828
File: packages/desktop-client/src/components/reports/reports/Calendar.tsx:575-631
Timestamp: 2024-11-12T18:18:07.283Z
Learning: In `Calendar.tsx`, transaction-related callbacks such as `onBatchDelete`, `onBatchDuplicate`, `onCreateRule`, and `onScheduleAction` are intentionally left as empty functions because these operations should not be usable on that page.
🔇 Additional comments (9)
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (1)
1-17
: LGTM! Well-structured imports and type definitions.
The imports are properly organized, and the CalendarDataType
interface is well-defined with clear field names.
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (1)
64-68
: 🛠️ Refactor suggestion
Add date validation in calculateTimeRange
The time range calculation doesn't validate the input dates. This could lead to unexpected behavior if meta?.timeFrame
contains invalid values.
Consider adding date validation:
const [start, end] = calculateTimeRange(meta?.timeFrame, {
+ // Ensure valid dates or fallback to defaults
+ start: monthUtils.isValidDate(monthUtils.dayFromDate(monthUtils.currentMonth()))
+ ? monthUtils.dayFromDate(monthUtils.currentMonth())
+ : new Date(),
+ end: monthUtils.isValidDate(monthUtils.currentDay())
+ ? monthUtils.currentDay()
+ : new Date(),
- start: monthUtils.dayFromDate(monthUtils.currentMonth()),
- end: monthUtils.currentDay(),
mode: 'full',
});
packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
73-86
: LGTM! Clean and well-structured component.
The Calendar component properly handles loading states and provides good type safety.
417-421
: LGTM! Well-structured constants.
Good practice extracting magic numbers into named constants for the drag gesture configuration.
853-924
: LGTM! Well-structured responsive component.
The CalendarCardHeader component handles responsive layout well and properly formats financial data.
packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (1)
Line range hint 1-1000
: Well-structured and comprehensive test suite!
The test suite demonstrates excellent practices:
- Thorough setup with mock data and helper functions
- Comprehensive coverage of transaction operations
- Clear test descriptions and assertions
- Good isolation of test cases
- Proper cleanup after tests
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (3)
Line range hint 182-235
: LGTM! Clean implementation of conditional selection in header
The implementation properly handles both selection and non-selection cases while maintaining layout consistency. The SelectCell component is well configured with appropriate styling and event handling.
886-887
: LGTM! Well-integrated selection handling in Transaction component
The changes properly integrate selection handling while maintaining existing functionality. The selection cell rendering logic correctly handles all cases including previews and child transactions.
Also applies to: 1193-1203
1524-1524
: LGTM! Good enhancement of split transaction control
The allowSplitTransaction
prop is well utilized to provide granular control over split transaction functionality in the category autocomplete component.
packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx
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: 3
🧹 Outside diff range and nitpick comments (13)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (6)
34-43
: Clean up commented code or document future plansThe commented
onFilter
prop suggests an incomplete feature for filtering conditions. Either implement the filtering functionality, remove the commented code, or add a TODO comment explaining future plans.
64-71
: Optimize font size calculationThe font size calculation could be memoized to prevent unnecessary updates. Consider using
useMemo
for the calculation.- const buttonRef = useResizeObserver(rect => { + const buttonRef = useResizeObserver(rect => { + const calculateFontSize = (height: number) => { + const newValue = Math.floor(height / 2); + return newValue > 14 ? 14 : newValue; + }; + - const newValue = Math.floor(rect.height / 2); - if (newValue > 14) { - setFontSize(14); - } else { - setFontSize(newValue); - } + setFontSize(calculateFontSize(rect.height)); });
79-81
: Extract grid layout constantsMagic numbers in the grid layout could make maintenance difficult. Consider extracting these values into named constants.
+const CALENDAR_GRID = { + COLUMNS: 7, + GAP: 2, +}; + <View style={{ color: theme.pageTextSubdued, display: 'grid', - gridTemplateColumns: 'repeat(7, 1fr)', + gridTemplateColumns: `repeat(${CALENDAR_GRID.COLUMNS}, 1fr)`, gridAutoRows: '1fr', - gap: 2, + gap: CALENDAR_GRID.GAP, }} >
120-177
: Extract tooltip content into a separate componentThe tooltip content is complex and deeply nested. Consider extracting it into a separate component for better maintainability and reusability.
type DayTooltipProps = { day: { date: Date; incomeValue: number; expenseValue: number; incomeSize: number; expenseSize: number; }; }; function DayTooltip({ day }: DayTooltipProps) { const { t } = useTranslation(); return ( <View> <View style={{ marginBottom: 10 }}> <strong> {t('Day:') + ' '} {format(day.date, 'dd')} </strong> </View> {/* Rest of the tooltip content */} </View> ); }
219-225
: Simplify state managementThe
currentFontSize
state and effect could be eliminated since it's just mirroring thefontSize
prop.- const [currentFontSize, setCurrentFontSize] = useState(fontSize); - - useEffect(() => { - setCurrentFontSize(fontSize); - }, [fontSize]); return ( <Button // ... <span style={{ - fontSize: `${currentFontSize}px`, + fontSize: `${fontSize}px`, fontWeight: 500, position: 'relative', }} >
242-292
: Extract common styles and calculationsThe bar styles contain duplicated code and inline calculations. Consider extracting common styles and calculations into constants or utility functions.
const BAR_STYLES = { base: { position: 'absolute', width: '50%', height: '100%', opacity: 0.2, }, active: { position: 'absolute', width: '50%', opacity: 0.9, bottom: 0, transition: 'height 0.5s ease-out', }, }; const calculateBarHeight = (size: number) => `${Math.ceil(size)}%`;Then use these in your component:
- <View - style={{ - position: 'absolute', - width: '50%', - height: '100%', - background: chartTheme.colors.red, - opacity: 0.2, - right: 0, - }} - /> + <View + style={{ + ...BAR_STYLES.base, + background: chartTheme.colors.red, + right: 0, + }} + />packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (4)
69-79
: Consider adding error boundaries for calendarSpreadsheet calculations.The
useMemo
hook wraps complex calculations that could potentially throw errors. Adding error boundaries would improve error handling and user experience.const params = useMemo( () => + try { calendarSpreadsheet( start, end, meta?.conditions, meta?.conditionsOp, firstDayOfWeekIdx, ), + } catch (error) { + console.error('Failed to calculate calendar data:', error); + return null; + } [start, end, meta?.conditions, meta?.conditionsOp, firstDayOfWeekIdx], );
198-248
: Consider extracting tooltip content into a separate component.The tooltip content is complex enough to warrant its own component, which would improve readability and maintainability.
159-170
: Consider using TypeScript discriminated unions for menu items.The current switch statement could benefit from TypeScript's discriminated unions to ensure type safety and exhaustive checking.
type MenuItem = | { name: 'rename'; text: string } | { name: 'remove'; text: string }; const handleMenuSelect = (item: MenuItem['name']) => { switch (item) { case 'rename': setNameMenuOpen(true); break; case 'remove': onRemove(); break; } };
487-493
: Add keyboard navigation support for calendar day selection.The calendar day click handler should be accompanied by keyboard navigation support for better accessibility.
onDayClick={date => { navigate( isDashboardsFeatureEnabled ? `/reports/calendar/${widgetId}?day=${format(date, 'yyyy-MM-dd')}` : '/reports/calendar', ); }} +onKeyDown={e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + // Handle day selection + } +}} +role="button" +tabIndex={0}packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
121-122
: Consider making the default value more explicitThe fallback value for firstDayOfWeekIdx could be more explicitly defined with a named constant.
+const DEFAULT_FIRST_DAY_OF_WEEK = '0'; const [_firstDayOfWeekIdx] = useSyncedPref('firstDayOfWeekIdx'); -const firstDayOfWeekIdx = _firstDayOfWeekIdx || '0'; +const firstDayOfWeekIdx = _firstDayOfWeekIdx || DEFAULT_FIRST_DAY_OF_WEEK;
730-757
: Consider extracting button stylesThe button styles could be extracted into a shared style object or component to improve maintainability.
+const calendarHeaderButtonStyle = { + color: theme.pageTextSubdued, + fontWeight: 'bold' as const, + fontSize: '14px', + margin: 0, + padding: 0, + display: 'inline-block', + width: 'max-content', +}; <Button variant="bare" - style={{ - color: theme.pageTextSubdued, - fontWeight: 'bold', - fontSize: '14px', - margin: 0, - padding: 0, - display: 'inline-block', - width: 'max-content', - }} + style={calendarHeaderButtonStyle} onPress={() => {
914-933
: Consider using TypeScript enum for field mappingsThe field mapping could be more type-safe using a TypeScript enum or const object.
+const FIELD_MAPPINGS = { + account: 'account.name', + payee: 'payee.name', + category: 'category.name', + payment: 'amount', + deposit: 'amount', +} as const; + +type FieldKey = keyof typeof FIELD_MAPPINGS; + -function getField(field?: string) { +function getField(field?: FieldKey | string) { if (!field) { return 'date'; } - switch (field) { - case 'account': - return 'account.name'; - case 'payee': - return 'payee.name'; - case 'category': - return 'category.name'; - case 'payment': - return 'amount'; - case 'deposit': - return 'amount'; - default: - return field; - } + return FIELD_MAPPINGS[field as FieldKey] || field; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
(1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
Learnt from: lelemm
PR: actualbudget/actual#3828
File: packages/desktop-client/src/components/reports/reports/Calendar.tsx:575-631
Timestamp: 2024-11-12T18:18:07.283Z
Learning: In `Calendar.tsx`, transaction-related callbacks such as `onBatchDelete`, `onBatchDuplicate`, `onCreateRule`, and `onScheduleAction` are intentionally left as empty functions because these operations should not be usable on that page.
🔇 Additional comments (4)
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (1)
1-52
: LGTM! Well-structured imports and type definitions.
The imports are logically grouped and the type definitions are comprehensive with proper TypeScript features.
packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
73-86
: LGTM! Well-structured component with proper loading state handling.
The Calendar component is implemented correctly with:
- Proper TypeScript typing
- Loading state handling
- Clean props passing to CalendarInner
565-621
: LGTM! Desktop transaction list implementation
The implementation correctly handles the desktop view with proper props configuration. Empty callbacks are intentionally left empty as these operations should not be usable on this page.
841-912
: LGTM! Well-implemented responsive header
The CalendarCardHeader component is well structured with:
- Proper handling of zero values
- Responsive layout adaptation
- Clear TypeScript types
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: 0
🧹 Outside diff range and nitpick comments (6)
packages/desktop-client/src/components/reports/Overview.tsx (1)
55-56
: Consider adding type annotation for firstDayOfWeekIdxThe firstDayOfWeekIdx state could benefit from explicit typing to ensure type safety throughout the component.
- const [_firstDayOfWeekIdx] = useSyncedPref('firstDayOfWeekIdx'); - const firstDayOfWeekIdx = _firstDayOfWeekIdx || '0'; + const [_firstDayOfWeekIdx] = useSyncedPref<string>('firstDayOfWeekIdx'); + const firstDayOfWeekIdx: string = _firstDayOfWeekIdx || '0';packages/desktop-client/src/components/transactions/TransactionsTable.jsx (5)
224-231
: Consider extracting empty field styles to a constant.The empty field styles could be extracted to a constant or theme variable for better maintainability and reuse.
+const emptyFieldStyle = { + width: '20px', + border: 0, +}; {!showSelection && ( <Field style={{ - width: '20px', - border: 0, + ...emptyFieldStyle, }} /> )}
883-884
: Add prop type definitions and documentation.The new props lack type definitions and documentation explaining their purpose.
+/** + * @typedef {Object} TransactionProps + * @property {boolean} [showSelection=true] - Whether to show selection checkboxes + * @property {boolean} [allowSplitTransaction=true] - Whether to allow splitting transactions + */ const Transaction = memo(function Transaction({ allTransactions, transaction: originalTransaction, // ... other props showSelection, allowSplitTransaction, }) {
1790-1791
: Consider forwarding props instead of hardcoding.The
showSelection
andallowSplitTransaction
props are hardcoded totrue
, which reduces component flexibility. Consider forwarding these props from the parent component.-showSelection={true} -allowSplitTransaction={true} +showSelection={props.showSelection} +allowSplitTransaction={props.allowSplitTransaction}
1912-1913
: Add default values for new props.Consider adding default values for
showSelection
andallowSplitTransaction
to ensure consistent behavior when props are not provided.function TransactionTableInner({ tableNavigator, tableRef, listContainerRef, dateFormat = 'MM/dd/yyyy', newNavigator, renderEmpty, onScroll, + showSelection = true, + allowSplitTransaction = true, ...props }) {
2638-2639
: Add prop validation for new props.Consider adding PropTypes or TypeScript type definitions to validate the prop types at runtime or compile time.
+import PropTypes from 'prop-types'; TransactionTable.displayName = 'TransactionTable'; + +TransactionTable.propTypes = { + showSelection: PropTypes.bool, + allowSplitTransaction: PropTypes.bool, +}; + +TransactionTable.defaultProps = { + showSelection: true, + allowSplitTransaction: true, +};
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
packages/desktop-client/src/components/reports/Overview.tsx
(5 hunks)packages/desktop-client/src/components/transactions/TransactionsTable.jsx
(10 hunks)
🔇 Additional comments (3)
packages/desktop-client/src/components/reports/Overview.tsx (3)
388-391
: LGTM!
The calendar card menu item is properly integrated into the existing menu structure.
533-541
: Consider mobile-specific handling for CalendarCard
Based on the PR comments, there are concerns about the calendar's mobile view. Consider adding mobile-specific props or handling to address reported issues like:
- Filter tooltip behavior
- Button sizes
- Calendar alignment
Let's check for any mobile-specific handling in the CalendarCard component:
533-541
: Verify feature flag handling
The PR mentions that the calendar report is inaccessible when the customizable dashboards feature flag is disabled. Ensure proper feature flag handling is implemented.
Let's verify the feature flag implementation:
✅ Verification successful
Let's search more specifically for calendar and dashboard related feature flags.
Let's check the CalendarCard component itself and any feature flag usage there.
Based on the code analysis, I can now provide the final response:
Feature flag handling is properly implemented
The CalendarCard component correctly implements the feature flag handling through the useFeatureFlag('dashboards')
hook. When the dashboards feature flag is disabled:
- The calendar card's navigation link falls back to '/reports/calendar' instead of the widget-specific route
- The component remains accessible through the general calendar report route
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for feature flag handling related to dashboards and calendar
rg -A 5 "feature.*flag|isFeatureEnabled" "packages/desktop-client/src/"
Length of output: 749
Script:
#!/bin/bash
# Search for calendar and dashboard feature flags
rg -B 2 -A 5 "customizableDashboards|dashboards.*feature|calendar.*feature" "packages/desktop-client/src/"
# Search for feature flag usage in Overview.tsx
rg -B 2 -A 5 "useFeatureFlag|isFeatureEnabled" "packages/desktop-client/src/components/reports/Overview.tsx"
# Search for calendar card imports and usage
rg -B 2 -A 5 "CalendarCard" "packages/desktop-client/src/components/reports/Overview.tsx"
Length of output: 1126
Script:
#!/bin/bash
# Search for feature flag usage in CalendarCard
rg -B 2 -A 5 "useFeatureFlag|isFeatureEnabled" "packages/desktop-client/src/components/reports/CalendarCard.tsx"
# Look at the CalendarCard implementation
ast-grep --pattern $'export function CalendarCard($_) {
$$$
}'
# Check for any feature flag checks in the parent components or routes
rg -B 2 -A 5 "calendar.*route|calendar.*path" "packages/desktop-client/src/"
Length of output: 29995
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 (4)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (3)
50-59
: Extract magic numbers and improve font size calculation logic.The font size calculation uses a hardcoded value of 14 multiple times. Consider extracting this as a named constant and adding a minimum font size for better maintainability.
+const MAX_FONT_SIZE = 14; +const MIN_FONT_SIZE = 8; + const [fontSize, setFontSize] = useState(14); const buttonRef = useResizeObserver(rect => { const newValue = Math.floor(rect.height / 2); - if (newValue > 14) { - setFontSize(14); + if (newValue > MAX_FONT_SIZE) { + setFontSize(MAX_FONT_SIZE); + } else if (newValue < MIN_FONT_SIZE) { + setFontSize(MIN_FONT_SIZE); } else { setFontSize(newValue); } });
107-184
: Consider handling edge cases in tooltip display.The tooltip shows empty strings for zero values. Consider either:
- Showing "0" or "No transactions" for better user experience
- Completely hiding the row when the value is zero
- {day.incomeValue !== 0 ? ( - <PrivacyFilter> - {amountToCurrency(day.incomeValue)} - </PrivacyFilter> - ) : ( - '' - )} + <PrivacyFilter> + {day.incomeValue !== 0 + ? amountToCurrency(day.incomeValue) + : 'No income'} + </PrivacyFilter>
248-296
: Consider memoizing static styles for better performance.The style objects are recreated on every render. Consider extracting static styles to constants or using a CSS-in-JS solution with proper caching.
+const incomeOverlayStyle = { + position: 'absolute', + width: '50%', + height: '100%', + background: chartTheme.colors.blue, + opacity: 0.2, + left: 0, +}; + +const expenseOverlayStyle = { + ...incomeOverlayStyle, + background: chartTheme.colors.red, + left: undefined, + right: 0, +}; + {day.expenseSize !== 0 && ( <View - style={{ - position: 'absolute', - width: '50%', - height: '100%', - background: chartTheme.colors.red, - opacity: 0.2, - right: 0, - }} + style={expenseOverlayStyle} /> )}packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
869-924
: Enhance accessibility for monetary valuesThe monetary values in CalendarCardHeader could benefit from more descriptive ARIA labels.
Apply this diff to improve accessibility:
<View style={{ textAlign: 'right', marginRight: 4, }} + aria-hidden="true" > Income: </View> - <View style={{ color: chartTheme.colors.blue }}> + <View + style={{ color: chartTheme.colors.blue }} + role="text" + aria-label={`Total income: ${amountToCurrency(totalIncome)}`} + > <PrivacyFilter>{amountToCurrency(totalIncome)}</PrivacyFilter> </View> <View style={{ textAlign: 'right', marginRight: 4, }} + aria-hidden="true" > Expenses: </View> - <View style={{ color: chartTheme.colors.red }}> + <View + style={{ color: chartTheme.colors.red }} + role="text" + aria-label={`Total expenses: ${amountToCurrency(totalExpense)}`} + > <PrivacyFilter>{amountToCurrency(totalExpense)}</PrivacyFilter> </View>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
Learnt from: lelemm
PR: actualbudget/actual#3828
File: packages/desktop-client/src/components/reports/reports/Calendar.tsx:575-631
Timestamp: 2024-11-12T18:18:07.283Z
Learning: In `Calendar.tsx`, transaction-related callbacks such as `onBatchDelete`, `onBatchDuplicate`, `onCreateRule`, and `onScheduleAction` are intentionally left as empty functions because these operations should not be usable on that page.
🪛 Biome
packages/desktop-client/src/components/reports/reports/Calendar.tsx
[error] 142-142: Unsafe usage of optional chaining.
If it short-circuits with 'undefined' the evaluation will throw TypeError here:
(lint/correctness/noUnsafeOptionalChaining)
[error] 157-157: Unsafe usage of optional chaining.
If it short-circuits with 'undefined' the evaluation will throw TypeError here:
(lint/correctness/noUnsafeOptionalChaining)
🔇 Additional comments (2)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (2)
1-34
: LGTM! Well-structured imports and type definitions.
The imports are logically organized, and the type definitions are comprehensive and well-documented.
61-90
: LGTM! Well-implemented calendar header.
The grid layout ensures consistent spacing, and the weekday abbreviations are rendered efficiently.
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
96-701
: Consider breaking down the CalendarInner component.The component is quite large and handles multiple responsibilities. Consider extracting the following into separate components:
- Transaction list rendering logic
- Mobile drag gesture implementation
- Filter handling logic
This would improve maintainability and testability.
🧰 Tools
🪛 GitHub Check: typecheck
[failure] 454-454:
Object literal may only specify known properties, and 'top' does not exist in type 'Vector2'.
[failure] 463-463:
Property 'clean' does not exist on type 'void'.
[failure] 644-644:
This expression is not callable.
726-861
: Enhance accessibility for interactive elements.The calendar header buttons and interactive elements need improved accessibility attributes.
<Button variant="bare" + aria-label={`View transactions for ${format(calendar.start, 'MMMM yyyy')}`} style={{ color: theme.pageTextSubdued, fontWeight: 'bold', fontSize: '14px', margin: 0, padding: 0, display: 'inline-block', width: 'max-content', }} onPress={() => { onApplyFilter({ // ... filter logic }); }} > {format(calendar.start, 'MMMM yyyy')} </Button>
937-956
: Improve type safety in getField function.The function could benefit from stricter typing and exhaustive checks.
+type ValidField = 'account' | 'payee' | 'category' | 'payment' | 'deposit' | 'date'; -function getField(field?: string) { +function getField(field?: ValidField) { if (!field) { return 'date'; } switch (field) { case 'account': return 'account.name'; case 'payee': return 'payee.name'; case 'category': return 'category.name'; case 'payment': return 'amount'; case 'deposit': return 'amount'; case 'date': return 'date'; - default: - return field; } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
Learnt from: lelemm
PR: actualbudget/actual#3828
File: packages/desktop-client/src/components/reports/reports/Calendar.tsx:575-631
Timestamp: 2024-11-12T18:18:07.283Z
Learning: In `Calendar.tsx`, transaction-related callbacks such as `onBatchDelete`, `onBatchDuplicate`, `onCreateRule`, and `onScheduleAction` are intentionally left as empty functions because these operations should not be usable on that page.
🪛 GitHub Check: typecheck
packages/desktop-client/src/components/reports/reports/Calendar.tsx
[failure] 454-454:
Object literal may only specify known properties, and 'top' does not exist in type 'Vector2'.
[failure] 463-463:
Property 'clean' does not exist on type 'void'.
[failure] 644-644:
This expression is not callable.
🔇 Additional comments (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
76-89
: 🛠️ Refactor suggestion
Add error handling for widget data fetching.
The component should handle potential errors from the useWidget
hook to provide better user feedback.
export function Calendar() {
const params = useParams();
const [searchParams] = useSearchParams();
- const { data: widget, isLoading } = useWidget<CalendarWidget>(
+ const { data: widget, isLoading, error } = useWidget<CalendarWidget>(
params.id ?? '',
'calendar-card',
);
+ if (error) {
+ return (
+ <View style={{ padding: 20, textAlign: 'center', color: theme.errorText }}>
+ Error loading calendar widget: {error.message}
+ </View>
+ );
+ }
if (isLoading) {
return <LoadingIndicator />;
}
return <CalendarInner widget={widget} parameters={searchParams} />;
}
Likely invalid or redundant comment.
packages/desktop-client/src/components/reports/reports/Calendar.tsx
Outdated
Show resolved
Hide resolved
done @youngcw
ty @joel-jeremy :)
I noticed that too, I have to investigate what is going on. Any suspicion?
changed how it works, take a look
change the case and size of the button
atm the calendar direction depends on how it shows on the desktop version, if height > width -> flex direction = column, otherwise, flex direction = row: Actual.-.Google.Chrome.2024-11-21.10-52-18.mp4
removed the conditions to hide when zero |
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: 4
🧹 Outside diff range and nitpick comments (8)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (5)
51-59
: Simplify font size calculation logicThe font size calculation could be simplified using Math.min to avoid the if-else structure.
const buttonRef = useResizeObserver(rect => { - const newValue = Math.floor(rect.height / 2); - if (newValue > 14) { - setFontSize(14); - } else { - setFontSize(newValue); - } + setFontSize(Math.min(14, Math.floor(rect.height / 2))); });
73-89
: Use semantic HTML for weekday headersConsider using semantic HTML elements for better accessibility. The weekday headers should be wrapped in a
thead
-like structure.-{Array.from({ length: 7 }, (_, index) => ( - <View - key={index} - style={{ - textAlign: 'center', - fontSize: 14, - fontWeight: 500, - padding: '3px 0', - height: '100%', - width: '100%', - position: 'relative', - marginBottom: 4, - }} - > - {format(addDays(startingDate, index), 'EEEEE')} - </View> -))} +<View role="rowgroup"> + <View role="row"> + {Array.from({ length: 7 }, (_, index) => ( + <View + role="columnheader" + key={`weekday-${index}`} + style={{ + textAlign: 'center', + fontSize: 14, + fontWeight: 500, + padding: '3px 0', + height: '100%', + width: '100%', + position: 'relative', + marginBottom: 4, + }} + > + {format(addDays(startingDate, index), 'EEEEE')} + </View> + ))} + </View> +</View>
103-106
: Extract empty cell click handlerThe click handler for empty cells is duplicated. Consider extracting it to a reusable function.
+const handleEmptyClick = () => onDayClick(null); + {data.map((day, index) => !isSameMonth(day.date, startOfMonth(start)) ? ( <View key={`empty-${day.date.getTime()}`} - onClick={() => onDayClick(null)} + onClick={handleEmptyClick} /> ) : // ...
289-290
: Be more specific with CSS transitionsUsing transition on 'height' alone is more performant than transitioning all properties.
- transition: 'height 0.5s ease-out', + transition: 'height 0.5s ease-out, opacity 0.5s ease-out',Also applies to: 302-303
286-287
: Memoize percentage calculationsThe percentage calculations for income and expense sizes could be memoized to avoid recalculation on every render.
+import { useMemo } from 'react'; + function DayButton({ day, onPress, fontSize, resizeRef }: DayButtonProps) { + const { incomeHeight, expenseHeight } = useMemo(() => ({ + incomeHeight: `${Math.ceil(day.incomeSize)}%`, + expenseHeight: `${Math.ceil(day.expenseSize)}%`, + }), [day.incomeSize, day.expenseSize]); + return ( // ... style={{ position: 'absolute', left: 0, bottom: 0, opacity: 0.9, - height: `${Math.ceil(day.incomeSize)}%`, + height: incomeHeight, // ... }} // ... style={{ position: 'absolute', right: 0, bottom: 0, opacity: 0.9, - height: `${Math.ceil(day.expenseSize)}%`, + height: expenseHeight, // ... }} // ... ); }Also applies to: 299-300
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (1)
266-273
: Simplify complex conditional logicThe nested ternary operators make the code hard to read. Consider extracting this logic into a separate function or using a more readable approach.
- overflowX: - cardOrientation === 'row' - ? isNarrowWidth - ? 'auto' - : calendarLenSize > 4 - ? 'auto' - : 'hidden' - : 'hidden', + overflowX: getOverflowX(cardOrientation, isNarrowWidth, calendarLenSize), // Add this function above the component: + function getOverflowX( + orientation: 'row' | 'column', + isNarrow: boolean, + calendarSize: number + ): 'auto' | 'hidden' { + if (orientation !== 'row') return 'hidden'; + if (isNarrow) return 'auto'; + return calendarSize > 4 ? 'auto' : 'hidden'; + }packages/desktop-client/src/components/reports/reports/Calendar.tsx (2)
428-460
: Extract magic numbers and simplify drag gesture logic.The drag gesture implementation uses magic numbers and could be simplified for better maintainability.
Apply this diff to improve the implementation:
+const DRAG_THRESHOLDS = { + CLOSE_THRESHOLD: 0.05, + OPEN_THRESHOLD: 0.05, +}; const bind = useDrag( ({ offset: [, oy], cancel }) => { if (oy < 0) { cancel(); api.start({ y: 0, immediate: true }); return; } - if (oy > totalHeight * 0.05 && mobileTransactionsOpen) { + if (oy > totalHeight * DRAG_THRESHOLDS.CLOSE_THRESHOLD && mobileTransactionsOpen) { cancel(); close(); setMobileTransactionsOpen(false); } else if (!mobileTransactionsOpen) { - if (oy / totalHeight > 0.05) { + if (oy / totalHeight > DRAG_THRESHOLDS.OPEN_THRESHOLD) { cancel(); open({ canceled: true }); setMobileTransactionsOpen(true); } else { api.start({ y: oy, immediate: true }); } } }, { from: () => [0, y.get()], filterTaps: true, bounds: { top: -totalHeight + CHEVRON_HEIGHT, bottom: totalHeight - CHEVRON_HEIGHT, }, axis: 'y', rubberband: true, }, );
932-951
: Simplify field mapping with an object map.The field mapping function could be simplified and made more maintainable using an object map instead of a switch statement.
Apply this diff to improve the implementation:
+const FIELD_MAPPINGS = { + account: 'account.name', + payee: 'payee.name', + category: 'category.name', + payment: 'amount', + deposit: 'amount', +} as const; function getField(field?: string) { - if (!field) { - return 'date'; - } - - switch (field) { - case 'account': - return 'account.name'; - case 'payee': - return 'payee.name'; - case 'category': - return 'category.name'; - case 'payment': - return 'amount'; - case 'deposit': - return 'amount'; - default: - return field; - } + return field ? (FIELD_MAPPINGS[field as keyof typeof FIELD_MAPPINGS] || field) : 'date'; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)packages/desktop-client/src/components/reports/reports/CalendarCard.tsx
(1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
Learnt from: lelemm
PR: actualbudget/actual#3828
File: packages/desktop-client/src/components/reports/reports/Calendar.tsx:575-631
Timestamp: 2024-11-12T18:18:07.283Z
Learning: In `Calendar.tsx`, transaction-related callbacks such as `onBatchDelete`, `onBatchDuplicate`, `onCreateRule`, and `onScheduleAction` are intentionally left as empty functions because these operations should not be usable on that page.
🔇 Additional comments (3)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (1)
1-34
: LGTM! Well-structured imports and type definitions.
The imports are properly organized, and the type definitions are comprehensive and well-documented.
packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (1)
1-52
: LGTM! Well-structured type definitions and imports.
The type definitions are comprehensive and the imports are well-organized.
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
76-89
: 🛠️ Refactor suggestion
Add error handling for widget loading failures.
The component handles the loading state but doesn't handle potential errors from the useWidget
hook.
Apply this diff to add error handling:
export function Calendar() {
const params = useParams();
const [searchParams] = useSearchParams();
- const { data: widget, isLoading } = useWidget<CalendarWidget>(
+ const { data: widget, isLoading, error } = useWidget<CalendarWidget>(
params.id ?? '',
'calendar-card',
);
+ if (error) {
+ return (
+ <View style={{ padding: 20, textAlign: 'center', color: theme.errorText }}>
+ Error loading calendar widget: {error.message}
+ </View>
+ );
+ }
if (isLoading) {
return <LoadingIndicator />;
}
return <CalendarInner widget={widget} parameters={searchParams} />;
}
Likely invalid or redundant comment.
My only nitpik now is that the whole page shifts down when you add in a filter for the first time, which is a bit jarring. Thats not that big of a deal for now though. |
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)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)
428-460
: Improve drag gesture implementation.The drag gesture implementation uses magic numbers and has complex conditional logic that could be simplified.
Apply this diff to improve the implementation:
+const DRAG_THRESHOLDS = { + OPEN_THRESHOLD: 0.05, + CLOSE_THRESHOLD: 0.05, +}; const bind = useDrag( ({ offset: [, oy], cancel }) => { if (oy < 0) { cancel(); api.start({ y: 0, immediate: true }); return; } - if (oy > totalHeight * 0.05 && mobileTransactionsOpen) { + if (oy > totalHeight * DRAG_THRESHOLDS.CLOSE_THRESHOLD && mobileTransactionsOpen) { cancel(); close(); setMobileTransactionsOpen(false); } else if (!mobileTransactionsOpen) { - if (oy / totalHeight > 0.05) { + if (oy / totalHeight > DRAG_THRESHOLDS.OPEN_THRESHOLD) { cancel(); open({ canceled: true }); setMobileTransactionsOpen(true); } else { api.start({ y: oy, immediate: true }); } } }, { from: () => [0, y.get()], filterTaps: true, bounds: { top: -totalHeight + CHEVRON_HEIGHT, bottom: totalHeight - CHEVRON_HEIGHT, }, axis: 'y', rubberband: true, }, );
135-171
: Optimize URL parameter handling.The useEffect for URL parameters could be optimized by memoizing the parameters and using proper dependency array.
Apply this diff to optimize the implementation:
+const useURLParameters = (parameters: URLSearchParams, widget?: CalendarWidget) => { + const day = parameters.get('day'); + const month = parameters.get('month'); + const conditions = widget?.meta?.conditions || []; + + return useMemo( + () => ({ day, month, conditions }), + [day, month, conditions] + ); +}; function CalendarInner({ widget, parameters }: CalendarInnerProps) { + const { day, month, conditions: widgetConditions } = useURLParameters(parameters, widget); useEffect(() => { - const day = parameters.get('day'); - const month = parameters.get('month'); - if (day && onApplyFilter) { onApplyFilter({ conditions: [ - ...(widget?.meta?.conditions || []), + ...widgetConditions, { op: 'is', field: 'date', value: day, } as RuleConditionEntity, ], conditionsOp: 'and', id: [], }); } if (month && onApplyFilter) { onApplyFilter({ conditions: [ - ...(widget?.meta?.conditions || []), + ...widgetConditions, { field: 'date', op: 'is', value: month, options: { month: true, }, }, ], conditionsOp: 'and', id: [], }); } - }, [widget?.meta?.conditions, onApplyFilter, parameters]); + }, [widgetConditions, day, month, onApplyFilter]);
934-953
: Improve type safety of field mapping.The getField function could benefit from stricter typing to prevent potential errors.
Apply this diff to add type safety:
+type FieldMapping = 'account' | 'payee' | 'category' | 'payment' | 'deposit' | string; -function getField(field?: string) { +function getField(field?: FieldMapping) { if (!field) { return 'date'; } + const fieldMap: Record<FieldMapping, string> = { + account: 'account.name', + payee: 'payee.name', + category: 'category.name', + payment: 'amount', + deposit: 'amount', + }; + return fieldMap[field] || field; - switch (field) { - case 'account': - return 'account.name'; - case 'payee': - return 'payee.name'; - case 'category': - return 'category.name'; - case 'payment': - return 'amount'; - case 'deposit': - return 'amount'; - default: - return field; - } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
Learnt from: lelemm
PR: actualbudget/actual#3828
File: packages/desktop-client/src/components/reports/reports/Calendar.tsx:575-631
Timestamp: 2024-11-12T18:18:07.283Z
Learning: In `Calendar.tsx`, transaction-related callbacks such as `onBatchDelete`, `onBatchDuplicate`, `onCreateRule`, and `onScheduleAction` are intentionally left as empty functions because these operations should not be usable on that page.
🔇 Additional comments (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
76-89
: 🛠️ Refactor suggestion
Add error handling for widget loading state.
The component should handle potential errors that might occur during widget data fetching.
Apply this diff to add error handling:
export function Calendar() {
const params = useParams();
const [searchParams] = useSearchParams();
- const { data: widget, isLoading } = useWidget<CalendarWidget>(
+ const { data: widget, isLoading, error } = useWidget<CalendarWidget>(
params.id ?? '',
'calendar-card',
);
+ if (error) {
+ return (
+ <View style={{ padding: 20, textAlign: 'center', color: theme.errorText }}>
+ Error loading calendar widget: {error.message}
+ </View>
+ );
+ }
if (isLoading) {
return <LoadingIndicator />;
}
return <CalendarInner widget={widget} parameters={searchParams} />;
}
Likely invalid or redundant comment.
@youngcw, I added a minHeight to the header, so as long as there is not too many filters, it will keep the screen static |
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: 0
🧹 Outside diff range and nitpick comments (1)
packages/desktop-client/src/components/reports/Overview.tsx (1)
55-56
: Consider using number type for firstDayOfWeekIdxThe default value '0' suggests firstDayOfWeekIdx is handled as a string. Consider using a number type for better type safety and consistency with typical day index representations.
- const [_firstDayOfWeekIdx] = useSyncedPref('firstDayOfWeekIdx'); - const firstDayOfWeekIdx = _firstDayOfWeekIdx || '0'; + const [_firstDayOfWeekIdx] = useSyncedPref<number>('firstDayOfWeekIdx'); + const firstDayOfWeekIdx = _firstDayOfWeekIdx ?? 0;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
packages/desktop-client/src/components/reports/Overview.tsx
(5 hunks)packages/desktop-client/src/components/reports/ReportRouter.tsx
(2 hunks)packages/loot-core/src/types/models/dashboard.d.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/desktop-client/src/components/reports/ReportRouter.tsx
- packages/loot-core/src/types/models/dashboard.d.ts
🔇 Additional comments (3)
packages/desktop-client/src/components/reports/Overview.tsx (3)
24-24
: LGTM: Import additions are well-organized
The new imports for useSyncedPref
hook and CalendarCard
component are correctly placed in their respective sections, maintaining good code organization.
Also applies to: 37-37
392-395
: LGTM: Menu item addition is well-structured
The calendar card menu item is correctly integrated with the existing menu structure and follows the translation pattern.
545-553
: Verify mobile view handling
Based on the PR comments, please verify that the CalendarCard handles mobile view appropriately, particularly:
- Filter tooltip behavior
- "Show/Hide transactions" button size
- Vertical alignment of calendars
✅ Verification successful
Mobile view implementation is properly handled
The CalendarCard component has comprehensive mobile and responsive handling:
- Uses
useResponsive
hook for responsive behavior - Dynamically adjusts layout orientation (row/column) based on container dimensions
- Implements adaptive width calculations for different viewport sizes
- Handles overflow with proper scrolling for narrow widths
- Maintains consistent button and icon sizes with fixed dimensions
- Uses flexible layouts with proper flex properties for vertical alignment
The specific concerns are addressed:
- Filter tooltip inherits responsive container behavior
- Button sizes are explicitly set with proper dimensions
- Calendar alignment is handled through flex layouts and dynamic orientation switching
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for mobile-specific handling in CalendarCard
rg -A 5 "mobile|responsive|width|height|layout" "src/components/reports/CalendarCard.tsx"
# Search for related style definitions
rg -A 5 "mobile|responsive|width|height|layout" "src/**/*.scss"
Length of output: 289
Script:
#!/bin/bash
# Let's first find the correct path for CalendarCard component
fd "CalendarCard" --type f
# Then search for mobile-specific handling in the found files
fd "CalendarCard" --type f --exec rg -A 5 "mobile|responsive|width|height|layout" {}
# Look for style definitions in all CSS/SCSS files
fd "\.(css|scss)$" --type f --exec rg -A 5 "mobile|responsive|width|height|layout" {}
Length of output: 2914
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: 3
🧹 Outside diff range and nitpick comments (2)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (2)
74-75
: Consider using a descriptive name for the constantThe constant
SUMMARY_HEIGHT
would be clearer with a more descriptive name likeCALENDAR_SUMMARY_HEIGHT
to indicate its specific purpose.-const SUMMARY_HEIGHT = 140; +const CALENDAR_SUMMARY_HEIGHT = 140;
429-461
: Improve drag gesture handlingThe drag gesture implementation could be improved for better user experience and performance:
- Add velocity-based animations
- Implement smoother transitions
- Add haptic feedback for iOS devices
const bind = useDrag( ({ offset: [, oy], cancel, velocity: [, vy] }) => { if (oy < 0) { cancel(); - api.start({ y: 0, immediate: true }); + api.start({ + y: 0, + immediate: false, + config: { ...config.stiff, velocity: vy } + }); return; } if (oy > totalHeight * 0.05 && mobileTransactionsOpen) { cancel(); - close(); + close(vy); setMobileTransactionsOpen(false); + // Add haptic feedback for iOS + if (window.navigator && window.navigator.vibrate) { + window.navigator.vibrate(50); + } } else if (!mobileTransactionsOpen) { // ... rest of the code } }, { from: () => [0, y.get()], filterTaps: true, bounds: { top: -totalHeight + CHEVRON_HEIGHT, bottom: totalHeight - CHEVRON_HEIGHT, }, axis: 'y', - rubberband: true, + rubberband: { + top: 0.5, + bottom: 0.3 + }, }, );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx
(1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/Calendar.tsx (1)
Learnt from: lelemm
PR: actualbudget/actual#3828
File: packages/desktop-client/src/components/reports/reports/Calendar.tsx:575-631
Timestamp: 2024-11-12T18:18:07.283Z
Learning: In `Calendar.tsx`, transaction-related callbacks such as `onBatchDelete`, `onBatchDuplicate`, `onCreateRule`, and `onScheduleAction` are intentionally left as empty functions because these operations should not be usable on that page.
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.
LGTM! Would prefer another maintainer to approve before merging.
I think the next phase for the calendar report is to include preview transactions i.e. #1928. Not sure if we want to do it here or in a separate PR. I'm fine either ways. :)
This needs the same update as the summary card to allow importing a dashboard.json file |
I'm not sure what we need to do here. can you clarify please? |
You need to copy what Mike did on the summary report |
ohh, got it! done ✔️ |
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: 0
🧹 Outside diff range and nitpick comments (1)
packages/loot-core/src/server/dashboard/app.ts (1)
Line range hint
85-89
: Consider documenting widget types.To improve code maintainability and developer experience, consider adding JSDoc documentation for the widget types array, describing each type's purpose and requirements.
Example:
+/** + * List of valid dashboard widget types: + * - net-worth-card: Displays net worth over time + * - cash-flow-card: Shows cash flow analysis + * - spending-card: Presents spending breakdown + * - custom-report: Allows custom report configuration + * - markdown-card: Renders markdown content + * - summary-card: Shows account summaries + * - calendar-card: Displays data in calendar format + */ [ 'net-worth-card', 'cash-flow-card', 'spending-card', 'custom-report', 'markdown-card', 'summary-card', 'calendar-card', ]
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
packages/loot-core/src/server/dashboard/app.ts
(1 hunks)
🔇 Additional comments (1)
packages/loot-core/src/server/dashboard/app.ts (1)
85-85
: LGTM: Calendar card widget type added successfully.
The addition of 'calendar-card' to the list of valid widget types aligns with the PR objectives and is properly integrated into the existing validation logic.
A new report type where data shows in a calendar format`:
Actual.-.Google.Chrome.2024-11-12.13-38-51.mp4