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

[WIP] Calendar report #3758

Closed
wants to merge 1 commit into from
Closed

Conversation

lelemm
Copy link
Contributor

@lelemm lelemm commented Oct 31, 2024

A calendar view report:

Actual.-.Google.Chrome.2024-10-31.15-43-38.mp4

Copy link

netlify bot commented Oct 31, 2024

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit df17843
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/67337ffea69e30000808357d
😎 Deploy Preview https://deploy-preview-3758.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 Oct 31, 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
9 5.35 MB → 5.41 MB (+59.87 kB) +1.09%
Changeset
File Δ Size
src/components/reports/reports/Calendar.tsx 🆕 +24.18 kB 0 B → 24.18 kB
src/components/reports/reports/CalendarCard.tsx 🆕 +14.97 kB 0 B → 14.97 kB
src/components/reports/graphs/CalendarGraph.tsx 🆕 +7.65 kB 0 B → 7.65 kB
src/components/reports/spreadsheets/calendar-spreadsheet.ts 🆕 +4.23 kB 0 B → 4.23 kB
node_modules/date-fns/esm/differenceInDays/index.js 🆕 +3.04 kB 0 B → 3.04 kB
node_modules/date-fns/esm/isSameDay/index.js 🆕 +1.19 kB 0 B → 1.19 kB
node_modules/date-fns/esm/isSameMonth/index.js 🆕 +1.02 kB 0 B → 1.02 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 (+20.65%) 1.52 kB → 1.84 kB
src/components/reports/Overview.tsx 📈 +608 B (+3.91%) 15.2 kB → 15.79 kB
src/components/transactions/TransactionList.jsx 📈 +98 B (+1.87%) 5.12 kB → 5.22 kB
src/components/transactions/TransactionsTable.jsx 📈 +562 B (+0.83%) 66.42 kB → 66.97 kB
src/components/reports/reportRanges.ts 📈 +31 B (+0.75%) 4.05 kB → 4.08 kB
node_modules/lodash/throttle.js 📈 +2 B (+0.07%) 2.69 kB → 2.69 kB
View detailed bundle breakdown

Added

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

Removed

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

Bigger

Asset File Size % Changed
static/js/ReportRouter.js 1.49 MB → 1.55 MB (+59.22 kB) +3.88%

Smaller

Asset File Size % Changed
static/js/wide.js 241.96 kB → 162.04 kB (-79.92 kB) -33.03%

Unchanged

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

Copy link
Contributor

github-actions bot commented Oct 31, 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.27 MB 0%

Changeset

No files were changed

View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

No assets were bigger

Smaller

No assets were smaller

Unchanged

Asset File Size % Changed
kcab.worker.js 1.27 MB 0%

@lelemm
Copy link
Contributor Author

lelemm commented Oct 31, 2024

This uses useTransactions added on PR #3685 from @joel-jeremy

@psybers
Copy link
Contributor

psybers commented Oct 31, 2024

Early feedback from manual testing:

  • month names in the calendar widget should left align, to avoid crowing the blue/red amounts on the right
  • clicking a day filters to that date, what about clicking the month name to filter to the month?
  • on the dashboard, month names tend to wrap
  • amounts are shown incorrectly, adding '00' to the amount. E.g. "52.23" becomes "5223.00"
  • not able to reproduce at the moment, but somehow I went to 'all time' and the calendars were basically empty (except for December 2023). Then I clicked to 3 months and it fixed itself. (I think I maybe accidentally clicked a day and it was filtering. Marking as done for now.)
  • clicking the 'select all' checkbox at the top of the transactions does nothing
  • big black gap in the middle below the calendars:
    image

@lelemm
Copy link
Contributor Author

lelemm commented Nov 1, 2024

@psybers

  • month names in the calendar widget should left align, to avoid crowing the blue/red amounts on the right
  • clicking a day filters to that date, what about clicking the month name to filter to the month?
  • on the dashboard, month names tend to wrap
  • amounts are shown incorrectly, adding '00' to the amount. E.g. "52.23" becomes "5223.00"
  • big black gap in the middle below the calendars

@psybers
Copy link
Contributor

psybers commented Nov 1, 2024

@lelemm Nice, thanks for the fixes! I still see the month name not being left-aligned (it is centered, in the button). Though I think you tweaked the spacing so now it is not as crowded. I would still recommend left aligning it as it feels a bit off as is.

I'm not sure I like the ':' in the month name on the dashboard. Is it really needed?

BTW I get a crash now. I set the view to 6 months, then clicked 'save' and tried to go back to the dashboard.

TypeError: Cannot read properties of undefined (reading 'length') at https://deploy-preview-3758.demo.actualbudget.org/static/js/ReportRouter.ydg2iLpI.chunk.js:3:75310 at Array.reduce (<anonymous>) at https://deploy-preview-3758.demo.actualbudget.org/static/js/ReportRouter.ydg2iLpI.chunk.js:3:75284 at Vy (https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:59:24270) at Uu (https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:59:42396) at https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:59:40713 at E (https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:44:1552) at MessagePort.L (https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:44:1914)

@lelemm
Copy link
Contributor Author

lelemm commented Nov 2, 2024

@lelemm Nice, thanks for the fixes! I still see the month name not being left-aligned (it is centered, in the button). Though I think you tweaked the spacing so now it is not as crowded. I would still recommend left aligning it as it feels a bit off as is.

I'm not sure I like the ':' in the month name on the dashboard. Is it really needed?

BTW I get a crash now. I set the view to 6 months, then clicked 'save' and tried to go back to the dashboard.

TypeError: Cannot read properties of undefined (reading 'length') at https://deploy-preview-3758.demo.actualbudget.org/static/js/ReportRouter.ydg2iLpI.chunk.js:3:75310 at Array.reduce (<anonymous>) at https://deploy-preview-3758.demo.actualbudget.org/static/js/ReportRouter.ydg2iLpI.chunk.js:3:75284 at Vy (https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:59:24270) at Uu (https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:59:42396) at https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:59:40713 at E (https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:44:1552) at MessagePort.L (https://deploy-preview-3758.demo.actualbudget.org/static/js/index.tOAhwMza.js:44:1914)

nice catch.

  • removed the :
  • fixed the crash
  • changed from centered to left aligned

@jsehnoutka
Copy link

jsehnoutka commented Nov 3, 2024

This is probably not relevant for the initial build, but I see the most potential using this for month-based planning as outlined in #1928

In any case, thanks for this, great feature!

Edit: Found an inconsistency between symbols for incomes and expenses once the amount goes through xx xxx,xx amount

image
image
image

@lelemm
Copy link
Contributor Author

lelemm commented Nov 4, 2024

Edit: Found an inconsistency between symbols for incomes and expenses once the amount goes through xx xxx,xx amount

image image image

I've noticed that too. when there is no space available it is shrinking the icon

@joel-jeremy
Copy link
Contributor

Edit: Found an inconsistency between symbols for incomes and expenses once the amount goes through xx xxx,xx amount
image image image

I've noticed that too. when there is no space available it is shrinking the icon

You can set flexShrink: 0 on the icon style to prevent it from shrinking

@lelemm
Copy link
Contributor Author

lelemm commented Nov 5, 2024

  • month names in the calendar widget should left align, to avoid crowing the blue/red amounts on the right
  • clicking the 'select all' checkbox at the top of the transactions does nothing
  • Changed the way month names work and also aligned to left
  • Removed checkboxes from the transaction list in the report details (removed split transaction operation too)

@lelemm
Copy link
Contributor Author

lelemm commented Nov 5, 2024

Edit: Found an inconsistency between symbols for incomes and expenses once the amount goes through xx xxx,xx amount

image image image

solved

@lelemm
Copy link
Contributor Author

lelemm commented Nov 5, 2024

Added mobile page details:

image

@lelemm lelemm changed the title [WIP] Calendar report Calendar report Nov 7, 2024
Copy link
Contributor

coderabbitai bot commented Nov 7, 2024

Walkthrough

The pull request introduces several modifications across various files, primarily focusing on enhancing the functionality and reliability of components and hooks within the application. Key changes include updates to the ESLint configuration for React hooks, specifically enhancing the react-hooks/exhaustive-deps rule to recognize an additional hook, useQuery. Significant adjustments were made to testing files, where waitFor() calls were added to ensure proper loading states before assertions are made, thereby improving test reliability.

New asynchronous methods, waitFor and clearSearch, were added to several page model classes to enhance synchronization and search functionality. Various components, such as ManageRules, Account, and Balance, underwent refactoring to improve data fetching mechanisms, type safety, and error handling. Additionally, new hooks for managing transactions were introduced, streamlining transaction handling and search capabilities. The overall structure of the application remains intact, but the changes enhance functionality, improve user experience, and ensure better state management throughout the codebase.

Possibly related PRs

Suggested labels

:sparkles: Merged

Suggested reviewers

  • youngcw
  • carkom

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

🧹 Outside diff range and nitpick comments (59)
packages/loot-core/src/client/data-hooks/dashboard.ts (1)

7-20: Consider adding JSDoc documentation

Since this is a core data hook that other features (including the new calendar view) depend on, consider adding JSDoc documentation to explain:

  • The purpose of the hook
  • The shape of the returned data
  • The loading state behavior

Example:

/**
 * Hook to fetch and manage dashboard widgets data
 * @returns {Object} Object containing:
 *   - isLoading: boolean indicating if the query is in progress
 *   - data: Array of Widget objects, empty array if no data
 */
packages/loot-core/src/client/data-hooks/widget.ts (1)

8-11: Consider adding error handling

The query could fail due to network issues or invalid data. Consider handling potential errors by destructuring the error property from useQuery and including it in the return value.

-  const { data = [], isLoading } = useQuery<W>(
+  const { data = [], isLoading, error } = useQuery<W>(
     () => q('dashboard').filter({ id, type }).select('*'),
     [id, type],
   );

   return useMemo(
     () => ({
       isLoading,
       data: data?.[0],
+      error,
     }),
     [data, isLoading, error],
   );
packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1)

11-13: Add JSDoc documentation for consistency.

The waitFor method implementation is good and aligns with the goal of improving test reliability. However, for consistency with other methods in this class, consider adding JSDoc documentation.

Add documentation like this:

+  /**
+   * Wait for the account list to be present in the DOM
+   */
  async waitFor() {
    await this.accountList.waitFor();
  }
packages/loot-core/src/client/data-hooks/filters.ts (2)

Line range hint 1-7: Consider enabling TypeScript strict mode.

The @ts-strict-ignore comment disables TypeScript's strict mode checks. Consider enabling strict mode to catch potential type-related issues early in development.

Remove the comment and fix any type-related issues that arise:

-// @ts-strict-ignore

28-36: Consider optimizing array spread operation.

The spread operation [...data] creates a new array on every render, even when the data hasn't changed. Since data is already an array, you can optimize this by moving the spread operation outside the useMemo.

+ const sortedData = data ? [...data] : [];
  return useMemo(
    () =>
-     toJS(data ? [...data] : []).sort((a, b) =>
+     toJS(sortedData).sort((a, b) =>
        a.name
          .trim()
          .localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }),
      ),
-   [data],
+   [sortedData],
  );
packages/desktop-client/e2e/page-models/mobile-account-page.js (3)

18-20: Add timeout and documentation to the waitFor method.

The method improves test reliability by ensuring the transaction list is present. However, consider these enhancements:

+  /**
+   * Waits for the transaction list to be present in the DOM
+   * @param {Object} options Optional parameters including timeout
+   * @throws {Error} If element is not found within timeout
+   */
   async waitFor() {
-    await this.transactionList.waitFor();
+    await this.transactionList.waitFor({ timeout: 10000 });
   }

36-38: Add documentation to the clearSearch method.

The method provides necessary functionality for clearing search input. Consider adding documentation:

+  /**
+   * Clears the search box text
+   * @returns {Promise<void>}
+   */
   async clearSearch() {
     await this.searchBox.clear();
   }

Line range hint 1-48: Consider implementing a base page class.

The MobileAccountPage class could benefit from inheriting from a base page class that provides common functionality like waiting for elements and error handling. This would promote code reuse across different page objects and establish consistent patterns for handling timeouts and errors.

Example structure:

class BasePage {
  constructor(page) {
    this.page = page;
  }

  async waitForElement(locator, options = { timeout: 10000 }) {
    try {
      await locator.waitFor(options);
    } catch (error) {
      throw new Error(`Element not found: ${error.message}`);
    }
  }
}

class MobileAccountPage extends BasePage {
  // Current implementation
}
packages/desktop-client/src/components/rules/ScheduleValue.tsx (3)

22-23: Consider moving the static query outside the component

Since the query is static and doesn't depend on any props or state, it could be defined outside the component to further optimize performance.

+const schedulesQuery = q('schedules').select('*');

