Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Calendar Report #3828

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open

Calendar Report #3828

wants to merge 39 commits into from

Conversation

lelemm
Copy link
Contributor

@lelemm lelemm commented Nov 12, 2024

A new report type where data shows in a calendar format`:

Actual.-.Google.Chrome.2024-11-12.13-38-51.mp4

@actual-github-bot actual-github-bot bot changed the title Calendar Report [WIP] Calendar Report Nov 12, 2024
Copy link

netlify bot commented Nov 12, 2024

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit 12570a0
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/6745fe558f03e300086d6b36
😎 Deploy Preview https://deploy-preview-3828.demo.actualbudget.org
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

github-actions bot commented Nov 12, 2024

Bundle Stats — desktop-client

Hey 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

Files count Total bundle size % Changed
10 5.45 MB → 5.51 MB (+60.89 kB) +1.09%
Changeset
File Δ Size
src/components/reports/reports/Calendar.tsx 🆕 +25.3 kB 0 B → 25.3 kB
src/components/reports/reports/CalendarCard.tsx 🆕 +14.78 kB 0 B → 14.78 kB
src/components/reports/graphs/CalendarGraph.tsx 🆕 +8.28 kB 0 B → 8.28 kB
src/components/reports/spreadsheets/calendar-spreadsheet.ts 🆕 +5.74 kB 0 B → 5.74 kB
node_modules/date-fns/esm/differenceInDays/index.js 🆕 +3.04 kB 0 B → 3.04 kB
node_modules/date-fns/esm/getDaysInMonth/index.js 🆕 +796 B 0 B → 796 B
node_modules/date-fns/esm/getDate/index.js 🆕 +576 B 0 B → 576 B
src/icons/v1/ArrowThickDown.tsx 🆕 +345 B 0 B → 345 B
src/icons/v1/ArrowThickUp.tsx 🆕 +344 B 0 B → 344 B
src/components/reports/ReportRouter.tsx 📈 +322 B (+17.16%) 1.83 kB → 2.15 kB
src/components/reports/Overview.tsx 📈 +592 B (+4.07%) 14.2 kB → 14.78 kB
src/components/transactions/TransactionList.jsx 📈 +98 B (+1.87%) 5.12 kB → 5.22 kB
src/components/transactions/TransactionsTable.jsx 📈 +562 B (+0.82%) 66.88 kB → 67.43 kB
src/components/reports/reportRanges.ts 📈 +31 B (+0.75%) 4.05 kB → 4.08 kB
src/style/themes/development.ts 📈 +42 B (+0.53%) 7.72 kB → 7.76 kB
src/style/themes/dark.ts 📈 +42 B (+0.53%) 7.75 kB → 7.79 kB
src/style/themes/light.ts 📈 +42 B (+0.52%) 7.81 kB → 7.86 kB
src/style/themes/midnight.ts 📈 +40 B (+0.52%) 7.45 kB → 7.49 kB
node_modules/lodash/throttle.js 📈 +2 B (+0.07%) 2.69 kB → 2.69 kB
src/components/reports/SummaryNumber.tsx 📉 -2 B (-0.10%) 2.01 kB → 2 kB
View detailed bundle breakdown

Added

Asset File Size % Changed
static/js/TransactionList.js 0 B → 102.51 kB (+102.51 kB) -

Removed

Asset File Size % Changed
static/js/AppliedFilters.js 21.32 kB → 0 B (-21.32 kB) -100%

Bigger

Asset File Size % Changed
static/js/ReportRouter.js 1.52 MB → 1.58 MB (+60.08 kB) +3.86%
static/js/index.js 3.44 MB → 3.44 MB (+166 B) +0.00%

Smaller

Asset File Size % Changed
static/js/wide.js 241.19 kB → 160.64 kB (-80.55 kB) -33.40%

Unchanged

Asset File Size % Changed
static/js/workbox-window.prod.es5.js 5.69 kB 0%
static/js/indexeddb-main-thread-worker-e59fee74.js 13.5 kB 0%
static/js/resize-observer.js 18.37 kB 0%
static/js/BackgroundImage.js 122.29 kB 0%
static/js/useAccountPreviewTransactions.js 1.68 kB 0%
static/js/narrow.js 82.93 kB 0%

Copy link
Contributor

github-actions bot commented Nov 12, 2024

Bundle Stats — loot-core

Hey 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

Files count Total bundle size % Changed
1 1.32 MB → 1.32 MB (+16 B) +0.00%
Changeset
File Δ Size
packages/loot-core/src/server/dashboard/app.ts 📈 +33 B (+0.44%) 7.39 kB → 7.42 kB
View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

Asset File Size % Changed
kcab.worker.js 1.32 MB → 1.32 MB (+16 B) +0.00%

Smaller

No assets were smaller

Unchanged

No assets were unchanged

@lelemm lelemm changed the title [WIP] Calendar Report Calendar Report Nov 12, 2024
Copy link
Contributor

coderabbitai bot commented Nov 12, 2024

Walkthrough

The pull request introduces several enhancements to the reporting functionality within the desktop client application. Key modifications include the addition of a new CalendarCard component integrated into the Overview component to visualize calendar data. A new hook, useSyncedPref, retrieves user preferences for the first day of the week, which is passed to the CalendarCard. The ReportRouter component is updated to include new routes for the calendar view, enabling direct navigation to calendar-related pages. A new CalendarGraph component is introduced to visualize income and expense data over a week. Additionally, the Calendar component manages the calendar interface, while the CalendarCard displays a report card with financial data. The calendar-spreadsheet module is added to generate reports based on income and expenses. Lastly, the TransactionList and TransactionsTable components are updated to support new props for transaction selection and splitting. Overall, these changes enhance the dashboard's functionality and user interaction with calendar and transaction data.

Possibly related PRs

Suggested labels

:sparkles: Merged

Suggested reviewers

  • youngcw
  • joel-jeremy

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 usage

While 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 in start equaling end 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 experience

Good implementation, but consider these improvements:

  1. Add unit tests for the calendar widget integration
  2. 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 and allowSplitTransaction 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 zero

Currently, totalExpenseValue and totalIncomeValue are set to null 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' function

The function name recalculate might not clearly indicate its purpose. Consider renaming it to better reflect its functionality, such as generateCalendarData or computeCalendarMetrics, to enhance code readability and maintainability.


37-67: Combine income and expense queries to reduce database calls

Currently, 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 items

The types for incomeData and expenseData 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 codebase

The 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 declaration

Line 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 in DayButton component

The currentFontSize state mirrors the fontSize prop without additional computation. You can simplify the component by using fontSize 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 code

Using 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 Name

In 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 Rendering

Currently, when totalIncome or totalExpense 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 Logging

In 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 test

There'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 using test.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' props

To ensure the new props showSelection={true} and allowSplitTransaction={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 the TransactionTable.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9e47801 and ad6ac02.

⛔ 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:

  1. The CalendarCard component has proper TypeScript interfaces for all props
  2. 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: ⚠️ Potential issue

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);

@lelemm lelemm changed the title Calendar Report [WIP] Calendar Report Nov 12, 2024
@lelemm lelemm changed the title [WIP] Calendar Report Calendar Report Nov 12, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 using eachMonthOfInterval 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 out onFilter prop

The 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 constants

Consider 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 component

The 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 components

The 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 prop

The 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 reduce

The 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 indicators

While 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 management

The 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 maintainability

The 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 literal

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between ad6ac02 and 2e89ef2.

📒 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (20)
packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (2)

206-211: Simplify firstDayOfWeekIdx validation

The 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 failures

The 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 functionality

The 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 parsing

The 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 constants

The 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 labels

The 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 dependencies

The 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 theme

The 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 items

The 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 tooltip

The 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 button

The 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 elements

The 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 firstDayOfWeekIdx

The 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 constants

Consider 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 logic

The 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 mapping

Consider 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2e89ef2 and e3bd0d0.

📒 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 functionality

The 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 definitions

Consider 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 calculation

There'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 component

The 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 performance

The component could benefit from several performance optimizations:

  1. Memoize the component to prevent unnecessary re-renders
  2. Extract static styles
  3. 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 layout

The 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 content

The 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 handling

The 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 measurements

The 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 handler

The 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 propagation

While 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

📥 Commits

Reviewing files that changed from the base of the PR and between e3bd0d0 and a2cebb2.

⛔ 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 plans

The 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 calculation

The 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 constants

Magic 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 component

The 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 management

The currentFontSize state and effect could be eliminated since it's just mirroring the fontSize 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 calculations

The 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 explicit

The 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 styles

The 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 mappings

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between a2cebb2 and c887f22.

📒 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

lelemm and others added 3 commits November 13, 2024 08:51
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (6)
packages/desktop-client/src/components/reports/Overview.tsx (1)

55-56: Consider adding type annotation for firstDayOfWeekIdx

The 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 and allowSplitTransaction props are hardcoded to true, 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 and allowSplitTransaction 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8cc40cb and d86855c.

📒 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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:

  1. Showing "0" or "No transactions" for better user experience
  2. 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 values

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between d86855c and 662d547.

📒 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.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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:

  1. Transaction list rendering logic
  2. Mobile drag gesture implementation
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 662d547 and 2954139.

📒 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.

@lelemm
Copy link
Contributor Author

lelemm commented Nov 21, 2024

It may be nice from a UX view to unselect the selected day in the full report if you click outside the calendar. Right now you have to remove the filter that gets generated.

yeah, nice one. I will do it

turns out, not easy, lol. I will check if I can find a nice way to do this

done @youngcw

This is looking very good! Thank you for your time and effort on this!

ty @joel-jeremy :)

I do have some comments on mobile view:

  1. When opening the filter in mobile, the whole page seems to re-render and the filter tooltip automatically closes

I noticed that too, I have to investigate what is going on. Any suspicion?

  1. When the transaction list/drawer is open and you swipe down on the top of it, the drawer moves down to where your finger stops. I think swiping down should fully close the drawer.

changed how it works, take a look

  1. Can we make the Show transactions/Hide transactions button larger so it's easier to click? Please also make it sentence case since we use sentence case throughout the app.

change the case and size of the button

  1. Would it be easy to make the calendars line up vertically on mobile instead of horizontally so that we can utilize more screen real estate? (flexDirection = column for mobile?)

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

Should we still display the Income and Expense summary as 0 when there are none for the selected days? image

removed the conditions to hide when zero

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (8)
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (5)

51-59: Simplify font size calculation logic

The 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 headers

Consider 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 handler

The 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 transitions

Using 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 calculations

The 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 logic

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2954139 and 7c97f71.

📒 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.

@youngcw
Copy link
Member

youngcw commented Nov 21, 2024

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7c97f71 and b960fb1.

📒 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.

@lelemm
Copy link
Contributor Author

lelemm commented Nov 21, 2024

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.

@youngcw, I added a minHeight to the header, so as long as there is not too many filters, it will keep the screen static

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
packages/desktop-client/src/components/reports/Overview.tsx (1)

55-56: Consider using number type for firstDayOfWeekIdx

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between b960fb1 and 8c06842.

📒 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:

  1. Filter tooltip behavior
  2. "Show/Hide transactions" button size
  3. 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 constant

The constant SUMMARY_HEIGHT would be clearer with a more descriptive name like CALENDAR_SUMMARY_HEIGHT to indicate its specific purpose.

-const SUMMARY_HEIGHT = 140;
+const CALENDAR_SUMMARY_HEIGHT = 140;

429-461: Improve drag gesture handling

The drag gesture implementation could be improved for better user experience and performance:

  1. Add velocity-based animations
  2. Implement smoother transitions
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8c06842 and 190e157.

📒 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.

joel-jeremy
joel-jeremy previously approved these changes Nov 22, 2024
Copy link
Contributor

@joel-jeremy joel-jeremy left a 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. :)

@youngcw
Copy link
Member

youngcw commented Nov 23, 2024

This needs the same update as the summary card to allow importing a dashboard.json file

@lelemm
Copy link
Contributor Author

lelemm commented Nov 25, 2024

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?

@youngcw
Copy link
Member

youngcw commented Nov 25, 2024

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

@lelemm
Copy link
Contributor Author

lelemm commented Nov 25, 2024

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 ✔️

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 190e157 and 342c764.

📒 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.

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

Successfully merging this pull request may close these issues.

5 participants