Skip to content

feat(insights): add booking analytics dashboard#29397

Closed
regisstedile wants to merge 25 commits into
calcom:mainfrom
regisstedile:feat/insights
Closed

feat(insights): add booking analytics dashboard#29397
regisstedile wants to merge 25 commits into
calcom:mainfrom
regisstedile:feat/insights

Conversation

@regisstedile
Copy link
Copy Markdown

@regisstedile regisstedile commented May 17, 2026

What does this PR do?

Ports the Insights feature from upstream cal.com to cal.diy.

The upstream Insights module depends on EE-only packages and routing-forms internals absent from this MIT fork. This PR replaces them with:

  • Direct Prisma membership queries for team/org access control
  • Empty stubs for routing-form analytics (data model absent from cal.diy schema)
  • Standalone `userBelongsToTeam` procedure validating `selectedTeamId ?? teamId` to prevent cross-team data access

What's included

Booking analytics (fully functional)

  • KPI cards: total/completed/cancelled/rescheduled + delta vs previous period
  • Event trends chart, bookings by hour, average event duration
  • Most/least booked team members, popular events table
  • No-show hosts over time, CSAT over time
  • CSV export (paginated, UTF-8 BOM, CSV injection protection)

Routing analytics (stub)

  • Routing funnel and routed-to-per-period chart skeletons
  • Routing form responses table with column filters

Security notes

  • `userBelongsToTeam`: validates `selectedTeamId ?? teamId` — prevents bypass where callers pass a different `selectedTeamId` to read another team's data
  • CSV export: prefixes `=+-@\t\r` values with `'` to block formula injection

How should this be tested?

  1. `yarn dev` → navigate to `/insights`
  2. Create some bookings across event types and team members
  3. Verify KPI cards show correct counts and deltas
  4. Verify charts render, date range filter and team selector work
  5. Click Export → CSV downloads with correct headers and rows

Env vars needed: standard cal.diy setup only (`DATABASE_URL`, `NEXTAUTH_SECRET`, `CALENDSO_ENCRYPTION_KEY`)

Mandatory Tasks

  • I have self-reviewed the code
  • N/A — no documentation change required (internal analytics feature, no public API changes)
  • Automated tests not included — upstream tests depend on EE fixtures absent from cal.diy

@github-actions
Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @regisstedile! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough
📝 Walkthrough
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Copy link
Copy Markdown
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: 20

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (24)
apps/web/app/(use-page-wrapper)/insights/UpgradeTipWrapper.tsx-45-45 (1)

45-45: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add rel for the external _blank link.

Please set rel="noopener noreferrer" on the external “learn more” button to avoid opener access.

🔧 Suggested patch
-            <Button color="minimal" href="https://go.cal.com/insights" target="_blank">
+            <Button
+              color="minimal"
+              href="https://go.cal.com/insights"
+              target="_blank"
+              rel="noopener noreferrer">
               {t("learn_more")}
             </Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(use-page-wrapper)/insights/UpgradeTipWrapper.tsx at line 45,
The external "learn more" Button in UpgradeTipWrapper should include
rel="noopener noreferrer" when using target="_blank" to prevent opener access;
update the Button element in UpgradeTipWrapper.tsx (the Button with props
color="minimal" href="https://go.cal.com/insights" target="_blank") to add
rel="noopener noreferrer" so the external link is opened safely.
apps/web/modules/insights/components/ChartCard.tsx-139-139 (1)

139-139: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the legend toggle aria-label.

aria-label={Toggle ${item.label}} is hard-coded English and should go through translations.

As per coding guidelines: "Add translations to packages/i18n/locales/en/common.json for all UI strings".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/ChartCard.tsx` at line 139, Replace the
hard-coded aria-label in ChartCard (aria-label={`Toggle ${item.label}`}) with a
localized string: add a new key (e.g. "chart.legendToggle") to the common.json
i18n file and use the i18n translator in the ChartCard component (e.g. const { t
} = useTranslation('common')) then set aria-label={t('chart.legendToggle', {
label: item.label })} so the label is translated while preserving the dynamic
item.label parameter.
apps/web/modules/insights/components/booking/BookingsByHourChart.tsx-60-60 (1)

60-60: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Drop the “what” comment above CustomTooltip.

It repeats the code intent and doesn’t add reasoning.
As per coding guidelines: "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/BookingsByHourChart.tsx` at line
60, Remove the redundant “// Custom Tooltip component” comment that sits above
the CustomTooltip declaration; locate the CustomTooltip component (function or
const named CustomTooltip) and delete that single-line comment so only
meaningful comments that explain why remain.
apps/web/modules/insights/components/booking/BookingKPICards.tsx-166-172 (1)

166-172: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove comments that only restate the code.

These comments describe what the functions do and add noise without intent/context.
As per coding guidelines: "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/BookingKPICards.tsx` around
lines 166 - 172, Remove the redundant "StatContainer: wraps the grid" and
"StatItem: handles border logic" comments that merely restate the function names
and behavior; edit the BookingKPICards.tsx snippet to delete those two comment
lines above the StatContainer and StatItem function declarations so only code
(or meaningful "why" comments if needed) remains.
apps/web/modules/insights/components/booking/EventTrendsChart.tsx-32-32 (1)

32-32: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the tooltip “what” comment.

It repeats what the code already makes obvious.
As per coding guidelines: "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/EventTrendsChart.tsx` at line
32, Remove the redundant "what" comment that restates the code: delete the line
containing "// Custom Tooltip component" in EventTrendsChart.tsx (the comment
above the CustomTooltip implementation) so only meaningful "why" comments remain
and the code stays clean and adheres to the guideline.
apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx-16-16 (1)

16-16: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the redundant tooltip comment.

This comment restates the following declaration and doesn’t capture rationale.
As per coding guidelines: "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx` at line
16, Remove the redundant comment "// Custom Tooltip component" which merely
restates the declaration; delete this line near the CustomTooltip (or tooltip)
component declaration in CSATOverTimeChart.tsx so only meaningful "why" comments
remain and avoid duplicative "what" comments.
apps/web/modules/insights/components/booking/EventTrendsChart.tsx-21-28 (1)

21-28: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize legend labels instead of hardcoding English strings.

The labels are user-facing and currently bypass translation, so non-English locales will still see English legend/tooltips. Keep a stable key for dataKey and derive a translated label for display.
As per coding guidelines: "Add translations to packages/i18n/locales/en/common.json for all UI strings."

Suggested direction
-export const legend = [
-  { label: "Created", color: COLOR.CREATED },
-  { label: "Completed", color: COLOR.COMPLETED },
-  { label: "Rescheduled", color: COLOR.RESCHEDULED },
-  { label: "Cancelled", color: COLOR.CANCELLED },
-  { label: "No-Show (Host)", color: COLOR.NO_SHOW_HOST },
-  { label: "No-Show (Guest)", color: COLOR.NO_SHOW_GUEST },
-];
+export const legend = [
+  { key: "Created", labelKey: "events_created", color: COLOR.CREATED },
+  { key: "Completed", labelKey: "events_completed", color: COLOR.COMPLETED },
+  { key: "Rescheduled", labelKey: "events_rescheduled", color: COLOR.RESCHEDULED },
+  { key: "Cancelled", labelKey: "events_cancelled", color: COLOR.CANCELLED },
+  { key: "No-Show (Host)", labelKey: "event_no_show", color: COLOR.NO_SHOW_HOST },
+  { key: "No-Show (Guest)", labelKey: "event_no_show_guest", color: COLOR.NO_SHOW_GUEST },
+];

Also applies to: 104-110

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/EventTrendsChart.tsx` around
lines 21 - 28, The legend array currently hardcodes English labels — replace the
static label strings in the exported legend with stable keys (keep the existing
legend variable name and dataKey values) and derive display labels via the app
i18n translator (e.g., call translation function for each key when rendering
tooltips/legend) so UI text is localized; also add corresponding entries for
each legend key to packages/i18n/locales/en/common.json (e.g., created,
completed, rescheduled, cancelled, no_show_host, no_show_guest) to satisfy the
guideline and ensure translations are available.
apps/web/modules/insights/components/booking/BookingsByHourChart.tsx-29-30 (1)

29-30: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix empty-data detection for the no-data state.

When data is empty, Math.max(...[]) becomes -Infinity, so isEmpty is false and the empty-state message never renders.

Suggested fix
-  const maxBookings = Math.max(...data.map((item) => item.count));
-  const isEmpty = maxBookings === 0;
+  const isEmpty = data.length === 0 || data.every((item) => item.count === 0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/BookingsByHourChart.tsx` around
lines 29 - 30, The empty-data detection is broken because
Math.max(...data.map(...)) yields -Infinity when data is empty; in
BookingsByHourChart update the calculation of maxBookings to account for empty
arrays (e.g., use a fallback or conditional) so that when data.length === 0
maxBookings becomes 0 and isEmpty correctly becomes true; change the expression
that computes maxBookings (referencing maxBookings, isEmpty, and data) to return
0 for an empty data set before computing Math.max over mapped counts.
apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx-18-21 (1)

18-21: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a unique key per feedback row, not userId alone.

userId can repeat in recent ratings, which can produce duplicate keys and unstable row reconciliation.