export function ScheduleValue({ value }: ScheduleValueProps) {
  const payees = usePayees();
  const byId = getPayeesById(payees);
-  const schedulesQuery = useMemo(() => q('schedules').select('*'), []);
  const { schedules = [], isLoading } = useSchedules({ query: schedulesQuery });

25-31: Consider improving loading state presentation

While the loading state implementation is good, there are a few potential improvements:

  1. The loading indicator size is hardcoded (10x10)
  2. The inline-flex style could be moved to a styled component for better maintainability
+const LoadingContainer = styled(View)`
+  display: inline-flex;
+  align-items: center;
+`;

 if (isLoading) {
   return (
-    <View aria-label="Loading..." style={{ display: 'inline-flex' }}>
-      <AnimatedLoading width={10} height={10} />
+    <LoadingContainer aria-label="Loading...">
+      <AnimatedLoading width={12} height={12} />
+    </LoadingContainer>
   );
 }

Line range hint 33-45: Address TODO: Improve type safety for payee handling

The current type coercion (schedule._payee as unknown as string) is a code smell and could lead to runtime errors. Consider refactoring the payee handling to be more type-safe.

Would you like me to help create a proper type definition for the schedule object that includes the correct payee type? This would eliminate the need for the unsafe type assertion.

packages/loot-core/src/client/data-hooks/reports.ts (1)

57-60: Consider these performance optimizations.

  1. Avoid unnecessary array spread:
-data: sort(toJS(queryData ? [...queryData] : [])),
+data: sort(toJS(queryData ? queryData : [])),
  1. Consider memoizing the sort function to prevent recreating it on every render:
const sortReports = useMemo(
  () => (reports: CustomReportEntity[]) =>
    reports.sort((a, b) =>
      a.name && b.name
        ? a.name.trim().localeCompare(b.name.trim(), undefined, {
            ignorePunctuation: true,
          })
        : 0
    ),
  []
);
packages/desktop-client/e2e/accounts.mobile.test.js (1)

57-59: Consider making the assertion more specific.

Good addition of the clear search verification. While not.toHaveCount(0) works, consider making the assertion more explicit about the expected state.

 await accountPage.clearSearch();
-await expect(accountPage.transactions).not.toHaveCount(0);
+// Assert the specific number of transactions if known from test data
+await expect(accountPage.transactions).toHaveCount(expectedCount);
+// Or at least verify a minimum number of transactions
+const transactionCount = await accountPage.transactions.count();
+expect(transactionCount).toBeGreaterThan(0);
packages/desktop-client/src/hooks/usePreviewTransactions.ts (3)

16-25: Enhance deprecation notice with migration steps.

While the deprecation notice is helpful, it would be more useful to include specific migration steps or examples.

Consider updating the JSDoc comment:

/**
 * @deprecated Please use `usePreviewTransactions` hook from `loot-core/client/data-hooks/transactions` instead.
+ * @example
+ * // Before
+ * const preview = usePreviewTransactions(collapseTransactions);
+ * 
+ * // After
+ * import { usePreviewTransactions } from 'loot-core/client/data-hooks/transactions';
+ * const preview = usePreviewTransactions(collapseTransactions);
 */

26-31: Ensure consistent return types.

The early return [] during loading state should be explicitly typed to match the return type of previewTransactions (TransactionEntity[]).

Consider updating the early return:

  if (scheduleData.isLoading) {
-    return [];
+    return [] as TransactionEntity[];
  }

Line range hint 69-75: Improve status check maintainability.

Consider using a Set for the valid statuses to make the code more maintainable and efficient.

 function isForPreview(schedule: ScheduleEntity, statuses: ScheduleStatuses) {
+  const VALID_STATUSES = new Set(['due', 'upcoming', 'missed']);
   const status = statuses.get(schedule.id);
-  return (
-    !schedule.completed &&
-    (status === 'due' || status === 'upcoming' || status === 'missed')
-  );
+  return !schedule.completed && VALID_STATUSES.has(status);
 }
packages/loot-core/src/types/models/dashboard.d.ts (1)

93-101: Consider enhancing the CalendarWidget type with calendar-specific settings.

While the current type definition covers basic widget functionality, consider adding:

  1. Calendar-specific settings like firstDayOfWeekIdx to properly type the configuration mentioned in the PR objectives
  2. JSDoc comments to document the purpose of each property, especially conditions and timeFrame
+/** Configuration for calendar widget display and behavior */
 export type CalendarWidget = AbstractWidget<
   'calendar-card',
   {
+    /** Optional widget title */
     name?: string;
+    /** Filter conditions for transactions */
     conditions?: RuleConditionEntity[];
+    /** Operator for combining multiple conditions */
     conditionsOp?: 'and' | 'or';
+    /** Date range configuration */
     timeFrame?: TimeFrame;
+    /** Index of first day of week (0 = Sunday, 1 = Monday, etc.) */
+    firstDayOfWeekIdx?: number;
   } | null
 >;
packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3)

36-36: Add validation for scheduleId extraction.

The current implementation assumes the transactionId split will always yield a valid scheduleId. Consider adding validation to handle potential malformed transaction IDs.

-const scheduleId = transactionId?.split('/')?.[1];
+const scheduleId = transactionId?.split('/')?.at(1);
+if (!scheduleId) {
+  console.warn('Invalid transaction ID format:', transactionId);
+  return null;
+}

44-49: Consider adding a loading indicator.

While returning null during loading is valid, users might benefit from visual feedback. Consider showing a loading spinner or skeleton UI instead of a blank screen.

  if (isSchedulesLoading) {
-   return null;
+   return (
+     <Modal name="scheduled-transaction-menu">
+       <View style={{ padding: 20, alignItems: 'center' }}>
+         <LoadingSpinner />
+       </View>
+     </Modal>
+   );
  }

55-55: Enhance fallback values for better user experience.

While optional chaining prevents errors, consider using more meaningful fallback values instead of empty strings or potentially invalid dates.

-title={<ModalTitle title={schedule?.name || ''} shrinkOnOverflow />}
+title={<ModalTitle title={schedule?.name || 'Untitled Schedule'} shrinkOnOverflow />}

-{format(schedule?.next_date || '', 'MMMM dd, yyyy')}
+{schedule?.next_date ? format(schedule.next_date, 'MMMM dd, yyyy') : 'No date scheduled'}

Also applies to: 69-69

packages/desktop-client/src/components/schedules/ScheduleLink.tsx (1)

40-43: LGTM: Clean query memoization implementation.

The query construction is properly memoized and filters for non-completed schedules. Consider adding a comment explaining why the dependency array is empty to improve maintainability.

 const schedulesQuery = useMemo(
   () => q('schedules').filter({ completed: false }).select('*'),
-  [],
+  // Empty dependency array as the query structure is static
+  [],
 );
packages/desktop-client/e2e/accounts.test.js (1)

65-66: LGTM! Consider adding an assertion message.

The addition of waitFor() improves test reliability by ensuring the account page is fully loaded before proceeding with complex operations.

Consider adding an assertion message to help with debugging:

-await accountPage.waitFor();
+await accountPage.waitFor('Waiting for account page to load');
packages/desktop-client/src/components/reports/reportRanges.ts (1)

164-167: Add test coverage for the new behavior.

The changes to getLatestRange introduce a special case for offset === 1 that should be covered by unit tests.

Would you like me to help generate test cases for:

  • When offset is 1 (start equals end)
  • When offset is greater than 1 (start is offset months before end)
  • Edge cases with different offset values
packages/desktop-client/src/components/accounts/Balance.jsx (1)

72-76: Consider adding error handling.

While the loading state is properly handled, consider adding error handling for cases where the schedule data fetch fails. This would improve the robustness of the component.

- const { isLoading, schedules = [] } = useCachedSchedules();
+ const { isLoading, error, schedules = [] } = useCachedSchedules();

  if (isLoading) {
    return null;
  }
+ 
+ if (error) {
+   console.error('Failed to load schedules:', error);
+   return <Text style={{ color: theme.errorText }}>Failed to load balance data</Text>;
+ }
packages/desktop-client/e2e/page-models/account-page.js (1)

33-35: Add JSDoc documentation and consider timeout handling.

The implementation looks good and follows Playwright's best practices. Consider these enhancements:

+  /**
+   * Waits for the transaction table to be available.
+   * @param {number} [timeout] - Optional timeout in milliseconds
+   * @throws {TimeoutError} If the element is not visible within the timeout
+   */
   async waitFor() {
-    await this.transactionTable.waitFor();
+    try {
+      await this.transactionTable.waitFor({ timeout: 10000 });
+    } catch (error) {
+      throw new Error(`Transaction table not available: ${error.message}`);
+    }
   }
packages/desktop-client/src/components/transactions/TransactionList.jsx (1)

98-99: Consider adding PropTypes for the new props.

The new props showSelection and allowSplitTransaction have good default values, but would benefit from proper type definitions for better maintainability and documentation.

Consider adding PropTypes or TypeScript types:

TransactionList.propTypes = {
  // ... existing prop types
  showSelection: PropTypes.bool,
  allowSplitTransaction: PropTypes.bool
};
packages/loot-core/src/client/queries.ts (2)

31-34: LGTM! Consider extracting the account filter type.

The renaming and type improvements enhance code clarity and type safety. Consider extracting the union type to a named type for reusability:

type AccountFilter = AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized';

Also applies to: 70-72


Line range hint 83-124: Security: Protect against SQL injection in search queries.

The current implementation directly interpolates the search string into SQL-like queries without proper escaping:

'payee.name': { $like: `%${search}%` }

This could potentially lead to SQL injection if the underlying query system doesn't handle escaping.

Consider these improvements:

  1. Escape special characters in the search string
  2. Add input validation for the search string length
  3. Consider using parameterized queries if supported by the query system

Example fix:

function escapeSearchString(str: string): string {
  return str.replace(/[%_\\]/g, '\\$&');
}

const escapedSearch = escapeSearchString(search);
return currentQuery.filter({
  $or: {
    'payee.name': { $like: `%${escapedSearch}%` },
    // ... apply to other fields
  }
});
packages/desktop-client/src/components/ManageRules.tsx (1)

117-119: Consider optimizing schedule fetching and improving type safety.

While the implementation is functionally correct, consider these improvements:

  1. Performance: Instead of fetching all schedules with select('*'), consider implementing pagination or limiting the fields if not all are needed.
  2. Type Safety: Consider removing the @ts-strict-ignore and properly typing the schedules array.

Example of adding type safety:

interface Schedule {
  id: string;
  _payee: string;
  // add other required fields
}

const { schedules = [] as Schedule[] } = useSchedules({
  query: useMemo(() => q('schedules').select('*'), []),
});
packages/loot-core/src/client/query-helpers.test.ts (1)

Line range hint 1-1: Consider adding TypeScript interfaces for query parameters.

While the changes improve the API, adding TypeScript interfaces for the query parameter objects would enhance type safety and developer experience. This would help catch potential errors at compile time and provide better IDE support.

Example:

interface QueryOptions {
  onlySync?: boolean;
  pageCount?: number;
}

interface QueryCallbacks {
  onData: (data: any) => void;
  onPageData?: (data: any) => void;
}

interface QueryParams extends QueryCallbacks {
  options?: QueryOptions;
}
packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (3)

Line range hint 27-65: Enhance validation in updateScheduleConditions.

While the basic validation for required fields is good, consider adding:

  1. Validation for negative amounts
  2. More descriptive error messages including field context
  3. Validation for date ranges when frequency is set

Example enhancement:

 if (fields.date == null) {
-  return { error: t('Date is required') };
+  return { error: t('Schedule date is required') };
 }

 if (fields.amount == null) {
-  return { error: t('A valid amount is required') };
+  return { error: t('A valid amount is required for the schedule') };
+}
+
+if (typeof fields.amount === 'number' && fields.amount < 0) {
+  return { error: t('Amount cannot be negative') };
 }

Line range hint 395-404: Improve error message clarity for schedule name conflicts.

The error message for duplicate schedule names could be more helpful by including the conflicting name and suggesting alternatives.

Consider this enhancement:

 if (sameName.length > 0 && sameName[0].id !== state.schedule.id) {
   dispatch({
     type: 'form-error',
-    error: t('There is already a schedule with this name'),
+    error: t('A schedule named "{{name}}" already exists. Please choose a different name.', 
+      { name: state.fields.name }
+    ),
   });
   return;
 }

Line range hint 66-303: Consider breaking down the ScheduleDetails component for better maintainability.

The component is quite large and handles multiple responsibilities. Consider:

  1. Extracting the transaction list section into a separate component
  2. Moving the schedule form fields into a dedicated component
  3. Memoizing expensive computations and callbacks

This would improve:

  • Code maintainability
  • Performance through better memo-ization
  • Testing isolation

Example structure:

// ScheduleTransactionList.jsx
function ScheduleTransactionList({ transactions, onLink, onUnlink }) {
  // Transaction list logic
}

// ScheduleForm.jsx
function ScheduleForm({ fields, dispatch }) {
  // Form fields logic
}

