Skip to content

Added custom quotas #112

Open
Bloxion wants to merge 11 commits intoPlanetaryOrbit:mainfrom
Bloxion:main
Open

Added custom quotas #112
Bloxion wants to merge 11 commits intoPlanetaryOrbit:mainfrom
Bloxion:main

Conversation

@Bloxion
Copy link
Contributor

@Bloxion Bloxion commented Feb 2, 2026

  • Added custom quotas with only description and title
IMG_0357

Summary by CodeRabbit

  • New Features

    • Added a "Custom" quota type: can be created, appears with a "Custom Quota" indicator, and is treated as manually tracked (no automatic value/progress).
  • Bug Fixes

    • Creation/validation adjusted: custom quotas may omit numeric values; non-custom quotas still require name/type/value and session-type where applicable.
    • UI/backend now consistently show null/0 for custom current value/percentage and hide automatic progress.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

Adds a "custom" quota type: backend validates and creates quotas without numeric value/sessionType for custom quotas; frontend hides related inputs and shows manual-tracking UI; database schema makes Quota.value nullable to support value-less quotas.

Changes

Cohort / File(s) Summary
Backend Quota Logic
pages/api/workspace/[id]/activity/quotas/new.ts
Adds isCustom, hasRoles, hasDepartments; parses value and requires it only for non-custom quotas; sets quotaData.value and quotaData.sessionType only for non-custom quotas; validation/creation/association/audit flow preserved.
Frontend Quota Management
pages/workspace/[id]/quotas.tsx
Adds "custom" quota type option; hides requirement/value/sessionType inputs for custom quotas; omits value/sessionType from create payload for custom quotas; displays manual-tracking message and hides progress/currentValue/percentage for custom quotas.
Database Schema
prisma/schema.prisma
Makes Quota.value nullable (IntInt?) so quotas can exist without a numeric value.

Sequence Diagram(s)

sequenceDiagram
    participant Frontend as Frontend UI
    participant API as Quota API
    participant DB as Database
    participant Audit as Audit Log

    Frontend->>API: POST /workspace/:id/activity/quotas/new (payload with type)
    API->>API: determine isCustom, parse value, validate inputs
    alt non-custom quota
        API->>DB: create Quota with value and sessionType
    else custom quota
        API->>DB: create Quota without value/sessionType
    end
    API->>DB: associate roles/departments if provided
    DB-->>API: return full Quota with relations
    API->>Audit: record quota creation
    API-->>Frontend: 201 Created (Quota object)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 I hopped through code with gentle feet,
Some quotas count, some I keep neat.
Custom ones skip the numbered trail,
I mark them by nibble, not by scale.
Hooray — new ways to track and meet!

🚥 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 'Added custom quotas' directly and accurately reflects the main change across all modified files—the introduction of a new custom quota type with corresponding backend, frontend, and schema updates.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
pages/workspace/[id]/quotas.tsx (1)

552-569: ⚠️ Potential issue | 🟡 Minor

Avoid “null custom per timeframe” in the manage list.

With value nullable and types.custom = "custom", the manage list still interpolates {quota.value} {types[quota.type]}. For custom quotas this renders poorly. Consider a custom label instead.

📝 Suggested display tweak
- <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
-   {quota.value} {types[quota.type]} per timeframe
- </p>
+ {quota.type === "custom" ? (
+   <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
+     Custom quota
+   </p>
+ ) : (
+   <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
+     {quota.value} {types[quota.type]} per timeframe
+   </p>
+ )}
🤖 Fix all issues with AI agents
In `@pages/api/workspace/`[id]/activity/quotas/new.ts:
- Around line 27-45: The current validation lets non-finite numbers
(NaN/Infinity) and ambiguous roles/departments states pass through; update the
check around the request body so value is validated with Number.isFinite(value)
and (optionally) Number.isInteger(value) when type !== "custom" and
convert/assign quotaData.value using the already-validated numeric value (or
parseInt from a validated string) in the quota creation block; also simplify the
roles/departments guard to require that at least one of roles or departments is
an array with length>0 (instead of the complex mixed boolean expression) so a
request with one missing and the other empty no longer slips through, and keep
the sessionType assignment conditional on !isCustom as currently written.

In `@pages/workspace/`[id]/quotas.tsx:
- Around line 388-395: The custom-quota branch in the myQuotasWithProgress
mapping sets currentValue/value to null but doesn’t signal the UI to hide the
progress block, causing a " / " fraction and an empty bar; update the mapping in
myQuotas.map (the quota.type === "custom" branch) to include a clear flag or
display value such as showProgress: false and displayValue: "N/A" (or value:
"N/A"), and then change the progress-rendering code to check showProgress (or
whether value is a valid number) before rendering the fraction/bar so custom
quotas render "N/A" or no progress UI instead of an empty fraction.

In `@prisma/schema.prisma`:
- Around line 393-397: Quota.value is now optional so any percentage math or
string interpolation using quota.value must guard against null/undefined; update
all places (e.g., the reset handler in
pages/api/workspace/[id]/activity/reset.ts, pages/workspace/[id]/quotas.tsx,
pages/workspace/[id]/activity/index.tsx, and components/profile/quotas.tsx)
where you compute (currentValue / quota.value) * 100 or embed `${quota.value}`
to instead use a null-safe alternative (e.g., use quota.value ?? 0 for the
divisor or conditionally render a placeholder like '—' and avoid division by
zero), and clamp or short-circuit percent results to a sensible default when
quota.value is null/0 to prevent NaN/Infinity.