Suggested change
-        data.map((item) => (
+        data.map((item, index) => (
           <div
-            key={item.userId}
+            key={`${item.userId}-${index}`}
             className="border-subtle flex items-center justify-between border-b px-4 py-3 last:border-b-0">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx`
around lines 18 - 21, In RecentFeedbackTableContent, the JSX map uses
item.userId as the element key which can be duplicated; change the key in the
data.map callback to a unique, stable identifier (e.g. item.id or a
concatenation like `${item.userId}-${item.id}` or
`${item.userId}-${item.createdAt}`) and only fall back to the map index if no
unique id exists so each feedback row rendered by data.map has a truly unique
key instead of userId alone.
apps/web/modules/insights/components/booking/NoShowHostsOverTimeChart.tsx-17-17 (1)

17-17: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove non-informative comment text.

This comment only restates the code and adds noise; please drop it or replace with rationale if there is one.

Suggested change
-// Custom Tooltip component
 const CustomTooltip = ({
As per coding guidelines, "Only add code comments that explain why, not what — follow code comment guidelines for quality comments."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/NoShowHostsOverTimeChart.tsx` at
line 17, Remove the non-informative inline comment "// Custom Tooltip component"
inside the NoShowHostsOverTimeChart component file; either delete it or replace
it with a brief rationale describing why a custom Tooltip is needed (for
example: "Custom Tooltip to format date and metric for chart hover") and
reference the Tooltip implementation so reviewers understand its purpose.
apps/web/modules/insights/components/booking/PopularEventsTable.tsx-28-40 (1)

28-40: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Empty-state condition uses the wrong dataset.

You render from a filtered list, but the empty-state check uses unfiltered data.length. When all items are filtered out, the card appears blank.

Suggested change
   return (
     <ChartCard title={t("popular_events")} isPending={isPending} isError={isError}>
       {isSuccess && data ? (
         <>
+          {(() => {
+            const visibleItems = data.filter((item) => item.eventTypeId);
+            return (
+              <>
           <div>
-            {data
-              .filter((item) => item.eventTypeId)
-              .map((item) => (
+                {visibleItems.map((item) => (
                 <ChartCardItem key={item.eventTypeId} count={item.count}>
                   <div className="flex">
                     <div className="bg-subtle mr-1.5 h-5 w-[2px] shrink-0 rounded-sm" />
                     <p>{item.eventTypeName}</p>
                   </div>
                 </ChartCardItem>
               ))}
           </div>
-          {data.length === 0 && (
+              {visibleItems.length === 0 && (
             <div className="flex h-60 text-center">
               <p className="m-auto text-sm font-light">{t("insights_no_data_found_for_filter")}</p>
             </div>
           )}
+              </>
+            );
+          })()}
         </>
       ) : null}
     </ChartCard>
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/PopularEventsTable.tsx` around
lines 28 - 40, The empty-state check in PopularEventsTable uses the original
`data.length` while the UI renders `data.filter((item) => item.eventTypeId)`,
causing a blank card when all entries are filtered out; fix by computing and
using a filtered array (e.g., `const filtered = data.filter(item =>
item.eventTypeId)`) and render `filtered.map(...)` and check `filtered.length
=== 0` for the empty state instead of `data.length`. Ensure all references to
mapping/empty checks (the map that renders `ChartCardItem` and the empty-state
conditional) use this `filtered` variable.
apps/web/modules/insights/components/booking/RecentNoShowGuestsChart.tsx-28-31 (1)

28-31: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Track copied state per row instead of using one global copied flag.

isCopied is shared for the whole component, so copying one email flips all row buttons to the copied state.

💡 Suggested fix
 import { useCopy } from "`@calcom/lib/hooks/useCopy`";
@@
-import { ChartCard, ChartCardItem } from "../ChartCard";
+import { ChartCard, ChartCardItem } from "../ChartCard";
+import { useState } from "react";
@@
   const { copyToClipboard, isCopied } = useCopy();
+  const [copiedEmail, setCopiedEmail] = useState<string | null>(null);
@@
   const handleCopyEmail = (email: string) => {
     copyToClipboard(email);
+    setCopiedEmail(email);
     showToast(t("email_copied"), "success");
   };
@@
-                    StartIcon={isCopied ? "clipboard-check" : "clipboard"}
+                    StartIcon={isCopied && copiedEmail === item.guestEmail ? "clipboard-check" : "clipboard"}
                     onClick={() => handleCopyEmail(item.guestEmail)}>
-                    {!isCopied ? t("email") : t("copied")}
+                    {isCopied && copiedEmail === item.guestEmail ? t("copied") : t("email")}
                   </Button>

Also applies to: 65-67

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/booking/RecentNoShowGuestsChart.tsx`
around lines 28 - 31, The component currently uses a single shared isCopied flag
so clicking any "copy" toggles every row; change to track copied state per row
by replacing the single boolean with a map keyed by a unique row identifier
(email or guest id) in the component state, update handleCopyEmail(email) (and
the other copy handlers referenced around the lines 65-67) to set only that key
to true and reset it after a timeout, and pass the per-row value (e.g.,
copiedMap[email]) into the button/renderer so only the clicked row shows the
copied state. Ensure you clear/reset the specific key rather than mutating a
global flag.
apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx-25-29 (1)

25-29: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize legend labels instead of hard-coding English text.

These labels are user-facing and should come from translation keys like the rest of the chart copy.

As per coding guidelines: "Add translations to packages/i18n/locales/en/common.json for all UI strings".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx` around
lines 25 - 29, The legend array currently hard-codes English labels; replace
these with translated strings by using the app's i18n mechanism (e.g., call to
t('...') from useTranslation) when constructing legend in
RoutingFunnelContent.tsx: update the three labels for "Total Submissions",
"Successful Routings", and "Accepted Bookings" to translation keys (add
corresponding entries to packages/i18n/locales/en/common.json) and import/use
the translation hook so legend = [{ label: t('insights.totalSubmissions'),
color: COLOR.TOTAL }, { label: t('insights.successfulRoutings'), color:
COLOR.SUCCESFUL }, { label: t('insights.acceptedBookings'), color:
COLOR.ACCEPTED }].
apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx-141-142 (1)

141-142: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a stable unique checkbox id for each team row.

Using team.name || "" can create duplicate/empty id values, which breaks label targeting and keyboard accessibility.

💡 Suggested fix
-              id={team.name || ""}
+              id={`team-${team.id}`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx` around lines
141 - 142, The checkbox id/label currently use team.name which can be empty or
duplicate; update OrgTeamsFilter so each checkbox uses a stable unique
identifier (e.g., use team.id or team.slug) instead of team.name, and ensure the
label prop still displays the human-readable team.name; replace the id and label
assignments in the checkbox rendering (the JSX that sets id={team.name || ""}
and label={team.name || ""}) to id={`team-${team.id || team.slug}`} (or directly
id={team.id} if present) and label={team.name} so ids are unique and labels
remain correct.
apps/web/modules/insights/components/routing/FailedBookingsByField.tsx-120-123 (1)

120-123: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove debug JSON output and localize this empty-state message.

This branch currently exposes debug payload text to end users and bypasses localization.

💡 Suggested fix
-          <div className="mt-4 p-4 text-center text-gray-500">
-            No data available for selected field
-            <div className="mt-2 text-xs">Data: {JSON.stringify(selectedFieldData, null, 2)}</div>
-          </div>
+          <div className="mt-4 p-4 text-center text-gray-500">
+            {t("insights_no_data_found_for_filter")}
+          </div>

As per coding guidelines: "Add translations to packages/i18n/locales/en/common.json for all UI strings".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/routing/FailedBookingsByField.tsx`
around lines 120 - 123, Remove the debug JSON output and replace the hard-coded
empty-state string with a localized translation: delete the inner <div> that
outputs Data: {JSON.stringify(selectedFieldData, null, 2)} and replace the text
"No data available for selected field" in the FailedBookingsByField component
with a translation lookup (e.g. useTranslation hook and
t('insights.noDataForField') or the project's i18n helper). Add the new key
"insights.noDataForField": "No data available for selected field" to
packages/i18n/locales/en/common.json so the string is localized.
apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx-37-45 (1)

37-45: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid render-time randomness for skeleton bar heights.

Math.random() in render makes the skeleton jump on re-renders. Use fixed/mock heights for stable UI.

💡 Suggested fix
+const BAR_HEIGHTS = [
+  { a: "62%", b: "41%", c: "24%" },
+  { a: "58%", b: "39%", c: "21%" },
+  { a: "64%", b: "44%", c: "26%" },
+  { a: "55%", b: "36%", c: "20%" },
+  { a: "61%", b: "40%", c: "23%" },
+  { a: "57%", b: "37%", c: "22%" },
+  { a: "63%", b: "42%", c: "25%" },
+];
...
-              {Array.from({ length: 7 }).map((_, i) => (
+              {BAR_HEIGHTS.map((h, i) => (
                 <div key={i} className="flex w-8 flex-col items-center">
                   <div
                     className="w-full rounded-t bg-[`#8884d8`] opacity-20"
-                    style={{ height: `${Math.random() * 40 + 30}%` }}
+                    style={{ height: h.a }}
                   />
                   <div
                     className="w-full bg-[`#83a6ed`] opacity-20"
-                    style={{ height: `${Math.random() * 30 + 20}%` }}
+                    style={{ height: h.b }}
                   />
                   <div
                     className="w-full rounded-b bg-[`#82ca9d`] opacity-20"
-                    style={{ height: `${Math.random() * 20 + 10}%` }}
+                    style={{ height: h.c }}
                   />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx`
around lines 37 - 45, RoutingFunnelSkeleton uses Math.random() directly in
render for the three skeleton bar heights which causes visual jumps on
re-renders; replace the render-time randomness by using fixed/mock heights
(e.g., a constant array of percentages or heights) or derive deterministic
values once (outside the component or via a useMemo) and reference those values
in the three divs whose style currently uses Math.random(); update the three
style={{ height: ... }} occurrences in the RoutingFunnelSkeleton component to
use the fixed/deterministic heights instead of Math.random().
apps/web/modules/insights/hooks/useInsightsColumns.tsx-345-345 (1)

345-345: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include t in the useMemo dependency array.

The memoized columns use translated headers; without t in deps, headers can stay stale after locale changes.

Proposed change
-  }, [isHeadersSuccess, headers]);
+  }, [isHeadersSuccess, headers, t]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useInsightsColumns.tsx` at line 345, The
memoized columns in useInsightsColumns (the useMemo call that depends on
isHeadersSuccess and headers) uses the translation function t but does not
include t in its dependency array; update the useMemo dependency array to
include t so translated header labels refresh on locale changes, keeping
isHeadersSuccess and headers as well.
apps/web/modules/insights/hooks/useDefaultRoutingForm.ts-22-22 (1)

22-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use stricter route matching for insights routing pages.

includes("/insights/routing") can produce false positives on unrelated paths containing that substring.

Proposed change
-  const isRoutingInsights = pathname?.includes("/insights/routing");
+  const isRoutingInsights = pathname?.startsWith("/insights/routing") ?? false;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useDefaultRoutingForm.ts` at line 22, The
current boolean isRoutingInsights uses pathname?.includes("/insights/routing")
which can match unintended paths; update the check in useDefaultRoutingForm
(isRoutingInsights) to perform strict route matching instead—e.g., use
pathname?.startsWith("/insights/routing") or a regex anchor like
^/insights/routing(/|$) so only the exact routing pages (and their subpaths)
return true.
apps/web/modules/insights/hooks/useInsightsColumns.tsx-278-321 (1)

278-321: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize UTM header labels instead of hardcoded strings.

These are user-facing table headers and should go through t(...).

Proposed change
-        header: "utm_medium",
+        header: t("utm_medium"),
...
-        header: "utm_term",
+        header: t("utm_term"),
...
-        header: "utm_content",
+        header: t("utm_content"),
...
-        header: "utm_campaign",
+        header: t("utm_campaign"),

As per coding guidelines "Add translations to packages/i18n/locales/en/common.json for all UI strings".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useInsightsColumns.tsx` around lines 278 -
321, The headers for UTM columns in useInsightsColumns (the
columnHelper.accessor entries for "utm_medium", "utm_term", "utm_content", and
"utm_campaign") are hardcoded strings; change each header value to call the i18n
translator (t("...")) with new keys (e.g., insights.utm_medium,
insights.utm_term, insights.utm_content, insights.utm_campaign) and add those
keys with English values to packages/i18n/locales/en/common.json so the table
headers are localized.
apps/web/modules/insights/views/insights-virtual-queues-view.tsx-38-38 (1)

38-38: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the select placeholder.

Line 38 uses a hardcoded UI string ("Select project"). Please replace it with a translation key.

As per coding guidelines: "Add translations to packages/i18n/locales/en/common.json for all UI strings".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/views/insights-virtual-queues-view.tsx` at line 38,
Replace the hardcoded placeholder="Select project" in the
InsightsVirtualQueuesView component with a translation key (e.g.,
placeholder={t('insights.selectProject')}) using the existing i18n hook or
useTranslation import in that file, and add the corresponding key/value
("insights.selectProject": "Select project") to
packages/i18n/locales/en/common.json; ensure the component imports and calls the
translation function (t) where needed so the placeholder uses the localized
string.
apps/web/modules/insights/hooks/useToggleableLegend.ts-4-6 (1)

4-6: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Sync enabledSeries with legend changes to prevent stale selections.

If legend updates after mount, enabledSeries can contain removed labels, potentially resulting in an empty enabledLegend when the new legend has no matching items. While legend is currently static in all usages, this pattern is brittle and will break if legend becomes dynamic.

Suggested change
-import { useCallback, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
@@
   const [enabledSeries, setEnabledSeries] = useState<string[]>(
     initialEnabled ?? legend.map((item) => item.label)
   );
+
+  useEffect(() => {
+    const allowed = new Set(legend.map((item) => item.label));
+    setEnabledSeries((prev) => {
+      const next = prev.filter((label) => allowed.has(label));
+      if (next.length > 0) return next;
+      return (initialEnabled ?? legend.map((item) => item.label)).filter((label) => allowed.has(label));
+    });
+  }, [legend, initialEnabled]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useToggleableLegend.ts` around lines 4 - 6,
The hook useToggleableLegend currently initializes enabledSeries from
initialEnabled or legend but never updates it when legend changes; add a
useEffect in useToggleableLegend that runs on changes to legend (and optionally
initialEnabled) that computes legendLabels = legend.map(item => item.label),
filters the current enabledSeries to only labels present in legendLabels, and
then calls setEnabledSeries(filtered) — but if the filtered array is empty, fall
back to legendLabels to avoid producing an empty enabledLegend; reference
enabledSeries, setEnabledSeries, legend and initialEnabled when making this
change.
packages/features/insights/server/insightsDateUtils.ts-151-153 (1)

151-153: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove extra whitespace in weekly label formatting.

"${startFormat} , YYYY" renders with an extra space before the comma in user-facing labels.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/insights/server/insightsDateUtils.ts` around lines 151 -
153, The weekly label includes an extra space before the comma because the
template uses `${startFormat} , YYYY` and `${endFormat}, YYYY`; update the
formatting in the branch where start.format("YYYY") !== end.format("YYYY") to
remove the stray space so both use `${startFormat}, YYYY` and `${endFormat},
YYYY` (adjust the string passed to start.format and end.format accordingly) to
eliminate the unintended whitespace in user-facing labels.
packages/features/insights/server/raw-data.schema.ts-3-3 (1)

3-3: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align with codebase pattern: use named import for Zod.

The majority of the codebase uses import { z } from "zod"; (named import). Switch to this pattern for consistency.

Suggested fix
-import z from "zod";
+import { z } from "zod";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/insights/server/raw-data.schema.ts` at line 3, Replace the
default import of Zod with the codebase-standard named import: change the import
statement `import z from "zod";` to `import { z } from "zod";` so that all
references to `z` (the Zod identifier used in this module) follow the project's
consistent import pattern.
packages/features/insights/services/csvDataTransformer.ts-71-74 (1)

71-74: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize array elements before joining extracted values

extractFieldValue joins raw array entries directly, so object entries serialize as "[object Object]" and corrupt CSV output.

💡 Proposed fix
   if (Array.isArray(value)) {
-    const filtered = value.filter((v) => v != null && v !== "");
-    return filtered.length > 0 ? filtered.join(", ") : null;
+    const normalized = value
+      .map((v) => extractFieldValue(v))
+      .filter((v): v is string => v != null && v !== "");
+    return normalized.length > 0 ? normalized.join(", ") : null;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/insights/services/csvDataTransformer.ts` around lines 71 -
74, extractFieldValue currently joins array elements raw which yields "[object
Object]" for objects; update the Array.isArray(value) branch to first
map/normalize each element (e.g., for each v: if v == null return null/skip,
else if typeof v === "object" use JSON.stringify(v), else if v instanceof Date
use v.toISOString(), else use String(v)), then filter out null/empty strings and
join with ", " as before so object/complex entries serialize correctly; keep
this logic inside the extractFieldValue function where value is handled as an
array.
🧹 Nitpick comments (13)
apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx (1)

1-1: ⚡ Quick win

Use native Date or date-fns for filename date formatting.

Day.js isn’t necessary for this non-timezone formatting path.

💡 Suggested fix
-import dayjs from "`@calcom/dayjs`";
@@
+const formatDateForFile = (value: Date | string) => new Date(value).toISOString().slice(0, 10);
@@
-        const filename = `RoutingFormResponses-${dayjs(startDate).format("YYYY-MM-DD")}-${dayjs(
-          endDate
-        ).format("YYYY-MM-DD")}.csv`;
+        const filename = `RoutingFormResponses-${formatDateForFile(startDate)}-${formatDateForFile(
+          endDate
+        )}.csv`;
As per coding guidelines: Use `date-fns` or native `Date` instead of Day.js when timezone awareness isn't needed.

Also applies to: 73-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx`
at line 1, Replace the Day.js dependency usage in RoutingFormResponsesDownload
(remove the import "dayjs" and any calls to dayjs(...).format(...)) with either
native Date or date-fns formatting for the filename generation logic; locate the
filename construction inside the RoutingFormResponsesDownload component (and the
date formatting calls around lines ~73-75) and change those calls to use new
Date() with toISOString()/toLocaleString or date-fns/format(date, pattern) so
timezone-aware libraries aren’t pulled in for simple non-timezone formatting,
and update imports accordingly.
apps/web/modules/insights/components/filters/Download/Download.tsx (1)

1-1: ⚡ Quick win

Prefer native Date or date-fns here instead of Day.js.

This filename formatting path doesn’t need timezone-aware Day.js behavior.

💡 Suggested fix
-import dayjs from "`@calcom/dayjs`";
@@
+const formatDateForFile = (value: Date | string) => new Date(value).toISOString().slice(0, 10);
@@
-        const filename = `Insights-${dayjs(startDate).format("YYYY-MM-DD")}-${dayjs(endDate).format(
-          "YYYY-MM-DD"
-        )}.csv`;
+        const filename = `Insights-${formatDateForFile(startDate)}-${formatDateForFile(endDate)}.csv`;
As per coding guidelines: Use `date-fns` or native `Date` instead of Day.js when timezone awareness isn't needed.

Also applies to: 80-82

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/filters/Download/Download.tsx` at line
1, The file Download.tsx imports Day.js via the identifier dayjs; replace that
import and all usages of the dayjs identifier (search for "dayjs" and usages
around the Download component and the block referenced at lines ~80-82) with
either native Date or date-fns utilities (e.g., parse/format functions) so
timezone-aware Day.js is not used; update any formatting/parsing calls to the
chosen API, remove the dayjs import, and adjust tests/types if needed to match
date-fns/native Date function signatures.
apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx (1)

10-10: ⚡ Quick win

Import FilterSearchField from its direct source file instead of the form barrel.

This keeps import boundaries aligned with repo standards and avoids barrel coupling.

As per coding guidelines: "Import directly from source files, not barrel files (e.g., @calcom/ui/components/button not @calcom/ui)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx` at line 10,
Replace the barrel import of FilterSearchField with a direct import from its
source module: change the import statement that currently reads "import {
FilterSearchField } from '`@calcom/ui/components/form`';" to import
FilterSearchField directly from its component file (for example "import
FilterSearchField from '`@calcom/ui/components/form/FilterSearchField`'"),
updating the import path to the correct direct source file so the OrgTeamsFilter
component uses the direct module export.
apps/web/modules/insights/components/routing/RoutingFormResponsesTable.tsx (1)

23-23: ⚡ Quick win

Import TimezoneBadge from its concrete source file, not the booking barrel.

This keeps dependency direction explicit and avoids growing barrel coupling.

As per coding guidelines: "Import directly from source files, not barrel files (e.g., @calcom/ui/components/button not @calcom/ui)"; "Never use barrel imports from index.ts files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/routing/RoutingFormResponsesTable.tsx`
at line 23, The import currently pulls TimezoneBadge from the booking barrel;
replace that barrel import with a direct import from the concrete source file
that actually defines TimezoneBadge (update the import in
RoutingFormResponsesTable.tsx to point to the TimezoneBadge implementation file
instead of "`@calcom/web/modules/insights/components/booking`") so dependency
direction remains explicit and avoids barrel coupling.
apps/web/modules/insights/hooks/useInsightsColumns.tsx (1)

1-1: ⚡ Quick win

Replace Day.js usage here with native Date/date-fns.

This formatting path does not need Day.js-specific timezone behavior.

As per coding guidelines "Use date-fns or native Date instead of Day.js when timezone awareness isn't needed".

Also applies to: 340-340

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useInsightsColumns.tsx` at line 1, Replace
Day.js usage in the useInsightsColumns hook by removing the import of dayjs and
switching any dayjs-based parsing/formatting to native Date or date-fns calls;
locate the hook named useInsightsColumns and any occurrences around the noted
area (including the call near line ~340) and update those Date operations
(parsing, formatting, comparisons) to use Date or date-fns equivalents, then
remove the import line `import dayjs from "`@calcom/dayjs`";` and any
Day.js-specific APIs so timezone-aware behavior is not used.
apps/web/modules/insights/hooks/useDefaultRoutingForm.ts (1)

48-48: ⚡ Quick win

Use router.replace when auto-applying default routingFormId.

This is an automatic normalization step, so replace avoids unnecessary back-stack entries.

Proposed change
-      router.push(`${pathname}?${newSearchParams.toString()}`);
+      router.replace(`${pathname}?${newSearchParams.toString()}`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useDefaultRoutingForm.ts` at line 48, The
code currently uses router.push to update the URL when auto-applying the default
routingFormId; replace that call with router.replace to avoid adding a new
history entry. Locate the
router.push(`${pathname}?${newSearchParams.toString()}`) invocation in
useDefaultRoutingForm (or the hook body) and change it to
router.replace(`${pathname}?${newSearchParams.toString()}`) so the normalization
updates the URL without creating a back-stack entry.
apps/web/modules/insights/hooks/useInsightsBookingParameters.ts (1)

1-1: ⚡ Quick win

Prefer native Date/date-fns over Day.js for this date-range defaulting logic.

This path only normalizes day boundaries and does not require timezone-aware Day.js behavior.

As per coding guidelines "Use date-fns or native Date instead of Day.js when timezone awareness isn't needed".

Also applies to: 32-33

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useInsightsBookingParameters.ts` at line 1,
The file imports dayjs and uses it to normalize date-range boundaries in the
useInsightsBookingParameters hook; replace Day.js with native Date or date-fns:
remove the import of dayjs, and change any dayjs(...).startOf('day') /
dayjs(...).endOf('day') uses (notably around the defaulting logic referenced on
lines 32-33) to either date-fns startOfDay()/endOfDay() or native Date
normalization (new Date(date).setHours(0,0,0,0) and setHours(23,59,59,999)),
updating the relevant variables and any tests/types accordingly so the hook
still returns the same start/end-day values without timezone-aware Day.js.
apps/web/modules/insights/hooks/useInsightsBookings.ts (1)

1-1: ⚡ Quick win

Import ColumnFilterType from its source module, not the package barrel.

Use the concrete module path to align with the no-barrel-import rule.

As per coding guidelines "Import directly from source files, not barrel files (e.g., @calcom/ui/components/button not @calcom/ui)" and "Never use barrel imports from index.ts files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useInsightsBookings.ts` at line 1, The file
useInsightsBookings.ts currently imports ColumnFilterType via the package barrel
("`@calcom/features/data-table`"); update the import to reference the concrete
source module that actually exports ColumnFilterType (the specific module inside
the data-table package where ColumnFilterType is defined) instead of the barrel
export, by replacing the import statement for ColumnFilterType with a direct
import from that source module so it follows the no-barrel-import rule.
apps/web/modules/insights/components/routing/RoutingKPICards.tsx (1)

3-3: ⚡ Quick win

Avoid barrel import for valueFormatter.

Import this utility from its source file instead of @calcom/features/insights/lib to keep dependency boundaries explicit.

Proposed change
-import { valueFormatter } from "`@calcom/features/insights/lib`";
+import { valueFormatter } from "`@calcom/features/insights/lib/valueFormatter`";

As per coding guidelines "Import directly from source files, not barrel files (e.g., @calcom/ui/components/button not @calcom/ui)" and "Never use barrel imports from index.ts files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/routing/RoutingKPICards.tsx` at line 3,
The file RoutingKPICards.tsx currently imports valueFormatter from the barrel
module "`@calcom/features/insights/lib`"; change that to import valueFormatter
directly from its defining source file (the module that actually exports the
utility, e.g., the specific utils/ or valueFormatter file inside the
features/insights package) to avoid barrel imports—update the import statement
in RoutingKPICards.tsx to point to the concrete source module exporting
valueFormatter.
apps/web/modules/insights/views/insights-routing-view.tsx (1)

6-11: ⚡ Quick win

Avoid barrel import for routing components.

Lines 6-11 import from a barrel path; switch to direct source-file imports for each component.

As per coding guidelines: "Import directly from source files, not barrel files" and "Never use barrel imports from index.ts files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/views/insights-routing-view.tsx` around lines 6 -
11, Replace the barrel import that pulls FailedBookingsByField,
RoutedToPerPeriod, RoutingFormResponsesTable, and RoutingFunnel from
"`@calcom/web/modules/insights/components/routing`" with direct imports from each
component's source file; update the import statement(s) so each symbol
(FailedBookingsByField, RoutedToPerPeriod, RoutingFormResponsesTable,
RoutingFunnel) is imported from its own module file (e.g., the component's
.tsx/.ts source) instead of the index barrel.
apps/web/modules/insights/views/insights-view.tsx (1)

9-28: ⚡ Quick win

Replace booking barrel import with direct file imports.

Lines 9-28 currently pull from a barrel; import each booking component from its concrete source file instead.

As per coding guidelines: "Import directly from source files, not barrel files" and "Never use barrel imports from index.ts files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/views/insights-view.tsx` around lines 9 - 28, The
current import statement pulls many booking components from a barrel; replace
that single barrel import with direct imports from each component's concrete
source file by importing AverageEventDurationChart, BookingKPICards,
BookingsByHourChart, CSATOverTimeChart, EventTrendsChart,
HighestNoShowHostTable, HighestRatedMembersTable, LeastBookedTeamMembersTable,
LeastCompletedTeamMembersTable, LowestRatedMembersTable,
MostBookedTeamMembersTable, MostCancelledBookingsTables,
MostCompletedTeamMembersTable, NoShowHostsOverTimeChart, PopularEventsTable,
RecentFeedbackTable, RecentNoShowGuestsChart, and TimezoneBadge from their
individual module files (the actual component source files) instead of the
barrel index.
apps/web/modules/insights/hooks/useInsightsRoutingParameters.ts (1)

1-3: ⚡ Quick win

Replace Day.js here with native Date or date-fns.

Line 1 and Lines 12-22 only need day-boundary normalization + ISO conversion, so dayjs is unnecessary in this hook.

Suggested change
-import dayjs from "`@calcom/dayjs`";
 import { useColumnFilters, useFilterValue, ZDateRangeFilterValue } from "`@calcom/features/data-table`";
 import { getDefaultEndDate, getDefaultStartDate } from "`@calcom/features/data-table/lib/dateRange`";
 import { useMemo } from "react";
 import { useInsightsOrgTeams } from "./useInsightsOrgTeams";
@@
   const startDate = useMemo(() => {
-    return dayjs(createdAtRange?.startDate ?? getDefaultStartDate().toISOString())
-      .startOf("day")
-      .toISOString();
+    const d = createdAtRange?.startDate ? new Date(createdAtRange.startDate) : getDefaultStartDate();
+    d.setHours(0, 0, 0, 0);
+    return d.toISOString();
   }, [createdAtRange?.startDate]);
@@
   const endDate = useMemo(() => {
-    return dayjs(createdAtRange?.endDate ?? getDefaultEndDate().toISOString())
-      .endOf("day")
-      .toISOString();
+    const d = createdAtRange?.endDate ? new Date(createdAtRange.endDate) : getDefaultEndDate();
+    d.setHours(23, 59, 59, 999);
+    return d.toISOString();
   }, [createdAtRange?.endDate]);

As per coding guidelines: "Use date-fns or native Date instead of Day.js when timezone awareness isn't needed".

Also applies to: 12-22

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/hooks/useInsightsRoutingParameters.ts` around lines
1 - 3, Remove the dayjs import and replace its usage in
useInsightsRoutingParameters with native Date or date-fns utilities: for any
normalization currently done with dayjs(...).startOf('day').toISOString() or
.endOf('day').toISOString(), use date-fns startOfDay/endOfDay + toISOString (or
set hours/minutes/seconds on a native Date and call toISOString()). Update the
logic in the useInsightsRoutingParameters hook (references: variables/params
named start, end and any helper normalization code around lines 12-22) to call
getDefaultStartDate/getDefaultEndDate as before but normalize using
startOfDay(endOfDay) or equivalent native Date adjustments and return ISO
strings; remove the dayjs import line.
packages/features/insights/server/events.ts (1)

15-22: ⚡ Quick win

Make reducer increments resilient to unseen status keys.

aggregate[item.timeStatus] += ... can produce NaN for any status not preinitialized in the seed object. Initialize on first use before incrementing.

Proposed fix
-        if (typeof item.timeStatus === "string" && item) {
-          aggregate[item.timeStatus] += item?._count?._all ?? 0;
-          aggregate["_all"] += item?._count?._all ?? 0;
+        if (typeof item.timeStatus === "string") {
+          const count = item?._count?._all ?? 0;
+          aggregate[item.timeStatus] = (aggregate[item.timeStatus] ?? 0) + count;
+          aggregate["_all"] += count;
 
           if (item.noShowHost) {
-            aggregate["noShowHost"] += item?._count?._all ?? 0;
+            aggregate["noShowHost"] += count;
           }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/insights/server/events.ts` around lines 15 - 22, The
reducer in events.ts uses aggregate[item.timeStatus] without ensuring the key
exists, causing NaN for unseen statuses; update the reducer (the function with
parameters aggregate and item that references item.timeStatus and fields like
item._count._all, aggregate["_all"], aggregate["noShowHost"]) to lazily
initialize aggregate[item.timeStatus] = 0 (and aggregate["noShowHost"] where
applicable) before any += operation so increments are safe for new status keys.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 710b2798-0bb3-4ba3-9793-8d07046ba4e1

📥 Commits

Reviewing files that changed from the base of the PR and between 180ede2 and d46f2cd.

📒 Files selected for processing (94)
  • apps/web/app/(use-page-wrapper)/insights/UpgradeTipWrapper.tsx
  • apps/web/app/(use-page-wrapper)/insights/call-history/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/checkInsightsPagePermission.ts
  • apps/web/app/(use-page-wrapper)/insights/layout.tsx
  • apps/web/app/(use-page-wrapper)/insights/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/router-position/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/routing/page.tsx
  • apps/web/modules/insights/components/BookedByCell.tsx
  • apps/web/modules/insights/components/BookingAtCell.tsx
  • apps/web/modules/insights/components/BookingStatusBadge.tsx
  • apps/web/modules/insights/components/CellWithOverflowX.tsx
  • apps/web/modules/insights/components/ChartCard.tsx
  • apps/web/modules/insights/components/KPICard.tsx
  • apps/web/modules/insights/components/ResponseValueCell.tsx
  • apps/web/modules/insights/components/UserStatsTable.tsx
  • apps/web/modules/insights/components/booking/AverageEventDurationChart.tsx
  • apps/web/modules/insights/components/booking/BookingKPICards.tsx
  • apps/web/modules/insights/components/booking/BookingsByHourChart.tsx
  • apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx
  • apps/web/modules/insights/components/booking/EventTrendsChart.tsx
  • apps/web/modules/insights/components/booking/HighestNoShowHostTable.tsx
  • apps/web/modules/insights/components/booking/HighestRatedMembersTable.tsx
  • apps/web/modules/insights/components/booking/LeastBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/booking/LeastCompletedBookings.tsx
  • apps/web/modules/insights/components/booking/LowestRatedMembersTable.tsx
  • apps/web/modules/insights/components/booking/MostBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/booking/MostCancelledBookingsTables.tsx
  • apps/web/modules/insights/components/booking/MostCompletedBookings.tsx
  • apps/web/modules/insights/components/booking/NoShowHostsOverTimeChart.tsx
  • apps/web/modules/insights/components/booking/PopularEventsTable.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTable.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx
  • apps/web/modules/insights/components/booking/RecentNoShowGuestsChart.tsx
  • apps/web/modules/insights/components/booking/TimezoneBadge.tsx
  • apps/web/modules/insights/components/booking/index.ts
  • apps/web/modules/insights/components/context/InsightsOrgTeamsProvider.tsx
  • apps/web/modules/insights/components/filters/DateTargetSelector.tsx
  • apps/web/modules/insights/components/filters/Download/Download.tsx
  • apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx
  • apps/web/modules/insights/components/filters/Download/index.tsx
  • apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx
  • apps/web/modules/insights/components/filters/index.tsx
  • apps/web/modules/insights/components/index.ts
  • apps/web/modules/insights/components/routing/FailedBookingsByField.tsx
  • apps/web/modules/insights/components/routing/RoutedToPerPeriod.tsx
  • apps/web/modules/insights/components/routing/RoutingFormResponsesTable.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnel.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx
  • apps/web/modules/insights/components/routing/RoutingKPICards.tsx
  • apps/web/modules/insights/components/routing/index.ts
  • apps/web/modules/insights/hooks/useDefaultRoutingForm.ts
  • apps/web/modules/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
  • apps/web/modules/insights/hooks/useInsightsBookingParameters.ts
  • apps/web/modules/insights/hooks/useInsightsBookings.ts
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • apps/web/modules/insights/hooks/useInsightsOrgTeams.ts
  • apps/web/modules/insights/hooks/useInsightsRoutingFacetedUniqueValues.ts
  • apps/web/modules/insights/hooks/useInsightsRoutingParameters.ts
  • apps/web/modules/insights/hooks/useToggleableLegend.ts
  • apps/web/modules/insights/views/insights-call-history-view.tsx
  • apps/web/modules/insights/views/insights-routing-view.tsx
  • apps/web/modules/insights/views/insights-view.tsx
  • apps/web/modules/insights/views/insights-virtual-queues-view.tsx
  • apps/web/modules/shell/navigation/Navigation.tsx
  • apps/web/pages/api/trpc/insights/[trpc].ts
  • packages/features/di/containers/InsightsBooking.ts
  • packages/features/di/containers/InsightsRouting.ts
  • packages/features/di/modules/InsightsBooking.ts
  • packages/features/di/modules/InsightsRouting.ts
  • packages/features/di/tokens.ts
  • packages/features/insights/lib/bookingStatusToText.ts
  • packages/features/insights/lib/bookingUtils.ts
  • packages/features/insights/lib/calculateDeltaType.ts
  • packages/features/insights/lib/index.ts
  • packages/features/insights/lib/objectToCsv.ts
  • packages/features/insights/lib/repositories/TeamRepository.ts
  • packages/features/insights/lib/types.ts
  • packages/features/insights/lib/valueFormatter.ts
  • packages/features/insights/server/buildBaseWhereCondition.ts
  • packages/features/insights/server/events.ts
  • packages/features/insights/server/insightsDateUtils.ts
  • packages/features/insights/server/raw-data.schema.ts
  • packages/features/insights/server/routing-events.ts
  • packages/features/insights/server/virtual-queues.ts
  • packages/features/insights/services/InsightsBookingBaseService.ts
  • packages/features/insights/services/InsightsBookingDIService.ts
  • packages/features/insights/services/InsightsRoutingBaseService.ts
  • packages/features/insights/services/InsightsRoutingDIService.ts
  • packages/features/insights/services/csvDataTransformer.ts
  • packages/trpc/react/shared.ts
  • packages/trpc/server/routers/viewer/_router.tsx
  • packages/trpc/server/routers/viewer/insights/_router.ts
  • packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts

Comment thread apps/web/modules/insights/components/booking/BookingKPICards.tsx Outdated
Comment thread apps/web/modules/insights/components/BookingAtCell.tsx Outdated
Comment thread apps/web/modules/insights/components/BookingAtCell.tsx
Comment thread apps/web/modules/insights/components/context/InsightsOrgTeamsProvider.tsx Outdated
Comment thread apps/web/modules/insights/components/filters/Download/Download.tsx Outdated
Comment thread packages/features/insights/server/insightsDateUtils.ts Outdated
Comment thread packages/features/insights/services/InsightsBookingBaseService.ts Outdated
Comment thread packages/trpc/server/routers/viewer/insights/_router.ts Outdated
Comment thread packages/trpc/server/routers/viewer/insights/_router.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx (1)

510-513: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the badge label.

"New" is hardcoded, so this badge ignores the active locale while the rest of the sidebar uses t(...).

Proposed fix
                   {child.isBadged && (
                     <Badge variant="blue" className="mt-0.5 text-xs">
-                      New
+                      {t("new")}
                     </Badge>
                   )}

As per coding guidelines, Add translations to packages/i18n/locales/en/common.json for all UI strings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx
around lines 510 - 513, Replace the hardcoded "New" badge label with a localized
string: use the existing translation helper (t) where the Badge is rendered
inside SettingsLayoutAppDirClient.tsx (the block that checks child.isBadged and
renders <Badge>), and add the corresponding key (e.g. "sidebar.badge.new" or
similar) to packages/i18n/locales/en/common.json so the label respects the
active locale; ensure you import/use the same t instance used elsewhere in this
component so the badge text matches other sidebar translations.
🟡 Minor comments (10)
apps/web/modules/auth/saml-idp/saml-idp-view.tsx-7-10 (1)

7-10: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle the signIn promise to avoid unhandled rejections.

Line [8] fires signIn without handling rejection. Wrapping with void + catch keeps this effect safe.

Suggested change
   useEffect(() => {
-    signIn("saml-idp", { callbackUrl: "/", code });
+    void signIn("saml-idp", { callbackUrl: "/", code }).catch(() => {
+      // optionally report auth bootstrap failure
+    });
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/auth/saml-idp/saml-idp-view.tsx` around lines 7 - 10, The
effect calls signIn("saml-idp", { callbackUrl: "/", code }) without handling its
returned promise which can cause unhandled rejections; update the useEffect in
saml-idp-view.tsx to handle the promise (e.g., use void signIn(...).catch(err =>
/* log or ignore */) or run an async IIFE and try/catch) so any rejection from
signIn is caught; keep the call site as the existing useEffect and signIn
invocation but add a .catch handler (or try/catch in an async function) to
handle errors for the code variable path.
apps/web/app/(use-page-wrapper)/auth/saml-idp/page.tsx-8-13 (1)

8-13: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject empty code values in query validation.

Line [8] currently allows ?code= (empty string), which still reaches the client sign-in flow. Tightening this to non-empty avoids invalid auth attempts.

Suggested change
-const querySchema = z.object({ code: z.string() });
+const querySchema = z.object({ code: z.string().min(1) });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/`(use-page-wrapper)/auth/saml-idp/page.tsx around lines 8 - 13,
The query validation currently allows an empty code value; update the Zod schema
used in SamlIdpPage so the code parameter rejects empty strings (e.g., change
querySchema from z.object({ code: z.string() }) to require non-empty strings via
z.string().min(1) or z.string().nonempty()), then keep the existing safeParse
and notFound logic so empty or missing codes do not render SamlIdpClient.
apps/web/app/api/auth/saml/userinfo/route.ts-17-19 (1)

17-19: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Enforce Bearer scheme for authorization parsing.

Line [18] accepts any two-part header (not only Bearer), which makes auth parsing too permissive.

Suggested change
-  const parts = (authHeader || "").split(" ");
-  if (parts.length > 1) return parts[1];
+  const parts = (authHeader || "").split(" ");
+  if (parts.length === 2 && /^Bearer$/i.test(parts[0]) && parts[1]) return parts[1];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/api/auth/saml/userinfo/route.ts` around lines 17 - 19, The
authorization parsing is too permissive because the current split logic returns
any two-part header; update the extraction to enforce the Bearer scheme by
verifying the first token equals "Bearer" (case-insensitive) before returning
the token. Locate the authHeader handling in route.ts (the parts = (authHeader
|| "").split(" ") logic) and change it to check parts[0].toLowerCase() ===
"bearer" (or equivalent) and only then return parts[1]; otherwise return
null/undefined or handle as an invalid authorization header.
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx-143-143 (1)

143-143: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate the permission notice string.

This user-facing message should be routed through t(...) and backed by locale entries.

As per coding guidelines: **/*.{ts,tsx,jsx}: Add translations to packages/i18n/locales/en/common.json for all UI strings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx
at line 143, Wrap the user-facing permission notice string "Only organization
owners and admins can update these settings." with the i18n translator call
t(...), e.g. t('orgs.permissionNotice'), and replace the hard-coded paragraph
accordingly in page.tsx; then add a matching key "orgs.permissionNotice": "Only
organization owners and admins can update these settings." to
packages/i18n/locales/en/common.json so the string is available in English.
Ensure you import/use the same t function already used in this file (or from the
i18n hook/util) so the translated text is rendered.
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx-22-23 (1)

22-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize hardcoded UI/toast strings.

These strings should use translation keys (t(...)) instead of inline English literals.

As per coding guidelines: **/*.{ts,tsx,jsx}: Add translations to packages/i18n/locales/en/common.json for all UI strings.

Also applies to: 31-31, 41-49

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx
around lines 22 - 23, Hardcoded UI/toast strings (e.g., the message passed to
showToast and any labels/strings around router.push in this component) must be
replaced with translation keys via the t(...) function and corresponding entries
added to packages/i18n/locales/en/common.json; locate usages such as
showToast("You have joined the organization.", "success"), and any similar
strings at the other noted locations (lines ~31 and ~41-49), replace the literal
strings with t('settings.organizations.joined') or appropriately named keys, and
add those keys and English values to common.json so the component imports/uses
the i18n t function consistently.
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/sso/page.tsx-15-18 (1)

15-18: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle clipboard-write failures explicitly.

If navigator.clipboard.writeText fails (permissions/insecure context), the promise rejects and users get no actionable feedback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/sso/page.tsx
around lines 15 - 18, The copyToClipboard function currently awaits
navigator.clipboard.writeText without handling rejection; wrap the writeText
call in a try/catch inside copyToClipboard, and on error call showToast with an
error variant and include the error message to give user feedback; optionally
attempt a fallback copy using a temporary textarea +
document.execCommand('copy') before failing, and ensure you still call showToast
on success (using the existing success message) or on final failure (with the
error text) so users know what happened.
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx-22-23 (1)

22-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize hard-coded page text.

Line 22 and Lines 55-56 use hard-coded English strings, so this page will not fully localize.

As per coding guidelines **/*.{ts,tsx,jsx}: Add translations to packages/i18n/locales/en/common.json for all UI strings.

Also applies to: 55-56

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx
around lines 22 - 23, Replace hard-coded UI strings with localized keys: add
"organization_profile_title" (value "Organization profile") and any other
literal strings used at lines 55-56 to packages/i18n/locales/en/common.json,
then update the JSX in page.tsx to call the i18n translator
(t("organization_profile_title") and t(...) for the other strings) instead of
the hard-coded English text; ensure keys are unique and follow existing naming
(e.g., profile_org_description already used) so the page is fully localized.
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/sso/page.tsx-52-53 (1)

52-53: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace hard-coded UI copy with translation keys.

These user-facing strings are currently hard-coded, which breaks localization coverage on this page.

As per coding guidelines **/*.{ts,tsx,jsx}: Add translations to packages/i18n/locales/en/common.json for all UI strings.

Also applies to: 58-59, 65-67, 74-74, 83-83, 88-88, 105-105, 112-112, 124-125, 133-133, 145-145, 153-153

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/sso/page.tsx
around lines 52 - 53, Replace all hard-coded user-facing strings in this file
with i18n keys by using the translation hook (e.g., call
useTranslations('common') and replace title="Single sign-on" and
description="Configure SAML SSO for your organization." as well as the other
literal strings referenced (lines with headings/buttons/text) with
t('sso.title'), t('sso.description'), etc.; then add matching keys and English
values in packages/i18n/locales/en/common.json under an "sso" namespace (e.g.,
"sso.title", "sso.description", and entries for the strings at the other listed
locations) so the UI reads from translations instead of hard-coded copy.
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx-21-23 (1)

21-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize hard-coded strings in members UI.

This page still has multiple hard-coded English strings (including toasts and role labels), so localization is incomplete.

As per coding guidelines **/*.{ts,tsx,jsx}: Add translations to packages/i18n/locales/en/common.json for all UI strings.

Also applies to: 35-35, 47-47, 71-71, 74-74, 78-78, 80-80, 111-111, 122-122, 132-133, 137-137, 150-150

packages/features/insights/services/InsightsBookingBaseService.ts-31-35 (1)

31-35: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard null usernames before building the fallback avatar URL.

When username is null, this produces "/null/avatar.png", so stats that rely on this map render a broken avatar URL instead of a usable fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/insights/services/InsightsBookingBaseService.ts` around
lines 31 - 35, When building the avatarUrl inside the usersFromTeam.forEach
block (the call that populates userHashMap via userHashMap.set), guard against
null/undefined user.username before constructing the fallback string; if
user.avatarUrl is falsy then use a safe fallback (e.g. AVATAR_FALLBACK or an
empty/default URL) instead of `/${user.username}/avatar.png` so a null username
does not produce "/null/avatar.png". Update the avatarUrl assignment in that
usersFromTeam.forEach/userHashMap.set site to choose between user.avatarUrl, a
username-based path only when user.username is truthy, and a final constant
fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/app/api/auth/saml/authorize/route.ts`:
- Around line 15-19: The code currently casts req.nextUrl.searchParams into
OAuthReq and passes it to oauthController.authorize (via the oAuthReq variable),
which trusts unvalidated client input; update the route handler in route.ts to
parse and validate the authorize query parameters before calling
oauthController.authorize (use a runtime validator such as a Zod/schema check or
explicit checks for required fields and types that match the OAuthReq shape),
reject or return a 400 on invalid/missing params, and only construct/passthrough
a validated OAuthReq to oauthController.authorize so NextResponse.redirect is
only invoked with verified input.

In `@apps/web/app/api/auth/saml/callback/route.ts`:
- Around line 17-20: The request payload parsing with parseRequestData is done
outside the try block so parse failures bypass the route's trace-aware error
handling; move the call to parseRequestData inside the same try that calls
oauthController.samlResponse (and similarly for the other occurrence around
lines 30-33) so both parsing and oauthController.samlResponse are executed
within the guarded error path, ensuring parse errors are caught and handled by
the route's error handler.
- Around line 27-28: The log statement in route.ts is printing the raw
user-supplied RelayState (requestData?.RelayState) which can leak sensitive
redirect/query data; update the log.error call that currently references
requestData?.RelayState to either omit the raw value or log a non-sensitive
representation (e.g., a boolean indicating presence, a truncated/redacted
string, or a deterministic hash like SHA‑256 of RelayState) and include context
(e.g., the uid/trace) instead; change the code around the log.error that
precedes the NextResponse.json return so it no longer writes the full RelayState
to logs but still provides enough info for debugging (use
requestData?.RelayState => "REDACTED" or hashed/flagged form).

In `@apps/web/app/api/auth/saml/token/route.ts`:
- Line 29: Replace the plain throw new Error in the SAML token route (the throw
using `uid`: `Error getting auth token. trace: ${uid}`) with an ErrorWithCode
instance: import ErrorWithCode where other non-tRPC modules use it, construct
the ErrorWithCode with an appropriate machine-readable code and the same message
including `uid`, and throw that instead to maintain typed/consistent error
handling in the route (ensure the import for ErrorWithCode is added if missing).

In `@apps/web/app/api/auth/saml/userinfo/route.ts`:
- Around line 40-43: Replace the plain Error thrown in the userinfo route with
an ErrorWithCode instance: locate the throw at the end of the catch block where
uid is generated and log.error(`trace: ${uid} Error getting user info:
${error}`) is called, import ErrorWithCode, and throw a new ErrorWithCode that
preserves the message and trace id (e.g. `Error getting user info. trace:
${uid}`) and sets an appropriate code (e.g. 'INTERNAL_ERROR' or similar per code
conventions) so structured error semantics are maintained in non-tRPC layers.

In `@packages/features/auth/lib/next-auth-options.ts`:
- Around line 434-436: Guard against a missing email before calling toLowerCase
by checking userInfo.email (e.g., if (!userInfo?.email) ...) and route the flow
to the controlled auth error path instead of invoking .toLowerCase(); update the
block that destructures { id, firstName, lastName, requested } and the
subsequent email = userInfo.email.toLowerCase() to first validate presence of
userInfo.email, log or surface a clear auth error, and return/throw the
project's standardized authentication failure (use the existing auth error
handling used elsewhere in next-auth-options.ts) so missing IdP emails fail
safely.

In `@packages/features/ee/sso/lib/saml.ts`:
- Line 7: Replace the insecure default for clientSecretVerifier so code fails
fast when SAML client secret is not provided: remove the fallback string "dummy"
and instead read process.env.SAML_CLIENT_SECRET_VERIFIER and throw a clear Error
(or return undefined and ensure SAML is disabled upstream) when it's missing;
update the exported symbol clientSecretVerifier (and any SAML initialization
logic that consumes it) to rely on this check so the app will not start or
enable SAML with a predictable secret.

In `@packages/features/insights/services/InsightsBookingBaseService.ts`:
- Around line 411-416: The membership lookup is skipped when teamsFromOrg is
empty, causing org-level members (whose id is already in teamIds) to be missed;
change the condition to base the query on teamIds (or always call
MembershipRepository.findAllByTeamIds when teamIds.length > 0) so userIdsFromOrg
is populated from MembershipRepository.findAllByTeamIds({ teamIds, select: {
userId: true } }) regardless of teamsFromOrg, then map to m.userId as before
(preserving the existing variable userIdsFromOrg and usage).
- Around line 204-214: The select handling in InsightsBookingBaseService is
honoring keys set to false because it uses Object.keys(select) unfiltered;
update the logic in the block that builds selectFields (the code that references
select, keys, bookingDataKeys, selectFields and uses Prisma.sql/Prisma.raw) to
first filter keys to only those with a truthy value (e.g.,
Object.entries(select).filter(([,v])=>v) or similar), validate those filtered
keys against bookingDataKeys, and then compose the SQL projection only from the
filtered keys so fields explicitly set to false are omitted, preserving the
SelectedFields<TSelect> contract.
- Around line 1187-1201: In calculatePreviousPeriodDates(), fix the off-by-one
by computing periodLength = differenceInCalendarDays(endDate, startDate) + 1
(use date-fns differenceInCalendarDays or native Date math) and then compute
lastPeriodStartDate = subDays(startDate, periodLength) and lastPeriodEndDate =
subDays(endDate, periodLength) (or equivalent native Date subtract), replacing
Day.js usage with date-fns (differenceInCalendarDays, subDays, format) or native
Date methods; return ISO strings and "YYYY-MM-DD" formatted strings (use format
from date-fns or toISOString/zero-pad for native) for startDate/ endDate/
formattedStartDate/ formattedEndDate.

In `@packages/trpc/server/routers/viewer/organizations/acceptInvite.handler.ts`:
- Around line 19-21: The membership lookup in prisma.membership.findUnique
currently allows any team invite and then uses the team's organizationId to set
user.organizationId; update the query predicate to require the team be an
organization by adding team: { isOrganization: true } (i.e., include team: {
isOrganization: true } inside the where clause for prisma.membership.findUnique)
so only organization memberships are accepted, and make the same change to the
other membership lookup later in this handler that performs an equivalent
membership check.

In `@packages/trpc/server/routers/viewer/organizations/create.handler.ts`:
- Around line 41-61: The conflict checks (the user.organizationId check, the
prisma.membership.findFirst call, and the slug availability check via
assertOrganizationSlugAvailable) must be made atomic with the organization
creation to prevent race conditions; move these validations and the subsequent
create/update DB operations into a single transactional block (e.g.,
prisma.$transaction) or rely on a DB-unique constraint on organization.slug and
catch unique-constraint errors, then throw TRPCError({ code: "CONFLICT", ... })
accordingly; ensure the code paths that call normalizeOrganizationSlug and
assertOrganizationSlugAvailable are executed inside that transaction or replaced
by a transactional uniqueness check so two concurrent requests cannot both
succeed.

In `@packages/trpc/server/routers/viewer/organizations/declineInvite.handler.ts`:
- Around line 24-30: The current check-then-delete has a race; replace the
separate existence check and prisma.membership.delete call with an atomic
conditional delete (e.g., use prisma.membership.deleteMany with where: { userId:
ctx.user.id, teamId: input.teamId, accepted: false }) and then assert the
returned count is 1; if count is 0 throw the same TRPCError ("Cannot decline an
already accepted invite." or "Invite not found") so the decline is only
performed when accepted is false in a single database operation (update the code
around the membership check and the prisma.membership.delete call in
declineInvite.handler).

In `@packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts`:
- Around line 34-44: The current check uses invitee.organizationId which can be
stale; instead query memberships to detect an accepted membership for the user
and reject the invite if one exists. Replace or augment the
invitee.organizationId check with a prisma query (e.g.,
prisma.membership.findFirst or findUnique variant) that searches for a
membership where userId === invitee.id and accepted === true (across teams), and
throw the same TRPCError if such a record is found; continue using the existing
membership.team.id logic only for invite-specific lookups (the existing
prisma.membership.findUnique for userId_teamId is fine for duplicate invites).

---

Outside diff comments:
In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx:
- Around line 510-513: Replace the hardcoded "New" badge label with a localized
string: use the existing translation helper (t) where the Badge is rendered
inside SettingsLayoutAppDirClient.tsx (the block that checks child.isBadged and
renders <Badge>), and add the corresponding key (e.g. "sidebar.badge.new" or
similar) to packages/i18n/locales/en/common.json so the label respects the
active locale; ensure you import/use the same t instance used elsewhere in this
component so the badge text matches other sidebar translations.

---

Minor comments:
In `@apps/web/app/`(use-page-wrapper)/auth/saml-idp/page.tsx:
- Around line 8-13: The query validation currently allows an empty code value;
update the Zod schema used in SamlIdpPage so the code parameter rejects empty
strings (e.g., change querySchema from z.object({ code: z.string() }) to require
non-empty strings via z.string().min(1) or z.string().nonempty()), then keep the
existing safeParse and notFound logic so empty or missing codes do not render
SamlIdpClient.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx:
- Line 143: Wrap the user-facing permission notice string "Only organization
owners and admins can update these settings." with the i18n translator call
t(...), e.g. t('orgs.permissionNotice'), and replace the hard-coded paragraph
accordingly in page.tsx; then add a matching key "orgs.permissionNotice": "Only
organization owners and admins can update these settings." to
packages/i18n/locales/en/common.json so the string is available in English.
Ensure you import/use the same t function already used in this file (or from the
i18n hook/util) so the translated text is rendered.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx:
- Around line 22-23: Hardcoded UI/toast strings (e.g., the message passed to
showToast and any labels/strings around router.push in this component) must be
replaced with translation keys via the t(...) function and corresponding entries
added to packages/i18n/locales/en/common.json; locate usages such as
showToast("You have joined the organization.", "success"), and any similar
strings at the other noted locations (lines ~31 and ~41-49), replace the literal
strings with t('settings.organizations.joined') or appropriately named keys, and
add those keys and English values to common.json so the component imports/uses
the i18n t function consistently.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx:
- Around line 22-23: Replace hard-coded UI strings with localized keys: add
"organization_profile_title" (value "Organization profile") and any other
literal strings used at lines 55-56 to packages/i18n/locales/en/common.json,
then update the JSX in page.tsx to call the i18n translator
(t("organization_profile_title") and t(...) for the other strings) instead of
the hard-coded English text; ensure keys are unique and follow existing naming
(e.g., profile_org_description already used) so the page is fully localized.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/sso/page.tsx:
- Around line 15-18: The copyToClipboard function currently awaits
navigator.clipboard.writeText without handling rejection; wrap the writeText
call in a try/catch inside copyToClipboard, and on error call showToast with an
error variant and include the error message to give user feedback; optionally
attempt a fallback copy using a temporary textarea +
document.execCommand('copy') before failing, and ensure you still call showToast
on success (using the existing success message) or on final failure (with the
error text) so users know what happened.
- Around line 52-53: Replace all hard-coded user-facing strings in this file
with i18n keys by using the translation hook (e.g., call
useTranslations('common') and replace title="Single sign-on" and
description="Configure SAML SSO for your organization." as well as the other
literal strings referenced (lines with headings/buttons/text) with
t('sso.title'), t('sso.description'), etc.; then add matching keys and English
values in packages/i18n/locales/en/common.json under an "sso" namespace (e.g.,
"sso.title", "sso.description", and entries for the strings at the other listed
locations) so the UI reads from translations instead of hard-coded copy.

In `@apps/web/app/api/auth/saml/userinfo/route.ts`:
- Around line 17-19: The authorization parsing is too permissive because the
current split logic returns any two-part header; update the extraction to
enforce the Bearer scheme by verifying the first token equals "Bearer"
(case-insensitive) before returning the token. Locate the authHeader handling in
route.ts (the parts = (authHeader || "").split(" ") logic) and change it to
check parts[0].toLowerCase() === "bearer" (or equivalent) and only then return
parts[1]; otherwise return null/undefined or handle as an invalid authorization
header.

In `@apps/web/modules/auth/saml-idp/saml-idp-view.tsx`:
- Around line 7-10: The effect calls signIn("saml-idp", { callbackUrl: "/", code
}) without handling its returned promise which can cause unhandled rejections;
update the useEffect in saml-idp-view.tsx to handle the promise (e.g., use void
signIn(...).catch(err => /* log or ignore */) or run an async IIFE and
try/catch) so any rejection from signIn is caught; keep the call site as the
existing useEffect and signIn invocation but add a .catch handler (or try/catch
in an async function) to handle errors for the code variable path.

In `@packages/features/insights/services/InsightsBookingBaseService.ts`:
- Around line 31-35: When building the avatarUrl inside the
usersFromTeam.forEach block (the call that populates userHashMap via
userHashMap.set), guard against null/undefined user.username before constructing
the fallback string; if user.avatarUrl is falsy then use a safe fallback (e.g.
AVATAR_FALLBACK or an empty/default URL) instead of
`/${user.username}/avatar.png` so a null username does not produce
"/null/avatar.png". Update the avatarUrl assignment in that
usersFromTeam.forEach/userHashMap.set site to choose between user.avatarUrl, a
username-based path only when user.username is truthy, and a final constant
fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 53424c94-2bab-4ff8-b70b-f214825cda91

📥 Commits

Reviewing files that changed from the base of the PR and between d46f2cd and 51a19c5.

📒 Files selected for processing (43)
  • apps/web/app/(use-page-wrapper)/auth/saml-idp/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/sso/page.tsx
  • apps/web/app/api/auth/saml/authorize/route.ts
  • apps/web/app/api/auth/saml/callback/route.ts
  • apps/web/app/api/auth/saml/token/route.ts
  • apps/web/app/api/auth/saml/userinfo/route.ts
  • apps/web/modules/auth/saml-idp/saml-idp-view.tsx
  • apps/web/modules/insights/components/BookingAtCell.tsx
  • apps/web/modules/insights/components/booking/BookingKPICards.tsx
  • apps/web/modules/insights/components/context/InsightsOrgTeamsProvider.tsx
  • apps/web/modules/insights/components/filters/Download/Download.tsx
  • apps/web/next.config.ts
  • apps/web/pages/api/trpc/organizations/[trpc].ts
  • apps/web/playwright/settings/organizations.e2e.ts
  • packages/features/auth/lib/next-auth-options.ts
  • packages/features/ee/sso/lib/jackson.ts
  • packages/features/ee/sso/lib/saml.ts
  • packages/features/insights/server/insightsDateUtils.ts
  • packages/features/insights/services/InsightsBookingBaseService.ts
  • packages/trpc/react/shared.ts
  • packages/trpc/server/routers/viewer/_router.tsx
  • packages/trpc/server/routers/viewer/insights/_router.ts
  • packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • packages/trpc/server/routers/viewer/organizations/acceptInvite.handler.ts
  • packages/trpc/server/routers/viewer/organizations/create.handler.ts
  • packages/trpc/server/routers/viewer/organizations/declineInvite.handler.ts
  • packages/trpc/server/routers/viewer/organizations/getCurrent.handler.ts
  • packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts
  • packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts
  • packages/trpc/server/routers/viewer/organizations/listPendingInvites.handler.ts
  • packages/trpc/server/routers/viewer/organizations/organizationUtils.ts
  • packages/trpc/server/routers/viewer/organizations/removeMember.handler.ts
  • packages/trpc/server/routers/viewer/organizations/saml.handler.ts
  • packages/trpc/server/routers/viewer/organizations/schema.ts
  • packages/trpc/server/routers/viewer/organizations/update.handler.ts
  • packages/trpc/server/routers/viewer/organizations/updateMemberRole.handler.ts
💤 Files with no reviewable changes (1)
  • apps/web/next.config.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/page.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
  • apps/web/modules/insights/components/BookingAtCell.tsx
  • packages/trpc/server/routers/viewer/_router.tsx
  • apps/web/modules/insights/components/context/InsightsOrgTeamsProvider.tsx
  • packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts
  • apps/web/modules/insights/components/filters/Download/Download.tsx
  • packages/features/insights/server/insightsDateUtils.ts
  • packages/trpc/server/routers/viewer/insights/_router.ts

Comment thread apps/web/app/api/auth/saml/authorize/route.ts Outdated
Comment thread apps/web/app/api/auth/saml/callback/route.ts Outdated
Comment thread apps/web/app/api/auth/saml/callback/route.ts Outdated
Comment thread apps/web/app/api/auth/saml/token/route.ts Outdated
Comment thread apps/web/app/api/auth/saml/userinfo/route.ts Outdated
Comment thread packages/features/insights/services/InsightsBookingBaseService.ts Outdated
Comment thread packages/trpc/server/routers/viewer/organizations/acceptInvite.handler.ts Outdated
Comment thread packages/trpc/server/routers/viewer/organizations/create.handler.ts Outdated
Comment thread packages/trpc/server/routers/viewer/organizations/declineInvite.handler.ts Outdated
Comment thread packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx`:
- Around line 25-29: The legend currently uses hardcoded English labels and the
toggle logic relies on those labels; change the exported legend array (legend)
to use stable keys (e.g., key: "totalSubmissions", "successfulRoutings",
"acceptedBookings") paired with the existing color values (COLOR.TOTAL,
COLOR.SUCCESFUL, COLOR.ACCEPTED), then update any area visibility checks (the
code that checks labels to hide/show areas) to match these keys instead of label
text; at render time map legend entries to translated strings using the i18n
lookup (e.g., t("common.totalSubmissions")) so UI text is pulled from
packages/i18n/locales/en/common.json where you must add the three new
translation keys, and remove any direct string comparisons against English
labels elsewhere in the component.

In `@apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx`:
- Around line 37-38: In RoutingFunnelSkeleton, remove Math.random() from inline
JSX style (used on the height at the shown lines and also at 41-42, 45-46) and
instead precompute deterministic heights once per component render: inside the
RoutingFunnelSkeleton component create a fixed array or a useMemo hook (e.g.,
heights = useMemo(() => [...], [])) or a seeded pseudo-random generator to
produce the same heights on every render, then reference heights[index] in the
style properties; this ensures stable/hydratable skeleton geometry without
changing the JSX structure.

In `@apps/web/modules/insights/hooks/useInsightsColumns.tsx`:
- Around line 34-35: The memoized column definitions inside useInsightsColumns
call t(...) but t is not included in the useMemo dependency arrays, causing
stale localized labels after locale changes; update the dependency arrays for
the useMemo that returns columns (the block that checks isHeadersSuccess) to
include t, and also add t to the other useMemo that builds columns later in the
same file so both memoized column definitions re-compute when the localization
function changes.
- Line 278: The UTM column headers in useInsightsColumns.tsx are hardcoded
strings ("utm_medium", "utm_term", "utm_content", "utm_campaign"); replace those
literal header values with calls to the i18n translation function (e.g.,
t('insights.columns.utm_medium')) wherever the headers are set (look for the
header properties in the columns returned by useInsightsColumns) and add
corresponding keys and English values to packages/i18n/locales/en/common.json
(e.g., "insights.columns.utm_medium": "UTM Medium", etc.) so the UI uses
localized strings.
- Line 1: Remove the Day.js dependency and replace its usage with native Date
formatting in useInsightsColumns: delete the import "dayjs" and change the
expression dayjs(info.getValue()).format("MMM D, YYYY HH:mm") to create a Date
from info.getValue() and format it using Intl.DateTimeFormat (or manual
pad/locale logic) to produce "MMM D, YYYY HH:mm" output; update any tests/uses
of the getValue formatting in the same file (e.g., inside the cell renderer
where info.getValue() is formatted) to use the new Date-based formatter.

In `@apps/web/modules/insights/views/insights-view.tsx`:
- Around line 10-28: The grouped import that pulls components from the booking
barrel index (the import listing AverageEventDurationChart, BookingKPICards,
BookingsByHourChart, CSATOverTimeChart, EventTrendsChart,
HighestNoShowHostTable, HighestRatedMembersTable, LeastBookedTeamMembersTable,
LeastCompletedTeamMembersTable, LowestRatedMembersTable,
MostBookedTeamMembersTable, MostCancelledBookingsTables,
MostCompletedTeamMembersTable, NoShowHostsOverTimeChart, PopularEventsTable,
RecentFeedbackTable, RecentNoShowGuestsChart, TimezoneBadge) should be replaced
with direct imports from each component’s source file; locate the single import
statement referencing the booking barrel and replace it with individual imports
for each symbol (e.g., import AverageEventDurationChart from its component file,
import BookingKPICards from its component file, etc.), ensuring each component
name above is imported from its own source module rather than the barrel index.

In `@packages/features/insights/server/events.ts`:
- Around line 16-18: The dynamic status bucket increment can operate on
undefined keys (aggregate[item.timeStatus]) causing NaN; before adding counts in
the block that checks typeof item.timeStatus === "string" (and the similar block
around lines 27-32), ensure the targeted aggregate bucket is initialized to a
numeric zero if missing (e.g., set aggregate[item.timeStatus] = 0 when
undefined) and then add item._count._all (with fallback 0). Update the
increments for both the specific bucket and aggregate["_all"] to use these
guarded/initialized values so unseeded statuses don't corrupt totals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7cbb3798-8307-46e9-8624-d34ed77db5ee

📥 Commits

Reviewing files that changed from the base of the PR and between 51a19c5 and e7c4a0c.

📒 Files selected for processing (94)
  • apps/web/app/(use-page-wrapper)/insights/UpgradeTipWrapper.tsx
  • apps/web/app/(use-page-wrapper)/insights/call-history/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/checkInsightsPagePermission.ts
  • apps/web/app/(use-page-wrapper)/insights/layout.tsx
  • apps/web/app/(use-page-wrapper)/insights/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/router-position/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/routing/page.tsx
  • apps/web/modules/insights/components/BookedByCell.tsx
  • apps/web/modules/insights/components/BookingAtCell.tsx
  • apps/web/modules/insights/components/BookingStatusBadge.tsx
  • apps/web/modules/insights/components/CellWithOverflowX.tsx
  • apps/web/modules/insights/components/ChartCard.tsx
  • apps/web/modules/insights/components/KPICard.tsx
  • apps/web/modules/insights/components/ResponseValueCell.tsx
  • apps/web/modules/insights/components/UserStatsTable.tsx
  • apps/web/modules/insights/components/booking/AverageEventDurationChart.tsx
  • apps/web/modules/insights/components/booking/BookingKPICards.tsx
  • apps/web/modules/insights/components/booking/BookingsByHourChart.tsx
  • apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx
  • apps/web/modules/insights/components/booking/EventTrendsChart.tsx
  • apps/web/modules/insights/components/booking/HighestNoShowHostTable.tsx
  • apps/web/modules/insights/components/booking/HighestRatedMembersTable.tsx
  • apps/web/modules/insights/components/booking/LeastBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/booking/LeastCompletedBookings.tsx
  • apps/web/modules/insights/components/booking/LowestRatedMembersTable.tsx
  • apps/web/modules/insights/components/booking/MostBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/booking/MostCancelledBookingsTables.tsx
  • apps/web/modules/insights/components/booking/MostCompletedBookings.tsx
  • apps/web/modules/insights/components/booking/NoShowHostsOverTimeChart.tsx
  • apps/web/modules/insights/components/booking/PopularEventsTable.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTable.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx
  • apps/web/modules/insights/components/booking/RecentNoShowGuestsChart.tsx
  • apps/web/modules/insights/components/booking/TimezoneBadge.tsx
  • apps/web/modules/insights/components/booking/index.ts
  • apps/web/modules/insights/components/context/InsightsOrgTeamsProvider.tsx
  • apps/web/modules/insights/components/filters/DateTargetSelector.tsx
  • apps/web/modules/insights/components/filters/Download/Download.tsx
  • apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx
  • apps/web/modules/insights/components/filters/Download/index.tsx
  • apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx
  • apps/web/modules/insights/components/filters/index.tsx
  • apps/web/modules/insights/components/index.ts
  • apps/web/modules/insights/components/routing/FailedBookingsByField.tsx
  • apps/web/modules/insights/components/routing/RoutedToPerPeriod.tsx
  • apps/web/modules/insights/components/routing/RoutingFormResponsesTable.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnel.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx
  • apps/web/modules/insights/components/routing/RoutingKPICards.tsx
  • apps/web/modules/insights/components/routing/index.ts
  • apps/web/modules/insights/hooks/useDefaultRoutingForm.ts
  • apps/web/modules/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
  • apps/web/modules/insights/hooks/useInsightsBookingParameters.ts
  • apps/web/modules/insights/hooks/useInsightsBookings.ts
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • apps/web/modules/insights/hooks/useInsightsOrgTeams.ts
  • apps/web/modules/insights/hooks/useInsightsRoutingFacetedUniqueValues.ts
  • apps/web/modules/insights/hooks/useInsightsRoutingParameters.ts
  • apps/web/modules/insights/hooks/useToggleableLegend.ts
  • apps/web/modules/insights/views/insights-call-history-view.tsx
  • apps/web/modules/insights/views/insights-routing-view.tsx
  • apps/web/modules/insights/views/insights-view.tsx
  • apps/web/modules/insights/views/insights-virtual-queues-view.tsx
  • apps/web/modules/shell/navigation/Navigation.tsx
  • apps/web/pages/api/trpc/insights/[trpc].ts
  • packages/features/di/containers/InsightsBooking.ts
  • packages/features/di/containers/InsightsRouting.ts
  • packages/features/di/modules/InsightsBooking.ts
  • packages/features/di/modules/InsightsRouting.ts
  • packages/features/di/tokens.ts
  • packages/features/insights/lib/bookingStatusToText.ts
  • packages/features/insights/lib/bookingUtils.ts
  • packages/features/insights/lib/calculateDeltaType.ts
  • packages/features/insights/lib/index.ts
  • packages/features/insights/lib/objectToCsv.ts
  • packages/features/insights/lib/repositories/TeamRepository.ts
  • packages/features/insights/lib/types.ts
  • packages/features/insights/lib/valueFormatter.ts
  • packages/features/insights/server/buildBaseWhereCondition.ts
  • packages/features/insights/server/events.ts
  • packages/features/insights/server/insightsDateUtils.ts
  • packages/features/insights/server/raw-data.schema.ts
  • packages/features/insights/server/routing-events.ts
  • packages/features/insights/server/virtual-queues.ts
  • packages/features/insights/services/InsightsBookingBaseService.ts
  • packages/features/insights/services/InsightsBookingDIService.ts
  • packages/features/insights/services/InsightsRoutingBaseService.ts
  • packages/features/insights/services/InsightsRoutingDIService.ts
  • packages/features/insights/services/csvDataTransformer.ts
  • packages/trpc/react/shared.ts
  • packages/trpc/server/routers/viewer/_router.tsx
  • packages/trpc/server/routers/viewer/insights/_router.ts
  • packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts
✅ Files skipped from review due to trivial changes (3)
  • apps/web/modules/insights/components/booking/index.ts
  • apps/web/modules/insights/components/filters/Download/index.tsx
  • packages/features/insights/lib/index.ts
🚧 Files skipped from review as they are similar to previous changes (77)
  • apps/web/app/(use-page-wrapper)/insights/routing/page.tsx
  • apps/web/modules/insights/components/index.ts
  • packages/features/insights/lib/valueFormatter.ts
  • packages/features/di/containers/InsightsBooking.ts
  • packages/features/insights/lib/calculateDeltaType.ts
  • packages/features/insights/server/routing-events.ts
  • packages/features/insights/services/InsightsRoutingDIService.ts
  • apps/web/modules/insights/components/BookedByCell.tsx
  • packages/features/di/containers/InsightsRouting.ts
  • packages/trpc/server/routers/viewer/_router.tsx
  • packages/features/di/modules/InsightsBooking.ts
  • apps/web/app/(use-page-wrapper)/insights/checkInsightsPagePermission.ts
  • packages/features/insights/services/InsightsBookingDIService.ts
  • apps/web/modules/insights/components/UserStatsTable.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnel.tsx
  • apps/web/modules/insights/components/CellWithOverflowX.tsx
  • apps/web/app/(use-page-wrapper)/insights/call-history/page.tsx
  • apps/web/modules/insights/components/routing/index.ts
  • apps/web/modules/insights/components/booking/LeastBookedTeamMembersTable.tsx
  • packages/features/insights/lib/types.ts
  • apps/web/modules/insights/hooks/useInsightsRoutingParameters.ts
  • apps/web/pages/api/trpc/insights/[trpc].ts
  • apps/web/modules/insights/components/booking/MostBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/booking/MostCancelledBookingsTables.tsx
  • apps/web/modules/insights/components/routing/RoutingFormResponsesTable.tsx
  • apps/web/modules/insights/components/booking/RecentNoShowGuestsChart.tsx
  • apps/web/modules/insights/components/booking/LowestRatedMembersTable.tsx
  • apps/web/modules/insights/hooks/useInsightsBookingParameters.ts
  • packages/features/di/modules/InsightsRouting.ts
  • packages/features/insights/lib/bookingStatusToText.ts
  • apps/web/modules/insights/components/booking/HighestRatedMembersTable.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTable.tsx
  • apps/web/app/(use-page-wrapper)/insights/page.tsx
  • apps/web/modules/insights/hooks/useInsightsBookings.ts
  • apps/web/app/(use-page-wrapper)/insights/layout.tsx
  • apps/web/modules/insights/components/routing/RoutingKPICards.tsx
  • apps/web/modules/insights/components/booking/BookingKPICards.tsx
  • apps/web/modules/insights/components/filters/Download/Download.tsx
  • packages/features/insights/lib/objectToCsv.ts
  • apps/web/modules/insights/components/booking/TimezoneBadge.tsx
  • packages/features/di/tokens.ts
  • apps/web/modules/insights/components/booking/AverageEventDurationChart.tsx
  • apps/web/modules/insights/components/booking/HighestNoShowHostTable.tsx
  • apps/web/app/(use-page-wrapper)/insights/router-position/page.tsx
  • packages/features/insights/server/buildBaseWhereCondition.ts
  • apps/web/modules/insights/views/insights-call-history-view.tsx
  • apps/web/modules/insights/components/booking/MostCompletedBookings.tsx
  • apps/web/modules/insights/components/BookingAtCell.tsx
  • apps/web/modules/insights/components/booking/EventTrendsChart.tsx
  • apps/web/modules/insights/views/insights-routing-view.tsx
  • apps/web/modules/insights/components/context/InsightsOrgTeamsProvider.tsx
  • apps/web/modules/insights/components/filters/DateTargetSelector.tsx
  • apps/web/modules/insights/components/KPICard.tsx
  • apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx
  • apps/web/app/(use-page-wrapper)/insights/UpgradeTipWrapper.tsx
  • packages/features/insights/server/raw-data.schema.ts
  • apps/web/modules/insights/components/BookingStatusBadge.tsx
  • apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx
  • apps/web/modules/insights/hooks/useToggleableLegend.ts
  • packages/features/insights/lib/bookingUtils.ts
  • apps/web/modules/insights/hooks/useInsightsOrgTeams.ts
  • apps/web/modules/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
  • packages/features/insights/services/InsightsRoutingBaseService.ts
  • apps/web/modules/insights/hooks/useDefaultRoutingForm.ts
  • packages/features/insights/services/csvDataTransformer.ts
  • apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx
  • packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts
  • packages/features/insights/server/insightsDateUtils.ts
  • apps/web/modules/insights/components/routing/RoutedToPerPeriod.tsx
  • apps/web/modules/insights/components/booking/PopularEventsTable.tsx
  • apps/web/modules/insights/components/booking/BookingsByHourChart.tsx
  • apps/web/modules/insights/components/ResponseValueCell.tsx
  • apps/web/modules/insights/components/routing/FailedBookingsByField.tsx
  • packages/trpc/server/routers/viewer/insights/_router.ts
  • packages/features/insights/services/InsightsBookingBaseService.ts
  • apps/web/modules/insights/components/booking/LeastCompletedBookings.tsx

Comment thread apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx Outdated
Comment thread apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx Outdated
Comment thread apps/web/modules/insights/hooks/useInsightsColumns.tsx Outdated
Comment thread apps/web/modules/insights/hooks/useInsightsColumns.tsx
Comment thread apps/web/modules/insights/hooks/useInsightsColumns.tsx Outdated
Comment thread apps/web/modules/insights/views/insights-view.tsx Outdated
Comment thread packages/features/insights/server/events.ts Outdated
regisstedile and others added 4 commits May 17, 2026 19:35
Ports the Insights feature from upstream, replacing EE/routing-forms
dependencies that don't exist in this fork with stubs and direct Prisma
membership checks. Covers full booking analytics (KPIs, trends, charts,
raw data export) with empty stubs for routing analytics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fix(security): validate selectedTeamId in userBelongsToTeam middleware to prevent cross-team read bypass
- fix(ui): render KPI cards in error state instead of returning null
- fix(ui): fix orgTeamsType race condition with useEffect after session hydration
- fix(ui): replace Day.js with date-fns in BookingAtCell, add aria-label, localize "Status:"
- fix(download): propagate fetch errors and guard against empty-page infinite loop
- fix(analytics): use completed_bookings for completed.deltaPrevious KPI
- fix(query): replace include with select in membership queries
- fix(error): replace plain Error with ErrorWithCode in InsightsBookingBaseService
- fix(date): correct week boundary calculation for non-Sunday weekStart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix KPICard delta calculation (was incorrectly subtracting count)
- Use stable React key (emailMd5) instead of Math.random() in UserStatsTable
- Fix setState-in-render anti-pattern in virtual queues view → useEffect
- Fix pagination offset logic in RoutingFormResponsesDownload
- Replace generic Error with typed ErrorWithCode in bookingUtils and useInsightsOrgTeams
- Add CSV injection protection in objectToCsv (neutralize =+-@ prefixes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix RoutedToPerPeriod: setIsDownloading(true) before fetch
- Fix RoutingKPICards: import valueFormatter directly from source
- Fix virtual queues view: localize Select placeholder with t()
- Remove barrel export insights/lib/index.ts — import from source files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts (1)

46-75: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add a final unauthorized guard when neither team membership nor org-admin access is present.

Currently, users without organizationId and without team membership can still pass through this middleware. Add an explicit final check before next(...).

Suggested fix
   if ((parse.data.isAll && ctx.user.organizationId) || (!membership && ctx.user.organizationId)) {
@@
     isOwnerAdminOfParentTeam = true;
   }

+  if (!membership && !isOwnerAdminOfParentTeam) {
+    throw new TRPCError({ code: "UNAUTHORIZED" });
+  }
+
   return next({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts`
around lines 46 - 75, Add an explicit final guard before calling next(...) that
throws TRPCError({ code: "UNAUTHORIZED" }) when the requestor has neither team
membership nor organization-level access; specifically, after computing
membership, evaluating parse.data.isAll, running the org/parent-team checks and
setting isOwnerAdminOfParentTeam, add a check like: if (!membership &&
!ctx.user.organizationId && !isOwnerAdminOfParentTeam) throw new TRPCError({
code: "UNAUTHORIZED" }); this ensures functions referenced here (membership,
parse.data.isAll, ctx.user.organizationId, effectiveTeamId, hasInsightsAccess,
isOwnerAdminOfParentTeam, next) will not allow users without any membership or
org-admin rights to proceed.
apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx (1)

62-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not silently succeed with partial exports on pagination stalls.

If a middle page is unexpectedly empty before totalRecords is reached, the loop exits and still emits a “100%” successful CSV with missing rows. Fail fast (or surface a warning) instead of breaking silently.

Suggested fix
       while (allData.length < totalRecords) {
         const result = await fetchBatch(offset);
-        if (result.data.length === 0) break;
+        if (result.data.length === 0) {
+          throw new Error("Pagination stalled before all records were fetched.");
+        }
         allData = [...allData, ...result.data];
         offset += BATCH_SIZE;

Also applies to: 72-76

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx`
around lines 62 - 66, The loop that paginates using fetchBatch (inside
RoutingFormResponsesDownload) currently breaks on an empty page and continues,
which can produce a 100% CSV with missing rows; change the logic to treat an
empty result.data before reaching totalRecords as an error (or surface a
warning) instead of silently breaking: when result.data.length === 0 and
allData.length < totalRecords, throw or return a failure/validation indication
including offset/BATCH_SIZE and totalRecords so the caller can surface the
problem; keep the normal completion path when allData.length >= totalRecords and
retain use of fetchBatch, offset and BATCH_SIZE to compute which page failed.
🧹 Nitpick comments (2)
packages/trpc/server/routers/viewer/insights/_router.ts (1)

1-1: ⚡ Quick win

Prefer date-fns/native Date for non-timezone date-only formatting.

This filename generation only needs calendar dates and can follow the repo’s standard without Day.js.

As per coding guidelines: Use date-fns or native Date instead of Day.js when timezone awareness isn't needed.

Also applies to: 614-616

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/trpc/server/routers/viewer/insights/_router.ts` at line 1, The file
imports Day.js but the filename generation only needs calendar dates; replace
the Day.js import (import dayjs from "`@calcom/dayjs`") and all uses of dayjs()
for building filenames with date-fns or native Date functions — e.g., replace
dayjs().format("YYYY-MM-DD") calls used in the filename generation logic with
date-fns/format (format(new Date(), "yyyy-MM-dd")) or new
Date().toISOString().slice(0,10); update any related helpers in this module that
reference dayjs so they use the chosen date-fns/native API and remove the Day.js
import.
apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx (1)

1-1: ⚡ Quick win

Use date-fns (or native Date) for filename date formatting.

This formatting does not require timezone-aware Day.js behavior, and the repo guideline prefers date-fns/native Date here.

As per coding guidelines: Use date-fns or native Date instead of Day.js when timezone awareness isn't needed.

Also applies to: 73-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx`
at line 1, Replace the Day.js import and its filename-formatting usage with
date-fns (or native Date) since timezone-aware Day.js isn't needed: remove the
import line "import dayjs from '`@calcom/dayjs`'" and update the filename
construction in the RoutingFormResponsesDownload component (the code around the
current dayjs usage at lines ~73-75) to use date-fns' format(new Date(),
'yyyy-MM-dd') or new Date().toISOString()/toLocaleDateString() as appropriate;
ensure any variable names involved in filename creation are kept the same so
references in the component (e.g., the filename string builder) still work and
update imports to include format from 'date-fns' if you choose date-fns.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/modules/insights/components/CellWithOverflowX.tsx`:
- Line 28: The gradient overlay div in CellWithOverflowX.tsx (the element with
className "from-default absolute right-0 top-0 hidden h-full w-8 bg-linear-to-l
to-transparent") is intercepting pointer events and blocking interactions;
update that element's className to include pointer-events-none so the overlay
does not capture clicks/scroll/selection while preserving the visual gradient.

In `@apps/web/modules/insights/components/ChartCard.tsx`:
- Line 139: The aria-label for the toggle is hardcoded ("Toggle ..."); update
ChartCard's accessible label to use the i18n translation system instead of a
literal string: import/use the translation function (e.g., t) in ChartCard.tsx
and replace aria-label={`Toggle ${item.label}`} with a translated key (e.g.,
t('toggle', { label: item.label }) or a more specific key like t('chart.toggle',
{ label: item.label })), then add the corresponding entry to
packages/i18n/locales/en/common.json (and other locales) so the UI string is
translatable; alternatively, consider using aria-labelledby to reference visible
text if that fits the UI.

---

Duplicate comments:
In
`@apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx`:
- Around line 62-66: The loop that paginates using fetchBatch (inside
RoutingFormResponsesDownload) currently breaks on an empty page and continues,
which can produce a 100% CSV with missing rows; change the logic to treat an
empty result.data before reaching totalRecords as an error (or surface a
warning) instead of silently breaking: when result.data.length === 0 and
allData.length < totalRecords, throw or return a failure/validation indication
including offset/BATCH_SIZE and totalRecords so the caller can surface the
problem; keep the normal completion path when allData.length >= totalRecords and
retain use of fetchBatch, offset and BATCH_SIZE to compute which page failed.

In
`@packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts`:
- Around line 46-75: Add an explicit final guard before calling next(...) that
throws TRPCError({ code: "UNAUTHORIZED" }) when the requestor has neither team
membership nor organization-level access; specifically, after computing
membership, evaluating parse.data.isAll, running the org/parent-team checks and
setting isOwnerAdminOfParentTeam, add a check like: if (!membership &&
!ctx.user.organizationId && !isOwnerAdminOfParentTeam) throw new TRPCError({
code: "UNAUTHORIZED" }); this ensures functions referenced here (membership,
parse.data.isAll, ctx.user.organizationId, effectiveTeamId, hasInsightsAccess,
isOwnerAdminOfParentTeam, next) will not allow users without any membership or
org-admin rights to proceed.

---

Nitpick comments:
In
`@apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx`:
- Line 1: Replace the Day.js import and its filename-formatting usage with
date-fns (or native Date) since timezone-aware Day.js isn't needed: remove the
import line "import dayjs from '`@calcom/dayjs`'" and update the filename
construction in the RoutingFormResponsesDownload component (the code around the
current dayjs usage at lines ~73-75) to use date-fns' format(new Date(),
'yyyy-MM-dd') or new Date().toISOString()/toLocaleDateString() as appropriate;
ensure any variable names involved in filename creation are kept the same so
references in the component (e.g., the filename string builder) still work and
update imports to include format from 'date-fns' if you choose date-fns.

In `@packages/trpc/server/routers/viewer/insights/_router.ts`:
- Line 1: The file imports Day.js but the filename generation only needs
calendar dates; replace the Day.js import (import dayjs from "`@calcom/dayjs`")
and all uses of dayjs() for building filenames with date-fns or native Date
functions — e.g., replace dayjs().format("YYYY-MM-DD") calls used in the
filename generation logic with date-fns/format (format(new Date(),
"yyyy-MM-dd")) or new Date().toISOString().slice(0,10); update any related
helpers in this module that reference dayjs so they use the chosen
date-fns/native API and remove the Day.js import.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f78e15c-a927-48ca-b463-4a5a0fd0009b

📥 Commits

Reviewing files that changed from the base of the PR and between e7c4a0c and 2920a24.

📒 Files selected for processing (93)
  • apps/web/app/(use-page-wrapper)/insights/UpgradeTipWrapper.tsx
  • apps/web/app/(use-page-wrapper)/insights/call-history/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/checkInsightsPagePermission.ts
  • apps/web/app/(use-page-wrapper)/insights/layout.tsx
  • apps/web/app/(use-page-wrapper)/insights/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/router-position/page.tsx
  • apps/web/app/(use-page-wrapper)/insights/routing/page.tsx
  • apps/web/modules/insights/components/BookedByCell.tsx
  • apps/web/modules/insights/components/BookingAtCell.tsx
  • apps/web/modules/insights/components/BookingStatusBadge.tsx
  • apps/web/modules/insights/components/CellWithOverflowX.tsx
  • apps/web/modules/insights/components/ChartCard.tsx
  • apps/web/modules/insights/components/KPICard.tsx
  • apps/web/modules/insights/components/ResponseValueCell.tsx
  • apps/web/modules/insights/components/UserStatsTable.tsx
  • apps/web/modules/insights/components/booking/AverageEventDurationChart.tsx
  • apps/web/modules/insights/components/booking/BookingKPICards.tsx
  • apps/web/modules/insights/components/booking/BookingsByHourChart.tsx
  • apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx
  • apps/web/modules/insights/components/booking/EventTrendsChart.tsx
  • apps/web/modules/insights/components/booking/HighestNoShowHostTable.tsx
  • apps/web/modules/insights/components/booking/HighestRatedMembersTable.tsx
  • apps/web/modules/insights/components/booking/LeastBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/booking/LeastCompletedBookings.tsx
  • apps/web/modules/insights/components/booking/LowestRatedMembersTable.tsx
  • apps/web/modules/insights/components/booking/MostBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/booking/MostCancelledBookingsTables.tsx
  • apps/web/modules/insights/components/booking/MostCompletedBookings.tsx
  • apps/web/modules/insights/components/booking/NoShowHostsOverTimeChart.tsx
  • apps/web/modules/insights/components/booking/PopularEventsTable.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTable.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx
  • apps/web/modules/insights/components/booking/RecentNoShowGuestsChart.tsx
  • apps/web/modules/insights/components/booking/TimezoneBadge.tsx
  • apps/web/modules/insights/components/booking/index.ts
  • apps/web/modules/insights/components/context/InsightsOrgTeamsProvider.tsx
  • apps/web/modules/insights/components/filters/DateTargetSelector.tsx
  • apps/web/modules/insights/components/filters/Download/Download.tsx
  • apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx
  • apps/web/modules/insights/components/filters/Download/index.tsx
  • apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx
  • apps/web/modules/insights/components/filters/index.tsx
  • apps/web/modules/insights/components/index.ts
  • apps/web/modules/insights/components/routing/FailedBookingsByField.tsx
  • apps/web/modules/insights/components/routing/RoutedToPerPeriod.tsx
  • apps/web/modules/insights/components/routing/RoutingFormResponsesTable.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnel.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx
  • apps/web/modules/insights/components/routing/RoutingKPICards.tsx
  • apps/web/modules/insights/components/routing/index.ts
  • apps/web/modules/insights/hooks/useDefaultRoutingForm.ts
  • apps/web/modules/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
  • apps/web/modules/insights/hooks/useInsightsBookingParameters.ts
  • apps/web/modules/insights/hooks/useInsightsBookings.ts
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • apps/web/modules/insights/hooks/useInsightsOrgTeams.ts
  • apps/web/modules/insights/hooks/useInsightsRoutingFacetedUniqueValues.ts
  • apps/web/modules/insights/hooks/useInsightsRoutingParameters.ts
  • apps/web/modules/insights/hooks/useToggleableLegend.ts
  • apps/web/modules/insights/views/insights-call-history-view.tsx
  • apps/web/modules/insights/views/insights-routing-view.tsx
  • apps/web/modules/insights/views/insights-view.tsx
  • apps/web/modules/insights/views/insights-virtual-queues-view.tsx
  • apps/web/modules/shell/navigation/Navigation.tsx
  • apps/web/pages/api/trpc/insights/[trpc].ts
  • packages/features/di/containers/InsightsBooking.ts
  • packages/features/di/containers/InsightsRouting.ts
  • packages/features/di/modules/InsightsBooking.ts
  • packages/features/di/modules/InsightsRouting.ts
  • packages/features/di/tokens.ts
  • packages/features/insights/lib/bookingStatusToText.ts
  • packages/features/insights/lib/bookingUtils.ts
  • packages/features/insights/lib/calculateDeltaType.ts
  • packages/features/insights/lib/objectToCsv.ts
  • packages/features/insights/lib/repositories/TeamRepository.ts
  • packages/features/insights/lib/types.ts
  • packages/features/insights/lib/valueFormatter.ts
  • packages/features/insights/server/buildBaseWhereCondition.ts
  • packages/features/insights/server/events.ts
  • packages/features/insights/server/insightsDateUtils.ts
  • packages/features/insights/server/raw-data.schema.ts
  • packages/features/insights/server/routing-events.ts
  • packages/features/insights/server/virtual-queues.ts
  • packages/features/insights/services/InsightsBookingBaseService.ts
  • packages/features/insights/services/InsightsBookingDIService.ts
  • packages/features/insights/services/InsightsRoutingBaseService.ts
  • packages/features/insights/services/InsightsRoutingDIService.ts
  • packages/features/insights/services/csvDataTransformer.ts
  • packages/trpc/react/shared.ts
  • packages/trpc/server/routers/viewer/_router.tsx
  • packages/trpc/server/routers/viewer/insights/_router.ts
  • packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/features/insights/lib/calculateDeltaType.ts
  • apps/web/modules/insights/components/filters/Download/index.tsx
🚧 Files skipped from review as they are similar to previous changes (82)
  • packages/trpc/react/shared.ts
  • apps/web/modules/insights/components/booking/LeastBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/booking/MostBookedTeamMembersTable.tsx
  • apps/web/modules/insights/components/routing/index.ts
  • apps/web/modules/insights/views/insights-routing-view.tsx
  • packages/features/insights/services/InsightsBookingDIService.ts
  • packages/features/di/tokens.ts
  • packages/features/di/containers/InsightsBooking.ts
  • apps/web/modules/insights/components/index.ts
  • packages/features/insights/server/routing-events.ts
  • apps/web/pages/api/trpc/insights/[trpc].ts
  • packages/features/di/modules/InsightsRouting.ts
  • apps/web/modules/insights/components/booking/RecentFeedbackTable.tsx
  • packages/features/insights/lib/bookingStatusToText.ts
  • apps/web/modules/insights/components/booking/HighestRatedMembersTable.tsx
  • apps/web/modules/insights/components/UserStatsTable.tsx
  • apps/web/app/(use-page-wrapper)/insights/router-position/page.tsx
  • apps/web/modules/insights/components/routing/FailedBookingsByField.tsx
  • packages/features/di/modules/InsightsBooking.ts
  • apps/web/modules/insights/components/booking/MostCancelledBookingsTables.tsx
  • packages/features/insights/lib/valueFormatter.ts
  • apps/web/modules/insights/components/booking/HighestNoShowHostTable.tsx
  • apps/web/modules/insights/components/BookingStatusBadge.tsx
  • apps/web/modules/insights/components/booking/MostCompletedBookings.tsx
  • packages/features/insights/lib/bookingUtils.ts
  • packages/features/di/containers/InsightsRouting.ts
  • apps/web/modules/insights/hooks/useInsightsRoutingParameters.ts
  • apps/web/modules/shell/navigation/Navigation.tsx
  • apps/web/modules/insights/components/BookedByCell.tsx
  • apps/web/app/(use-page-wrapper)/insights/call-history/page.tsx
  • apps/web/modules/insights/views/insights-virtual-queues-view.tsx
  • packages/features/insights/lib/objectToCsv.ts
  • apps/web/modules/insights/hooks/useInsightsOrgTeams.ts
  • apps/web/modules/insights/components/booking/LeastCompletedBookings.tsx
  • apps/web/modules/insights/components/filters/DateTargetSelector.tsx
  • packages/trpc/server/routers/viewer/_router.tsx
  • apps/web/modules/insights/components/booking/BookingsByHourChart.tsx
  • apps/web/modules/insights/components/ResponseValueCell.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnel.tsx
  • apps/web/modules/insights/hooks/useInsightsRoutingFacetedUniqueValues.ts
  • apps/web/app/(use-page-wrapper)/insights/page.tsx
  • apps/web/modules/insights/components/booking/TimezoneBadge.tsx
  • packages/features/insights/services/InsightsRoutingBaseService.ts
  • apps/web/modules/insights/components/booking/AverageEventDurationChart.tsx
  • apps/web/modules/insights/hooks/useInsightsBookings.ts
  • apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx
  • apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx
  • apps/web/app/(use-page-wrapper)/insights/UpgradeTipWrapper.tsx
  • apps/web/modules/insights/components/KPICard.tsx
  • packages/features/insights/lib/repositories/TeamRepository.ts
  • apps/web/modules/insights/components/context/InsightsOrgTeamsProvider.tsx
  • apps/web/modules/insights/hooks/useToggleableLegend.ts
  • packages/features/insights/server/raw-data.schema.ts
  • apps/web/modules/insights/components/booking/NoShowHostsOverTimeChart.tsx
  • packages/features/insights/services/csvDataTransformer.ts
  • apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx
  • packages/features/insights/server/events.ts
  • apps/web/modules/insights/components/booking/index.ts
  • apps/web/modules/insights/components/booking/EventTrendsChart.tsx
  • apps/web/modules/insights/components/routing/RoutingFormResponsesTable.tsx
  • packages/features/insights/server/buildBaseWhereCondition.ts
  • apps/web/modules/insights/components/booking/BookingKPICards.tsx
  • apps/web/modules/insights/components/booking/PopularEventsTable.tsx
  • apps/web/modules/insights/components/BookingAtCell.tsx
  • apps/web/modules/insights/views/insights-view.tsx
  • apps/web/modules/insights/components/filters/Download/Download.tsx
  • packages/features/insights/services/InsightsRoutingDIService.ts
  • apps/web/modules/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
  • apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx
  • apps/web/app/(use-page-wrapper)/insights/layout.tsx
  • apps/web/modules/insights/components/routing/RoutedToPerPeriod.tsx
  • apps/web/app/(use-page-wrapper)/insights/routing/page.tsx
  • packages/features/insights/lib/types.ts
  • apps/web/modules/insights/components/booking/RecentNoShowGuestsChart.tsx
  • apps/web/modules/insights/components/booking/LowestRatedMembersTable.tsx
  • apps/web/modules/insights/components/routing/RoutingKPICards.tsx
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • apps/web/modules/insights/hooks/useInsightsBookingParameters.ts
  • apps/web/modules/insights/hooks/useDefaultRoutingForm.ts
  • apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx
  • packages/features/insights/services/InsightsBookingBaseService.ts
  • packages/features/insights/server/insightsDateUtils.ts

Comment thread apps/web/modules/insights/components/CellWithOverflowX.tsx Outdated
Comment thread apps/web/modules/insights/components/ChartCard.tsx Outdated
- Replace Math.random() in RoutingFunnelSkeleton with deterministic heights
- Replace dayjs with date-fns in useInsightsColumns createdAt cell
- Add t() to useMemo deps in useInsightsColumns
- Wrap UTM column headers with t() and add i18n keys to en/common.json
- Replace barrel import in insights-view.tsx with direct per-component imports
- Fix NaN corruption in events.ts aggregate accumulation
- Fix calculatePreviousPeriodDates off-by-one and remove dayjs from InsightsBookingBaseService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx (1)

62-62: ⚡ Quick win

Drop the structural “Custom Tooltip component” comment.

This is a “what” comment and can be removed to align with the repo’s comment policy.

As per coding guidelines: “Only add code comments that explain why, not what … Never add comments that simply restate what the code does.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx` at
line 62, Remove the redundant "Custom Tooltip component" comment that simply
restates what the code does; locate the comment above the Tooltip-related
component/function (e.g., the CustomTooltip declaration or the JSX return inside
RoutingFunnelContent) and delete that single-line comment so the file only
contains comments that explain why, not what.
apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx (1)

18-23: ⚡ Quick win

Remove JSX “what” comments that restate layout structure.

These comments describe what the markup already makes obvious. Please remove them (or keep only comments that explain non-obvious why decisions).

As per coding guidelines: “Only add code comments that explain why, not what … Never add comments that simply restate what the code does.”

Also applies to: 31-34, 40-41, 57-57, 65-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx`
around lines 18 - 23, In the RoutingFunnelSkeleton component remove the
redundant JSX "what" comments that merely restate the DOM (e.g., comments above
the div.h-full.w-full.p-4, the div.relative.h-[220px], and the
div.text-muted-foreground.absolute... plus the other noted comment locations),
leaving only comments that explain non-obvious "why" decisions; search for
comment text like "Chart container", "Chart area", "Y-axis" (and similar
comments referenced) and delete them so only purposeful explanatory comments
remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx`:
- Around line 82-93: The percentage calculation breaks when the
"totalSubmissions" series is hidden because totalSubmissions is computed by
searching the tooltip payload array (which omits hidden series) — update
RoutingFunnelContent to read the canonical total submissions from the full row
in the tooltip entry (use payload[0].payload.totalSubmissions || 0) instead of
payload.find(...). This ensures totalSubmissions uses the underlying record for
that date when computing percentages for successfulRoutings and
acceptedBookings.

In `@apps/web/modules/insights/views/insights-view.tsx`:
- Line 148: The mailto href in the InsightsView component currently hardcodes
English subject/body; replace that hardcoded string with localized values (use
the project's translation hook/function used elsewhere in insights-view.tsx) and
construct the mailto using those translated subject/body strings. Add the new
translation keys (e.g., "insights.contactEmailSubject" and
"insights.contactEmailBody") to packages/i18n/locales/en/common.json and use
those keys in the component when building the href instead of the hardcoded
text.

---

Nitpick comments:
In `@apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx`:
- Line 62: Remove the redundant "Custom Tooltip component" comment that simply
restates what the code does; locate the comment above the Tooltip-related
component/function (e.g., the CustomTooltip declaration or the JSX return inside
RoutingFunnelContent) and delete that single-line comment so the file only
contains comments that explain why, not what.

In `@apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx`:
- Around line 18-23: In the RoutingFunnelSkeleton component remove the redundant
JSX "what" comments that merely restate the DOM (e.g., comments above the
div.h-full.w-full.p-4, the div.relative.h-[220px], and the
div.text-muted-foreground.absolute... plus the other noted comment locations),
leaving only comments that explain non-obvious "why" decisions; search for
comment text like "Chart container", "Chart area", "Y-axis" (and similar
comments referenced) and delete them so only purposeful explanatory comments
remain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b80fafa-acd8-4d27-8dee-bb5dc56a8cab

📥 Commits

Reviewing files that changed from the base of the PR and between 2920a24 and 49001fc.

📒 Files selected for processing (7)
  • apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelSkeleton.tsx
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • apps/web/modules/insights/views/insights-view.tsx
  • packages/features/insights/server/events.ts
  • packages/features/insights/services/InsightsBookingBaseService.ts
  • packages/i18n/locales/en/common.json
✅ Files skipped from review due to trivial changes (1)
  • packages/i18n/locales/en/common.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/features/insights/server/events.ts
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • packages/features/insights/services/InsightsBookingBaseService.ts

Comment thread apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx Outdated
Comment thread apps/web/modules/insights/views/insights-view.tsx Outdated
regisstedile and others added 2 commits May 17, 2026 19:59
- Add pointer-events-none to gradient overlay in CellWithOverflowX
- Translate aria-label in ChartCard Legend using t() with toggle_series key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix tooltip percentage calculation: read totalSubmissions from raw data
  point instead of visible payload to avoid broken percentages when the
  totalSubmissions series is toggled off
- Translate mailto subject/body in insights-view using t() + encodeURIComponent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@regisstedile
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

regisstedile and others added 3 commits May 17, 2026 20:53
- Add DataTableProvider context + useDataTable/useColumnFilters/useSegments hooks
- Add FilterCheckboxField/FilterCheckboxFieldsContainer stubs (TeamsFilter)
- Inline RoutingFormFieldType enum (routing-forms package not in fork)
- Simplify virtual-queues view to empty state (no TestForm dependency)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Export `legend` alias from RoutingFunnelContent (was LEGEND_KEYS, consumed as legend)
- Add useFilterValue hook stub for data-table EE replacement
- Re-export hooks (useDataTable, useColumnFilters, useFilterValue, useSegments) from data-table index
- Export lib/dateRange from data-table index (getDefaultStartDate/End needed by routing params hook)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TypeScript build was failing because "insights" was not a member of
the AppFlags union type used by checkIfFeatureIsEnabledGlobally().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added the ❗️ migrations contains migration files label May 17, 2026
regisstedile and others added 6 commits May 17, 2026 21:04
- Add bookingUserId/Name/Email/AvatarUrl to InsightsRoutingTableItem so
  they're properly typed instead of falling through to [key:string]:unknown
- Fix getRoutedToPerPeriodData stub shape to match RoutedToPerPeriod component
  (needs { users: { data, total }, periodStats: { data } })
- Fix getRoutingFormPaginatedResponsesForDownload stub to return { data, total }
  instead of { data, headers } as expected by RoutingFormResponsesDownload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add displayLabel support to ChartCard Legend component
- Add missing UNAUTHORIZED check in userBelongsToTeam procedure
- Minor UI cleanups in booking chart components

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add rel="noopener noreferrer" to external link in UpgradeTipWrapper
- Remove unused dayjs import in Download components, use native Date instead

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@regisstedile
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/web/modules/insights/components/filters/Download/Download.tsx (1)

58-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when pagination ends before reaching totalRecords.

Line 60 currently breaks on empty pages and still proceeds to a 100% success toast and CSV export, which can silently produce partial data. Treat this as an error when allData.length < totalRecords.

💡 Suggested fix
       while (allData.length < totalRecords) {
         const result = await fetchBatch(offset);
-        if (result.data.length === 0) break;
+        if (result.data.length === 0) {
+          throw new Error("Pagination ended early before all records were fetched.");
+        }
         allData = [...allData, ...result.data];
         offset += BATCH_SIZE;
@@
       showProgressToast(100);
+      if (allData.length < totalRecords) {
+        throw new Error("Download incomplete: fetched fewer records than expected.");
+      }
       const toDateStr = (d: string) => new Date(d).toISOString().slice(0, 10);

Also applies to: 68-71

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/insights/components/filters/Download/Download.tsx` around
lines 58 - 61, The loop that paginates using fetchBatch (see the while loop
using allData, offset, and totalRecords) currently breaks on an empty result and
continues to show a 100% success toast and export a CSV even if allData.length <
totalRecords; change the behavior so that when fetchBatch returns an empty page
before allData.length >= totalRecords you treat it as a failure: stop
pagination, surface an error (throw or return a rejected promise / set an error
state), log/report the short-fetch condition, and ensure the success toast and
CSV export logic (the downstream code that runs after the loop) are skipped and
a failure toast is shown instead; apply the same change to the other pagination
block that repeats the pattern (lines around the second loop using fetchBatch
and allData).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx`:
- Around line 45-46: The code in OrgTeamsFilter.tsx relies on data[0] for org
detection and avatar selection (used when building the "all" option and later
around the orgTeamsType branches), which breaks if the array order changes;
replace direct index access with a lookup that finds the correct org entry from
data (e.g., data.find(...)) based on a stable identifier or predicate (the
property that identifies the "all" org or current org) and use that found entry
to set imageUrl and to decide visibility for the "all" option; update any
occurrences where data[0] is used (refer to the orgTeamsType variable and the
imageUrl assignment in the return object) so they use the foundOrg variable
instead.
- Around line 76-77: The avatar alt text in OrgTeamsFilter.tsx is hardcoded and
can produce "undefined logo"; update the alt generation to use the i18n
translation helper (e.g., t) and a safe fallback instead of concatenating raw
placeholder. Specifically, in the OrgTeamsFilter component where alt uses
`${placeholder} logo`, replace with a localized string (use a key like
common.teamAvatar or common.avatarLabel with optional interpolation for the
placeholder) and ensure you fall back to a generic translated label when
placeholder is falsy; apply the same change for the other occurrence around
getPlaceholderAvatar/imageSrc. Add the corresponding keys and English text to
packages/i18n/locales/en/common.json (e.g., "teamAvatar": "{{name}} logo" and
"avatar": "logo" or similar).

---

Duplicate comments:
In `@apps/web/modules/insights/components/filters/Download/Download.tsx`:
- Around line 58-61: The loop that paginates using fetchBatch (see the while
loop using allData, offset, and totalRecords) currently breaks on an empty
result and continues to show a 100% success toast and export a CSV even if
allData.length < totalRecords; change the behavior so that when fetchBatch
returns an empty page before allData.length >= totalRecords you treat it as a
failure: stop pagination, surface an error (throw or return a rejected promise /
set an error state), log/report the short-fetch condition, and ensure the
success toast and CSV export logic (the downstream code that runs after the
loop) are skipped and a failure toast is shown instead; apply the same change to
the other pagination block that repeats the pattern (lines around the second
loop using fetchBatch and allData).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30515ff1-9738-4e11-a378-9f4b734affca

📥 Commits

Reviewing files that changed from the base of the PR and between d4c3937 and 9f4c2d3.

📒 Files selected for processing (26)
  • apps/web/app/(use-page-wrapper)/insights/UpgradeTipWrapper.tsx
  • apps/web/modules/insights/components/ChartCard.tsx
  • apps/web/modules/insights/components/booking/BookingKPICards.tsx
  • apps/web/modules/insights/components/booking/BookingsByHourChart.tsx
  • apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx
  • apps/web/modules/insights/components/booking/EventTrendsChart.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx
  • apps/web/modules/insights/components/filters/Download/Download.tsx
  • apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx
  • apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx
  • apps/web/modules/insights/hooks/useDefaultRoutingForm.ts
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • apps/web/modules/insights/views/insights-virtual-queues-view.tsx
  • packages/features/data-table/DataTableProvider.tsx
  • packages/features/data-table/hooks/useColumnFilters.ts
  • packages/features/data-table/hooks/useDataTable.ts
  • packages/features/data-table/hooks/useFilterValue.ts
  • packages/features/data-table/hooks/useSegments.ts
  • packages/features/data-table/index.ts
  • packages/features/filters/components/TeamsFilter.tsx
  • packages/features/flags/config.ts
  • packages/features/insights/server/routing-events.ts
  • packages/features/insights/services/InsightsRoutingBaseService.ts
  • packages/i18n/locales/en/common.json
  • packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/features/data-table/hooks/useSegments.ts
  • packages/features/data-table/hooks/useColumnFilters.ts
  • packages/features/data-table/index.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/features/insights/server/routing-events.ts
  • packages/i18n/locales/en/common.json
  • apps/web/app/(use-page-wrapper)/insights/UpgradeTipWrapper.tsx
  • apps/web/modules/insights/hooks/useDefaultRoutingForm.ts
  • apps/web/modules/insights/components/booking/BookingsByHourChart.tsx
  • apps/web/modules/insights/components/routing/RoutingFunnelContent.tsx
  • packages/features/insights/services/InsightsRoutingBaseService.ts
  • packages/trpc/server/routers/viewer/insights/procedures/userBelongsToTeam.ts
  • apps/web/modules/insights/components/ChartCard.tsx
  • apps/web/modules/insights/components/booking/RecentFeedbackTableContent.tsx
  • apps/web/modules/insights/components/booking/CSATOverTimeChart.tsx
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • apps/web/modules/insights/components/filters/Download/RoutingFormResponsesDownload.tsx

Comment thread apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx Outdated
Comment thread apps/web/modules/insights/components/filters/OrgTeamsFilter.tsx Outdated
- OrgTeamsFilter: find org entry by id instead of assuming data[0] is always the org
- OrgTeamsFilter: localize avatar alt text, avoid "undefined logo" output

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@regisstedile
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@regisstedile
Copy link
Copy Markdown
Author

Closing to reorganize: the branch accidentally includes commits for Organizations, SAML, and auth that belong in separate PRs. Will reopen a clean insights-only PR shortly.

Three root causes:
1. `getRoutingFormHeaders` returned untyped `[]` → `HeaderRow = never` → cascade
   Fix: explicit `Promise<RoutingFormHeaderRow[]>` return type on stub
2. `InsightsRoutingTableItem` missing explicit fields (bookingAssignmentReason,
   utm_*, createdAt, fields) → index signature `[key]:unknown` hit → `unknown`
   Fix: add all accessed properties with proper types
3. `useColumnFilters` in packages/features accepted no args, but routing hooks
   call it with `{ exclude: [...] }`
   Fix: add optional `exclude` parameter with filter logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@regisstedile
Copy link
Copy Markdown
Author

Closing — will use locally only.

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

Labels

❗️ migrations contains migration files size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant