Skip to content

Enhance dashboard with selectable periodic tax estimation#13

Merged
mhaadiabu merged 5 commits into
mhaadiabu:mainfrom
lord-shola:main
Apr 19, 2026
Merged

Enhance dashboard with selectable periodic tax estimation#13
mhaadiabu merged 5 commits into
mhaadiabu:mainfrom
lord-shola:main

Conversation

@lord-shola

@lord-shola lord-shola commented Apr 19, 2026

Copy link
Copy Markdown

This pull request adds support for selecting a tax estimation period (e.g., 30, 60, 90 days, or 1 year) on both the Influencers dashboard and the main overview dashboard. Tax estimates are now dynamically calculated based on the selected period using a new tax estimation utility, providing more flexible and accurate projections. The UI has been updated to include a period selector and to display the projected period in tax estimate labels.

Tax estimation logic and utilities:

  • Added a new module src/lib/tax-period-estimate.ts that provides functions and constants for estimating tax over a selectable period using Ghana Revenue Authority tax brackets and progressive rates. This includes estimateTaxForPeriod, TAX_PERIOD_OPTIONS, and DEFAULT_TAX_PERIOD_DAYS.

Influencers dashboard updates (src/app/(dashboard)/influencers/page.tsx):

  • Integrated the period selector (Select component) allowing users to choose the tax period. The table now displays tax estimates calculated for the selected period using estimateTaxForPeriod, and labels are updated to indicate the projection period. [1] [2] [3] [4] [5] [6] [7]

Main dashboard updates (src/app/(dashboard)/page.tsx):

  • Added the tax period selector to the "Revenue Inputs & Tax Estimates" section. The "Estimated Tax Output" metric and related UI now display the sum of tax estimates for all channels, projected for the selected period. [1] [2] [3] [4]

Summary by CodeRabbit

  • New Features
    • Added tax period selection controls to the dashboard and influencers page, enabling users to estimate and project taxes for custom periods (30, 60, 90, or 365 days)
    • Tax estimates now dynamically calculate and display accurate projections based on the selected time period
    • Added period selector dropdown for seamless switching between different estimation periods

Copilot AI and others added 5 commits April 19, 2026 18:06
Agent-Logs-Url: https://github.com/lord-shola/graitld/sessions/d5511975-a155-461c-86a5-e976cf0a619b

Co-authored-by: lord-shola <154298518+lord-shola@users.noreply.github.com>
Agent-Logs-Url: https://github.com/lord-shola/graitld/sessions/d5511975-a155-461c-86a5-e976cf0a619b

Co-authored-by: lord-shola <154298518+lord-shola@users.noreply.github.com>
…rkflow

Add selectable periodic tax estimation in dashboard views
@vercel

vercel Bot commented Apr 19, 2026

Copy link
Copy Markdown

@lord-shola is attempting to deploy a commit to the mhaadi Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The pull request introduces tax-period selection controls to the main dashboard and influencers page, allowing users to project tax estimates across different time periods (30, 60, 90, or 365 days). A new utility module implements Ghana progressive tax bracket calculations and revenue estimation logic to compute period-specific tax projections.

Changes