// ScheduleDetails.jsx
function ScheduleDetails({ id, transaction }) {
  // Main component with reduced complexity
}
packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (1)

152-176: Consider parameterizing the test setup for better coverage.

The test setup could be more flexible by:

  1. Parameterizing showSelection and allowSplitTransaction props to test different combinations.
  2. Adding test cases for when these features are disabled.
-              <TransactionTable
-                {...props}
-                transactions={transactions}
-                loadMoreTransactions={() => {}}
-                commonPayees={[]}
-                payees={payees}
-                addNotification={n => console.log(n)}
-                onSave={onSave}
-                onSplit={onSplit}
-                onAdd={onAdd}
-                onAddSplit={onAddSplit}
-                onCreatePayee={onCreatePayee}
-                showSelection={true}
-                allowSplitTransaction={true}
-              />
+              <TransactionTable
+                {...props}
+                transactions={transactions}
+                loadMoreTransactions={() => {}}
+                commonPayees={[]}
+                payees={payees}
+                addNotification={n => console.log(n)}
+                onSave={onSave}
+                onSplit={onSplit}
+                onAdd={onAdd}
+                onAddSplit={onAddSplit}
+                onCreatePayee={onCreatePayee}
+                showSelection={props.showSelection ?? true}
+                allowSplitTransaction={props.allowSplitTransaction ?? true}
+              />
packages/desktop-client/src/components/modals/EditRuleModal.jsx (1)

316-325: Consider improving loading state handling.

While the loading check is good, returning null during loading might cause layout shifts. Consider showing a loading skeleton or placeholder to maintain consistent layout.

-  if (isSchedulesLoading) {
-    return null;
-  }
+  if (isSchedulesLoading) {
+    return (
+      <View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
+        <Text>Loading schedule...</Text>
+      </View>
+    );
+  }
packages/desktop-client/src/components/transactions/TransactionsTable.jsx (2)

207-232: Improve accessibility for selection controls.

The selection cell implementation should include proper ARIA attributes for better screen reader support.

 <SelectCell
   exposed={true}
   focused={false}
   selected={hasSelected}
   width={20}
+  aria-label="Select all transactions"
+  role="checkbox"
+  aria-checked={hasSelected}
   style={{
     borderTopWidth: 0,
     borderBottomWidth: 0,
   }}
   onSelect={e =>
     dispatchSelected({
       type: 'select-all',
       isRangeSelect: e.shiftKey,
     })
   }
 />

Line range hint 1992-2614: Verify prop types for new boolean flags.

The new boolean props showSelection and allowSplitTransaction are propagated correctly through the component hierarchy. However, they should be properly typed and documented.

Add PropTypes or TypeScript types for the new props:

TransactionTable.propTypes = {
  showSelection: PropTypes.bool,
  allowSplitTransaction: PropTypes.bool,
  // ... other props
};

TransactionTable.defaultProps = {
  showSelection: true,
  allowSplitTransaction: true,
  // ... other defaults
};
packages/loot-core/src/client/query-hooks.ts (1)

35-44: Consider removing the isUnmounted flag by leveraging cleanup

The isUnmounted flag is used to prevent state updates after the component has unmounted, which is important for avoiding memory leaks. However, since you're unsubscribing from live in the cleanup function, additional checks might be redundant if liveQuery properly handles unsubscription.

Consider the following refactor:

     let live: null | LiveQuery<Response> = liveQuery<Response>(query, {
       onData: data => {
-        if (!isUnmounted) {
           setData(data);
           setIsLoading(false);
-        }
       },
       onError: setError,
     });

     return () => {
-      isUnmounted = true;
       live?.unsubscribe();
       live = null;
     };

Ensure that liveQuery does not invoke onData after unsubscription. If it does, you might need to adjust liveQuery to handle this case or keep the flag.

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3)

55-58: Consider optimizing the 'payee_mapping' table checks

The payee_mapping table is checked in both conditional statements for reloading transactions and dispatching payees. If this duplication is not intentional, consider combining or restructuring the conditions to avoid redundant checks.


59-59: Verify necessity of optional chaining for reloadTransactions

The use of optional chaining in reloadTransactions?.(); suggests that reloadTransactions might be undefined. Verify if reloadTransactions is always defined from the useTransactions hook. If it is guaranteed to be available, you can remove the optional chaining to simplify the code.


77-77: Enhance comment for better clarity

Referencing a commit hash in the comment may not provide sufficient context for future readers. Consider elaborating the comment to explain how preview transactions are handled, or provide a link to relevant documentation for better clarity.

packages/loot-core/src/shared/query.ts (5)

3-5: Consider refining the ObjectExpression type for better type safety

The ObjectExpression type is defined recursively with unknown:

type ObjectExpression = {
  [key: string]: ObjectExpression | unknown;
};

Using unknown makes it difficult to enforce type safety and can lead to runtime errors. Consider defining a more specific type or using generics to better represent the structure of your expressions. This will enhance type checking and reduce potential bugs.


Line range hint 25-38: Ensure default values are not overridden by undefined properties in the constructor

In the Query class constructor, default values are set before spreading the state object:

this.state = {
  tableOptions: state.tableOptions || {},
  filterExpressions: state.filterExpressions || [],
  selectExpressions: state.selectExpressions || [],
  groupExpressions: state.groupExpressions || [],
  orderExpressions: state.orderExpressions || [],
  calculation: false,
  rawMode: false,
  withDead: false,
  validateRefs: true,
  limit: null,
  offset: null,
  ...state,
};

If state contains properties with undefined values, these will overwrite the defaults, potentially leading to unintended behavior. To ensure defaults are maintained, consider using the nullish coalescing operator (??) or reordering the spread:

this.state = {
  ...state,
  tableOptions: state.tableOptions ?? {},
  filterExpressions: state.filterExpressions ?? [],
  selectExpressions: state.selectExpressions ?? [],
  groupExpressions: state.groupExpressions ?? [],
  orderExpressions: state.orderExpressions ?? [],
  calculation: state.calculation ?? false,
  rawMode: state.rawMode ?? false,
  withDead: state.withDead ?? false,
  validateRefs: state.validateRefs ?? true,
  limit: state.limit ?? null,
  offset: state.offset ?? null,
};

This ensures that undefined values do not override the intended defaults.


Line range hint 49-65: Potential issue with filter removal logic in unfilter method

The unfilter method attempts to remove specific filters based on provided keys:

const exprSet = new Set(exprs);
return new Query({
  ...this.state,
  filterExpressions: this.state.filterExpressions.filter(
    expr => !exprSet.has(Object.keys(expr)[0]),
  ),
});

This approach assumes that each filter expression object has a single key and that the key order is consistent, which may not always be the case. If a filter expression contains multiple keys or the key order changes, the filter may not be correctly removed.

Consider enhancing the filter removal logic by:

  • Matching filters using a unique identifier or a specific property.
  • Iterating over all keys within each expr to check for matches.
  • Structuring filter expressions to include metadata that aids in identification.

This will ensure that filters are reliably added and removed as intended.


Line range hint 145-161: Type safety concerns in getPrimaryOrderBy function

In the getPrimaryOrderBy function, when handling an ObjectExpression, the code retrieves the field and order like this:

const [field] = Object.keys(firstOrder);
return { field, order: firstOrder[field] };

Since firstOrder[field] is of type unknown (from the ObjectExpression definition), using it directly may lead to type safety issues or runtime errors if it doesn't match the expected type (e.g., 'asc' or 'desc').

To improve type safety:

  • Narrow the type of firstOrder[field] using type assertions or type guards.
  • Refine the ObjectExpression type to more accurately represent possible values for order.
  • Validate that firstOrder[field] is a string and matches expected order values before returning.

Example:

const [field] = Object.keys(firstOrder);
const order = firstOrder[field];
if (order === 'asc' || order === 'desc') {
  return { field, order };
} else {
  throw new Error(`Invalid order value for field ${field}`);
}

This ensures that only valid order values are returned and potential errors are caught early.


136-138: Clarify the behavior of the reset method

The reset method resets the query to a new instance with the same table:

reset() {
  return q(this.state.table);
}

It's unclear whether other state properties like tableOptions, withDead, limit, and offset should be reset or preserved. This might lead to confusion about which parts of the query are being reset.

Consider explicitly documenting the behavior or modifying the method to accept options for resetting specific parts of the state. For example:

reset(options?: { preserveOptions?: boolean }) {
  return new Query({
    table: this.state.table,
    ...(options?.preserveOptions && { tableOptions: this.state.tableOptions }),
  });
}

This provides clearer control over what is retained and what is reset, improving the usability of the method.

packages/loot-core/src/client/data-hooks/schedules.tsx (1)

125-125: Consider memoizing query to prevent unnecessary re-executions of useEffect

If query is recreated on every render, the useEffect hook will re-run each time, potentially leading to performance issues. Memoize query using useMemo to ensure it maintains a stable reference.

packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (2)

189-190: Clarify division by 100 for amount values

The incomeValue and expenseValue are divided by 100, likely to convert cents to dollars. This may not be immediately clear to readers.

Consider adding comments or using a utility function for clarity:

            incomeValue: Math.abs(currentIncome) / 100, // Convert cents to dollars
            expenseValue: Math.abs(currentExpense) / 100, // Convert cents to dollars

Alternatively, create a helper function:

function centsToDollars(amount: number) {
  return amount / 100;
}

// Usage
incomeValue: centsToDollars(Math.abs(currentIncome)),
expenseValue: centsToDollars(Math.abs(currentExpense)),

105-211: Refactor 'recalculate' function for better readability

The recalculate function is lengthy and contains nested logic, which can make it harder to read and maintain. Breaking it into smaller helper functions can improve clarity.

Consider the following refactoring steps:

  • Extract the calculation of totalExpenseValue and totalIncomeValue into separate helper functions.
  • Create a function to generate daysArray for each month.
  • Simplify the getBarLength function if possible.

This modular approach enhances readability and makes unit testing individual components easier.

packages/loot-core/src/client/data-hooks/transactions.ts (1)

138-184: Optimize dependency array in useEffect to avoid unnecessary re-executions

In the useEffect, consider removing schedules and statuses from the dependency array since scheduleTransactions already depends on them. This will prevent unnecessary re-executions when schedules or statuses change but scheduleTransactions doesn't.

Apply this diff to adjust the dependencies:

   }, [scheduleTransactions, schedules, statuses]);
+  }, [scheduleTransactions]);
packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (5)

207-211: Remove unnecessary state duplication for font size

The currentFontSize state mirrors the fontSize prop without modification. This additional state and useEffect are unnecessary and can be removed by directly using fontSize in the style.

Apply this diff to simplify the component:

function DayButton({ day, onPress, fontSize, resizeRef }: DayButtonProps) {
-  const [currentFontSize, setCurrentFontSize] = useState(fontSize);

-  useEffect(() => {
-    setCurrentFontSize(fontSize);
-  }, [fontSize]);

   return (
     // ...
     <span
       style={{
-        fontSize: `${currentFontSize}px`,
+        fontSize: `${fontSize}px`,
         fontWeight: 500,
         position: 'relative',
       }}
     >
       {getDate(day.date)}
     </span>
     // ...
   );
}

130-169: Refactor to reduce code duplication in tooltip content

The tooltip content for income and expenses uses similar structures, leading to duplicated code. Refactoring into a reusable component or function can enhance maintainability.

Example refactoring:

+function TooltipRow({ label, value, size, color }) {
+  return (
+    <>
+      <View style={{ textAlign: 'right', marginRight: 4 }}>{label}:</View>
+      <View style={{ color: color }}>
+        {amountToCurrency(value)}
+      </View>
+      <View style={{ marginLeft: 4 }}>
+        ({Math.round(size * 100) / 100}%)
+      </View>
+    </>
+  );
+}

  // Inside your Tooltip content
  <View
    style={{
      display: 'grid',
      gridTemplateColumns: '70px 1fr 60px',
      gridAutoRows: '1fr',
    }}
  >
    {day.incomeValue !== 0 && (
      <TooltipRow
        label="Income"
        value={day.incomeValue}
        size={day.incomeSize}
        color={chartTheme.colors.blue}
      />
    )}
    {day.expenseValue !== 0 && (
      <TooltipRow
        label="Expenses"
        value={day.expenseValue}
        size={day.expenseSize}
        color={chartTheme.colors.red}
      />
    )}
  </View>

215-225: Enhance accessibility by adding descriptive labels

The Button component lacks accessibility attributes such as aria-label, which can aid users relying on assistive technologies. Including an aria-label or title can improve the overall accessibility of the component.

Example addition:

<Button
  ref={resizeRef}
+ aria-label={`Day ${getDate(day.date)}`}
  style={{
    // existing styles
  }}
  onPress={() => onPress()}
>

228-278: Consider using CSS classes or styled-components for styling

The inline styles in DayButton can make the code harder to maintain and read. Using CSS classes or a styling solution like styled-components can improve code organization.

Example using CSS classes:

-<View
-  style={{
-    position: 'absolute',
-    width: '50%',
-    height: '100%',
-    background: chartTheme.colors.red,
-    opacity: 0.2,
-    right: 0,
-  }}
-/>

+<View className="expense-background" />

// In your CSS file
.expense-background {
  position: absolute;
  width: 50%;
  height: 100%;
  background: chartTheme.colors.red;
  opacity: 0.2;
  right: 0;
}

54-56: Simplify week start day calculation

The computation of weekStartsOn can be simplified by directly casting firstDayOfWeekIdx to a number and ensuring its value is within the valid range (0-6).

Simplified code:

   weekStartsOn: firstDayOfWeekIdx
-    ? (parseInt(firstDayOfWeekIdx) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
-    : 0,
+    ? ((+firstDayOfWeekIdx % 7) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
+    : 0,
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (1)

285-307: Avoid defining complex callbacks within useCallback

The onOpenTransaction function includes nested asynchronous functions (onPost and onSkip) within the pushModal call. Consider moving these nested functions outside of useCallback to improve readability and maintainability.

Apply this refactor to extract onPost and onSkip:

 const onOpenTransaction = useCallback(
   (transaction: TransactionEntity) => {
     if (!isPreviewId(transaction.id)) {
       navigate(`/transactions/${transaction.id}`);
     } else {
       dispatch(
         pushModal('scheduled-transaction-menu', {
           transactionId: transaction.id,
-          onPost: async transactionId => {
-            const parts = transactionId.split('/');
-            await send('schedule/post-transaction', { id: parts[1] });
-            dispatch(collapseModals('scheduled-transaction-menu'));
-          },
-          onSkip: async transactionId => {
-            const parts = transactionId.split('/');
-            await send('schedule/skip-next-date', { id: parts[1] });
-            dispatch(collapseModals('scheduled-transaction-menu'));
-          },
+          onPost,
+          onSkip,
         }),
       );
     }
   },
-  [dispatch, navigate],
+  [dispatch, navigate, onPost, onSkip],
 );

+const onPost = useCallback(async (transactionId: string) => {
+  const parts = transactionId.split('/');
+  await send('schedule/post-transaction', { id: parts[1] });
+  dispatch(collapseModals('scheduled-transaction-menu'));
+}, [dispatch]);
+
+const onSkip = useCallback(async (transactionId: string) => {
+  const parts = transactionId.split('/');
+  await send('schedule/skip-next-date', { id: parts[1] });
+  dispatch(collapseModals('scheduled-transaction-menu'));
+}, [dispatch]);
packages/desktop-client/src/components/reports/reports/Calendar.tsx (3)

870-872: Simplify conditional rendering by removing redundant checks.

The checks for totalIncome !== 0 inside the ternary operators are unnecessary since the parent condition already ensures that totalIncome and totalExpense are not zero. Simplifying the code improves readability.

Apply this diff to remove redundant ternary operators:

{totalIncome !== 0 && (
  <>
    <!-- ... -->
-   {totalIncome !== 0 ? amountToCurrency(totalIncome) : ''}
+   {amountToCurrency(totalIncome)}
    <!-- ... -->
  </>
)}
{totalExpense !== 0 && (
  <>
    <!-- ... -->
-   {totalExpense !== 0 ? amountToCurrency(totalExpense) : ''}
+   {amountToCurrency(totalExpense)}
    <!-- ... -->
  </>
)}

Also applies to: 885-887


413-413: Remove unnecessary commented-out code.

There's a stray commented-out closing brace // } which can cause confusion. Removing it cleans up the code.

Apply this diff to remove the commented-out code:

-// }

505-507: Use stable keys in list rendering to prevent rendering issues.

Using the index as a key in lists can lead to rendering issues if the list changes. It's better to use a unique identifier from the data, such as a date string.

Apply this diff to use a stable key:

{data.calendarData.map((calendar, index) => (
-  <CalendarWithHeader
-    key={index}
+  <CalendarWithHeader
+    key={calendar.start.toISOString()}
     calendar={calendar}
     totalIncome={totalIncome}
     totalExpense={totalExpense}
     onApplyFilter={onApplyFilter}
     firstDayOfWeekIdx={firstDayOfWeekIdx}
   />
))}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 2b72b2f and ae21d64.

⛔ Files ignored due to path filters (2)
  • upcoming-release-notes/3685.md is excluded by !**/*.md
  • upcoming-release-notes/3758.md is excluded by !**/*.md
📒 Files selected for processing (45)
  • .eslintrc.js (1 hunks)
  • packages/desktop-client/e2e/accounts.mobile.test.js (3 hunks)
  • packages/desktop-client/e2e/accounts.test.js (2 hunks)
  • packages/desktop-client/e2e/page-models/account-page.js (1 hunks)
  • packages/desktop-client/e2e/page-models/mobile-account-page.js (2 hunks)
  • packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1 hunks)
  • packages/desktop-client/src/components/ManageRules.tsx (2 hunks)
  • packages/desktop-client/src/components/accounts/Account.tsx (15 hunks)
  • packages/desktop-client/src/components/accounts/Balance.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (6 hunks)
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx (1 hunks)
  • packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (3 hunks)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx (0 hunks)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (2 hunks)
  • packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3 hunks)
  • 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/rules/ScheduleValue.tsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (2 hunks)
  • packages/desktop-client/src/components/schedules/ScheduleLink.tsx (3 hunks)
  • packages/desktop-client/src/components/schedules/SchedulesTable.tsx (4 hunks)
  • packages/desktop-client/src/components/schedules/index.tsx (3 hunks)
  • packages/desktop-client/src/components/transactions/TransactionList.jsx (2 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.jsx (11 hunks)
  • packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (2 hunks)
  • packages/desktop-client/src/hooks/useNotes.ts (1 hunks)
  • packages/desktop-client/src/hooks/usePreviewTransactions.ts (1 hunks)
  • packages/desktop-client/src/hooks/useSchedules.ts (0 hunks)
  • packages/loot-core/src/client/data-hooks/dashboard.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/filters.ts (2 hunks)
  • packages/loot-core/src/client/data-hooks/reports.ts (3 hunks)
  • packages/loot-core/src/client/data-hooks/schedules.tsx (3 hunks)
  • packages/loot-core/src/client/data-hooks/transactions.ts (1 hunks)
  • packages/loot-core/src/client/data-hooks/widget.ts (1 hunks)
  • packages/loot-core/src/client/queries.ts (2 hunks)
  • packages/loot-core/src/client/query-helpers.test.ts (17 hunks)
  • packages/loot-core/src/client/query-helpers.ts (4 hunks)
  • packages/loot-core/src/client/query-hooks.ts (1 hunks)
  • packages/loot-core/src/shared/query.ts (9 hunks)
  • packages/loot-core/src/types/models/dashboard.d.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
  • packages/desktop-client/src/hooks/useSchedules.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
🧰 Additional context used
📓 Learnings (10)
.eslintrc.js (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: .eslintrc.js:164-169
Timestamp: 2024-10-18T15:33:24.453Z
Learning: When updating the `additionalHooks` pattern for `react-hooks/exhaustive-deps`, only `useQuery` should be added, not generalized patterns.
packages/desktop-client/src/components/accounts/Account.tsx (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/accounts/Account.tsx:655-665
Timestamp: 2024-10-24T17:05:41.415Z
Learning: The Account component in 'packages/desktop-client/src/components/accounts/Account.tsx' is being rewritten in a separate PR.
packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx:261-277
Timestamp: 2024-10-22T05:34:56.976Z
Learning: In React components (TypeScript), avoid using hooks like `useCallback` inside callbacks or nested functions. Hooks must be called at the top level of functional components.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-09-27T14:15:46.637Z
Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
Learnt from: jfdoming
PR: actualbudget/actual#3402
File: packages/desktop-client/src/components/accounts/Account.tsx:8-8
Timestamp: 2024-10-08T15:46:15.739Z
Learning: In the `AllTransactions` component of `Account.tsx`, the `useLayoutEffect` hook is appropriately used to dispatch an action that closes splits for parent transactions when `prependTransactions` changes, ensuring this occurs synchronously before the component is painted.
packages/desktop-client/src/components/schedules/index.tsx (3)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:33-35
Timestamp: 2024-10-22T05:32:57.033Z
Learning: In the project's React components, when using `dispatch` from `useDispatch` in `useCallback` or `useEffect` hooks, `dispatch` must be included in the dependency array due to the `react-hooks/exhaustive-deps` ESLint rule.
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:37-39
Timestamp: 2024-10-22T05:32:30.530Z
Learning: In our React function components, we should include `dispatch` in the dependency array of `useCallback` hooks to comply with ESLint rules, even though `dispatch` is a stable function.
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/desktop-client/src/components/schedules/index.tsx:26-31
Timestamp: 2024-10-22T05:32:55.520Z
Learning: In this codebase, ESLint requires `dispatch` from `useDispatch` to be included in the dependency array of `useCallback` hooks, even though `dispatch` is stable.
packages/desktop-client/src/hooks/useNotes.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/data-hooks/dashboard.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/data-hooks/filters.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/data-hooks/reports.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
packages/loot-core/src/client/data-hooks/transactions.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/transactions.ts:32-88
Timestamp: 2024-10-22T02:08:48.162Z
Learning: In the React `useTransactions` hook defined in `packages/loot-core/src/client/data-hooks/transactions.ts`, changes to `options` (e.g., `options.pageCount`) should be applied on the next query without adding them to the `useEffect` dependency array, to avoid unnecessary rerenders when options change.
packages/loot-core/src/client/query-hooks.ts (1)
Learnt from: joel-jeremy
PR: actualbudget/actual#3685
File: packages/loot-core/src/client/data-hooks/reports.ts:8-8
Timestamp: 2024-10-18T15:37:01.917Z
Learning: Within the codebase, `useLiveQuery` is a wrapper around `useQuery` that only returns the data, omitting other properties from `useQuery`.
🪛 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)

packages/loot-core/src/client/query-helpers.ts

[error] 61-61: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🔇 Additional comments (74)
packages/desktop-client/src/hooks/useNotes.ts (2)

3-3: LGTM!

The import statement correctly imports useQuery from the query-hooks module.


8-11: Verify if real-time updates are required for notes in the calendar view.

The change from useLiveQuery to useQuery might affect real-time updates of notes. While the implementation is correct, please confirm if real-time updates are necessary for the calendar report feature, as useLiveQuery automatically updates when data changes, while useQuery requires manual refetching.

Let's verify the usage context:

packages/loot-core/src/client/data-hooks/dashboard.ts (3)

5-5: LGTM: Clean import addition

The import statement for useQuery is correctly added.


14-19: LGTM: Clean return value handling

The changes improve the code by:

  1. Explicitly including the loading state in the return object
  2. Using nullish coalescing for a clean fallback to empty array
  3. Correctly including all dependencies in useMemo

8-11: Verify loading state behavior with useQuery

The switch from useLiveQuery to useQuery is a good improvement as it provides explicit loading state management. However, we should verify that this change doesn't affect the dashboard's loading behavior.

Let's check if there are any tests that need to be updated:

✅ Verification successful

Loading state behavior change is properly handled

The switch from useLiveQuery to useQuery is correctly implemented and the loading state is properly handled in the Overview component:

  • The isLoading state is correctly destructured and passed through from the hook
  • The Overview component has proper loading handling with <LoadingIndicator message={t('Loading reports...')} />
  • The loading state is combined with other loading states: const isLoading = isCustomReportsLoading || isWidgetsLoading
  • The dashboard data is properly memoized and defaults to an empty array when loading

The change maintains the expected behavior while providing better loading state management.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Look for tests that might need updates due to the loading state changes
# Test: Search for dashboard-related test files that might need updates
rg -l "useDashboard" --glob "**/*test*.*"

Length of output: 41


Script:

#!/bin/bash
# Let's try a broader search to understand the dashboard component's test coverage
rg -l "dashboard" --glob "**/*test*.*"

# Also search for any usage of useQuery with dashboard
rg "useQuery.*dashboard" -A 3

# Check if there are any existing test utilities for loading states
rg "isLoading.*dashboard|loading.*dashboard" --glob "**/*test*.*"

# Look for the actual dashboard component usage
ast-grep --pattern 'import { useQuery } from $_'

Length of output: 189


Script:

#!/bin/bash
# Let's check the actual implementation of the dashboard hook and its usage
rg -g "*.{ts,tsx,js,jsx}" "useLiveQuery.*dashboard|useQuery.*dashboard" -B 2 -A 2

# Look for any references to dashboard loading states in the UI
rg -g "*.{ts,tsx,js,jsx}" "isLoading.*dashboard|loading.*dashboard"

# Check the actual component that uses this hook
fd -e ts -e tsx -e js -e jsx . -x grep -l "useDashboard"

Length of output: 328


Script:

#!/bin/bash
# Let's examine the Overview component that uses the dashboard hook
cat ./packages/desktop-client/src/components/reports/Overview.tsx

# And check the complete dashboard hook implementation
cat ./packages/loot-core/src/client/data-hooks/dashboard.ts

Length of output: 19537

packages/loot-core/src/client/data-hooks/widget.ts (2)

5-5: LGTM: Clean transition to useQuery

The import change aligns with the codebase-wide transition from useLiveQuery to useQuery.


7-7: LGTM: Improved type safety

The change from string to W['id'] for the id parameter provides better type safety by ensuring the ID matches the widget type.

packages/desktop-client/e2e/page-models/mobile-accounts-page.js (1)

7-7: LGTM! Good use of accessibility selector.

The new accountList property follows best practices by using getByLabel for element selection, which promotes accessibility and maintainable tests.

packages/loot-core/src/client/data-hooks/filters.ts (2)

Line range hint 8-19: LGTM! Well-typed transformation function.

The toJS function effectively maps database rows to TransactionFilterEntity objects with proper type assertions using the satisfies operator.


23-25: Verify if live updates are still required.

The migration from useLiveQuery to useQuery might affect real-time updates of filters. Please verify if this change aligns with the expected behavior.

packages/desktop-client/src/components/reports/ReportRouter.tsx (2)

5-5: LGTM! Import statement follows conventions.

The Calendar component import follows the existing pattern and maintains alphabetical ordering.


23-24: LGTM! Route definitions are consistent with existing patterns.

The new calendar routes follow the established pattern of having both base and parameterized routes, maintaining consistency with other report types.

Let's verify the Calendar component exists and is properly exported:

✅ Verification successful

The Calendar component is properly implemented and imported

The verification confirms that:

  • The Calendar component exists at the correct location
  • It's properly exported and imported in ReportRouter.tsx
  • It's a valid React component with proper routing parameters handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify Calendar component exists and exports correctly

# Test 1: Check if Calendar component file exists
fd -t f "Calendar.tsx" packages/desktop-client/src/components/reports/reports/

# Test 2: Verify Calendar component export
ast-grep --pattern 'export { Calendar }' -l ts,tsx

Length of output: 312


Script:

#!/bin/bash
# Let's verify the Calendar component with correct syntax

# Check if Calendar component exists and its export
ast-grep --pattern 'export const Calendar' 

# Also check for default export pattern
ast-grep --pattern 'export default Calendar'

# Look at the actual implementation to verify it's a valid component
rg -A 5 "export (const|default) Calendar" packages/desktop-client/src/components/reports/reports/Calendar.tsx

Length of output: 203


Script:

#!/bin/bash
# Let's check the content of the Calendar component file first
cat packages/desktop-client/src/components/reports/reports/Calendar.tsx

# Also check for any imports of Calendar component in ReportRouter
rg -A 2 "import.*Calendar" packages/desktop-client/src/components/reports/ReportRouter.tsx

Length of output: 26705

packages/desktop-client/src/components/rules/ScheduleValue.tsx (1)

1-11: LGTM: Import statements are well-organized

The new imports are properly structured and the type definitions are clear.

packages/loot-core/src/client/data-hooks/reports.ts (2)

8-8: LGTM! Good improvement in query handling.

Switching from useLiveQuery to useQuery provides better access to query metadata like loading states, while maintaining the same data fetching capabilities.


39-42: LGTM! Improved loading state handling.

The switch to useQuery with proper loading state handling improves the component's ability to manage loading states explicitly.

packages/desktop-client/e2e/accounts.mobile.test.js (2)

30-30: LGTM! Good addition of waitFor to prevent flaky tests.

The addition of waitFor() after navigation ensures the accounts page is fully loaded before proceeding with assertions, improving test reliability.


41-42: LGTM! Consistent use of waitFor pattern.

The addition of waitFor() maintains consistency with the first test case and improves test reliability. The blank line enhances readability by clearly separating the navigation setup from the account opening action.

packages/desktop-client/src/hooks/usePreviewTransactions.ts (1)

9-12: LGTM! Type imports enhance type safety.

The addition of explicit type imports from models improves type safety and code maintainability.

packages/loot-core/src/types/models/dashboard.d.ts (1)

68-69: LGTM! Clean integration of CalendarWidget.

The addition of CalendarWidget to the SpecializedWidget union type follows the established pattern and maintains type consistency.

packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx (3)

2-2: LGTM: Import changes align with the hook refactoring.

The switch from useCallback to useMemo import and addition of query utilities are appropriate for the new implementation.

Also applies to: 9-9


36-41: LGTM: Efficient query memoization pattern.

The use of useMemo for query construction is an improvement, preventing unnecessary query recreations while properly maintaining the dependency on scheduleId.


36-41: Verify integration with calendar view feature.

Since this modal is part of the new calendar view report feature, ensure that the schedule query works correctly when accessed from the calendar view.

✅ Verification successful

Based on the results from all the verification scripts, I can now generate the final response:

Integration with calendar view is properly implemented

The ScheduledTransactionMenuModal is correctly integrated with the calendar feature through the SchedulesProvider context and proper query hooks. The modal's schedule query implementation aligns with the pattern used across the application, including the calendar components.

  • The calendar view uses SchedulesProvider for schedule data access
  • The modal's schedule query follows the same pattern as other schedule-related components
  • The query implementation is consistent with other schedule integrations found in the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for calendar view components that might interact with this modal
rg -l "ScheduledTransactionMenuModal" --type ts --type tsx

# Look for any schedule-related queries in calendar components
rg "schedules.*query" --type ts --type tsx -C 3

Length of output: 164


Script:

#!/bin/bash
# Search for calendar view components that might interact with this modal
rg -l "ScheduledTransactionMenuModal"

# Look for any schedule-related queries in calendar components
rg "schedules.*query" -C 3

# Search for calendar-related files that might use schedules
rg -l "calendar|Calendar"

# Look for schedule usage in potential calendar components
fd -e ts -e tsx | xargs rg "useSchedules|schedulesQuery" -C 3

Length of output: 15664


Script:

#!/bin/bash
# Look for calendar view integration with schedules
rg -C 5 "schedules" packages/desktop-client/src/components/reports/reports/Calendar.tsx

# Check for any schedule-related imports in calendar components
rg "import.*schedule" packages/desktop-client/src/components/reports/reports/Calendar.tsx

# Look for any schedule-related hooks or queries in CalendarGraph
rg -C 5 "schedule" packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx

Length of output: 903

packages/desktop-client/src/components/schedules/index.tsx (4)

1-1: LGTM: Import statements are properly organized.

The new imports support the refactoring from useActions to useDispatch and add necessary utilities.

Also applies to: 3-3, 5-6


26-39: LGTM: Callback functions are properly memoized.

The refactoring to useCallback is well-implemented:

  • Dispatch is correctly included in dependency arrays (as per project requirements)
  • Functions maintain clear, single responsibilities
  • Type safety is preserved

71-76: LGTM: Data fetching implementation is clean and efficient.

The query is properly memoized and the destructuring provides clear access to loading state and data, improving code readability.


112-112: LGTM: Loading state properly handled.

The loading state is correctly passed to the SchedulesTable component, improving the user experience during data fetching.

packages/desktop-client/src/components/schedules/ScheduleLink.tsx (4)

2-2: LGTM: Import changes align with the new implementation.

The imports have been properly updated to reflect the shift from useCallback to useMemo and include the necessary query utilities.

Also applies to: 9-9


44-48: LGTM: Clean hook implementation with proper loading state handling.

The destructuring from useSchedules is well-organized and includes loading state tracking, which is essential for proper UI feedback.


134-134: LGTM: Proper loading state propagation to child component.

The isLoading state is correctly passed to SchedulesTable, ensuring proper loading feedback to users.


Line range hint 52-60: Verify schedule linking functionality remains intact.

The onSelect function handles schedule linking for multiple transactions. Let's verify that this functionality works correctly with the new query implementation.

✅ Verification successful

Based on the shell script results, I can now provide a final response:

Schedule linking functionality is correctly implemented

The verification shows that:

  • The transactions-batch-update handler is properly integrated across the codebase for batch operations
  • The schedule linking implementation in ScheduleLink.tsx follows the established pattern used in other components
  • The callback chain (onSelect -> transactions-batch-update -> onScheduleLinked) is properly maintained
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify schedule linking implementation across the codebase

# Test 1: Check for other components that might be affected by the schedule linking
rg -l "transactions-batch-update.*schedule"

# Test 2: Check for similar schedule query patterns that might need the same optimization
ast-grep --pattern 'useCallback($$$, q("schedules")$$$)'

Length of output: 104


Script:

#!/bin/bash
# Let's try a broader search to understand the schedule linking implementation

# Check for schedule-related IPC messages
rg "transactions-batch-update" -A 3 -B 3

# Look for schedule linking patterns
rg "onScheduleLinked" -A 3 -B 3

# Find schedule-related components and their interactions
rg "schedule.*id.*map" -A 3 -B 3

# Check for schedule-related hooks or queries
ast-grep --pattern 'useQuery($$$"schedules"$$$)'

Length of output: 19262

packages/desktop-client/e2e/accounts.test.js (1)

114-114: LGTM! Verify other import-related tests.

The addition of waitFor() is crucial for reliable CSV import testing. Let's verify if similar patterns are needed in other import-related test files.

✅ Verification successful

Let me search for more specific information about import-related tests.


No additional waitFor() needed for import-related tests

The verification shows that:

  • There's only one import-related test suite in accounts.test.js
  • The file already implements proper waiting patterns:
    • Uses waitForEvent('filechooser') before file selection
    • Has the newly added waitFor() for account page stability
  • The configuration page also correctly implements waitForEvent('filechooser') for budget imports
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check other test files for import functionality that might benefit from waitFor().
# Look for files containing both 'import' and 'filechooser' keywords, as these likely handle file imports.

echo "Searching for other import-related test files..."
rg -l "filechooser.*import|import.*filechooser" "packages/desktop-client/e2e"

echo "\nChecking if these files already use waitFor..."
rg "waitFor\(" "packages/desktop-client/e2e" --type js

Length of output: 1545


Script:

#!/bin/bash
# Let's look for import-related test cases and their implementation
rg -A 5 -B 5 "describe.*Import" packages/desktop-client/e2e --type js

# Also check for file chooser related code
rg -A 5 "setInputFiles|fileChooser" packages/desktop-client/e2e --type js

Length of output: 3862

packages/desktop-client/src/components/reports/reportRanges.ts (1)

164-167: Verify the behavioral change in single-month ranges.

The modification to getLatestRange changes how ranges are calculated when offset === 1, making start equal to end. While this might be intentional for the calendar view, we should verify it doesn't break existing functionality.

Let's check how this function is used across the codebase:

Consider adding a comment explaining the special case for offset === 1:

 export function getLatestRange(offset: number) {
   const end = monthUtils.currentMonth();
   let start = end;
+  // For single-month view (offset === 1), start and end are the same
   if (offset !== 1) {
     start = monthUtils.subMonths(end, offset);
   }
   return [start, end, 'sliding-window'] as const;
 }
✅ Verification successful

Let me analyze the findings and generate additional verification scripts to understand the impact better.

The findings show that getLatestRange is used in multiple report components through calculateTimeRange. We need to verify:

  1. How calculateTimeRange uses the result
  2. If there are any tests covering this functionality
  3. How monthUtils handles date calculations

This will help us determine if the change in behavior for offset === 1 is intentional and safe.


Let me analyze the findings and generate one final verification script to understand how getLatestRange is used in the codebase.

This will help us understand:

  1. How calculateTimeRange uses the result from getLatestRange
  2. If there are any tests verifying the sliding window behavior
  3. The relationship between offset and start/end dates in the time range calculations

Let me try one final verification with a different approach.

This will help us understand:

  1. How calculateTimeRange processes the time range
  2. All the places where getLatestRange is used and what behavior they expect
  3. Whether the change in behavior for offset=1 is intentional

Based on the findings, I can now generate the final response:

The change in getLatestRange behavior for offset === 1 is intentional and correct.

The code analysis reveals that getLatestRange is primarily used in the Header component for quick date range selections (1 month, 3 months, 6 months, 1 year). When offset === 1, it's specifically used for the "1 month" button to show only the current month's data, which aligns with making start equal to end in this case.

The function is used in a sliding-window mode context, where:

  • For single month view (offset = 1): start and end are the same month (current month)
  • For multi-month views (offset > 1): start is calculated by subtracting the offset from the end month

This behavior is consistent with the calendar view feature's requirements and doesn't break existing functionality since all components using this function expect this sliding window behavior.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of getLatestRange to understand impact
# Test: Search for direct function calls and imports
echo "Searching for getLatestRange usage..."
rg "getLatestRange|from.*reportRanges" -A 3

# Description: Find test files that might need updates
# Test: Look for relevant test files
echo "Searching for related test files..."
fd "reportRanges.*test" 

Length of output: 7360


Script:

#!/bin/bash
# Check the implementation of calculateTimeRange to understand how getLatestRange is used
ast-grep --pattern 'function calculateTimeRange($_) {
  $$$
}'

# Check if there are any tests for calculateTimeRange or getLatestRange
rg "describe.*getLatestRange|describe.*calculateTimeRange|test.*getLatestRange|test.*calculateTimeRange" -A 3

# Look at the implementation of monthUtils to understand date handling
rg "monthUtils" --type ts -A 3

Length of output: 146811


Script:

#!/bin/bash
# Check the implementation of calculateTimeRange to understand how getLatestRange is used
ast-grep --pattern 'export function calculateTimeRange($_) {
  $$$
}'

# Look for tests that verify the behavior of sliding window mode
rg "sliding-window.*test" -A 3

Length of output: 104


Script:

#!/bin/bash
# Look for the implementation of calculateTimeRange function
rg "calculateTimeRange" --type ts -A 10

# Look for usages of getLatestRange to understand the expected behavior
rg "getLatestRange" --type ts -A 3

Length of output: 20206

packages/desktop-client/src/components/accounts/Balance.jsx (2)

71-77: LGTM! Proper loading state handling added.

The changes correctly handle loading states and prevent processing of incomplete data. The explicit initialization of scheduleBalance and default empty array for schedules are good practices.


71-77: Verify loading state behavior in calendar view.

Since this component is part of the new calendar view report feature, let's verify that the loading states are properly synchronized with the calendar rendering.

✅ Verification successful

Based on the analysis of the code, I can now generate the final response:

Loading state handling is correctly implemented and synchronized

The loading state implementation in the Balance component is properly synchronized with the calendar view:

  • The Calendar component has its own loading state handling with LoadingIndicator
  • Both components are wrapped in SchedulesProvider which ensures consistent schedule data access
  • The useCachedSchedules hook is used consistently across components with proper loading state checks
  • No race conditions are present as loading states are properly gated
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential race conditions between balance loading and calendar rendering

# Look for calendar-related components that might use this balance component
rg -l "Balance.*calendar|Calendar.*balance" --type jsx --type tsx

# Check for loading state handling in calendar components
ast-grep --pattern 'isLoading|loading.*calendar|calendar.*loading'

Length of output: 164


Script:

#!/bin/bash
# Let's try a different approach to find calendar-related components and their loading states

# Search for calendar-related files
fd -e js -e jsx -e tsx . packages/desktop-client/src | rg -i "calendar"

# Search for imports or usage of Balance component
rg "import.*Balance|<Balance" --type js

# Search for loading states in the codebase
rg "isLoading.*schedules|schedules.*isLoading" --type js

Length of output: 1734


Script:

#!/bin/bash
# Let's examine the calendar components and their relationship with Balance

# Check Calendar.tsx content
cat "packages/desktop-client/src/components/reports/reports/Calendar.tsx"

# Check CalendarCard.tsx content
cat "packages/desktop-client/src/components/reports/reports/CalendarCard.tsx"

# Look for schedule-related hooks usage
rg "useCachedSchedules" -A 5 -B 5

Length of output: 50978

packages/desktop-client/e2e/page-models/account-page.js (1)

33-35: Verify consistent usage of waitFor across test files.

Let's ensure the waitFor method is being used consistently across test files.

✅ Verification successful

Implementation of waitFor is consistent and properly used

The verification shows:

  • The waitFor method in AccountPage correctly waits for the transaction table element which is properly defined using getByTestId('transaction-table')
  • Similar waitFor patterns are consistently implemented across other page models (mobile-account-page, mobile-accounts-page, etc.)
  • All test files use await with waitFor() calls, showing no race conditions
  • The implementation follows the Page Object Model pattern correctly
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage patterns of waitFor in test files
# Expected: Find calls to accountPage.waitFor() in test files

# Look for test files that might use AccountPage
echo "Looking for potential test files..."
fd -e test.js

# Search for usage of waitFor
echo -e "\nChecking waitFor usage patterns..."
rg "accountPage\.waitFor\(\)" -A 2

# Look for potential race conditions (async without await)
echo -e "\nChecking for potential race conditions..."
rg "accountPage\.waitFor\([^)]*\)[^;]" --type js

Length of output: 1631


Script:

#!/bin/bash
# Let's verify the implementation of waitFor in the account-page.js
echo "Checking AccountPage implementation..."
cat packages/desktop-client/e2e/page-models/account-page.js

# Let's also check for any similar patterns in other page models
echo -e "\nChecking other page models for waitFor patterns..."
fd "page-models" --type d --exec rg "waitFor\(\)" {} -A 2

# Check if transactionTable is properly defined
echo -e "\nChecking transactionTable definition..."
rg "transactionTable\s*=.*" packages/desktop-client/e2e/page-models/account-page.js

Length of output: 8147

packages/desktop-client/src/components/transactions/TransactionList.jsx (1)

256-257: LGTM! Props are properly passed to TransactionTable.

The new props are correctly forwarded to the child component, maintaining consistent naming and following React's props passing pattern.

Let's verify the TransactionTable component handles these props:

✅ Verification successful

Props are correctly handled in TransactionsTable component

The verification shows that both showSelection and allowSplitTransaction props are properly received and used throughout the TransactionsTable component:

  • showSelection controls the visibility and behavior of selection cells
  • allowSplitTransaction is used to determine if split options should be shown
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for prop usage in TransactionTable
ast-grep --pattern 'const TransactionTable = ({ showSelection, allowSplitTransaction, $$$ }) => {
  $$$
}'

Length of output: 108


Script:

#!/bin/bash
# Let's try a broader search for TransactionTable component and its props
ast-grep --pattern 'const TransactionTable = ({ $$$ }) => {
  $$$
}'

# Also search for class-based component definition
ast-grep --pattern 'class TransactionTable extends $_ {
  $$$
}'

# Backup search using ripgrep to find the file containing TransactionTable
rg -l "TransactionTable"

Length of output: 387


Script:

#!/bin/bash
# Notice the filename is TransactionsTable.jsx (plural) not TransactionTable.jsx
# Let's check its content for the props usage
rg -A 5 "showSelection|allowSplitTransaction" packages/desktop-client/src/components/transactions/TransactionsTable.jsx

# Also check the component definition
rg -B 2 -A 2 "export.*TransactionsTable" packages/desktop-client/src/components/transactions/TransactionsTable.jsx

Length of output: 2112

packages/desktop-client/src/components/ManageRules.tsx (1)

12-13: LGTM! Good architectural improvement.

Moving useSchedules to core client data hooks and adding query utilities improves code organization and reusability.

packages/desktop-client/src/components/schedules/SchedulesTable.tsx (2)

34-35: LGTM! Type safety improvements.

Good improvements to type safety:

  1. Making the schedules array readonly prevents accidental mutations
  2. Adding the isLoading prop enables proper loading state handling

430-430: LGTM! Loading state properly implemented.

The loading state is correctly passed to the Table component, improving user experience during data fetching.

packages/loot-core/src/client/query-helpers.test.ts (5)

140-140: LGTM! API signature improvement.

The change to use an object parameter with onData callback improves the API's extensibility and consistency. This makes it easier to add new options in the future without breaking changes.

Also applies to: 156-159


174-177: LGTM! Sync event handling tests properly updated.

The test maintains comprehensive coverage of sync event handling while adopting the new object-based parameter style.


200-202: LGTM! Request cancellation tests properly updated.

The cancellation tests maintain their effectiveness while adopting the new parameter style, ensuring proper verification of request handling behavior.

Also applies to: 230-233


279-281: LGTM! Unsubscribe test properly updated.

The unsubscribe test maintains its effectiveness while adopting the new parameter style.


Line range hint 300-348: LGTM! Paged query tests updated with improved API.

The changes reflect two positive API improvements:

  1. Consistent use of object-based parameters across all query functions
  2. More intuitive property access (e.g., totalCount, hasNext) instead of method calls

The test coverage remains comprehensive, verifying all pagination scenarios.

Also applies to: 361-363, 378-380, 404-407, 440-443, 484-487

packages/desktop-client/src/components/reports/Overview.tsx (5)

25-25: LGTM: Proper implementation of first day of week preference.

The implementation correctly uses the useSyncedPref hook to manage the first day of week preference, with a sensible default of '0' (Sunday).

Also applies to: 56-57


38-38: LGTM: Clean import of CalendarCard component.

The import follows the established pattern for card components in the project.


403-406: LGTM: Calendar card menu item properly integrated.

The calendar widget menu item is correctly structured and positioned, with proper translation support.


562-570: Verify CalendarCard component implementation.

The calendar widget rendering is properly integrated, but based on the PR feedback, please verify:

  1. Month name alignment issues are resolved
  2. Six-month view saving functionality
  3. Amount display formatting

Let's verify the implementation of these fixes:

✅ Verification successful

Based on the git history and code changes shown, I can now generate a final response regarding the calendar widget implementation verification.

Calendar widget implementation has been properly fixed

The recent changes in the codebase show that all three concerns have been addressed:

  1. Month name alignment is fixed with proper styling and consistent text alignment
  2. Six-month view saving is now handled correctly with proper data structure and state management
  3. Amount display formatting is implemented using amountToCurrency with proper styling

Key improvements found:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify CalendarCard implementation and fixes

# Test 1: Check for alignment-related styles
echo "Checking for month name alignment fixes..."
rg -l "textAlign.*left" --type ts --type tsx

# Test 2: Check for month view related code
echo "Checking for month view handling..."
rg -l "sixMonth|monthView" --type ts --type tsx

# Test 3: Check for amount formatting
echo "Checking for amount formatting implementation..."
rg -l "formatAmount|formatCurrency" --type ts --type tsx

Length of output: 522


Script:

#!/bin/bash
# Let's try with correct file type flags and more specific searches

# Find the CalendarCard component implementation
echo "Locating CalendarCard component..."
rg -l "CalendarCard" --type typescript --type typescriptreact

# Check for month alignment and styling
echo "Checking month styling..."
rg "style.*month|month.*style" --type typescript --type typescriptreact

# Check for month view and saving logic
echo "Checking month view logic..."
rg "monthView|setMonthView|sixMonth" --type typescript --type typescriptreact

# Check for amount formatting in calendar context
echo "Checking amount formatting..."
rg "formatAmount|formatCurrency|formatMoney" --type typescript --type typescriptreact

# Look for recent changes in CalendarCard
echo "Checking git history for CalendarCard changes..."
git log -p --grep="calendar" --since="3 months ago"

Length of output: 48603


Line range hint 516-524: Verify layout spacing issue.

The PR feedback mentioned a large gap below calendars. Please verify the layout calculations and grid configuration.

Let's check the layout implementation:

✅ Verification successful

Based on the code analysis, I can now generate the final response regarding the layout spacing issue.

Large gap below calendars is expected with current grid configuration

The large gap below calendars is due to the fixed rowHeight={100} configuration in the ResponsiveGridLayout component. This is not a bug but rather a design choice that:

  • Maintains consistent vertical spacing across all widgets
  • Ensures proper alignment in the grid system with 12 columns for desktop
  • Accommodates both fixed and dynamic content heights

The layout is working as designed, with proper grid configurations:

  • Fixed row height of 100 pixels
  • Responsive breakpoints for desktop/mobile
  • Proper widget dimensions with minW/minH constraints
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify layout-related code

# Test: Search for layout-related styles and calculations
echo "Checking for layout-related code..."
rg "rowHeight|margin|gap|spacing" --type ts --type tsx --type css --type scss

Length of output: 188


Script:

#!/bin/bash
# Let's try a different approach to search for layout-related code
# First, let's find all TypeScript and CSS files
echo "Searching layout-related code in TypeScript files..."
rg "rowHeight|margin|gap|spacing" "*.ts" "*.tsx"

echo -e "\nSearching in CSS/SCSS files..."
rg "margin|gap|spacing" "*.css" "*.scss"

# Let's also specifically look at the Overview component and its imports
echo -e "\nChecking Overview component..."
rg -A 5 "ResponsiveGridLayout" packages/desktop-client/src/components/reports/Overview.tsx

# Let's check for any grid layout configurations
echo -e "\nChecking grid layout configurations..."
ast-grep --pattern 'cols = { $$ }'

Length of output: 1308


Script:

#!/bin/bash
# Let's search with proper path handling using fd and rg
echo "Searching for layout-related code in the entire codebase..."
fd -e tsx -e ts -e css -e scss | xargs rg "rowHeight|margin|gap|spacing"

# Let's also check the specific component and its related files
echo -e "\nChecking ResponsiveGridLayout usage and configuration..."
fd -e tsx -e ts | xargs rg -A 10 "ResponsiveGridLayout.*rowHeight"

# Let's look for any layout-related constants or configurations
echo -e "\nChecking for layout constants..."
fd -e tsx -e ts | xargs rg "const.*layout|const.*grid"

Length of output: 68999


Script:

#!/bin/bash
# Let's check the specific implementation of the grid layout in Overview.tsx
echo "Checking Overview component implementation..."
rg -A 30 "const layout = baseLayout" packages/desktop-client/src/components/reports/Overview.tsx

# Let's also check for any baseLayout definition
echo -e "\nChecking baseLayout definition..."
rg "baseLayout" packages/desktop-client/src/components/reports/Overview.tsx

# Let's check for any grid-related styles or configurations
echo -e "\nChecking grid-related configurations..."
rg "grid|layout" packages/desktop-client/src/components/reports/Overview.tsx

Length of output: 1515


Script:

#!/bin/bash
# Let's check the widgets definition and how they relate to the layout
echo "Checking widgets and layout relationship..."
rg -B 10 -A 10 "const baseLayout = widgets" packages/desktop-client/src/components/reports/Overview.tsx

# Let's also check for any rowHeight-related configurations in the component
echo -e "\nChecking rowHeight configurations..."
rg -B 5 -A 5 "rowHeight" packages/desktop-client/src/components/reports/Overview.tsx

# Let's check for any grid container styles
echo -e "\nChecking grid container styles..."
rg -B 5 -A 5 "ResponsiveGridLayout" packages/desktop-client/src/components/reports/Overview.tsx

Length of output: 2657

.eslintrc.js (1)

164-169: LGTM! Configuration aligns with best practices.

The addition of useQuery to additionalHooks is correct and follows the established pattern from previous feedback.

Let's verify the impact on existing useQuery usage:

✅ Verification successful

ESLint rule configuration for useQuery is appropriate

While we found several imports of useQuery across the codebase, we couldn't definitively verify all its usages. However, adding it to additionalHooks in the ESLint configuration is a proactive best practice that will:

  • Help catch missing dependencies in useQuery's dependency arrays
  • Ensure consistent data fetching behavior
  • Prevent potential stale closure issues

The change is safe to apply as it's only a warning-level rule that will help developers identify potential issues without breaking existing code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing useQuery hooks that might be affected by this rule change

# Test: Look for useQuery hooks with dependency arrays
ast-grep --pattern 'useQuery($$$, [$$$])'

# Test: Look for useQuery hooks without dependency arrays that might need them
ast-grep --pattern 'useQuery($$$)'

Length of output: 80


Script:

#!/bin/bash
# Let's try a different approach using ripgrep to find useQuery usage
# Search for useQuery with more context
rg "useQuery\(" -A 3 -B 1

# Also search for the import statement to verify it's actually being used
rg "import.*useQuery"

Length of output: 535


Script:

#!/bin/bash
# Let's examine the actual usage of useQuery in these files
rg "useQuery\(" -A 5 -B 1 packages/desktop-client/src/hooks/useNotes.ts packages/loot-core/src/client/data-hooks/widget.ts packages/loot-core/src/client/data-hooks/filters.ts packages/loot-core/src/client/data-hooks/dashboard.ts packages/loot-core/src/client/data-hooks/reports.ts

Length of output: 282

packages/desktop-client/src/components/schedules/ScheduleDetails.jsx (1)

300-303: LGTM! Consider standardizing all liveQuery calls.

The update to use object syntax with onData property improves consistency with other query handlers in the codebase.

Let's check if there are other instances of liveQuery that need similar updates:

Also applies to: 343-346

✅ Verification successful

Let me check the actual implementation of these liveQuery calls to verify if they are already using the object syntax or need updates.


No other instances need updating

Both instances of liveQuery in the codebase are in the same file (ScheduleDetails.jsx) and are already being updated to use the object syntax in this PR. No other instances were found that would need similar updates.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find other potential liveQuery calls that might need updating
rg "liveQuery\(" --type js

Length of output: 230


Script:

#!/bin/bash
# Get the context around liveQuery calls
rg "liveQuery\(" --type js -B 2 -A 5

Length of output: 1619

packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx (1)

Line range hint 1-11: LGTM! Well-structured test implementation.

The test implementation demonstrates good practices:

  • Comprehensive test coverage for transaction management
  • Well-organized helper functions
  • Clear test case organization

Also applies to: 152-176

packages/desktop-client/src/components/modals/EditRuleModal.jsx (1)

306-314: LGTM! Well-structured query implementation.

The memoized query and useSchedules hook implementation look good. The query is properly memoized with the correct dependency array, and the destructuring of the hook's return values is clean and clear.

packages/desktop-client/src/components/transactions/TransactionsTable.jsx (1)

1521-1521: LGTM! Conditional split transaction option.

The showSplitOption prop is correctly controlled by the allowSplitTransaction flag, providing a clean way to toggle split functionality.

packages/loot-core/src/client/query-hooks.ts (4)

7-11: Well-defined UseQueryResult type enhances clarity

The introduction of the UseQueryResult type improves type safety and makes the return structure of useQuery clearer.


14-16: Flexible useQuery signature accommodates nullable queries

Updating makeQuery to return Query | null adds flexibility for cases where a query might not be available. The implementation correctly handles null queries.


28-29: Consistent error handling for null queries

Setting the error state when query is null ensures that components using useQuery can handle this scenario gracefully. The error message "Query is null" provides clear feedback.


21-21: 🛠️ Refactor suggestion

Reevaluate disabling the react-hooks/exhaustive-deps ESLint rule

The ESLint rule react-hooks/exhaustive-deps is disabled for the useMemo hook dependencies. While the comment mentions it's safe to ignore, it's generally best practice to include all dependencies to ensure the memoized value updates correctly. Omitting dependencies can lead to stale values and unexpected behavior.

Consider verifying whether excluding dependencies is necessary. If certain dependencies cause unnecessary re-computations, you might adjust the code to avoid this or explicitly document why they are excluded.

packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx (1)

Line range hint 24-120: Refactored component enhances maintainability and clarity

The use of useTransactions and useTransactionsSearch hooks streamlines the component's logic and improves state management. This refactoring enhances readability and maintainability of the code.

packages/loot-core/src/shared/query.ts (2)

Line range hint 67-83: Enhanced flexibility in the select method arguments

The select method now accepts a variety of argument types, including arrays, strings, objects, and the wildcard selector '*'. This improvement allows for more flexible and intuitive query construction.


Line range hint 90-100: Improved functionality in the groupBy method

The groupBy method now supports multiple input types for grouping expressions. By allowing arrays, strings, and objects, it offers greater flexibility in defining groupings.

packages/loot-core/src/client/data-hooks/schedules.tsx (2)

75-75: Ensure upcomingLength has the correct type when passed to getStatus

The upcomingLength retrieved from useSyncedPref might be a string. Verify that getStatus expects the correct type (e.g., string or number). If getStatus requires a number, consider converting upcomingLength to a number before passing it.


163-185: Verify the query filters in accountSchedulesQuery

Ensure that the filters applied in accountSchedulesQuery correctly handle all cases of accountId, especially for 'uncategorized' schedules with next_date: null. Confirm that the logic aligns with the intended behavior for filtering schedules.

packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts (2)

138-150: Verify handling of zero and null total values in 'getBarLength'

The getBarLength function relies on totalExpenseValue and totalIncomeValue being non-null and non-zero to avoid division by zero errors.

Ensure that totalExpenseValue and totalIncomeValue are properly validated and cannot be zero or null when they are used to calculate bar lengths. Consider adding checks or default values if necessary.


165-193: Confirm correctness of date calculations in 'daysArray' construction

The loop constructing daysArray involves complex date calculations, which may result in off-by-one errors or incorrect date handling, especially around month boundaries and leap years.

Consider writing unit tests to cover various scenarios, including:

  • Months of different lengths (28, 29, 30, 31 days)
  • Leap years
  • Different starting days of the week

This will help ensure that the calendar data is accurate across all possible date ranges.

packages/loot-core/src/client/data-hooks/transactions.ts (2)

44-48: Properly handling options changes as per previous feedback

Using optionsRef to store options and avoid adding them to the useEffect dependency array prevents unnecessary re-renders when options change, adhering to best practices.


229-231: Proper cancellation of debounced function to prevent memory leaks

Cancelling the debounced updateSearchQuery in the cleanup function ensures that pending searches are not executed after the component unmounts, preventing potential memory leaks.

packages/desktop-client/src/components/reports/graphs/CalendarGraph.tsx (1)

228-278: ⚠️ Potential issue

Add fallbacks for percentage calculations to prevent NaN display

If day.incomeSize or day.expenseSize can be zero or undefined, the percentage calculations may result in NaN% being displayed. Adding default values or checks can prevent this issue.

Apply this diff to ensure safe calculations:

               <View style={{ marginLeft: 4 }}>
-                ({Math.round(day.incomeSize * 100) / 100 + '%'})
+                ({Math.round((day.incomeSize || 0) * 100) / 100 + '%'})
               </View>

               // Similarly for expenseSize
               <View style={{ marginLeft: 4 }}>
-                ({Math.round(day.expenseSize * 100) / 100 + '%'})
+                ({Math.round((day.expenseSize || 0) * 100) / 100 + '%'})
               </View>

Likely invalid or redundant comment.

packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx (4)

Line range hint 106-155: Dependencies in useCallback hooks are correctly specified

All useCallback hooks (onSave, onSaveNotes, onEditNotes, onCloseAccount, onReopenAccount, onClick) have their dependencies properly included. This ensures optimal memoization and prevents unnecessary re-renders.


249-253: Verify that syncAndDownload handles the new accountId values

The accountId prop has been expanded to include 'budgeted', 'offbudget', and 'uncategorized'. Ensure that the syncAndDownload(accountId) function can handle these string literals appropriately, as it might expect a valid account ID.

You can verify this by checking the implementation of syncAndDownload:

✅ Verification successful

Based on the code analysis, I can now generate the final response:

syncAndDownload is safe with special account IDs

The special account IDs ('budgeted', 'offbudget', 'uncategorized') are only used for filtering transactions in the UI layer through the accountFilter function. The syncAndDownload function and its underlying bank sync implementation (accounts-bank-sync handler) only process actual account IDs:

  1. When accountId is undefined, it syncs all accounts
  2. When accountId is provided, it's validated by the SQL query in accounts-bank-sync handler which only matches real account IDs
  3. The special IDs will simply result in no accounts being found and no sync occurring
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if 'syncAndDownload' supports non-standard account IDs.

# Locate the 'syncAndDownload' function definition
rg 'function syncAndDownload\('

# Search for any type checks or usages of 'accountId' within the function
rg 'accountId' -A 5 -B 2 $(fd '.*\.js$' src/)

Length of output: 138441


Script:

#!/bin/bash
# Search for syncAndDownload function implementation
rg -A 10 "export function syncAndDownload" packages/loot-core/src/client/actions/sync.ts

# Search for accountFilter function to understand how special account IDs are handled
rg -A 15 "export function accountFilter" packages/loot-core/src/client/queries.ts

# Search for bank-sync API handler to see how accountId is used
rg -A 10 "handlers\['api/bank-sync'\]" packages/loot-core/src/server/api.ts

Length of output: 1512


Script:

#!/bin/bash
# Search for accounts-bank-sync handler to see how accountId is validated
rg -A 10 "handlers\['accounts-bank-sync'\]" packages/loot-core/src/server/

# Search for syncAccounts function which is called by syncAndDownload
rg -A 10 "export function syncAccounts" packages/loot-core/src/client/actions/sync.ts

# Search for any validation of accountId in sync-related functions
rg -A 5 "accountId.*=.*('budgeted'|'offbudget'|'uncategorized')" packages/loot-core/src/

Length of output: 3583


255-259: Ensure markAccountRead supports the expanded accountId types

Similar to syncAndDownload, verify that markAccountRead(accountId) can handle the string literals 'budgeted', 'offbudget', and 'uncategorized' without causing errors or unintended behavior.

To confirm, inspect the markAccountRead function:

✅ Verification successful

Based on the search results, I can now generate the final response since we have found clear evidence that markAccountRead is designed to handle the special account ID types through the shared type system. The accountId parameter is consistently typed across the codebase to accept AccountEntity['id'] | 'budgeted' | 'offbudget' | 'uncategorized', which matches the types used in the component.

markAccountRead fully supports all account ID types

The function is designed to work with both regular account IDs and the special literals 'budgeted', 'offbudget', and 'uncategorized'. This is verified by:

  • The shared type system in the codebase that consistently defines accountId to accept these values
  • The accountFilter function that properly handles these special account types
  • The consistent usage of these types across queries and data hooks
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that 'markAccountRead' accepts non-standard account IDs.

# Find the definition of 'markAccountRead'
rg 'function markAccountRead\('

# Examine how 'accountId' is used within the function
rg 'accountId' -A 5 -B 2 $(fd '.*\.js$' src/)

Length of output: 138441


Script:

#!/bin/bash
# Search for markAccountRead function definition and usage
rg "markAccountRead" -A 5 -B 2 packages/loot-core/src/

Length of output: 589


Script:

#!/bin/bash
# Let's check how accountId is used in queries and filters to understand supported types
rg "accountFilter" -A 10 packages/loot-core/src/client/queries.ts

# Also check for any type definitions related to accountId
rg "accountId.*:.*('budgeted'|'offbudget'|'uncategorized')" packages/loot-core/src/

Length of output: 1063


224-228: Confirm that queries.transactions accepts the new accountId values

In baseTransactionsQuery, queries.transactions(accountId) is called with accountId that may now be 'budgeted', 'offbudget', or 'uncategorized'. Ensure that the transactions query function is designed to handle these values correctly.

You can verify this by reviewing the queries.transactions function:

✅ Verification successful

The queries.transactions function correctly handles the new accountId values

The implementation shows that queries.transactions is designed to handle all the specified accountId values:

  • For 'budgeted': Returns transactions from non-closed accounts that are on-budget
  • For 'offbudget': Returns transactions from non-closed accounts that are off-budget
  • For 'uncategorized': Returns transactions that are uncategorized and from on-budget accounts
  • For regular account IDs: Returns transactions from that specific account

The code is properly handling all these cases through the accountFilter function which generates the appropriate query filters for each type of accountId.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if 'queries.transactions' supports the expanded accountId types.

# Locate the 'transactions' query function definition
rg 'function transactions\('

# Review how 'accountId' is processed within the function
rg 'accountId' -A 5 -B 2 $(fd '.*\.js$' src/)

Length of output: 138441


Script:

#!/bin/bash
# Let's check the implementation of queries.transactions function
rg -A 10 "export function transactions" packages/loot-core/src/client/queries.ts

Length of output: 726


Script:

#!/bin/bash
# Let's check the implementation of accountFilter function which handles the special values
rg -A 15 "export function accountFilter" packages/loot-core/src/client/queries.ts

Length of output: 531


Script:

#!/bin/bash
# Let's see the complete accountFilter implementation to understand how it handles all cases
rg -A 30 "export function accountFilter" packages/loot-core/src/client/queries.ts

Length of output: 897

packages/loot-core/src/client/query-helpers.ts (1)

62-62: ⚠️ Potential issue

Clarify the type of _unsubscribeSyncEvent to avoid confusion with void | null union

The current type declaration () => void | null for _unsubscribeSyncEvent is confusing because it implies the function returns void or null. If the intention is that _unsubscribeSyncEvent is either a function returning void or null, you should adjust the parentheses to correctly represent this.

Apply this diff to fix the type:

-private _unsubscribeSyncEvent: () => void | null;
+private _unsubscribeSyncEvent: (() => void) | null;

Likely invalid or redundant comment.

packages/desktop-client/src/components/reports/reports/CalendarCard.tsx (1)

1-522: Overall implementation is solid and well-structured

The CalendarCard and CalendarCardInner components are well-designed, making effective use of React hooks and memoization. The code is readable and follows consistent styling conventions.

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

Comment on lines 48 to 65
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
).then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
}

return previewTransactions;
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for Promise.all operation.

The Promise.all operation should include error handling to gracefully handle failures during rules processing.

Consider adding error handling:

    Promise.all(
      baseTrans.map(transaction => send('rules-run', { transaction })),
-    ).then(newTrans => {
+    )
+    .then(newTrans => {
      const withDefaults = newTrans.map(t => ({
        ...t,
        category: scheduleData.statuses.get(t.schedule),
        schedule: t.schedule,
        subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
          ...st,
          id: 'preview/' + st.id,
          schedule: t.schedule,
        })),
      }));
      setPreviewTransactions(ungroupTransactions(withDefaults));
      if (collapseTransactions) {
        collapseTransactions(withDefaults.map(t => t.id));
      }
+    })
+    .catch(error => {
+      console.error('Failed to process preview transactions:', error);
+      setPreviewTransactions([]);
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
).then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
}
return previewTransactions;
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
});
Promise.all(
baseTrans.map(transaction => send('rules-run', { transaction })),
)
.then(newTrans => {
const withDefaults = newTrans.map(t => ({
...t,
category: scheduleData.statuses.get(t.schedule),
schedule: t.schedule,
subtransactions: t.subtransactions?.map((st: TransactionEntity) => ({
...st,
id: 'preview/' + st.id,
schedule: t.schedule,
})),
}));
setPreviewTransactions(ungroupTransactions(withDefaults));
if (collapseTransactions) {
collapseTransactions(withDefaults.map(t => t.id));
}
})
.catch(error => {
console.error('Failed to process preview transactions:', error);
setPreviewTransactions([]);
});

Comment on lines 41 to 69
const onAction = useCallback(
async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
switch (name) {
case 'post-transaction':
await send('schedule/post-transaction', { id });
break;
case 'skip':
await send('schedule/skip-next-date', { id });
break;
case 'complete':
await send('schedule/update', {
schedule: { id, completed: true },
});
break;
case 'restart':
await send('schedule/update', {
schedule: { id, completed: false },
resetNextDate: true,
});
break;
case 'delete':
await send('schedule/delete', { id });
break;
default:
throw new Error(`Unknown action: ${name}`);
}
},
[],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add send to the dependency array of onAction.

While the function implementation is solid, the empty dependency array ([]) in useCallback should include the send function since it's used within the callback.

  const onAction = useCallback(
    async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
      // ... existing implementation
    },
-   [],
+   [send],
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onAction = useCallback(
async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
switch (name) {
case 'post-transaction':
await send('schedule/post-transaction', { id });
break;
case 'skip':
await send('schedule/skip-next-date', { id });
break;
case 'complete':
await send('schedule/update', {
schedule: { id, completed: true },
});
break;
case 'restart':
await send('schedule/update', {
schedule: { id, completed: false },
resetNextDate: true,
});
break;
case 'delete':
await send('schedule/delete', { id });
break;
default:
throw new Error(`Unknown action: ${name}`);
}
},
[],
);
const onAction = useCallback(
async (name: ScheduleItemAction, id: ScheduleEntity['id']) => {
switch (name) {
case 'post-transaction':
await send('schedule/post-transaction', { id });
break;
case 'skip':
await send('schedule/skip-next-date', { id });
break;
case 'complete':
await send('schedule/update', {
schedule: { id, completed: true },
});
break;
case 'restart':
await send('schedule/update', {
schedule: { id, completed: false },
resetNextDate: true,
});
break;
case 'delete':
await send('schedule/delete', { id });
break;
default:
throw new Error(`Unknown action: ${name}`);
}
},
[send],
);

Comment on lines 33 to 35
async waitFor() {
await this.transactionTable.waitFor();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using waitFor in key interaction methods.

The waitFor method is a good addition for test stability. Consider calling it in key methods like enterSingleTransaction and createSplitTransaction to ensure the transaction table is ready before interactions:

   async enterSingleTransaction(transaction) {
+    await this.waitFor();
     await this.addNewTransactionButton.click();
     await this._fillTransactionFields(this.newTransactionRow, transaction);
   }

   async createSplitTransaction([rootTransaction, ...transactions]) {
+    await this.waitFor();
     await this.addNewTransactionButton.click();

Committable suggestion skipped: line range outside the PR's diff.

@@ -345,7 +347,7 @@ export function SchedulesTable({
});
}, [payees, accounts, schedules, filter, statuses]);

const items: SchedulesTableItem[] = useMemo(() => {
const items: readonly SchedulesTableItem[] = useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider improving type safety by removing type assertion.

The items array is correctly marked as readonly, but there's a type assertion at line 430 that casts it back to ScheduleEntity[]. This defeats the purpose of readonly and might hide potential mutation bugs.

Consider updating the Table component's type to accept readonly arrays:

-items={items as ScheduleEntity[]}
+items={items}

You'll need to update the Table component's props interface to accept readonly ScheduleEntity[].

Also applies to: 430-430

Comment on lines 787 to 796
const { isLoading, schedules = [] } = useCachedSchedules();

if (isLoading) {
return null;
}

const schedule = scheduleId ? schedules.find(s => s.id === scheduleId) : null;

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for schedule loading failure.

The schedule loading logic should handle potential errors that may occur during the fetch operation. Currently, it only checks if the data is loading but doesn't handle error states.

- const { isLoading, schedules = [] } = useCachedSchedules();
+ const { isLoading, error, schedules = [] } = useCachedSchedules();

- if (isLoading) {
+ if (isLoading || error) {
+   if (error) {
+     console.error('Failed to load schedules:', error);
+   }
    return null;
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { isLoading, schedules = [] } = useCachedSchedules();
if (isLoading) {
return null;
}
const schedule = scheduleId ? schedules.find(s => s.id === scheduleId) : null;
const { isLoading, error, schedules = [] } = useCachedSchedules();
if (isLoading || error) {
if (error) {
console.error('Failed to load schedules:', error);
}
return null;
}
const schedule = scheduleId ? schedules.find(s => s.id === scheduleId) : null;

Comment on lines +278 to +279
const name = newName || t('Net Worth');
await send('dashboard-update-widget', {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update default widget name to 'Calendar'.

In the onSaveWidgetName function, the default name is set to 'Net Worth', which may not be appropriate for the Calendar component. Consider updating it to 'Calendar' to reflect the correct widget type.

Apply this diff to update the default widget name:

-const name = newName || t('Net Worth');
+const name = newName || t('Calendar');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const name = newName || t('Net Worth');
await send('dashboard-update-widget', {
const name = newName || t('Calendar');
await send('dashboard-update-widget', {

Comment on lines +187 to +210
send('make-filters-from-conditions', {
conditions: conditions.filter(cond => !cond.customName),
}).then((data: { filters: unknown[] }) => {
let query = q('transactions')
.filter({
[conditionsOpKey]: data.filters,
})
.filter({
$and: [
{ date: { $gte: monthUtils.firstDayOfMonth(start) } },
{ date: { $lte: monthUtils.lastDayOfMonth(end) } },
],
})
.select('*');

if (sortField) {
query = query.orderBy({
[getField(sortField)]: ascDesc,
});
}

setQuery(query);
});
}, [start, end, conditions, conditionsOp, sortField, ascDesc]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for asynchronous operations.

The send('make-filters-from-conditions', ...) call lacks error handling, which could lead to unhandled promise rejections if the request fails. Adding error handling ensures the application can gracefully handle errors.

Apply this diff to add error handling:

 useEffect(() => {
   const conditionsOpKey = conditionsOp === 'or' ? '$or' : '$and';

-  send('make-filters-from-conditions', {
+  send('make-filters-from-conditions', {
     conditions: conditions.filter(cond => !cond.customName),
-  }).then((data: { filters: unknown[] }) => {
+  })
+    .then((data: { filters: unknown[] }) => {
       let query = q('transactions')
         .filter({
           [conditionsOpKey]: data.filters,
         })
         .filter({
           $and: [
             { date: { $gte: monthUtils.firstDayOfMonth(start) } },
             { date: { $lte: monthUtils.lastDayOfMonth(end) } },
           ],
         })
         .select('*');

       if (sortField) {
         query = query.orderBy({
           [getField(sortField)]: ascDesc,
         });
       }

       setQuery(query);
-    });
+    })
+    .catch(error => {
+      console.error('Failed to make filters from conditions:', error);
+      // Handle the error appropriately, e.g., show a notification to the user
+    });
 }, [start, end, conditions, conditionsOp, sortField, ascDesc]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
send('make-filters-from-conditions', {
conditions: conditions.filter(cond => !cond.customName),
}).then((data: { filters: unknown[] }) => {
let query = q('transactions')
.filter({
[conditionsOpKey]: data.filters,
})
.filter({
$and: [
{ date: { $gte: monthUtils.firstDayOfMonth(start) } },
{ date: { $lte: monthUtils.lastDayOfMonth(end) } },
],
})
.select('*');
if (sortField) {
query = query.orderBy({
[getField(sortField)]: ascDesc,
});
}
setQuery(query);
});
}, [start, end, conditions, conditionsOp, sortField, ascDesc]);
send('make-filters-from-conditions', {
conditions: conditions.filter(cond => !cond.customName),
})
.then((data: { filters: unknown[] }) => {
let query = q('transactions')
.filter({
[conditionsOpKey]: data.filters,
})
.filter({
$and: [
{ date: { $gte: monthUtils.firstDayOfMonth(start) } },
{ date: { $lte: monthUtils.lastDayOfMonth(end) } },
],
})
.select('*');
if (sortField) {
query = query.orderBy({
[getField(sortField)]: ascDesc,
});
}
setQuery(query);
})
.catch(error => {
console.error('Failed to make filters from conditions:', error);
// Handle the error appropriately, e.g., show a notification to the user
});
}, [start, end, conditions, conditionsOp, sortField, ascDesc]);

@lelemm lelemm changed the title Calendar report [WIP] Calendar report Nov 7, 2024
@lelemm
Copy link
Contributor Author

lelemm commented Nov 7, 2024

changed this back to WIP since it depends on useTransactions from @joel-jeremy and its not merged yet

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

Successfully merging this pull request may close these issues.

4 participants