@Bloxion Bloxion marked this pull request as draft February 2, 2026 00:28
@Bloxion
Copy link
Contributor Author

Bloxion commented Feb 2, 2026

image

@Bloxion Bloxion marked this pull request as ready for review February 2, 2026 00:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
pages/workspace/[id]/quotas.tsx (2)

552-570: ⚠️ Potential issue | 🟡 Minor

Avoid rendering null custom per timeframe in the Manage Quotas list.

Custom quotas have value = null, but the manage list still renders {quota.value} {types[quota.type]} per timeframe, which will display a confusing “null custom…”. Consider a custom-only label there.

✅ Suggested UI guard (Manage Quotas card)
- <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
-   {quota.value} {types[quota.type]} per timeframe
- </p>
+ {quota.type === "custom" ? (
+   <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
+     Custom quota (manual tracking)
+   </p>
+ ) : (
+   <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
+     {quota.value} {types[quota.type]} per timeframe
+   </p>
+ )}

1053-1098: ⚠️ Potential issue | 🟠 Major

Fix validation logic to prevent blocking custom quota submissions when requirement field is unmounted.

The requirement field is conditionally rendered only when watchedType !== "custom", but React Hook Form keeps it registered with required: true by default. When users select "custom" type and submit, validation fails because the unmounted field is still marked as required.

Apply one of these fixes:

  • Set shouldUnregister: true in the useForm options so unmounted fields are automatically unregistered, OR
  • Make the required validation conditional: required: watchedType !== "custom"

The submission handler already correctly skips the requirement value for custom quotas (if (type !== "custom")), but this never gets called due to the validation blocking.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
pages/workspace/[id]/quotas.tsx (1)

878-879: ⚠️ Potential issue | 🟡 Minor

Custom quotas display incorrectly in All Quotas view.

For custom quotas, quota.value is null and types["custom"] is "custom", resulting in a display like "null custom per timeframe". The My Quotas view (lines 733-737) handles this conditionally, but the All Quotas management view does not.

Suggested fix
-                          <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
-                            {quota.value} {types[quota.type]} per timeframe
-                          </p>
+                          {quota.type !== "custom" ? (
+                            <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
+                              {quota.value} {types[quota.type]} per timeframe
+                            </p>
+                          ) : (
+                            <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400 italic">
+                              Manually tracked
+                            </p>
+                          )}
🧹 Nitpick comments (2)
pages/workspace/[id]/quotas.tsx (2)

560-560: Display label "custom" is not user-friendly.

The types map provides user-facing labels (e.g., "Minutes in game", "Sessions hosted"). Using "custom" as the label appears inconsistent. This affects line 879 in the All Quotas view where it renders {quota.value} {types[quota.type]} per timeframe, which for custom quotas would display something like "null custom per timeframe".

Consider a more descriptive label or handling custom quotas separately in the display logic.

Suggested fix
-    custom: "custom",
+    custom: "Custom",

And update line 879 to handle custom quotas:

{quota.type !== "custom" ? (
  <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400">
    {quota.value} {types[quota.type]} per timeframe
  </p>
) : (
  <p className="text-xs text-zinc-500 mt-1 dark:text-zinc-400 italic">
    Manually tracked
  </p>
)}

617-621: Redundant condition check.

The type !== "custom" check on line 617 is redundant since "custom" is not included in the array ["sessions_hosted", "sessions_attended", "sessions_logged"]. The includes() check alone would suffice.

Simplification
-    if ( type !== "custom" && 
-      ["sessions_hosted", "sessions_attended", "sessions_logged"].includes(type)
-    ) {
+    if (["sessions_hosted", "sessions_attended", "sessions_logged"].includes(type)) {

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
pages/workspace/[id]/quotas.tsx (1)

388-435: ⚠️ Potential issue | 🟡 Minor

Guard percentage calculations against null/zero quota values.

quota.value is now nullable, so a missing/0 value on non-custom quotas will yield NaN/Infinity and break the progress UI. Consider a safe denominator to keep percentage stable.

🛡️ Suggested guard for percentage calculations
+      const safePercent = (current: number) =>
+        typeof quota.value === "number" && quota.value > 0
+          ? (current / quota.value) * 100
+          : 0;

       switch (quota.type) {
         case "mins":
           currentValue = activeMinutes;
-          percentage = (activeMinutes / quota.value) * 100;
+          percentage = safePercent(activeMinutes);
           break;
         case "sessions_hosted":
           const hostedCount = quota.sessionType && quota.sessionType !== "all"
             ? hostedSessionsByType[quota.sessionType] || 0
             : sessionsHosted;
           currentValue = hostedCount;
-          percentage = (hostedCount / quota.value) * 100;
+          percentage = safePercent(hostedCount);
           break;
         case "sessions_attended":
           const attendedCount = quota.sessionType && quota.sessionType !== "all"
             ? attendedSessionsByType[quota.sessionType] || 0
             : sessionsAttended;
           currentValue = attendedCount;
-          percentage = (attendedCount / quota.value) * 100;
+          percentage = safePercent(attendedCount);
           break;
         case "sessions_logged":
           const loggedCount = quota.sessionType && quota.sessionType !== "all"
             ? loggedSessionsByType[quota.sessionType] || 0
             : totalSessionsLogged;
           currentValue = loggedCount;
-          percentage = (loggedCount / quota.value) * 100;
+          percentage = safePercent(loggedCount);
           break;
         case "alliance_visits":
           currentValue = allianceVisits;
-          percentage = (allianceVisits / quota.value) * 100;
+          percentage = safePercent(allianceVisits);
           break;
       }

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.

1 participant