Cohort / File(s) Summary
Dashboard Pages
src/app/(dashboard)/page.tsx, src/app/(dashboard)/influencers/page.tsx
Added client-side tax-period selection UI via React state and Select component. Both pages now compute period-adjusted tax estimates using estimateTaxForPeriod function and display projections with period-specific labels instead of static channel-level tax values.
Tax Estimation Library
src/lib/tax-period-estimate.ts
New module exporting tax-period constants (TAX_PERIOD_OPTIONS, DEFAULT_TAX_PERIOD_DAYS) and estimateTaxForPeriod function. Implements Ghana progressive tax bracket logic with revenue estimation from multiple sources (annual/monthly revenue, or derived from view counts with lifecycle scaling).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Pick thirty days or a full year's span,
Tax estimates flow from our cunning plan,
Ghana's brackets dance through progressive light,
Dashboard and influencers, now perfectly right,
Period selection—a fluffy delight! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding selectable tax estimation periods to dashboards with dynamic recalculation based on user selection.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`(dashboard)/page.tsx:
- Around line 85-100: The page is fetching full channel summaries via
useQuery(api.influencers.getChannels) just to compute totalTaxForPeriod,
exposing PII and blocking rendering; replace that client-side aggregation with a
server-side Convex query that returns only the period tax total (or a minimal
revenue-only projection). Add/use a new API hook (e.g.
api.analytics.getTotalTaxForPeriod or similar) that accepts taxPeriodDays,
remove the useQuery call to api.influencers.getChannels and stop using channels
in totalTaxForPeriod and estimateTaxForPeriod on the client; instead call the
new server-side query and use its numeric result directly wherever
totalTaxForPeriod is referenced so the client no longer downloads channel
summaries or uses estimateTaxForPeriod for aggregation.
- Around line 141-164: The period selector (Select using
value={String(taxPeriodDays)} and setter setTaxPeriodDays) currently sits by the
chart legend but only updates the metric card while the chart still renders
revenueData.tax unchanged; either move the Select and TAX_PERIOD_OPTIONS UI next
to the metric card it controls or make the chart consume taxPeriodDays so its
series are filtered/derived from revenueData.tax by period (update the chart
rendering logic that currently reads revenueData.tax to compute series using
taxPeriodDays). Ensure the Select remains wired to
taxPeriodDays/setTaxPeriodDays and update labels/legend to reflect whether the
chart is periodized.

In `@src/lib/tax-period-estimate.ts`:
- Around line 58-65: The code currently treats lifetime revenue from
estimateRevenueFromViews(channel.totalViews, channel.topicCategories ?? []) as
annualized when channel.channelCreatedAt is missing; change the logic so that if
channel.totalViews is present but channel.channelCreatedAt is undefined you
return undefined (or otherwise mark the value as lifetime-derived) instead of
computing lifetimeRevenue * (DAYS_IN_YEAR / ageDays); update the block around
the checks for channel.totalViews and channel.channelCreatedAt (referencing
estimateRevenueFromViews, channel.totalViews, channel.channelCreatedAt, and
DAYS_IN_YEAR) to only annualize when channelCreatedAt is available and otherwise
return undefined.
- Around line 71-87: The exported function estimateTaxForPeriod should guard
against invalid periodDays (NaN, Infinity, zero or negative) to avoid producing
NaN/meaningless taxes; update estimateTaxForPeriod to validate that
Number.isFinite(periodDays) && periodDays > 0 and return undefined for invalid
input before using DAYS_IN_YEAR, computing periodRevenue, mapping
GHA_TAX_BRACKETS, or calling calculateProgressiveTax so callers get a safe
undefined instead of broken results.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 44ac4c89-61f6-4065-a57e-04095875ccaf

📥 Commits

Reviewing files that changed from the base of the PR and between a54d2a6 and eab57dd.

📒 Files selected for processing (3)
  • src/app/(dashboard)/influencers/page.tsx
  • src/app/(dashboard)/page.tsx
  • src/lib/tax-period-estimate.ts

Comment on lines +85 to +100
const channels = useQuery(api.influencers.getChannels, {});
const revenueData = useQuery(api.analytics.getRevenueByMonth);
const topChannels = useQuery(api.analytics.getTopInfluencers);
const recentLogs = useQuery(api.auditLogs.getRecentLogs, { limit: 5 });
const selectedTaxPeriod = useMemo(
() => TAX_PERIOD_OPTIONS.find((option) => option.days === taxPeriodDays) ?? TAX_PERIOD_OPTIONS[0],
[taxPeriodDays],
);
const totalTaxForPeriod = useMemo(
() =>
(channels ?? []).reduce(
(sum, channel) => sum + (estimateTaxForPeriod(channel, taxPeriodDays) ?? 0),
0,
),
[channels, taxPeriodDays],
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Aggregate the tax total server-side instead of fetching every channel.

This page now downloads full channel summaries just to compute totalTaxForPeriod, and it blocks the entire dashboard until that query resolves. Per convex/channelData.ts, those summaries include fields like email, phone, taxIdNumber, and notes, so this also broadens client-side PII exposure for an aggregate metric. Prefer a Convex query that returns only the period tax total, or at least a minimal revenue-only projection.

♻️ Directional refactor
-  const channels = useQuery(api.influencers.getChannels, {});
+  const totalTaxForPeriod = useQuery(api.influencers.getTaxEstimateForPeriod, {
+    periodDays: taxPeriodDays,
+  });
...
-  const totalTaxForPeriod = useMemo(
-    () =>
-      (channels ?? []).reduce(
-        (sum, channel) => sum + (estimateTaxForPeriod(channel, taxPeriodDays) ?? 0),
-        0,
-      ),
-    [channels, taxPeriodDays],
-  );
...
-  if (stats === undefined || channels === undefined) {
+  if (stats === undefined || totalTaxForPeriod === undefined) {
     return <DashboardSkeleton />;
   }

Also applies to: 102-104

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(dashboard)/page.tsx around lines 85 - 100, The page is fetching
full channel summaries via useQuery(api.influencers.getChannels) just to compute
totalTaxForPeriod, exposing PII and blocking rendering; replace that client-side
aggregation with a server-side Convex query that returns only the period tax
total (or a minimal revenue-only projection). Add/use a new API hook (e.g.
api.analytics.getTotalTaxForPeriod or similar) that accepts taxPeriodDays,
remove the useQuery call to api.influencers.getChannels and stop using channels
in totalTaxForPeriod and estimateTaxForPeriod on the client; instead call the
new server-side query and use its numeric result directly wherever
totalTaxForPeriod is referenced so the client no longer downloads channel
summaries or uses estimateTaxForPeriod for aggregation.

Comment on lines +141 to +164
<div className='flex flex-wrap items-center gap-2'>
<span className='flex items-center gap-1.5 text-[10px] font-medium tracking-wider text-muted-foreground uppercase'>
<span className='h-2 w-2 rounded-full bg-[oklch(0.6_0.18_250)]' /> Revenue inputs
</span>
<span className='flex items-center gap-1.5 text-[10px] font-medium tracking-wider text-muted-foreground uppercase'>
<span className='h-2 w-2 rounded-full bg-[oklch(0.65_0.18_150)]' /> Tax estimates
</span>
<Select
value={String(taxPeriodDays)}
onValueChange={(value) => {
setTaxPeriodDays(Number(value));
}}
>
<SelectTrigger className='h-8 w-[110px] text-[10px] tracking-wider uppercase'>
<SelectValue placeholder='Tax period' />
</SelectTrigger>
<SelectContent>
{TAX_PERIOD_OPTIONS.map((option) => (
<SelectItem key={option.days} value={String(option.days)}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the period selector scoped to data it actually changes.

The selector sits in the chart header next to the “Tax estimates” legend, but the chart still renders revenueData.tax unchanged. Selecting “90 days” changes the metric card, not the chart, which makes the chart appear period-aware when it is not. Either move the selector next to the metric it controls or derive/label a periodized chart series.

Also applies to: 170-232

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(dashboard)/page.tsx around lines 141 - 164, The period selector
(Select using value={String(taxPeriodDays)} and setter setTaxPeriodDays)
currently sits by the chart legend but only updates the metric card while the
chart still renders revenueData.tax unchanged; either move the Select and
TAX_PERIOD_OPTIONS UI next to the metric card it controls or make the chart
consume taxPeriodDays so its series are filtered/derived from revenueData.tax by
period (update the chart rendering logic that currently reads revenueData.tax to
compute series using taxPeriodDays). Ensure the Select remains wired to
taxPeriodDays/setTaxPeriodDays and update labels/legend to reflect whether the
chart is periodized.

Comment on lines +58 to +65
if (channel.totalViews !== undefined) {
const lifetimeRevenue = estimateRevenueFromViews(channel.totalViews, channel.topicCategories ?? []);
if (!channel.channelCreatedAt) {
return lifetimeRevenue;
}

const ageDays = Math.max(30, (Date.now() - channel.channelCreatedAt) / (1000 * 60 * 60 * 24));
return lifetimeRevenue * (DAYS_IN_YEAR / ageDays);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t annualize lifetime view revenue without an age signal.

When totalViews exists but channelCreatedAt is missing, Line 61 treats lifetime estimated revenue as annual revenue. That makes the downstream “Projected for 30/60/90 days” tax label look precise even though the period basis is unknown. Prefer returning undefined until an age/source period is available, or label this path explicitly as lifetime-derived.

🐛 Proposed fix
   if (channel.totalViews !== undefined) {
     const lifetimeRevenue = estimateRevenueFromViews(channel.totalViews, channel.topicCategories ?? []);
-    if (!channel.channelCreatedAt) {
-      return lifetimeRevenue;
+    if (channel.channelCreatedAt === undefined) {
+      return undefined;
     }
 
-    const ageDays = Math.max(30, (Date.now() - channel.channelCreatedAt) / (1000 * 60 * 60 * 24));
+    const elapsedMs = Date.now() - channel.channelCreatedAt;
+    if (elapsedMs <= 0) {
+      return undefined;
+    }
+
+    const ageDays = Math.max(30, elapsedMs / (1000 * 60 * 60 * 24));
     return lifetimeRevenue * (DAYS_IN_YEAR / ageDays);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/tax-period-estimate.ts` around lines 58 - 65, The code currently
