Repository: Urigo/accounter-fullstack
Pull request #1941: Migrate ledger table into data-table
Author: login: gilgardosh
Created: 2025-03-20T20:22:37Z | Merged: 2025-03-21T12:37:40Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced the `AmountCell`, `CounterpartyCell`, `DateCell`, and enhanced the ledger table with improved column configuration, interactive sorting, and dynamic row coloring based on record status.

- **Bug Fixes**
  - Improved error handling and conditional rendering for new components to ensure graceful handling of missing data.

- **Refactor**
  - Removed legacy components for account details, general date rendering, and ledger record rows to streamline the ledger record interface.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/packages/client/src/components/charges/ledger-records/amount-cell.tsx b/packages/client/src/components/charges/ledger-records/amount-cell.tsx
new file mode 100644
index 0000000000..248b55caac
--- /dev/null
+++ b/packages/client/src/components/charges/ledger-records/amount-cell.tsx
@@ -0,0 +1,57 @@
+import { ReactElement } from 'react';
+import { Currency } from '../../../gql/graphql.js';
+
+type AmountData = {
+  foreignAmount?: {
+    formatted: string;
+    currency: string;
+  } | null;
+  localAmount?: {
+    formatted: string;
+  } | null;
+};
+
+type Props = AmountData & {
+  diff?: AmountData;
+};
+
+export const AmountCell = ({ foreignAmount, localAmount, diff }: Props): ReactElement => {
+  const isForeign = foreignAmount != null && foreignAmount.currency !== Currency.Ils;
+
+  const isLocalAmountDiff = diff && diff.localAmount?.formatted !== localAmount?.formatted;
+  const isForeignAmountDiff = diff && diff.foreignAmount?.formatted !== foreignAmount?.formatted;
+
+  return (
+    <td>
+      <div className="flex flex-col">
+        {(localAmount || isLocalAmountDiff) && (
+          <>
+            <div className="flex gap-2  items-center">
+              {isForeign && (
+                <p className={isForeignAmountDiff ? 'line-through' : ''}>
+                  {foreignAmount.formatted}
+                </p>
+              )}
+              {isForeignAmountDiff && diff.foreignAmount && (
+                <p className="border-2 border-yellow-500 rounded-md">
+                  {diff.foreignAmount.formatted}
+                </p>
+              )}
+            </div>
+
+            <div className="flex gap-2  items-center">
+              {localAmount != null && (
+                <p className={isLocalAmountDiff ? 'line-through' : ''}>{localAmount.formatted}</p>
+              )}
+              {isLocalAmountDiff && diff.localAmount && (
+                <p className="border-2 border-yellow-500 rounded-md">
+                  {diff.localAmount.formatted}
+                </p>
+              )}
+            </div>
+          </>
+        )}
+      </div>
+    </td>
+  );
+};
diff --git a/packages/client/src/components/charges/ledger-records/cells/account-details.tsx b/packages/client/src/components/charges/ledger-records/cells/account-details.tsx
deleted file mode 100644
index 707fb80e00..0000000000
--- a/packages/client/src/components/charges/ledger-records/cells/account-details.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-import { ReactElement, useCallback } from 'react';
-import { NavLink } from '@mantine/core';
-import {
-  ChargeFilter,
-  LedgerRecordsAccountDetailsFieldsFragmentDoc,
-} from '../../../../gql/graphql.js';
-import { FragmentType, getFragmentData } from '../../../../gql/index.js';
-import { useUrlQuery } from '../../../../hooks/use-url-query.js';
-
-// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
-/* GraphQL */ `
-  fragment LedgerRecordsAccountDetailsFields on LedgerRecord {
-    id
-    creditAccount1 {
-      __typename
-      id
-      name
-    }
-    creditAccount2 {
-      __typename
-      id
-      name
-    }
-    debitAccount1 {
-      __typename
-      id
-      name
-    }
-    debitAccount2 {
-      __typename
-      id
-      name
-    }
-    creditAmount1 {
-      formatted
-      currency
-    }
-    creditAmount2 {
-      formatted
-      currency
-    }
-    debitAmount1 {
-      formatted
-      currency
-    }
-    debitAmount2 {
-      formatted
-      currency
-    }
-    localCurrencyCreditAmount1 {
-      formatted
-    }
-    localCurrencyCreditAmount2 {
-      formatted
-    }
-    localCurrencyDebitAmount1 {
-      formatted
-    }
-    localCurrencyDebitAmount2 {
-      formatted
-    }
-  }
-`;
-
-type Props = {
-  data: FragmentType<typeof LedgerRecordsAccountDetailsFieldsFragmentDoc>;
-  diff?: FragmentType<typeof LedgerRecordsAccountDetailsFieldsFragmentDoc> | null;
-  cred: boolean;
-  first: boolean;
-};
-
-export const AccountDetails = ({ data, diff, cred, first }: Props): ReactElement => {
-  const { get } = useUrlQuery();
-  const {
-    creditAccount1,
-    creditAccount2,
-    debitAccount1,
-    debitAccount2,
-    creditAmount1,
-    creditAmount2,
-    debitAmount1,
-    debitAmount2,
-    localCurrencyCreditAmount1,
-    localCurrencyCreditAmount2,
-    localCurrencyDebitAmount1,
-    localCurrencyDebitAmount2,
-  } = getFragmentData(LedgerRecordsAccountDetailsFieldsFragmentDoc, data);
-
-  const creditAccount = cred
-    ? first
-      ? creditAccount1
-      : creditAccount2
-    : first
-      ? debitAccount1
-      : debitAccount2;
-
-  const localAmount = cred
-    ? first
-      ? localCurrencyCreditAmount1
-      : localCurrencyCreditAmount2
-    : first
-      ? localCurrencyDebitAmount1
-      : localCurrencyDebitAmount2;
-
-  const foreignAmount = cred
-    ? first
-      ? creditAmount1
-      : creditAmount2
-    : first
-      ? debitAmount1
-      : debitAmount2;
-
-  const isForeign = foreignAmount != null && foreignAmount.currency !== 'ILS';
-
-  const encodedFilters = get('chargesFilters');
-
-  const getHref = useCallback(
-    (businessID: string) => {
-      const currentFilters = encodedFilters
-        ? (JSON.parse(decodeURIComponent(encodedFilters as string)) as ChargeFilter)
-        : {};
-      const encodedNewFilters = {
-        fromDate: currentFilters.fromDate
-          ? `%252C%2522fromDate%2522%253A%2522${currentFilters.fromDate}%2522`
-          : '',
-        toDate: currentFilters.toDate
-          ? `%252C%2522toDate%2522%253A%2522${currentFilters.toDate}%2522`
-          : '',
-        financialEntityIds:
-          currentFilters.byOwners && currentFilters.byOwners.length > 0
-            ? `%2522${currentFilters.byOwners.join('%2522%252C%2522')}%2522`
-            : '',
-      };
-      return `/business-transactions?transactionsFilters=%257B%2522ownerIds%2522%253A%255B${
-        encodedNewFilters.financialEntityIds
-      }%255D%252C%2522businessIDs%2522%253A%255B%2522${encodeURIComponent(businessID)}%2522%255D${
-        encodedNewFilters.fromDate
-      }${encodedNewFilters.toDate}%257D`;
-    },
-    [encodedFilters],
-  );
-
-  const {
-    creditAccount1: diffCreditAccount1,
-    creditAccount2: diffCreditAccount2,
-    debitAccount1: diffDebitAccount1,
-    debitAccount2: diffDebitAccount2,
-    creditAmount1: diffCreditAmount1,
-    creditAmount2: diffCreditAmount2,
-    debitAmount1: diffDebitAmount1,
-    debitAmount2: diffDebitAmount2,
-    localCurrencyCreditAmount1: diffLocalCurrencyCreditAmount1,
-    localCurrencyCreditAmount2: diffLocalCurrencyCreditAmount2,
-    localCurrencyDebitAmount1: diffLocalCurrencyDebitAmount1,
-    localCurrencyDebitAmount2: diffLocalCurrencyDebitAmount2,
-  } = getFragmentData(LedgerRecordsAccountDetailsFieldsFragmentDoc, diff) ?? {};
-
-  const diffCreditAccount = cred
-    ? first
-      ? diffCreditAccount1
-      : diffCreditAccount2
-    : first
-      ? diffDebitAccount1
-      : diffDebitAccount2;
-
-  const diffLocalAmount = cred
-    ? first
-      ? diffLocalCurrencyCreditAmount1
-      : diffLocalCurrencyCreditAmount2
-    : first
-      ? diffLocalCurrencyDebitAmount1
-      : diffLocalCurrencyDebitAmount2;
-
-  const diffForeignAmount = cred
-    ? first
-      ? diffCreditAmount1
-      : diffCreditAmount2
-    : first
-      ? diffDebitAmount1
-      : diffDebitAmount2;
-
-  const isAccountDiff = diff && diffCreditAccount?.id !== creditAccount?.id;
-  const isLocalAmountDiff = diff && diffLocalAmount?.formatted !== localAmount?.formatted;
-  const isForeignAmountDiff = diff && diffForeignAmount?.formatted !== foreignAmount?.formatted;
-
-  return (
-    <td>
-      <div className="flex flex-col">
-        {(creditAccount || isAccountDiff) && (
-          <>
-            {creditAccount && (
-              <a href={getHref(creditAccount.id)} target="_blank" rel="noreferrer">
-                <NavLink
-                  label={creditAccount.name}
-                  className={`[&>*>.mantine-NavLink-label]:font-semibold ${isAccountDiff ? 'line-through' : ''}`}
-                />
-              </a>
-            )}
-            {isAccountDiff && diffCreditAccount && (
-              <div className="border-2 border-yellow-500 rounded-md">
-                <a href={getHref(diffCreditAccount.id)} target="_blank" rel="noreferrer">
-                  <NavLink
-                    label={diffCreditAccount.name}
-                    className="[&>*>.mantine-NavLink-label]:font-semibold"
-                  />
-                </a>
-              </div>
-            )}
-
-            <div className="flex gap-2  items-center">
-              {isForeign && (
-                <p className={isForeignAmountDiff ? 'line-through' : ''}>
-                  {foreignAmount.formatted}
-                </p>
-              )}
-              {isForeignAmountDiff && diffForeignAmount && (
-                <p className="border-2 border-yellow-500 rounded-md">
-                  {diffForeignAmount.formatted}
-                </p>
-              )}
-            </div>
-
-            <div className="flex gap-2  items-center">
-              {localAmount != null && (
-                <p className={isLocalAmountDiff ? 'line-through' : ''}>{localAmount.formatted}</p>
-              )}
-              {isLocalAmountDiff && diffLocalAmount && (
-                <p className="border-2 border-yellow-500 rounded-md">{diffLocalAmount.formatted}</p>
-              )}
-            </div>
-          </>
-        )}
-      </div>
-    </td>
-  );
-};
diff --git a/packages/client/src/components/charges/ledger-records/cells/general-date.tsx b/packages/client/src/components/charges/ledger-records/cells/general-date.tsx
deleted file mode 100644
index bee883c330..0000000000
--- a/packages/client/src/components/charges/ledger-records/cells/general-date.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { ReactElement } from 'react';
-import { format } from 'date-fns';
-import { LedgerRecordsGeneralDateFieldsFragmentDoc } from '../../../../gql/graphql.js';
-import { FragmentType, getFragmentData } from '../../../../gql/index.js';
-
-// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
-/* GraphQL */ `
-  fragment LedgerRecordsGeneralDateFields on LedgerRecord {
-    id
-    invoiceDate
-    valueDate
-  }
-`;
-
-type Props = {
-  data: FragmentType<typeof LedgerRecordsGeneralDateFieldsFragmentDoc>;
-  diff?: FragmentType<typeof LedgerRecordsGeneralDateFieldsFragmentDoc> | null;
-  type: 1 | 2;
-};
-
-export const GeneralDate = ({ data, diff, type }: Props): ReactElement => {
-  const { invoiceDate, valueDate } = getFragmentData(
-    LedgerRecordsGeneralDateFieldsFragmentDoc,
-    data,
-  );
-
-  const showDate = type === 1 ? invoiceDate : valueDate;
-
-  const formattedDate = showDate ? format(new Date(showDate), 'dd/MM/yy') : undefined;
-
-  // calculate diff date
-  const { invoiceDate: diffInvoiceDate, valueDate: diffValueDate } =
-    getFragmentData(LedgerRecordsGeneralDateFieldsFragmentDoc, diff) ?? {};
-  const diffShowDate = type === 1 ? diffInvoiceDate : diffValueDate;
-  const diffFormattedDate = diffShowDate ? format(new Date(diffShowDate), 'dd/MM/yy') : undefined;
-
-  return (
-    <td>
-      {formattedDate ?? 'Missing Data'}
-      {diffFormattedDate && showDate !== diffShowDate && (
-        <div className="flex flex-col border-2 border-yellow-500 rounded-md">
-          {diffFormattedDate}
-        </div>
-      )}
-    </td>
-  );
-};
diff --git a/packages/client/src/components/charges/ledger-records/cells/index.ts b/packages/client/src/components/charges/ledger-records/cells/index.ts
deleted file mode 100644
index 2849db3821..0000000000
--- a/packages/client/src/components/charges/ledger-records/cells/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { AccountDetails } from './account-details.jsx';
-export { GeneralDate } from './general-date.jsx';
diff --git a/packages/client/src/components/charges/ledger-records/columns.tsx b/packages/client/src/components/charges/ledger-records/columns.tsx
new file mode 100644
index 0000000000..f59b2863fc
--- /dev/null
+++ b/packages/client/src/components/charges/ledger-records/columns.tsx
@@ -0,0 +1,424 @@
+import { ChevronDown, ChevronUp } from 'lucide-react';
+import { ColumnDef } from '@tanstack/react-table';
+import { Badge } from '../../ui/badge.js';
+import { Button } from '../../ui/button.js';
+import { AmountCell } from './amount-cell.js';
+import { CounterpartyCell } from './counterparty-cell.js';
+import { DateCell } from './date-cell.js';
+import { LedgerRecordRow } from './ledger-record-table.js';
+
+export const columns: ColumnDef<LedgerRecordRow>[] = [
+  {
+    accessorKey: 'invoiceDate',
+    header: ({ column }) => {
+      return (
+        <Button
+          variant="ghost"
+          onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+        >
+          Invoice Date
+          {column.getIsSorted() &&
+            (column.getIsSorted() === 'asc' ? (
+              <ChevronUp className="ml-2 h-4 w-4" />
+            ) : (
+              <ChevronDown className="ml-2 h-4 w-4" />
+            ))}
+        </Button>
+      );
+    },
+    cell: ({ row }) => {
+      return <DateCell date={row.original.invoiceDate} diff={row.original.diff?.invoiceDate} />;
+    },
+  },
+  {
+    accessorKey: 'valueDate',
+    header: ({ column }) => {
+      return (
+        <Button
+          variant="ghost"
+          onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+        >
+          Value Date
+          {column.getIsSorted() &&
+            (column.getIsSorted() === 'asc' ? (
+              <ChevronUp className="ml-2 h-4 w-4" />
+            ) : (
+              <ChevronDown className="ml-2 h-4 w-4" />
+            ))}
+        </Button>
+      );
+    },
+    cell: ({ row }) => {
+      return <DateCell date={row.original.valueDate} diff={row.original.diff?.valueDate} />;
+    },
+  },
+  {
+    header: 'Debit Account1',
+    columns: [
+      {
+        accessorKey: 'debitAccount1.name',
+        header: ({ column }) => {
+          return (
+            <Button
+              variant="ghost"
+              onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+            >
+              Account
+              {column.getIsSorted() &&
+                (column.getIsSorted() === 'asc' ? (
+                  <ChevronUp className="ml-2 h-4 w-4" />
+                ) : (
+                  <ChevronDown className="ml-2 h-4 w-4" />
+                ))}
+            </Button>
+          );
+        },
+        cell: ({ row }) => {
+          return (
+            <CounterpartyCell
+              account={row.original.debitAccount1}
+              diffAccount={row.original.diff?.debitAccount1}
+            />
+          );
+        },
+      },
+      {
+        accessorKey: 'localCurrencyDebitAmount1.raw',
+        header: ({ column }) => {
+          return (
+            <Button
+              variant="ghost"
+              onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+            >
+              Amount
+              {column.getIsSorted() &&
+                (column.getIsSorted() === 'asc' ? (
+                  <ChevronUp className="ml-2 h-4 w-4" />
+                ) : (
+                  <ChevronDown className="ml-2 h-4 w-4" />
+                ))}
+            </Button>
+          );
+        },
+        cell: ({ row }) => {
+          return (
+            <AmountCell
+              foreignAmount={row.original.debitAmount1}
+              localAmount={row.original.localCurrencyDebitAmount1}
+              diff={
+                row.original.diff
+                  ? {
+                      foreignAmount: row.original.diff.debitAmount1,
+                      localAmount: row.original.diff.localCurrencyDebitAmount1,
+                    }
+                  : undefined
+              }
+            />
+          );
+        },
+      },
+    ],
+  },
+  {
+    header: 'Credit Account1',
+    columns: [
+      {
+        accessorKey: 'creditAccount1.name',
+        header: ({ column }) => {
+          return (
+            <Button
+              variant="ghost"
+              onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+            >
+              Account
+              {column.getIsSorted() &&
+                (column.getIsSorted() === 'asc' ? (
+                  <ChevronUp className="ml-2 h-4 w-4" />
+                ) : (
+                  <ChevronDown className="ml-2 h-4 w-4" />
+                ))}
+            </Button>
+          );
+        },
+        cell: ({ row }) => {
+          return (
+            <CounterpartyCell
+              account={row.original.creditAccount1}
+              diffAccount={row.original.diff?.creditAccount1}
+            />
+          );
+        },
+      },
+      {
+        accessorKey: 'localCurrencyCreditAmount1.raw',
+        header: ({ column }) => {
+          return (
+            <Button
+              variant="ghost"
+              onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+            >
+              Amount
+              {column.getIsSorted() &&
+                (column.getIsSorted() === 'asc' ? (
+                  <ChevronUp className="ml-2 h-4 w-4" />
+                ) : (
+                  <ChevronDown className="ml-2 h-4 w-4" />
+                ))}
+            </Button>
+          );
+        },
+        cell: ({ row }) => {
+          return (
+            <AmountCell
+              foreignAmount={row.original.creditAmount1}
+              localAmount={row.original.localCurrencyCreditAmount1}
+              diff={
+                row.original.diff
+                  ? {
+                      foreignAmount: row.original.diff.creditAmount1,
+                      localAmount: row.original.diff.localCurrencyCreditAmount1,
+                    }
+                  : undefined
+              }
+            />
+          );
+        },
+      },
+    ],
+  },
+  {
+    header: 'Debit Account2',
+    columns: [
+      {
+        accessorKey: 'debitAccount2.name',
+        header: ({ column }) => {
+          return (
+            <Button
+              variant="ghost"
+              onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+            >
+              Account
+              {column.getIsSorted() &&
+                (column.getIsSorted() === 'asc' ? (
+                  <ChevronUp className="ml-2 h-4 w-4" />
+                ) : (
+                  <ChevronDown className="ml-2 h-4 w-4" />
+                ))}
+            </Button>
+          );
+        },
+        cell: ({ row }) => {
+          return (
+            <CounterpartyCell
+              account={row.original.debitAccount2}
+              diffAccount={row.original.diff?.debitAccount2}
+            />
+          );
+        },
+      },
+      {
+        accessorKey: 'localCurrencyDebitAmount2.raw',
+        header: ({ column }) => {
+          return (
+            <Button
+              variant="ghost"
+              onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+            >
+              Amount
+              {column.getIsSorted() &&
+                (column.getIsSorted() === 'asc' ? (
+                  <ChevronUp className="ml-2 h-4 w-4" />
+                ) : (
+                  <ChevronDown className="ml-2 h-4 w-4" />
+                ))}
+            </Button>
+          );
+        },
+        cell: ({ row }) => {
+          return (
+            <AmountCell
+              foreignAmount={row.original.debitAmount2}
+              localAmount={row.original.localCurrencyDebitAmount2}
+              diff={
+                row.original.diff
+                  ? {
+                      foreignAmount: row.original.diff.debitAmount2,
+                      localAmount: row.original.diff.localCurrencyDebitAmount2,
+                    }
+                  : undefined
+              }
+            />
+          );
+        },
+      },
+    ],
+  },
+  {
+    header: 'Credit Account2',
+    columns: [
+      {
+        accessorKey: 'creditAccount2.name',
+        header: ({ column }) => {
+          return (
+            <Button
+              variant="ghost"
+              onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+            >
+              Account
+              {column.getIsSorted() &&
+                (column.getIsSorted() === 'asc' ? (
+                  <ChevronUp className="ml-2 h-4 w-4" />
+                ) : (
+                  <ChevronDown className="ml-2 h-4 w-4" />
+                ))}
+            </Button>
+          );
+        },
+        cell: ({ row }) => {
+          return (
+            <CounterpartyCell
+              account={row.original.creditAccount2}
+              diffAccount={row.original.diff?.creditAccount2}
+            />
+          );
+        },
+      },
+      {
+        accessorKey: 'localCurrencyCreditAmount2.raw',
+        header: ({ column }) => {
+          return (
+            <Button
+              variant="ghost"
+              onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+            >
+              Amount
+              {column.getIsSorted() &&
+                (column.getIsSorted() === 'asc' ? (
+                  <ChevronUp className="ml-2 h-4 w-4" />
+                ) : (
+                  <ChevronDown className="ml-2 h-4 w-4" />
+                ))}
+            </Button>
+          );
+        },
+        cell: ({ row }) => {
+          return (
+            <AmountCell
+              foreignAmount={row.original.creditAmount2}
+              localAmount={row.original.localCurrencyCreditAmount2}
+              diff={
+                row.original.diff
+                  ? {
+                      foreignAmount: row.original.diff.creditAmount2,
+                      localAmount: row.original.diff.localCurrencyCreditAmount2,
+                    }
+                  : undefined
+              }
+            />
+          );
+        },
+      },
+    ],
+  },
+  {
+    accessorKey: 'description',
+    header: ({ column }) => {
+      return (
+        <Button
+          variant="ghost"
+          onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+        >
+          Details
+          {column.getIsSorted() &&
+            (column.getIsSorted() === 'asc' ? (
+              <ChevronUp className="ml-2 h-4 w-4" />
+            ) : (
+              <ChevronDown className="ml-2 h-4 w-4" />
+            ))}
+        </Button>
+      );
+    },
+    cell: ({ row: { original } }) => {
+      const isDiff = original.diff && original.diff.description !== original.description;
+      return (
+        <div className="flex flex-col">
+          <p className={isDiff ? 'line-through' : ''}>{original.description}</p>
+          {isDiff && (
+            <div className="border-2 border-yellow-500 rounded-md">
+              {original.diff?.description}
+            </div>
+          )}
+        </div>
+      );
+    },
+  },
+  {
+    accessorKey: 'reference',
+    header: ({ column }) => {
+      return (
+        <Button
+          variant="ghost"
+          onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+        >
+          Reference
+          {column.getIsSorted() &&
+            (column.getIsSorted() === 'asc' ? (
+              <ChevronUp className="ml-2 h-4 w-4" />
+            ) : (
+              <ChevronDown className="ml-2 h-4 w-4" />
+            ))}
+        </Button>
+      );
+    },
+    cell: ({ row: { original } }) => {
+      const isDiff = original.diff && original.diff.reference !== original.reference;
+      return (
+        <div className="flex flex-col">
+          <p className={isDiff ? 'line-through' : ''}>{original.reference}</p>
+          {isDiff && (
+            <div className="border-2 border-yellow-500 rounded-md">{original.diff?.reference}</div>
+          )}
+        </div>
+      );
+    },
+  },
+  {
+    accessorKey: 'matchingStatus',
+    header: ({ column }) => {
+      return (
+        <Button
+          variant="ghost"
+          onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+        >
+          Status
+          {column.getIsSorted() &&
+            (column.getIsSorted() === 'asc' ? (
+              <ChevronUp className="ml-2 h-4 w-4" />
+            ) : (
+              <ChevronDown className="ml-2 h-4 w-4" />
+            ))}
+        </Button>
+      );
+    },
+    cell: ({
+      row: {
+        original: { matchingStatus },
+      },
+    }) => {
+      if (matchingStatus) {
+        const color =
+          matchingStatus === 'Deleted'
+            ? 'bg-red-500'
+            : matchingStatus === 'Diff'
+              ? 'bg-yellow-500'
+              : 'bg-green-500';
+        return (
+          <Badge variant="default" className={color}>
+            {matchingStatus}
+          </Badge>
+        );
+      }
+      return null;
+    },
+  },
+];
diff --git a/packages/client/src/components/charges/ledger-records/counterparty-cell.tsx b/packages/client/src/components/charges/ledger-records/counterparty-cell.tsx
new file mode 100644
index 0000000000..23aef0d525
--- /dev/null
+++ b/packages/client/src/components/charges/ledger-records/counterparty-cell.tsx
@@ -0,0 +1,83 @@
+import { ReactElement, useCallback } from 'react';
+import { NavLink } from '@mantine/core';
+import { ChargeFilter } from '../../../gql/graphql.js';
+import { useUrlQuery } from '../../../hooks/use-url-query.js';
+
+type Props = {
+  account?: {
+    name: string;
+    id: string;
+  } | null;
+  diffAccount?: {
+    name: string;
+    id: string;
+  } | null;
+};
+
+export const CounterpartyCell = ({ account, diffAccount }: Props): ReactElement => {
+  const { get } = useUrlQuery();
+
+  const creditAccount = account;
+
+  const encodedFilters = get('chargesFilters');
+
+  const getHref = useCallback(
+    (businessID: string) => {
+      let currentFilters: ChargeFilter = {};
+      if (encodedFilters) {
+        try {
+          const decoded = decodeURIComponent(encodedFilters);
+          const parsed = JSON.parse(decoded);
+          currentFilters = parsed as ChargeFilter;
+        } catch (error) {
+          console.error('Failed to parse filters from URL:', error);
+        }
+      }
+
+      const params = new URLSearchParams();
+      const filterObject = {
+        ownerIds: currentFilters.byOwners || [],
+        businessIDs: [businessID],
+        ...(currentFilters.fromDate && { fromDate: currentFilters.fromDate }),
+        ...(currentFilters.toDate && { toDate: currentFilters.toDate }),
+      };
+
+      // Add it as a single encoded parameter
+      params.append('transactionsFilters', JSON.stringify(filterObject));
+
+      return `/business-transactions?${params.toString()}`;
+    },
+    [encodedFilters],
+  );
+
+  const isAccountDiff = diffAccount && diffAccount?.id !== creditAccount?.id;
+
+  return (
+    <td>
+      <div className="flex flex-col">
+        {(creditAccount || isAccountDiff) && (
+          <>
+            {creditAccount && (
+              <a href={getHref(creditAccount.id)} target="_blank" rel="noreferrer">
+                <NavLink
+                  label={creditAccount.name}
+                  className={`[&>*>.mantine-NavLink-label]:font-semibold ${isAccountDiff ? 'line-through' : ''}`}
+                />
+              </a>
+            )}
+            {isAccountDiff && diffAccount && (
+              <div className="border-2 border-yellow-500 rounded-md">
+                <a href={getHref(diffAccount.id)} target="_blank" rel="noreferrer">
+                  <NavLink
+                    label={diffAccount.name}
+                    className="[&>*>.mantine-NavLink-label]:font-semibold"
+                  />
+                </a>
+              </div>
+            )}
+          </>
+        )}
+      </div>
+    </td>
+  );
+};
diff --git a/packages/client/src/components/charges/ledger-records/date-cell.tsx b/packages/client/src/components/charges/ledger-records/date-cell.tsx
new file mode 100644
index 0000000000..fc15ba19cd
--- /dev/null
+++ b/packages/client/src/components/charges/ledger-records/date-cell.tsx
@@ -0,0 +1,26 @@
+import { ReactElement } from 'react';
+import { format } from 'date-fns';
+
+type Props = {
+  date?: Date | null;
+  diff?: Date | null;
+};
+
+export const DateCell = ({ date, diff }: Props): ReactElement => {
+  const formattedDate = date ? format(new Date(date), 'dd/MM/yy') : undefined;
+
+  // calculate diff date
+  const diffFormattedDate = diff ? format(new Date(diff), 'dd/MM/yy') : undefined;
+  const isDiff = diffFormattedDate && formattedDate !== diffFormattedDate;
+
+  return (
+    <td>
+      <p className={isDiff ? 'line-through' : ''}>{formattedDate ?? 'Missing Data'}</p>
+      {isDiff && (
+        <div className="flex flex-col border-2 border-yellow-500 rounded-md">
+          {diffFormattedDate}
+        </div>
+      )}
+    </td>
+  );
+};
diff --git a/packages/client/src/components/charges/ledger-records/ledger-record-row.tsx b/packages/client/src/components/charges/ledger-records/ledger-record-row.tsx
deleted file mode 100644
index 5cbe232cd5..0000000000
--- a/packages/client/src/components/charges/ledger-records/ledger-record-row.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { ReactElement } from 'react';
-import { Badge } from '@mantine/core';
-import { TableLedgerRecordsRowFieldsFragmentDoc } from '../../../gql/graphql.js';
-import { FragmentType, getFragmentData } from '../../../gql/index.js';
-import { AccountDetails, GeneralDate } from './cells/index.js';
-
-// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
-/* GraphQL */ `
-  fragment TableLedgerRecordsRowFields on LedgerRecord {
-    id
-    ...LedgerRecordsAccountDetailsFields
-    ...LedgerRecordsGeneralDateFields
-    description
-    reference
-  }
-`;
-
-type Props = {
-  ledgerRecordProps: FragmentType<typeof TableLedgerRecordsRowFieldsFragmentDoc>;
-  matchingStatus?: 'New' | 'Diff' | 'Deleted';
-  diffs?: FragmentType<typeof TableLedgerRecordsRowFieldsFragmentDoc>;
-};
-
-export const LedgerRecordRow = ({
-  ledgerRecordProps,
-  diffs,
-  matchingStatus,
-}: Props): ReactElement => {
-  const record = getFragmentData(TableLedgerRecordsRowFieldsFragmentDoc, ledgerRecordProps);
-  const diffsRecord = getFragmentData(TableLedgerRecordsRowFieldsFragmentDoc, diffs);
-
-  if (matchingStatus === 'Diff' && !diffsRecord) {
-    matchingStatus = 'Deleted';
-  }
-
-  let rowStyle = '';
-  switch (matchingStatus) {
-    case 'New':
-      rowStyle = 'bg-green-100/30';
-      break;
-    case 'Deleted':
-      rowStyle = 'bg-red-100/30';
-      break;
-    case 'Diff':
-      rowStyle = 'bg-yellow-100/30';
-      break;
-  }
-
-  return (
-    <tr key={record.id} className={rowStyle}>
-      <GeneralDate data={record} diff={diffsRecord} type={1} />
-      <GeneralDate data={record} diff={diffsRecord} type={2} />
-      <AccountDetails data={record} diff={diffsRecord} cred={false} first />
-      <AccountDetails data={record} diff={diffsRecord} cred first />
-      <AccountDetails data={record} diff={diffsRecord} cred={false} first={false} />
-      <AccountDetails data={record} diff={diffsRecord} cred first={false} />
-      <td>
-        <div className="flex flex-col">
-          <p
-            className={
-              !diffsRecord?.description || diffsRecord.description === record.description
-                ? ''
-                : 'line-through'
-            }
-          >
-            {record.description}
-          </p>
-          {diffsRecord?.description && diffsRecord.description !== record.description && (
-            <div className="border-2 border-yellow-500 rounded-md">{diffsRecord?.description}</div>
-          )}
-        </div>
-      </td>
-      <td>
-        <div className="flex flex-col">
-          <p
-            className={
-              !diffsRecord?.reference || diffsRecord.reference === record.reference
-                ? ''
-                : 'line-through'
-            }
-          >
-            {record.reference}
-          </p>
-          {diffsRecord?.reference && diffsRecord?.reference !== record.reference && (
-            <div className="border-2 border-yellow-500 rounded-md">{diffsRecord?.reference}</div>
-          )}
-        </div>
-      </td>
-      <td>
-        {!!matchingStatus && (
-          <Badge
-            variant="filled"
-            color={
-              matchingStatus === 'Deleted' ? 'red' : matchingStatus === 'Diff' ? 'yellow' : 'green'
-            }
-          >
-            {matchingStatus}
-          </Badge>
-        )}
-      </td>
-    </tr>
-  );
-};
diff --git a/packages/client/src/components/charges/ledger-records/ledger-record-table.tsx b/packages/client/src/components/charges/ledger-records/ledger-record-table.tsx
index 2c3d0bf002..74a8028211 100644
--- a/packages/client/src/components/charges/ledger-records/ledger-record-table.tsx
+++ b/packages/client/src/components/charges/ledger-records/ledger-record-table.tsx
@@ -1,8 +1,19 @@
-import { ReactElement } from 'react';
-import { TableLedgerRecordsFieldsFragmentDoc } from '../../../gql/graphql.js';
+import { ReactElement, useMemo, useState } from 'react';
+import {
+  flexRender,
+  getCoreRowModel,
+  getSortedRowModel,
+  SortingState,
+  useReactTable,
+} from '@tanstack/react-table';
+import {
+  TableLedgerRecordsFieldsFragment,
+  TableLedgerRecordsFieldsFragmentDoc,
+} from '../../../gql/graphql.js';
 import { FragmentType, getFragmentData } from '../../../gql/index.js';
 import { EMPTY_UUID } from '../../../helpers/consts.js';
-import { LedgerRecordRow } from './ledger-record-row.jsx';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../ui/table.js';
+import { columns } from './columns.js';
 
 // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
 /* GraphQL */ `
@@ -12,7 +23,62 @@ import { LedgerRecordRow } from './ledger-record-row.jsx';
       __typename
       records {
         id
-        ...TableLedgerRecordsRowFields
+        creditAccount1 {
+          __typename
+          id
+          name
+        }
+        creditAccount2 {
+          __typename
+          id
+          name
+        }
+        debitAccount1 {
+          __typename
+          id
+          name
+        }
+        debitAccount2 {
+          __typename
+          id
+          name
+        }
+        creditAmount1 {
+          formatted
+          currency
+        }
+        creditAmount2 {
+          formatted
+          currency
+        }
+        debitAmount1 {
+          formatted
+          currency
+        }
+        debitAmount2 {
+          formatted
+          currency
+        }
+        localCurrencyCreditAmount1 {
+          formatted
+          raw
+        }
+        localCurrencyCreditAmount2 {
+          formatted
+          raw
+        }
+        localCurrencyDebitAmount1 {
+          formatted
+          raw
+        }
+        localCurrencyDebitAmount2 {
+          formatted
+          raw
+        }
+        invoiceDate
+        valueDate
+        description
+        reference
       }
       ... on Ledger @defer {
         validate {
@@ -20,7 +86,62 @@ import { LedgerRecordRow } from './ledger-record-row.jsx';
             matches
             differences {
               id
-              ...TableLedgerRecordsRowFields
+              creditAccount1 {
+                __typename
+                id
+                name
+              }
+              creditAccount2 {
+                __typename
+                id
+                name
+              }
+              debitAccount1 {
+                __typename
+                id
+                name
+              }
+              debitAccount2 {
+                __typename
+                id
+                name
+              }
+              creditAmount1 {
+                formatted
+                currency
+              }
+              creditAmount2 {
+                formatted
+                currency
+              }
+              debitAmount1 {
+                formatted
+                currency
+              }
+              debitAmount2 {
+                formatted
+                currency
+              }
+              localCurrencyCreditAmount1 {
+                formatted
+                raw
+              }
+              localCurrencyCreditAmount2 {
+                formatted
+                raw
+              }
+              localCurrencyDebitAmount1 {
+                formatted
+                raw
+              }
+              localCurrencyDebitAmount2 {
+                formatted
+                raw
+              }
+              invoiceDate
+              valueDate
+              description
+              reference
             }
           }
         }
@@ -29,50 +150,109 @@ import { LedgerRecordRow } from './ledger-record-row.jsx';
   }
 `;
 
+function getRowColorByStatus(status?: 'New' | 'Diff' | 'Deleted'): string {
+  let rowStyle = '';
+  switch (status) {
+    case 'New':
+      rowStyle = 'bg-green-100/30';
+      break;
+    case 'Deleted':
+      rowStyle = 'bg-red-100/30';
+      break;
+    case 'Diff':
+      rowStyle = 'bg-yellow-100/30';
+      break;
+  }
+  return rowStyle;
+}
+
+export type LedgerRecordRow = TableLedgerRecordsFieldsFragment['ledger']['records'][number] & {
+  matchingStatus?: 'New' | 'Diff' | 'Deleted';
+  diff?: TableLedgerRecordsFieldsFragment['ledger']['records'][number];
+};
+
 type Props = {
   ledgerRecordsProps: FragmentType<typeof TableLedgerRecordsFieldsFragmentDoc>;
 };
 
 export const LedgerRecordTable = ({ ledgerRecordsProps }: Props): ReactElement => {
-  const { ledger: data } = getFragmentData(TableLedgerRecordsFieldsFragmentDoc, ledgerRecordsProps);
+  const { ledger } = getFragmentData(TableLedgerRecordsFieldsFragmentDoc, ledgerRecordsProps);
+  const [sorting, setSorting] = useState<SortingState>([]);
+
+  const data = useMemo(() => {
+    const records: LedgerRecordRow[] = ledger.records.map(record => {
+      const diff = ledger.validate?.differences?.find(diffRecord => diffRecord.id === record.id);
+      return {
+        ...record,
+        matchingStatus:
+          !ledger.validate?.matches || ledger.validate.matches?.some(id => id === record.id)
+            ? undefined
+            : diff
+              ? 'Diff'
+              : 'Deleted',
+        diff,
+      };
+    });
+    const newRecords: LedgerRecordRow[] =
+      ledger?.validate?.differences
+        ?.filter(record => record.id === EMPTY_UUID)
+        .map(record => ({
+          ...record,
+          matchingStatus: 'New',
+        })) ?? [];
+    records.push(...newRecords);
+    return records;
+  }, [ledger]);
+
+  const table = useReactTable({
+    data,
+    columns,
+    getCoreRowModel: getCoreRowModel(),
+    onSortingChange: setSorting,
+    getSortedRowModel: getSortedRowModel(),
+    state: {
+      sorting,
+    },
+  });
 
   return (
-    <table className="w-full h-full">
-      <thead>
-        <tr>
-          <th>Invoice Date</th>
-          <th>Value Date</th>
-          <th>Debit Account1</th>
-          <th>Credit Account1</th>
-          <th>Debit Account2</th>
-          <th>Credit Account2</th>
-          <th>Details</th>
-          <th>Ref1</th>
-          <th />
-        </tr>
-      </thead>
-      <tbody>
-        {data?.records?.map(record => (
-          <LedgerRecordRow
-            key={record.id}
-            ledgerRecordProps={record}
-            matchingStatus={
-              !data.validate?.matches || data.validate.matches?.some(id => id === record.id)
-                ? undefined
-                : 'Diff'
-            }
-            diffs={data.validate?.differences?.find(diffRecord => diffRecord.id === record.id)}
-          />
+    <Table>
+      <TableHeader>
+        {table.getHeaderGroups().map(headerGroup => (
+          <TableRow key={headerGroup.id}>
+            {headerGroup.headers.map(header => (
+              <TableHead key={header.id} colSpan={header.colSpan}>
+                {header.isPlaceholder
+                  ? null
+                  : flexRender(header.column.columnDef.header, header.getContext())}
+              </TableHead>
+            ))}
+          </TableRow>
         ))}
-        {data?.validate?.differences
-          ?.filter(record => record.id === EMPTY_UUID)
-          .map(record => (
-            <LedgerRecordRow key={record.id} ledgerRecordProps={record} matchingStatus="New" />
-          ))}
-        <tr>
-          <td colSpan={8} />
-        </tr>
-      </tbody>
-    </table>
+      </TableHeader>
+      <TableBody>
+        {table.getRowModel().rows?.length ? (
+          table.getRowModel().rows.map(row => (
+            <TableRow
+              key={row.id}
+              data-state={row.getIsSelected() && 'selected'}
+              className={getRowColorByStatus(row.original.matchingStatus)}
+            >
+              {row.getVisibleCells().map(cell => (
+                <TableCell key={cell.id}>
+                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
+                </TableCell>
+              ))}
+            </TableRow>
+          ))
+        ) : (
+          <TableRow>
+            <TableCell colSpan={columns.length} className="h-24 text-center">
+              No results.
+            </TableCell>
+          </TableRow>
+        )}
+      </TableBody>
+    </Table>
   );
 };


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (15)</summary><blockquote>

<details>
<summary>packages/client/src/components/charges/ledger-records/date-cell.tsx (3)</summary><blockquote>

`10-10`: **Consider handling date objects more safely.**

When creating a new Date from an existing Date object, it can lead to unexpected behavior. The `new Date(date)` constructor will treat an already existing Date object as a timestamp.

```diff
-  const formattedDate = date ? format(new Date(date), 'dd/MM/yy') : undefined;
+  const formattedDate = date ? format(date, 'dd/MM/yy') : undefined;
```

---

`13-13`: **Apply the same date handling fix for consistency.**

Similar to the previous comment, this line should also avoid creating a new Date from a potential Date object.

```diff
-  const diffFormattedDate = diff ? format(new Date(diff), 'dd/MM/yy') : undefined;
+  const diffFormattedDate = diff ? format(diff, 'dd/MM/yy') : undefined;
```

---

`16-26`: **Enhance accessibility for date difference visualization.**

The component uses visual cues (line-through and yellow border) to indicate differences, but lacks ARIA attributes to convey this information to screen readers.

```diff
  return (
    <td>
-      <p className={isDiff ? 'line-through' : ''}>{formattedDate ?? 'Missing Data'}</p>
+      <p 
+         className={isDiff ? 'line-through' : ''} 
+         aria-label={isDiff ? `Original date: ${formattedDate ?? 'Missing Data'}` : undefined}
+      >
+         {formattedDate ?? 'Missing Data'}
+      </p>
      {isDiff && (
-        <div className="flex flex-col border-2 border-yellow-500 rounded-md">
+        <div 
+           className="flex flex-col border-2 border-yellow-500 rounded-md"
+           aria-label={`Updated date: ${diffFormattedDate}`}
+        >
          {diffFormattedDate}
        </div>
      )}
    </td>
  );
```

</blockquote></details>
<details>
<summary>packages/client/src/components/charges/ledger-records/amount-cell.tsx (2)</summary><blockquote>

`29-29`: **Fix inconsistent spacing in className.**

There are double spaces in the className strings which should be fixed for consistency.

```diff
-            <div className="flex gap-2  items-center">
+            <div className="flex gap-2 items-center">
```

```diff
-            <div className="flex gap-2  items-center">
+            <div className="flex gap-2 items-center">
```


Also applies to: 42-42

---

`26-55`: **Enhance accessibility for amount difference visualization.**

The component uses visual cues to indicate differences but lacks ARIA attributes to make this accessible.

Consider adding appropriate aria-labels to the elements that show differences, such as:

```diff
              {isForeign && (
-                <p className={isForeignAmountDiff ? 'line-through' : ''}>
+                <p 
+                   className={isForeignAmountDiff ? 'line-through' : ''}
+                   aria-label={isForeignAmountDiff ? `Original foreign amount: ${foreignAmount.formatted}` : undefined}
+                >
                  {foreignAmount.formatted}
                </p>
              )}
              {isForeignAmountDiff && diff.foreignAmount && (
-                <p className="border-2 border-yellow-500 rounded-md">
+                <p 
+                   className="border-2 border-yellow-500 rounded-md"
+                   aria-label={`Updated foreign amount: ${diff.foreignAmount.formatted}`}
+                >
                  {diff.foreignAmount.formatted}
                </p>
              )}
```

Apply similar changes to the local amount section as well.

</blockquote></details>
<details>
<summary>packages/client/src/components/charges/ledger-records/columns.tsx (3)</summary><blockquote>

`13-28`: **Reduce code duplication in column header components.**

The header rendering functions are nearly identical across all columns. Consider extracting this pattern into a reusable function.

You could create a helper function like:

```typescript
const createSortableHeader = (
  label: string
) => ({ column }: { column: Column<LedgerRecordRow, unknown> }) => (
  <Button
    variant="ghost"
    onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
  >
    {label}
    {column.getIsSorted() && (
      column.getIsSorted() === 'asc' ? (
        <ChevronUp className="ml-2 h-4 w-4" />
      ) : (
        <ChevronDown className="ml-2 h-4 w-4" />
      )
    )}
  </Button>
);

// Then use it like:
header: createSortableHeader('Invoice Date'),
```


Also applies to: 35-50, 60-75, 87-102, 127-142, 154-169, 194-209, 221-236, 261-276, 288-303, 325-340, 357-372, 387-402

---

`341-353`: **Create consistent cell components for all column types.**

While account and amount columns use dedicated components, description and reference columns define their rendering logic inline. For consistency, consider creating a reusable component for these fields too.

Consider creating a `TextCell` component similar to `DateCell` that can handle text differences:

```typescript
// text-cell.tsx
type Props = {
  value?: string | null;
  diff?: string | null;
};

export const TextCell = ({ value, diff }: Props): ReactElement => {
  const isDiff = diff !== undefined && diff !== value;
  
  return (
    <td>
      <div className="flex flex-col">
        <p className={isDiff ? 'line-through' : ''}>{value}</p>
        {isDiff && (
          <div className="border-2 border-yellow-500 rounded-md">{diff}</div>
        )}
      </div>
    </td>
  );
};
```

Then use it in the column definitions:
```typescript
cell: ({ row }) => (
  <TextCell value={row.original.description} diff={row.original.diff?.description} />
),
```


Also applies to: 373-383

---

`408-421`: **Simplify status color determination with a lookup object.**

The nested conditional for determining the status color can be simplified using a lookup object.

```diff
-        const color =
-          matchingStatus === 'Deleted'
-            ? 'bg-red-500'
-            : matchingStatus === 'Diff'
-              ? 'bg-yellow-500'
-              : 'bg-green-500';
+        const statusColors = {
+          'Deleted': 'bg-red-500',
+          'Diff': 'bg-yellow-500',
+          'New': 'bg-green-500'
+        };
+        const color = statusColors[matchingStatus] || 'bg-green-500';
```

</blockquote></details>
<details>
<summary>packages/client/src/components/charges/ledger-records/counterparty-cell.tsx (3)</summary><blockquote>

`20-20`: **Reconsider variable renaming for clarity.**

Renaming `account` to `creditAccount` might lead to confusion when the component is used with debit accounts, as it is in the columns configuration. Consider keeping the original name for flexibility.

```diff
-  const creditAccount = account;
```

Just use `account` directly throughout the component.

---

`50-50`: **Fix redundant optional chaining.**

There's a redundant null check in the expression. If `diffAccount` is null or undefined, the optional chaining is unnecessary.

```diff
-  const isAccountDiff = diffAccount && diffAccount?.id !== creditAccount?.id;
+  const isAccountDiff = diffAccount && diffAccount.id !== creditAccount?.id;
```

---

`52-78`: **Enhance accessibility for account difference visualization.**

Similar to the other components, add ARIA attributes to improve accessibility of the visual difference indicators.

```diff
              <a href={getHref(creditAccount.id)} target="_blank" rel="noreferrer">
                <NavLink
                  label={creditAccount.name}
-                  className={`[&>*>.mantine-NavLink-label]:font-semibold ${isAccountDiff ? 'line-through' : ''}`}
+                  className={`[&>*>.mantine-NavLink-label]:font-semibold ${isAccountDiff ? 'line-through' : ''}`}
+                  aria-label={isAccountDiff ? `Original account: ${creditAccount.name}` : undefined}
                />
              </a>
            )}
            {isAccountDiff && diffAccount && (
-              <div className="border-2 border-yellow-500 rounded-md">
+              <div 
+                className="border-2 border-yellow-500 rounded-md"
+                aria-label={`Updated account: ${diffAccount.name}`}
+              >
                <a href={getHref(diffAccount.id)} target="_blank" rel="noreferrer">
                  <NavLink
                    label={diffAccount.name}
                    className="[&>*>.mantine-NavLink-label]:font-semibold"
                  />
                </a>
              </div>
```

</blockquote></details>
<details>
<summary>packages/client/src/components/charges/ledger-records/ledger-record-table.tsx (4)</summary><blockquote>

`153-167`: **Consider externalizing row color mapping for maintainability.**  
The switch statement is clear, but you could store these mappings in a shared constants or dictionary. This would allow more flexible design changes and reduce duplication if you reuse these statuses elsewhere.

---

`169-172`: **Optionally switch to an enum for matching statuses.**  
By using a TypeScript `enum` or a string union type constant, consumers of `LedgerRecordRow` can more easily discover valid status strings and avoid typos.

---

`196-205`: **Relying on `EMPTY_UUID` for 'New' records may be brittle.**  
This approach functions but could lead to confusion if the ID-generation logic changes. Consider a dedicated server-side property indicating new records.

---

`234-255`: **Potential simplification for row selection styling.**  
If row selection is not used, you can remove the `data-state` attribute or conditionally apply a class without it. This helps keep the DOM cleaner.  


For example:
```diff
-            <TableRow
-              key={row.id}
-              data-state={row.getIsSelected() && 'selected'}
-              className={getRowColorByStatus(row.original.matchingStatus)}
-            >
+            <TableRow
+              key={row.id}
+              className={getRowColorByStatus(row.original.matchingStatus)}
+            >
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 630b2583354ab86a9d8f36c25e48a633d0926d30 and a57ea15a8e0a3ff50e16032dd5fb897378a52f89.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `packages/client/src/components/charges/ledger-records/amount-cell.tsx` (1 hunks)
* `packages/client/src/components/charges/ledger-records/cells/account-details.tsx` (0 hunks)
* `packages/client/src/components/charges/ledger-records/cells/general-date.tsx` (0 hunks)
* `packages/client/src/components/charges/ledger-records/cells/index.ts` (0 hunks)
* `packages/client/src/components/charges/ledger-records/columns.tsx` (1 hunks)
* `packages/client/src/components/charges/ledger-records/counterparty-cell.tsx` (1 hunks)
* `packages/client/src/components/charges/ledger-records/date-cell.tsx` (1 hunks)
* `packages/client/src/components/charges/ledger-records/ledger-record-row.tsx` (0 hunks)
* `packages/client/src/components/charges/ledger-records/ledger-record-table.tsx` (3 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (4)</summary>

* packages/client/src/components/charges/ledger-records/cells/index.ts
* packages/client/src/components/charges/ledger-records/cells/general-date.tsx
* packages/client/src/components/charges/ledger-records/ledger-record-row.tsx
* packages/client/src/components/charges/ledger-records/cells/account-details.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (2)</summary>

<details>
<summary>packages/client/src/components/charges/ledger-records/columns.tsx (4)</summary><blockquote>

<details>
<summary>packages/client/src/components/charges/ledger-records/ledger-record-table.tsx (1) (1)</summary>

* `LedgerRecordRow` (169-172)

</details>
<details>
<summary>packages/client/src/components/charges/ledger-records/date-cell.tsx (1) (1)</summary>

* `DateCell` (9-26)

</details>
<details>
<summary>packages/client/src/components/charges/ledger-records/counterparty-cell.tsx (1) (1)</summary>

* `CounterpartyCell` (17-80)

</details>
<details>
<summary>packages/client/src/components/charges/ledger-records/amount-cell.tsx (1) (1)</summary>

* `AmountCell` (18-57)

</details>

</blockquote></details>
<details>
<summary>packages/client/src/components/charges/ledger-records/ledger-record-table.tsx (1)</summary><blockquote>

<details>
<summary>packages/client/src/components/charges/ledger-records/columns.tsx (1) (1)</summary>

* `columns` (10-424)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: snapshot

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>packages/client/src/components/charges/ledger-records/amount-cell.tsx (1)</summary>

`19-19`: _🛠️ Refactor suggestion_

**Ensure foreign amount exists before attempting to access its properties.**

While you check if `foreignAmount` is not null, accessing `foreignAmount.currency` could still cause errors if `foreignAmount` is undefined.

```diff
-  const isForeign = foreignAmount != null && foreignAmount.currency !== Currency.Ils;
+  const isForeign = foreignAmount?.currency !== Currency.Ils;
```



> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>packages/client/src/components/charges/ledger-records/ledger-record-table.tsx (5)</summary>

`1-12`: **Good use of imports, no immediate issues noted.**  
These lines effectively import the required hooks and types from React, React Table, and your generated GraphQL fragments. Everything looks well organized and purposeful.

---

`179-181`: **Initialization of state and fragment data looks good.**  
You're properly extracting `ledger` from the fragment and setting up sorting state. This usage follows recommended patterns in React.

---

`182-195`: **Confirm intended logic for matching vs. diff vs. deleted.**  
Your logic sets `matchingStatus` to `undefined` if `!ledger.validate?.matches` or if `record.id` is found in `matches`. Otherwise, it checks for `diff` → `'Diff'` → `'Deleted'`. Ensure this matches the desired business rules for each status scenario.

---

`207-216`: **React Table usage is well configured.**  
Defining data, columns, sorting state, and row models looks correct and aligns with `@tanstack/react-table` best practices.

---

`15-16`: <details>
<summary>❓ Verification inconclusive</summary>

**Verify the existence of the UI table modules at the given path.**  
Please confirm that your `Table` components (`Table`, `TableBody`, etc.) are indeed exported from `'../../ui/table.js'` and remain valid over the long term.

---

🏁 Script executed:

```shell
#!/bin/bash
# Confirm that the 'table.js' file is present in 'ui' subdirectory (relative to this component).
fd table.js -t f
```

Length of output: 18

---



</details>

**Action Required: Confirm UI Table Module Existence**

The initial search with `fd table.js -t f` did not return any matches, so we couldn’t confirm that the UI table components are currently exported from the expected path (`../../ui/table.js`). This could mean one of two things:
  
- The file might be located in a different directory than anticipated.
- The export path may have changed but the import wasn’t updated accordingly.

Please manually verify that the file containing the `Table`, `TableBody`, `TableCell`, etc., components exists in the path relative to `packages/client/src/components/charges/ledger-records/ledger-record-table.tsx`. Specifically, ensure that the expected file exists at the location resolved by `'../../ui/table.js'` (likely within a `ui` folder under the `components` directory, such as `packages/client/src/components/ui/table.js`) or update the import path if it has moved.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T20:28:52Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>packages/client/src/components/charges/ledger-records/counterparty-cell.tsx (4)</summary><blockquote>

`20-20`: **Consider removing redundant variable.**

The `creditAccount` variable is simply a reference to the `account` prop without any transformation. This adds unnecessary complexity. You can use `account` directly throughout the component.

```diff
-  const creditAccount = account;
```

---

`53-53`: **Simplify conditional check.**

The optional chaining on `diffAccount?.id` is redundant since you've already verified `diffAccount` exists in the same expression.

```diff
-  const isAccountDiff = diffAccount && diffAccount?.id !== creditAccount?.id;
+  const isAccountDiff = diffAccount && diffAccount.id !== creditAccount?.id;
```

---

`61-67`: **Enhance accessibility for links that open in new tabs.**

When using links that open in new tabs, it's best practice to inform screen reader users about this behavior.

```diff
-              <a href={getHref(creditAccount.id)} target="_blank" rel="noreferrer">
+              <a 
+                href={getHref(creditAccount.id)} 
+                target="_blank" 
+                rel="noreferrer"
+                aria-label={`${creditAccount.name} (opens in new tab)`}>
               <NavLink
                 label={creditAccount.name}
                 className={`[&>*>.mantine-NavLink-label]:font-semibold ${isAccountDiff ? 'line-through' : ''}`}
               />
             </a>
```

Apply a similar change to the second link as well.


Also applies to: 70-75

---

`17-83`: **Consider memoizing the component for performance.**

Since this component might be used in a list rendering context (like a table of ledger records), consider wrapping it with React.memo to prevent unnecessary re-renders.

```diff
-export const CounterpartyCell = ({ account, diffAccount }: Props): ReactElement => {
+export const CounterpartyCell = React.memo(({ account, diffAccount }: Props): ReactElement => {
  // Component implementation
-};
+});
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e2bff57065bdb73f8c9de5e73dc3c1b1cf8ead1c and abfab83c9416c9935f3a0d0a77c56902d4ed0a79.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/client/src/components/charges/ledger-records/counterparty-cell.tsx` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: snapshot

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>packages/client/src/components/charges/ledger-records/counterparty-cell.tsx (2)</summary>

`27-35`: **Good implementation of error handling.**

Your implementation of try/catch for URL parameter parsing addresses the previous review suggestion well. This ensures more robust handling of potentially malformed input data.

---

`37-48`: **Well-structured URL parameter handling.**

The URL generation logic has been improved as suggested in the previous review. Using URLSearchParams for parameter management is a clean approach that improves maintainability.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T12:30:58Z

Review comments:
diff_hunk: @@ -0,0 +1,424 @@
+import { ChevronDown, ChevronUp } from 'lucide-react';
+import { ColumnDef } from '@tanstack/react-table';
+import { Badge } from '../../ui/badge.js';
+import { Button } from '../../ui/button.js';
+import { AmountCell } from './amount-cell.js';
+import { CounterpartyCell } from './counterparty-cell.js';
+import { DateCell } from './date-cell.js';
+import { LedgerRecordRow } from './ledger-record-table.js';
+
+export const columns: ColumnDef<LedgerRecordRow>[] = [
+  {
+    accessorKey: 'invoiceDate',
+    header: ({ column }) => {
+      return (
+        <Button
+          variant="ghost"
+          onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
+        >
+          Invoice Date
+          {column.getIsSorted() &&
+            (column.getIsSorted() === 'asc' ? (
+              <ChevronUp className="ml-2 h-4 w-4" />
+            ) : (
+              <ChevronDown className="ml-2 h-4 w-4" />
+            ))}
+        </Button>
+      );
+    },
+    cell: ({ row }) => {
+      return <DateCell date={row.original.invoiceDate} diff={row.original.diff?.valueDate} />;
path: packages/client/src/components/charges/ledger-records/columns.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix incorrect diff property reference for invoiceDate.**

You're passing `row.original.diff?.valueDate` as the diff prop for the invoiceDate, but this should likely be `invoiceDate` to correctly compare the same field.

```diff
-      return <DateCell date={row.original.invoiceDate} diff={row.original.diff?.valueDate} />;
+      return <DateCell date={row.original.invoiceDate} diff={row.original.diff?.invoiceDate} />;
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
      return <DateCell date={row.original.invoiceDate} diff={row.original.diff?.invoiceDate} />;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-20T20:28:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,80 @@
+import { ReactElement, useCallback } from 'react';
+import { NavLink } from '@mantine/core';
+import { ChargeFilter } from '../../../gql/graphql.js';
+import { useUrlQuery } from '../../../hooks/use-url-query.js';
+
+type Props = {
+  account?: {
+    name: string;
+    id: string;
+  } | null;
+  diffAccount?: {
+    name: string;
+    id: string;
+  } | null;
+};
+
+export const CounterpartyCell = ({ account, diffAccount }: Props): ReactElement => {
+  const { get } = useUrlQuery();
+
+  const creditAccount = account;
+
+  const encodedFilters = get('chargesFilters');
+
+  const getHref = useCallback(
+    (businessID: string) => {
+      const currentFilters = encodedFilters
+        ? (JSON.parse(decodeURIComponent(encodedFilters as string)) as ChargeFilter)
+        : {};
+      const encodedNewFilters = {
+        fromDate: currentFilters.fromDate
+          ? `%252C%2522fromDate%2522%253A%2522${currentFilters.fromDate}%2522`
+          : '',
+        toDate: currentFilters.toDate
+          ? `%252C%2522toDate%2522%253A%2522${currentFilters.toDate}%2522`
+          : '',
+        financialEntityIds:
+          currentFilters.byOwners && currentFilters.byOwners.length > 0
+            ? `%2522${currentFilters.byOwners.join('%2522%252C%2522')}%2522`
+            : '',
+      };
+      return `/business-transactions?transactionsFilters=%257B%2522ownerIds%2522%253A%255B${
+        encodedNewFilters.financialEntityIds
+      }%255D%252C%2522businessIDs%2522%253A%255B%2522${encodeURIComponent(businessID)}%2522%255D${
+        encodedNewFilters.fromDate
+      }${encodedNewFilters.toDate}%257D`;
+    },
+    [encodedFilters],
+  );
path: packages/client/src/components/charges/ledger-records/counterparty-cell.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Simplify URL generation logic.**

The URL generation logic is complex and difficult to maintain. Consider using a URL builder library or creating a more maintainable approach.

The current implementation manually constructs complex URL parameters with hard-coded encoding. A more maintainable approach would be:

```typescript
const getHref = useCallback((businessID: string) => {
  const currentFilters = encodedFilters
    ? (JSON.parse(decodeURIComponent(encodedFilters as string)) as ChargeFilter)
    : {};
    
  // Create a new URLSearchParams object
  const params = new URLSearchParams();
  
  // Build a clean filter object
  const filterObject = {
    ownerIds: currentFilters.byOwners || [],
    businessIDs: [businessID],
    ...(currentFilters.fromDate && { fromDate: currentFilters.fromDate }),
    ...(currentFilters.toDate && { toDate: currentFilters.toDate })
  };
  
  // Add it as a single encoded parameter
  params.append('transactionsFilters', JSON.stringify(filterObject));
  
  return `/business-transactions?${params.toString()}`;
}, [encodedFilters]);
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-20T20:28:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,80 @@
+import { ReactElement, useCallback } from 'react';
+import { NavLink } from '@mantine/core';
+import { ChargeFilter } from '../../../gql/graphql.js';
+import { useUrlQuery } from '../../../hooks/use-url-query.js';
+
+type Props = {
+  account?: {
+    name: string;
+    id: string;
+  } | null;
+  diffAccount?: {
+    name: string;
+    id: string;
+  } | null;
+};
+
+export const CounterpartyCell = ({ account, diffAccount }: Props): ReactElement => {
+  const { get } = useUrlQuery();
+
+  const creditAccount = account;
+
+  const encodedFilters = get('chargesFilters');
+
+  const getHref = useCallback(
+    (businessID: string) => {
+      const currentFilters = encodedFilters
+        ? (JSON.parse(decodeURIComponent(encodedFilters as string)) as ChargeFilter)
+        : {};
path: packages/client/src/components/charges/ledger-records/counterparty-cell.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Validate JSON parsing from URL parameters.**

The function parses and processes user input from URL parameters without validation, which could potentially lead to issues with malformed input.

```diff
-      const currentFilters = encodedFilters
-        ? (JSON.parse(decodeURIComponent(encodedFilters as string)) as ChargeFilter)
-        : {};
+      let currentFilters = {};
+      if (encodedFilters) {
+        try {
+          const decoded = decodeURIComponent(encodedFilters as string);
+          const parsed = JSON.parse(decoded);
+          currentFilters = parsed as ChargeFilter;
+        } catch (error) {
+          console.error('Failed to parse filters from URL:', error);
+        }
+      }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
      let currentFilters = {};
      if (encodedFilters) {
        try {
          const decoded = decodeURIComponent(encodedFilters as string);
          const parsed = JSON.parse(decoded);
          currentFilters = parsed as ChargeFilter;
        } catch (error) {
          console.error('Failed to parse filters from URL:', error);
        }
      }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-20T20:28:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-20T20:22:45Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request introduces new React components and updates to the ledger records UI. New components such as `AmountCell`, `CounterpartyCell`, and `DateCell` are added, along with a new `columns.tsx` configuration for the ledger table. At the same time, several outdated components—`AccountDetails`, `GeneralDate`, and `LedgerRecordRow`—and their corresponding exports have been removed. The `LedgerRecordTable` has been reworked to integrate enhanced data handling, sorting capabilities via the TanStack table hooks, and dynamic row styling based on status.

## Changes

| File(s) | Change Summary |
| ------- | -------------- |
| `.../ledger-records/amount-cell.tsx`<br>`.../ledger-records/counterparty-cell.tsx`<br>`.../ledger-records/date-cell.tsx`<br>`.../ledger-records/columns.tsx` | Added new components for displaying amounts with diff highlighting, rendering counterparties with URL-based links, formatting dates with diff support, and configuring ledger table columns with sorting. |
| `.../ledger-records/cells/account-details.tsx`<br>`.../ledger-records/cells/general-date.tsx`<br>`.../ledger-records/ledger-record-row.tsx` | Removed obsolete components that previously rendered account details, general dates, and ledger record rows. |
| `.../ledger-records/cells/index.ts` | Removed exports for `AccountDetails` and `GeneralDate` to reflect component deletions. |
| `.../ledger-records/ledger-record-table.tsx` | Modified the ledger table component to incorporate advanced table state management, sorting logic, and dynamic row coloring through a new `getRowColorByStatus` function. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
  participant U as User
  participant LRT as LedgerRecordTable
  participant URT as useReactTable
  participant Table as Table Components
  participant Cell as Cell Components (DateCell, AmountCell, etc.)

  U->>LRT: Load Ledger Records Page
  LRT->>URT: Initialize table with columns & data (useMemo)
  URT-->>LRT: Return table state
  LRT->>Table: Render table structure with headers and rows
  Table->>Cell: Render each cell with appropriate component
  alt Sorting Interaction
    U->>LRT: Click header button to sort
    LRT->>URT: Update sorting state
    URT-->>LRT: Provide updated table state
    LRT->>Table: Re-render sorted table
  end
```

```mermaid
sequenceDiagram
  participant CP as CounterpartyCell
  participant UQ as useUrlQuery
  participant Browser as Browser

  CP->>UQ: Retrieve URL query parameters (chargesFilters)
  UQ-->>CP: Return filter parameters
  CP->>CP: Construct URL (getHref) using businessID and filters
  CP->>Browser: Render clickable link targeting business transactions
```

## Suggested reviewers
- TuvalSimha
- Urigo
- dotansimha

## Poem
> I hopped into the code with delight,  
> Uncovering new cells shining bright.  
> Rows and columns dance in a rhythmic flow,  
> As old components quietly let go.  
> Hoppity hops in each line I review—  
> A bunny's cheer for changes anew! 🐇  
>  
Enjoy the code trail!

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Urigo/accounter-fullstack&utm_content=1941):

- 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 generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this 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](https://docs.coderabbit.ai/guides/configure-coderabbit) 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](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-20T20:28:45Z
body: <!-- changesetsSnapshotPrCommentKey -->
### 🚀 Snapshot Release (`alpha`)

The latest changes of this PR are available as `alpha` on npm (based on the declared `changesets`):
| Package | Version | Info |
|------|---------|----|
| `@accounter/client` | `0.0.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79` | [npm ↗︎](https://www.npmjs.com/package/@accounter/client/v/0.0.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79) [unpkg ↗︎](https://unpkg.com/browse/@accounter/client@0.0.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79/) |
| `@accounter/green-invoice-graphql` | `0.7.3-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79` | [npm ↗︎](https://www.npmjs.com/package/@accounter/green-invoice-graphql/v/0.7.3-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79) [unpkg ↗︎](https://unpkg.com/browse/@accounter/green-invoice-graphql@0.7.3-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79/) |
| `@accounter/hashavshevet-mesh` | `0.2.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79` | [npm ↗︎](https://www.npmjs.com/package/@accounter/hashavshevet-mesh/v/0.2.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79) [unpkg ↗︎](https://unpkg.com/browse/@accounter/hashavshevet-mesh@0.2.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79/) |
| `@accounter/israeli-vat-scraper` | `0.1.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79` | [npm ↗︎](https://www.npmjs.com/package/@accounter/israeli-vat-scraper/v/0.1.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79) [unpkg ↗︎](https://unpkg.com/browse/@accounter/israeli-vat-scraper@0.1.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79/) |
| `@accounter/modern-poalim-scraper` | `0.9.15-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79` | [npm ↗︎](https://www.npmjs.com/package/@accounter/modern-poalim-scraper/v/0.9.15-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79) [unpkg ↗︎](https://unpkg.com/browse/@accounter/modern-poalim-scraper@0.9.15-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79/) |
| `@accounter/payper-mesh` | `0.2.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79` | [npm ↗︎](https://www.npmjs.com/package/@accounter/payper-mesh/v/0.2.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79) [unpkg ↗︎](https://unpkg.com/browse/@accounter/payper-mesh@0.2.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79/) |
| `@accounter/server` | `0.0.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79` | [npm ↗︎](https://www.npmjs.com/package/@accounter/server/v/0.0.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79) [unpkg ↗︎](https://unpkg.com/browse/@accounter/server@0.0.4-alpha-20250321123023-abfab83c9416c9935f3a0d0a77c56902d4ed0a79/) |
author_association: CONTRIBUTOR