treats lifetime revenue from estimateRevenueFromViews(channel.totalViews,
channel.topicCategories ?? []) as annualized when channel.channelCreatedAt is
missing; change the logic so that if channel.totalViews is present but
channel.channelCreatedAt is undefined you return undefined (or otherwise mark
the value as lifetime-derived) instead of computing lifetimeRevenue *
(DAYS_IN_YEAR / ageDays); update the block around the checks for
channel.totalViews and channel.channelCreatedAt (referencing
estimateRevenueFromViews, channel.totalViews, channel.channelCreatedAt, and
DAYS_IN_YEAR) to only annualize when channelCreatedAt is available and otherwise
return undefined.

Comment on lines +71 to +87
export function estimateTaxForPeriod(channel: {
estimatedAnnualRevenue?: number;
estimatedMonthlyRevenue?: number;
totalViews?: number;
topicCategories?: string[];
channelCreatedAt?: number;
}, periodDays: number): number | undefined {
const annualRevenue = estimateAnnualRevenue(channel);
if (annualRevenue === undefined) return undefined;

const periodRevenue = (annualRevenue * periodDays) / DAYS_IN_YEAR;
const periodBrackets = GHA_TAX_BRACKETS.map((bracket) => ({
limit: Number.isFinite(bracket.limit) ? (bracket.limit * periodDays) / DAYS_IN_YEAR : Infinity,
rate: bracket.rate,
}));

return calculateProgressiveTax(periodRevenue, periodBrackets);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard the exported estimator against invalid periods.

estimateTaxForPeriod accepts any number; NaN, Infinity, zero, or negative periods can return NaN or meaningless tax values. Since this is exported from the utility module, add a runtime guard even if current UI values come from TAX_PERIOD_OPTIONS.

🛡️ Proposed fix
 export function estimateTaxForPeriod(channel: {
   estimatedAnnualRevenue?: number;
   estimatedMonthlyRevenue?: number;
   totalViews?: number;
   topicCategories?: string[];
   channelCreatedAt?: number;
 }, periodDays: number): number | undefined {
+  if (!Number.isFinite(periodDays) || periodDays <= 0) {
+    return undefined;
+  }
+
   const annualRevenue = estimateAnnualRevenue(channel);
   if (annualRevenue === undefined) return undefined;
📝 Committable suggestion

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

Suggested change
export function estimateTaxForPeriod(channel: {
estimatedAnnualRevenue?: number;
estimatedMonthlyRevenue?: number;
totalViews?: number;
topicCategories?: string[];
channelCreatedAt?: number;
}, periodDays: number): number | undefined {
const annualRevenue = estimateAnnualRevenue(channel);
if (annualRevenue === undefined) return undefined;
const periodRevenue = (annualRevenue * periodDays) / DAYS_IN_YEAR;
const periodBrackets = GHA_TAX_BRACKETS.map((bracket) => ({
limit: Number.isFinite(bracket.limit) ? (bracket.limit * periodDays) / DAYS_IN_YEAR : Infinity,
rate: bracket.rate,
}));
return calculateProgressiveTax(periodRevenue, periodBrackets);
export function estimateTaxForPeriod(channel: {
estimatedAnnualRevenue?: number;
estimatedMonthlyRevenue?: number;
totalViews?: number;
topicCategories?: string[];
channelCreatedAt?: number;
}, periodDays: number): number | undefined {
if (!Number.isFinite(periodDays) || periodDays <= 0) {
return undefined;
}
const annualRevenue = estimateAnnualRevenue(channel);
if (annualRevenue === undefined) return undefined;
const periodRevenue = (annualRevenue * periodDays) / DAYS_IN_YEAR;
const periodBrackets = GHA_TAX_BRACKETS.map((bracket) => ({
limit: Number.isFinite(bracket.limit) ? (bracket.limit * periodDays) / DAYS_IN_YEAR : Infinity,
rate: bracket.rate,
}));
return calculateProgressiveTax(periodRevenue, periodBrackets);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/tax-period-estimate.ts` around lines 71 - 87, The exported function
estimateTaxForPeriod should guard against invalid periodDays (NaN, Infinity,
zero or negative) to avoid producing NaN/meaningless taxes; update
estimateTaxForPeriod to validate that Number.isFinite(periodDays) && periodDays
> 0 and return undefined for invalid input before using DAYS_IN_YEAR, computing
periodRevenue, mapping GHA_TAX_BRACKETS, or calling calculateProgressiveTax so
callers get a safe undefined instead of broken results.

@mhaadiabu mhaadiabu merged commit 7a21361 into mhaadiabu:main Apr 19, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants