Skip to content

Vendor Management#156

Merged
claudfuen merged 8 commits intomainfrom
claudio/vendor-management
Mar 19, 2025
Merged

Vendor Management#156
claudfuen merged 8 commits intomainfrom
claudio/vendor-management

Conversation

@claudfuen
Copy link
Copy Markdown
Contributor

@claudfuen claudfuen commented Mar 19, 2025

Summary by CodeRabbit

  • New Features

    • Launched a comprehensive risk management interface with dedicated pages for risk overviews, detailed dashboards, and comments.
    • Introduced forms for creating and updating risks and tasks—including inherent and residual risk metrics—as well as support for attachments and task comments.
    • Enhanced the vendor registration experience with improved table views, dynamic filtering, and responsive layouts.
    • Updated multi-language support for clearer vendor and risk-related labels.
  • Style

    • Refined layout formatting and updated menu navigation options for a more consistent user interface.

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 19, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
app ❌ Failed (Inspect) Mar 19, 2025 7:09pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
comp-portal ⬜️ Skipped (Inspect) Mar 19, 2025 7:09pm

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 19, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 19, 2025

Caution

Review failed

The head commit changed during the review from e04eacf to 66dcbed.

Walkthrough

This pull request refactors the risk management section of the dashboard. It reformats an existing layout file by uncommenting a previously disabled menu item and adjusts the component structure from vendor management to risk management. New pages, layouts, and hooks are introduced for risk details, comments, tasks, and a risk register. Additional actions and custom hooks provide data fetching and caching for risks and attachments, while search parameters are defined for both tasks and the register.

Changes

Files Summary
apps/app/src/app/[locale]/.../vendors/(overview)/layout.tsx
apps/app/src/app/[locale]/.../vendors/(overview)/page.tsx
Reformatted layout; uncommented menu item and refactored the VendorOverviewPage into a RiskManagement component with updated data fetching and metadata.
apps/app/src/app/[locale]/.../vendors/[riskId]/comments/page.tsx
apps/app/src/app/[locale]/.../vendors/[riskId]/layout.tsx
apps/app/src/app/[locale]/.../vendors/[riskId]/page.tsx
Introduced new risk pages for comments, layout, and a comprehensive risk dashboard that handle authentication, risk fetching, and conditional redirection.
apps/app/src/app/[locale]/.../vendors/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts
apps/app/src/app/[locale]/.../vendors/[riskId]/tasks/[taskId]/hooks/useTaskAttachments.ts
apps/app/src/app/[locale]/.../vendors/[riskId]/tasks/[taskId]/page.tsx
Added a new authenticated action to fetch task attachments, a custom hook for managing attachments using SWR, and a page for displaying task information with data caching.
apps/app/src/app/[locale]/.../vendors/[riskId]/tasks/search-params.ts Added a search parameters cache configuration for task-related filtering.
apps/app/src/app/[locale]/.../vendors/register/** Implemented a full suite for the risk register, including a responsive table component, actions for fetching risks, table column and filter configurations, a custom hook for retrieving risk data, a dedicated layout, and search parameters for structured filtering.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Browser
  participant LayoutComponent
  participant AuthService
  participant RiskAPI as getRisk
  participant DB

  User->>Browser: Request risk page (riskId)
  Browser->>LayoutComponent: Load risk layout
  LayoutComponent->>AuthService: Validate session & organization
  AuthService-->>LayoutComponent: Session valid/invalid
  LayoutComponent->>RiskAPI: Fetch risk using riskId & organizationId
  RiskAPI->>DB: Query risk data
  DB-->>RiskAPI: Return risk data
  RiskAPI-->>LayoutComponent: Return risk data / null
  alt Risk data is found
    LayoutComponent->>Browser: Render risk details and components
  else Risk data is missing
    LayoutComponent->>Browser: Redirect to risk overview page
  end
Loading

Possibly related PRs

Poem

I'm a cheeky rabbit, code in my stride,
Hopping along where risks coincide.
New pages bloom with every little tweak,
Tasks and registers now play hide and seek.
With each refresh, my heart does race 🐇,
Celebrating the flow in this digital space!
Cheers to code that's agile and ace!


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (21)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (2)

6-8: Component implementation appears overly simplified.

The RiskRegisterPage component only renders the RiskRegisterTable without passing any props. This might indicate that all logic is encapsulated in the RiskRegisterTable component, which could lead to a lack of flexibility if you need to customize the table behavior from the parent component.

Consider passing configuration props to the table component:

-export default function RiskRegisterPage() {
-	return <RiskRegisterTable />;
-}
+export default function RiskRegisterPage() {
+	// Could include hooks for state management here if needed
+	return <RiskRegisterTable 
+		// Consider passing props for configuration
+	/>;
+}

10-22: Properly implemented metadata generation for SEO.

The generateMetadata function correctly handles internationalization for page titles, which is important for SEO and accessibility.

However, there's an indentation inconsistency - the code uses tabs instead of spaces which differs from other files in the PR.

Maintain consistent indentation throughout the codebase:

 export async function generateMetadata({
-	params,
+  params,
 }: {
-	params: Promise<{ locale: string }>;
+  params: Promise<{ locale: string }>;
 }): Promise<Metadata> {
-	const { locale } = await params;
-	setStaticParamsLocale(locale);
-	const t = await getI18n();
+  const { locale } = await params;
+  setStaticParamsLocale(locale);
+  const t = await getI18n();

-	return {
-		title: t("sub_pages.risk.register"),
-	};
+  return {
+    title: t("sub_pages.risk.register"),
+  };
 }
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterColumns.tsx (1)

30-32: Consider dynamic badge variants based on department

Using a hardcoded "marketing" variant for all departments seems inconsistent. Consider making the badge variant dynamic based on the department value.

-<Badge variant="marketing" className="uppercase w-fit">
+<Badge variant={row.original.department.toLowerCase()} className="uppercase w-fit">
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/actions/getRisks.ts (1)

51-51: Add a comment explaining pagination calculation

Consider adding a comment to explain the skip calculation for better code maintainability.

+// Calculate the number of items to skip based on page number and page size
 const skip = (page - 1) * (pageSize ?? 10);
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/hooks/useRisks.ts (1)

33-61: Consider adding retry strategy for error resilience

The SWR configuration is well set up with appropriate revalidation options. For improved error resilience, consider adding a retry mechanism using SWR's errorRetryCount and errorRetryInterval options, especially useful for transient network issues.

  {
    revalidateOnFocus: true,
    revalidateOnReconnect: true,
    revalidateOnMount: true,
    revalidateIfStale: true,
+   errorRetryCount: 3,
+   errorRetryInterval: 5000,
  }
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts (1)

54-60: Consider providing more specific error information

The current error handling logs the actual error but returns a generic message to the client. For better debugging, consider including more specific error details in the response (at least in development environments).

		} catch (error) {
			console.error("Error fetching task attachments:", error);
			return {
				success: false,
				error: "Failed to fetch task attachments",
+				details: process.env.NODE_ENV === 'development' ? String(error) : undefined,
			};
		}
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/RiskRegisterTable.tsx (1)

20-48: URL state management looks good, but consider refactoring for improved readability

The use of useQueryState for synchronizing state with URL parameters is a good practice for maintaining user navigation state. However, consider extracting these state declarations into a custom hook to improve component readability.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterFilters.tsx (1)

2-4: Improve import consistency and reduce import path complexity

The import statements could be improved for better maintainability:

  1. Use consistent format for type imports (add "type" prefix for Departments)
  2. Consider using absolute imports instead of deeply nested relative paths
- import { type Departments, RiskStatus } from "@bubba/db/types";
+ import type { Departments, RiskStatus } from "@bubba/db/types";
- import type { Admin } from "../../../../evidence/[id]/hooks/useOrganizationAdmins";
+ import type { Admin } from "@/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useOrganizationAdmins";
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/page.tsx (4)

34-63: Consider more explicit HTTP handling for missing or unauthorized sessions.

Your checks at lines 60 and 64 properly redirect users when their session is invalid or missing organization info. However, consider returning a dedicated HTTP status (e.g., 401 or 403) to signal unauthorized access more clearly. This can help upstream error handling, logging, or potential user notifications.


78-107: Unify fallback states for task loading and no-results scenarios.

The loadedTasks.length check works, but there's a subtle difference between no tasks and tasks still loading. Consider setting a clear loading state vs. genuinely zero tasks vs. no results from filters. This helps users distinguish between an in-progress load and confirmed empty data.


118-226: Enhance error handling during database queries.

While unstable_cache improves performance, there's minimal error handling if the database request fails or times out. Consider wrapping the Prisma calls in a try/catch block or implementing a fallback path if an exception occurs, ensuring graceful error handling and improved observability.


239-251: Validate and fallback for invalid locales in metadata generation.

When setting locale and generating titles, any unrecognized or invalid locale could lead to misconfigured UI. Consider defaulting to a known valid locale when locale is absent or invalid, ensuring consistent user experience.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/page.tsx (3)

16-33: Align redirections with HTTP status codes for clarity.

The logic properly redirects for missing sessions and IDs, but consider returning a 401/403 or 404 in addition to redirection. Explicit status codes can help external tools (like logs or analytics) to diagnose authorization failures or missing resources.


45-62: Add defensive query error handling.

When fetching a task via db.riskMitigationTask.findUnique, consider a try/catch block around database calls to guard against unexpected runtime errors or connection timeouts. This will provide more robust error handling and better debugging information.


75-87: Perform a fallback locale check in metadata generation.

If the locale is null or invalid, consider defaulting to a known locale. This ensures the page always renders with a valid i18n configuration and avoids subtle runtime errors.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/comments/page.tsx (4)

14-39: Consider explicit HTTP codes for unauthorized or missing risk.

Here, you redirect if the session or riskId is missing. For better clarity in logs and user feedback, consider sending a 401/403 or 404 before redirecting to communicate the exact nature of the error.


41-61: Provide fallback or graceful handling for query failures.

db.risk.findUnique can fail if the DB is offline or queries time out. Consider adding a try/catch block or other error-handling mechanism to ensure the page doesn't break unexpectedly when the DB query fails.


63-72: Validate or log missing organization data.

While you're returning an array of users, adding extra logging or checks for an empty or undefined organizationId can be beneficial to diagnose potential configuration issues or mistakes in environment variables.


74-86: Consider a default locale.

Similar to other metadata functions, ensure the code gracefully handles invalid locales by falling back to a default (e.g., "en"). This small refinement can prevent any breakages on misconfigured requests.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx (2)

25-29: Consider adding fallback or error handling for getRiskOverview.
If there's an error or the function returns invalid data, this code won't catch it. You may want to handle edge cases more robustly.


44-58: Leverage unstable_cache revalidation options for better performance.
unstable_cache can further optimize performance if you specify a revalidation period. For instance:

 const getRiskOverview = unstable_cache(
   async (organizationId: string) => {
     // ...
   },
-  ["risk-overview-cache"],
+  {
+    key: ["risk-overview-cache"],
+    revalidate: 3600  // revalidate every hour
+  }
 );
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 347c728 and eaaa8fb.

📒 Files selected for processing (17)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/layout.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/comments/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/layout.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/hooks/useTaskAttachments.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/search-params.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/RiskRegisterTable.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/actions/getRisks.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterColumns.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterFilters.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/hooks/useRisks.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/layout.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/search-params.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (6)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/search-params.ts (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/search-params.ts (1) (1)
  • searchParamsCache (7-15)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/search-params.ts (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/search-params.ts (1) (1)
  • searchParamsCache (7-14)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/hooks/useTaskAttachments.ts (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts (1) (1)
  • getTaskAttachments (7-61)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/hooks/useRisks.ts (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/actions/getRisks.ts (1) (1)
  • getRisks (8-65)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/page.tsx (2)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/comments/page.tsx (2) (2)
  • RiskPage (14-39)
  • generateMetadata (74-86)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/page.tsx (2) (2)
  • RiskPage (16-43)
  • generateMetadata (75-87)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/layout.tsx (2)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/layout.tsx (1) (1)
  • Layout (4-23)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/layout.tsx (1) (1)
  • Layout (14-46)
🔇 Additional comments (27)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/search-params.ts (1)

1-15: Well-structured search parameters cache for the risk register!

This implementation creates a clean search parameters cache using the nuqs library, maintaining consistency with similar implementations elsewhere in the codebase. I like the addition of the department parameter which enhances filtering capabilities for risk management.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/search-params.ts (2)

1-5: Appropriate imports for search parameter management.

The imports from nuqs/server bring in the necessary utilities for managing URL search parameters in a server context, which is essential for proper server-side handling of filtering and pagination.


7-14: Well-structured search parameters configuration.

The search parameter configuration follows the same pattern as used in the register path, providing consistent handling of search parameters across different parts of the application. The parameters included here (query, pagination, date range, status, and owner filtering) are appropriate for task filtering.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/layout.tsx (2)

4-8: Proper TypeScript interface definition for Layout component.

The component props are clearly typed with an appropriate React.ReactNode type for children.


11-22:

Details

❓ Verification inconclusive

Navigation structure enabled for risk register.

Line 16 has been uncommented to enable navigation to the risk register page. This aligns with the PR's objective of refactoring the risk management section.

However, there's an inconsistency in the paths: you're using "/vendors/register" here, but the component itself is for risk management according to the PR description.


🏁 Script executed:

#!/bin/bash
# Check if there are references to both "/vendors/register" and "/risk/register" paths
rg -e "/vendors/register" -e "/risk/register" --type ts --type tsx

Length of output: 92


Below is a revised review comment. However, before finalizing, it would be prudent to re-run the search using an updated command since the original command failed due to file type recognition. Please run the following shell script to verify if both path variants exist in the codebase:

#!/bin/bash
# Re-run search for references to "/vendors/register" and "/risk/register" in .tsx files
rg -e "/vendors/register" -e "/risk/register" --glob "*.tsx"

Once you verify whether a reference to /risk/register exists or if only /vendors/register is in use, please update this component accordingly to ensure consistency with the PR's objective of refactoring the risk management section.


Action Required:

  • Confirm if the navigation should lead to /risk/register rather than /vendors/register.
  • Update the path in apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/layout.tsx if necessary.

Thank you for verifying this detail.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (1)

1-5: Appropriate imports for the Risk Register page.

The imports include the required components and Next.js types for metadata and internationalization, which are essential for this page.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/layout.tsx (1)

1-8: Layout component with proper typing.

The imports and component props are appropriately defined with clear typing for React children.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterColumns.tsx (3)

14-14: Verify path consistency between file location and link target

Your file is located in vendors/register/ but the link points to /risk/${row.original.id}. Ensure this is intentional and that proper routing/redirects are in place.


36-55: Check accessor key and data property alignment

The column has an accessorKey of "assignee" but accesses data from row.original.owner. Verify that this mapping is correct or consider changing the accessor key to better reflect the data structure.


8-56:

Details

✅ Verification successful

Well-structured column definitions but check URL path consistency

The column definitions are well-structured and include proper handling for missing data in the Assignee column. However, there's a potential path inconsistency to address.


🏁 Script executed:

#!/bin/bash
# Verify if there's a redirect or routing rule that maps between /vendors and /risk paths
grep -r "redirect.*risk" --include="*.ts" --include="*.tsx" .

Length of output: 1072


URL paths are consistently applied

The risk column link in RiskRegisterColumns.tsx correctly directs to /risk/${row.original.id}, which aligns with our routing setup. The grep output confirms that redirected vendors routes (e.g., overview pages) consistently map to the corresponding risk pages. No changes are required here.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/hooks/useTaskAttachments.ts (2)

10-22: Good error handling implementation

The error handling in fetchTaskAttachments is thorough, correctly handling both undefined results and results containing errors.


24-29: Well-implemented SWR hook with proper caching strategy

The hook implementation follows SWR best practices with:

  • Array-based cache key for proper invalidation
  • Appropriate revalidation settings
  • Clean function composition
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/actions/getRisks.ts (3)

8-25: Well-structured action with proper schema validation

The action is well-defined with comprehensive input validation using zod and appropriate metadata for tracking.


31-36: Good authorization check

Properly checking for user organization ID before proceeding with the query.


38-49: Clean query construction with conditional filters

The where clause construction using spread operators for conditional filters is clean and readable.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/layout.tsx (3)

31-41: Path inconsistency between file location and navigation

The file is located in vendors/[riskId] but the back button and navigation items use /risk/ paths:

  • backButtonHref points to "/risk/register"
  • Navigation items use "/risk/${riskId.riskId}" paths

Ensure this is intentional and properly configured in your routing.


48-60: Good use of cache for database queries

Using unstable_cache for the database query is a good performance optimization. Note that the "unstable_" prefix indicates an experimental API, but it's being used correctly here.


9-12:

Details

✅ Verification successful

Unusual params typing as Promise

The params prop is typed as Promise<{ riskId: string }> which is unusual for Next.js route parameters, which are typically direct objects. Verify that this is necessary for your application's routing structure.


🏁 Script executed:

#!/bin/bash
# Check for other layouts using Promise-based params
grep -r "params: Promise<" --include="*.tsx" .

Length of output: 6447


Clarification on the asynchronous params typing

After verifying the codebase, it’s clear that using Promise<…> for the params prop is a common pattern across our Next.js routes. The instance in vendors/[riskId]/layout.tsx aligns with similar implementations in many other files (e.g., for locales, policies, risks, vendors, etc.). If the asynchronous behavior is intentional for data fetching or dynamic parameter resolution in our routing structure, then this usage is consistent and correct.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/hooks/useRisks.ts (1)

5-31: Well-structured fetchRisks function with thorough error handling

The error handling implementation is comprehensive, covering null responses, server errors, and validation errors. The function correctly extracts and formats error messages from the validation errors object.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts (1)

7-29: Authentication and schema validation look good

The authentication check and schema validation are well implemented, providing proper security controls before accessing the data.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/RiskRegisterTable.tsx (1)

50-57: LGTM: Data fetching implementation is clean

The useRisks hook implementation correctly passes all the necessary parameters for data fetching.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterFilters.tsx (1)

27-71: Well-structured filter configuration with consistent patterns

The filter implementation is clean and follows a consistent pattern across different filter types (status, department, assignee). The approach of returning an array of filter configurations is flexible and maintainable.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx (5)

2-3: Imports align with new risk strategy.
The updated imports for risk-based components are consistent with the broader shift from vendor to risk management. No issues found.


19-22: Session guard to enforce organization membership.
Redirecting users without a valid organization ID improves access control. This is a solid approach to safeguard the dashboard.


31-40: Layout updated for risk overview and assignee charts.
The new layout properly integrates risk components, aligning with the transition to risk management. Implementation looks great.


62-72: Metadata updated to show risk context.
Switching the title to t("sidebar.risk") is consistent with the risk-focused UI and naming. Looks good.


11-17:

Details

✅ Verification successful

Verify the promise-based approach for retrieving locale params.
Accepting params as a Promise<{ locale: string }> is somewhat unusual. To ensure no runtime issues, consider confirming that the promise always resolves with the expected shape.

Below is a script to search for other usage of RiskManagement and verify the type of params:


🏁 Script executed:

#!/bin/bash
# Description: Find references to RiskManagement and inspect usage of `params`.
rg -A 4 "function RiskManagement" 

Length of output: 955


Action: Confirm Promise Resolution Behavior for Locale Params

After verifying the (RiskManagement) implementations in both

  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx
  • apps/app/src/app/[locale]/(app)/(dashboard)/risk/(overview)/page.tsx

it is clear that the function accepts params as a Promise<{ locale: string }> in both cases and appropriately awaits it to extract the locale value before setting it via setStaticParamsLocale(locale). This consistent usage suggests that the expectation is for the promise to always resolve with the required shape.

However, please double-check that the framework or the calling context guarantees that params will always resolve to an object containing a valid locale string. If there’s any risk of the promise rejecting or resolving with an unexpected shape, consider adding error handling or explicit type validation to make the code more robust.

Comment on lines +11 to +22
return (
<div className="max-w-[1200px] m-auto">
<SecondaryMenu
items={[
{ path: "/vendors", label: t("vendors.dashboard.title") },
{ path: "/vendors/register", label: t("vendors.register.title") },
]}
/>

<main className="mt-8">{children}</main>
</div>
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Duplicate menu structure in multiple layouts.

This layout is almost identical to the one in (overview)/layout.tsx. This duplication creates a maintenance burden when menu items need to be updated in multiple places.

Consider creating a shared layout component or a dedicated menu configuration that can be reused:

- <SecondaryMenu
-   items={[
-     { path: "/vendors", label: t("vendors.dashboard.title") },
-     { path: "/vendors/register", label: t("vendors.register.title") },
-   ]}
- />
+ <VendorsMenu t={t} /> // A shared component that defines the menu items

Additionally, the path naming convention in the URL structure doesn't clearly reflect that this is for risk management rather than vendor management.


🏁 Script executed:

#!/bin/bash
# Check the directory structure and file paths for inconsistencies
find apps/app/src/app/ -path "*vendors*" | sort

Length of output: 2364


Action Required: Consolidate Duplicate Menu Configuration & Review URL Naming

The menu structure defined here is nearly identical to the one in the (overview)/layout.tsx file, which increases the maintenance overhead if updates are required across both locations. To address this:

  • Refactor the Menu Logic:
    Extract the duplicated menu configuration into a shared component (e.g., VendorsMenu) that encapsulates the menu items. This avoids duplicating code and simplifies future updates.

    - <SecondaryMenu
    -   items={[
    -     { path: "/vendors", label: t("vendors.dashboard.title") },
    -     { path: "/vendors/register", label: t("vendors.register.title") },
    -   ]}
    - />
    + <VendorsMenu t={t} /> // Shared component defining the menu items
  • Reassess URL Naming Conventions:
    The current vendors path does not clearly indicate that these routes pertain to risk management. Verify whether this naming aligns with the intended functionality or consider renaming to better reflect its domain (e.g., risk register).

📝 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
return (
<div className="max-w-[1200px] m-auto">
<SecondaryMenu
items={[
{ path: "/vendors", label: t("vendors.dashboard.title") },
{ path: "/vendors/register", label: t("vendors.register.title") },
]}
/>
<main className="mt-8">{children}</main>
</div>
);
return (
<div className="max-w-[1200px] m-auto">
<VendorsMenu t={t} /> // Shared component defining the menu items
<main className="mt-8">{children}</main>
</div>
);

Comment on lines +43 to +48
if (!attachments) {
return {
success: false,
error: "Task attachments not found",
};
}
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

Fix the empty attachments check

The findMany method returns an empty array (not null) when no records are found. This condition will never evaluate to true as written, which means the error message for "Task attachments not found" will never be returned.

-			if (!attachments) {
+			if (attachments.length === 0) {
				return {
					success: false,
					error: "Task attachments not found",
				};
			}
📝 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
if (!attachments) {
return {
success: false,
error: "Task attachments not found",
};
}
if (attachments.length === 0) {
return {
success: false,
error: "Task attachments not found",
};
}

Comment on lines +102 to +118
pagination={{
page: Number(page),
pageSize: Number(pageSize),
totalCount: data.length,
totalPages: Math.ceil(data.length / Number(pageSize)),
hasNextPage: Number(page) < Math.ceil(data.length / Number(pageSize)),
hasPreviousPage: Number(page) > 1,
}}
onPageChange={(newPage) => setPage(newPage)}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
filters={{
categories: filterCategories,
hasActiveFilters,
onClearFilters: handleClearFilters,
activeFilterCount: [status, department, assigneeId].filter(Boolean)
.length,
}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix pagination calculation to work with server-side pagination

The current pagination calculation uses data.length for both totalCount and to calculate totalPages. This is incorrect for server-side pagination because data.length only represents the current page's items, not the total count across all pages.

You need to modify the useRisks hook to return the total count from the server and use that for pagination calculations:

pagination={{
  page: Number(page),
  pageSize: Number(pageSize),
-  totalCount: data.length,
-  totalPages: Math.ceil(data.length / Number(pageSize)),
-  hasNextPage: Number(page) < Math.ceil(data.length / Number(pageSize)),
+  totalCount: data.totalCount || 0, // Assume useRisks returns this
+  totalPages: Math.ceil((data.totalCount || 0) / Number(pageSize)),
+  hasNextPage: Number(page) < Math.ceil((data.totalCount || 0) / Number(pageSize)),
  hasPreviousPage: Number(page) > 1,
}}

This requires modifying the getRisks action and useRisks hook to include the total count in the response.


🏁 Script executed:

#!/bin/bash
# Check if getRisks action returns total count
rg -A 5 -B 5 "const getRisks" --type ts

Length of output: 4334


Fix Server-Side Pagination Calculation

The current implementation uses data.length for pagination, which only reflects the number of items on the current page. For proper server-side pagination, the response must include the total number of items (totalCount), and the pagination values should be computed from that.

  • Update the getRisks Action:
    Modify the logic in apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/actions/getRisks.ts to ensure the server response includes a totalCount field.

  • Update the UI Pagination Calculation:
    Change the pagination configuration in RiskRegisterTable.tsx to use the server-provided totalCount instead of data.length.

Here’s an example diff for the pagination configuration:

 pagination={{
   page: Number(page),
   pageSize: Number(pageSize),
-  totalCount: data.length,
-  totalPages: Math.ceil(data.length / Number(pageSize)),
-  hasNextPage: Number(page) < Math.ceil(data.length / Number(pageSize)),
+  totalCount: data.totalCount || 0, // Expecting totalCount from useRisks hook
+  totalPages: Math.ceil((data.totalCount || 0) / Number(pageSize)),
+  hasNextPage: Number(page) < Math.ceil((data.totalCount || 0) / Number(pageSize)),
   hasPreviousPage: Number(page) > 1,
 }}

Please ensure that the useRisks hook is updated accordingly to extract this totalCount from the response.

📝 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
pagination={{
page: Number(page),
pageSize: Number(pageSize),
totalCount: data.length,
totalPages: Math.ceil(data.length / Number(pageSize)),
hasNextPage: Number(page) < Math.ceil(data.length / Number(pageSize)),
hasPreviousPage: Number(page) > 1,
}}
onPageChange={(newPage) => setPage(newPage)}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
filters={{
categories: filterCategories,
hasActiveFilters,
onClearFilters: handleClearFilters,
activeFilterCount: [status, department, assigneeId].filter(Boolean)
.length,
}}
pagination={{
page: Number(page),
pageSize: Number(pageSize),
totalCount: data.totalCount || 0,
totalPages: Math.ceil((data.totalCount || 0) / Number(pageSize)),
hasNextPage: Number(page) < Math.ceil((data.totalCount || 0) / Number(pageSize)),
hasPreviousPage: Number(page) > 1,
}}
onPageChange={(newPage) => setPage(newPage)}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
filters={{
categories: filterCategories,
hasActiveFilters,
onClearFilters: handleClearFilters,
activeFilterCount: [status, department, assigneeId].filter(Boolean)
.length,
}}

Copy link
Copy Markdown

@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

🧹 Nitpick comments (26)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-sheet.tsx (3)

16-21: Consider using boolean state value

Using string "true" for the open state and null for closed state works, but consider using a boolean state with a serializer for better type safety.

-const [open, setOpen] = useQueryState("create-risk-sheet");
-const isOpen = Boolean(open);
-
-const handleOpenChange = (open: boolean) => {
-  setOpen(open ? "true" : null);
-};
+const [open, setOpen] = useQueryState("create-risk-sheet", {
+  defaultValue: false,
+  serialize: (value) => (value ? "true" : null),
+  parse: (value) => value !== null,
+});
+
+const handleOpenChange = (open: boolean) => {
+  setOpen(open);
+};

32-33: Non-standard size utility class

The size-auto class is non-standard in most CSS frameworks. Consider using standard Tailwind utility classes.

-className="p-0 m-0 size-auto hover:bg-transparent"
+className="p-0 m-0 w-auto h-auto hover:bg-transparent"

49-50: Improve accessibility of hidden title

The hidden attribute on DrawerTitle may affect screen readers. For better accessibility, consider using visually hidden text that's still available to screen readers.

-<DrawerTitle hidden>{t("risk.create")}</DrawerTitle>
+<DrawerTitle className="sr-only">{t("risk.create")}</DrawerTitle>
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/create-task-comment-form.tsx (3)

32-33: Unused variable with underscore prefix

You're using an underscore prefix for an unused variable, but consider removing it entirely since you only need the setter.

-const [_, setCreateTaskCommentSheet] = useQueryState("task-comment-sheet");
+const [, setCreateTaskCommentSheet] = useQueryState("task-comment-sheet");

61-62: Consider more responsive height calculation

The fixed height calculation h-[calc(100vh-250px)] might cause issues on very small screens. Consider using a more responsive approach.

-<div className="h-[calc(100vh-250px)] scrollbar-hide overflow-auto">
+<div className="max-h-[calc(100vh-250px)] min-h-[300px] scrollbar-hide overflow-auto">

92-102: Add loading indicator during form submission

Unlike the inherent-risk-form, this form doesn't show a loading state during submission. Consider adding a loading indicator.

<Button
  type="submit"
  variant="action"
  disabled={createTaskComment.status === "executing"}
>
  <div className="flex items-center justify-center">
-   {t("common.comments.save")}
+   {createTaskComment.status === "executing" ? (
+     <span className="flex items-center">
+       <Loader2 className="h-4 w-4 animate-spin mr-2" />
+       {t("common.actions.saving")}
+     </span>
+   ) : (
+     t("common.comments.save")
+   )}
    <ArrowRightIcon className="ml-2 h-4 w-4" />
  </div>
</Button>
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/inherent-risk-form.tsx (2)

48-55: Handle undefined initial values gracefully

The form doesn't explicitly handle the case where initialProbability or initialImpact are undefined. Consider setting default values.

const form = useForm<z.infer<typeof updateInherentRiskSchema>>({
  resolver: zodResolver(updateInherentRiskSchema),
  defaultValues: {
    id: riskId,
-   probability: initialProbability,
-   impact: initialImpact,
+   probability: initialProbability ?? 5,
+   impact: initialImpact ?? 5,
  },
});

71-82: Add validation message for slider

The form doesn't display validation messages for the sliders if there's an error. Consider adding FormMessage components.

<FormControl>
  <Slider
    min={1}
    max={10}
    step={1}
    value={[field.value]}
    onValueChange={(value) => field.onChange(value[0])}
    className="py-4"
  />
</FormControl>
<FormDescription className="text-right">
  {field.value} / 10
</FormDescription>
+<FormMessage />
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-form.tsx (4)

62-73: Consider simplifying type casting

The type casting for query parameters can be simplified by using the generic type parameter of useQueryState directly.

-const [status] = useQueryState<RiskStatus | null>("status", {
-  defaultValue: null,
-  parse: (value) => value as RiskStatus | null,
-});
+const [status] = useQueryState<RiskStatus | null>("status", {
+  defaultValue: null,
+  parse: (value) => value || null,
+});

187-203: Extract formatting logic to a utility function

The category formatting logic is complex and embedded in the render method. Consider extracting it to a separate utility function for better readability and reusability.

+// Add this function outside the component
+function formatEnumValue(value: string): string {
+  return value
+    .toLowerCase()
+    .split("_")
+    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+    .join(" ");
+}

// Then in the component
<SelectContent>
  {Object.values(RiskCategory).map((category) => {
-   const formattedCategory = category
-     .toLowerCase()
-     .split("_")
-     .map(
-       (word) =>
-         word.charAt(0).toUpperCase() +
-         word.slice(1),
-     )
-     .join(" ");
+   const formattedCategory = formatEnumValue(category);
    return (
      <SelectItem key={category} value={category}>
        {formattedCategory}
      </SelectItem>
    );
  })}
</SelectContent>

233-236: Inconsistent formatting between categories and departments

Categories are formatted with proper title case conversion, but departments are just converted to uppercase. Consider using consistent formatting for both.

{Object.values(Departments).map(
  (department) => {
-   const formattedDepartment =
-     department.toUpperCase();
+   const formattedDepartment = formatEnumValue(department);

    return (
      <SelectItem
        key={department}
        value={department}
      >
        {formattedDepartment}
      </SelectItem>
    );
  },
)}

116-117: Fixed height may cause issues on smaller screens

Similar to the comment form, the fixed height calculation might cause issues on smaller screens.

-<div className="h-[calc(100vh-250px)] scrollbar-hide overflow-auto">
+<div className="max-h-[calc(100vh-250px)] min-h-[300px] scrollbar-hide overflow-auto">
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/update-task-overview-form.tsx (1)

95-95: Missing translation for description label

The description field label is hardcoded instead of using the translation function.

-                <FormLabel>Description</FormLabel>
+                <FormLabel>{t("risk.tasks.form.description")}</FormLabel>
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/update-risk-form.tsx (1)

97-97: Missing translation for description label

The description field label is hardcoded instead of using the translation function.

-                <FormLabel>Description</FormLabel>
+                <FormLabel>{t("risk.form.description")}</FormLabel>
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/create-task-form.tsx (4)

57-57: Unused variable in destructuring assignment

Using _ as a variable name indicates it's unused. Better to omit the variable if it's not needed.

-  const [_, setCreateTaskSheet] = useQueryState("create-task-sheet");
+  const [, setCreateTaskSheet] = useQueryState("create-task-sheet");

60-70: Function inside useEffect should be memoized

The loadUsers function is recreated on each render. Consider using useCallback to memoize this function.

+  const loadUsers = useCallback(async () => {
+    const result = await getOrganizationUsersAction();
+    if (result?.data?.success && result?.data?.data) {
+      setUsers(result.data.data);
+    }
+    setIsLoadingUsers(false);
+  }, []);

   useEffect(() => {
-    async function loadUsers() {
-      const result = await getOrganizationUsersAction();
-      if (result?.data?.success && result?.data?.data) {
-        setUsers(result.data.data);
-      }
-      setIsLoadingUsers(false);
-    }
-
     loadUsers();
   }, []);

Don't forget to add the import:

-import { useEffect, useState } from "react";
+import { useCallback, useEffect, useState } from "react";

72-80: Form not reset after successful submission

The form is not reset after successful submission, leaving stale values in the form. Consider adding form.reset() in the onSuccess callback.

   const createTask = useAction(createTaskAction, {
     onSuccess: () => {
       toast.success(t("risk.tasks.form.success"));
       setCreateTaskSheet(null);
+      form.reset();
     },
     onError: () => {
       toast.error(t("risk.tasks.form.error"));
     },
   });

188-188: Calendar disabled dates might be too restrictive

The calendar disables today's date, preventing tasks from being created with the current day as the due date. Consider allowing the current day as a valid due date.

-                              disabled={(date) => date <= new Date()}
+                              disabled={(date) => {
+                                const today = new Date();
+                                today.setHours(0, 0, 0, 0);
+                                return date < today;
+                              }}
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-comment-form.tsx (4)

32-32: Unused variable in destructuring assignment

Using _ as a variable name indicates it's unused. Better to omit the variable if it's not needed.

-  const [_, setCreateRiskCommentSheet] = useQueryState("risk-comment-sheet");
+  const [, setCreateRiskCommentSheet] = useQueryState("risk-comment-sheet");

62-64: Inconsistent accordion value for comments

The accordion's default value and item value are set to "task" but this form is for creating a comment. This could be confusing for maintenance.

-            <Accordion type="multiple" defaultValue={["task"]}>
-              <AccordionItem value="task">
+            <Accordion type="multiple" defaultValue={["comment"]}>
+              <AccordionItem value="comment">

45-55: Form not reset after successful submission

The form is not reset after successful submission, leaving stale values in the form. Consider adding form.reset() in the onSuccess callback.

   const createRiskComment = useAction(createRiskCommentAction, {
     onSuccess: () => {
       toast.success(t("common.comments.success"));
       setCreateRiskCommentSheet(null);
+      form.reset();
     },
     onError: () => {
       toast.error(t("common.comments.error"));
     },
   });

97-99: Inconsistent action button label

The other form in this PR (create-task-form.tsx) uses "common.actions.create" for its action button, but this one uses "common.actions.save". Consider standardizing the action labels for consistency.

Either change this to:

-                {t("common.actions.save")}
+                {t("common.actions.create")}

Or include the right-arrow icon as in the create-task-form:

               <div className="flex items-center justify-center">
                 {t("common.actions.save")}
+                <ArrowRightIcon className="ml-2 h-4 w-4" />
               </div>
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/residual-risk-form.tsx (2)

42-42: Consider using underscore for unused variable

In the destructuring assignment [_, setOpen], you're using a variable named _ which is good convention for unused variables, but it might be clearer to use [unused, setOpen] or simply omit the variable name with an underscore like [, setOpen].


48-49: Simplify conditional expressions

The conditional expressions for default values can be simplified using the logical OR operator:

-      probability: initialProbability ? initialProbability : 0,
-      impact: initialImpact ? initialImpact : 0,
+      probability: initialProbability || 0,
+      impact: initialImpact || 0,
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/risk-overview.tsx (2)

82-82: Remove console.log statement

The console.log(form.formState.defaultValues) statement should be removed before deploying to production. Console logs can affect performance and may expose sensitive information in the browser's console.

-  console.log(form.formState.defaultValues);

167-181: Consider extracting formatting logic to a utility function

The formatting logic for categories and departments could be extracted into utility functions for better code reusability. This would make the component cleaner and allow the formatting logic to be used elsewhere.

For example, create a utility function:

// utils/formatting.ts
export function formatEnumValue(value: string): string {
  return value
    .toLowerCase()
    .split("_")
    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
    .join(" ");
}

Then use it in your component:

-                        const formattedCategory = category
-                          .toLowerCase()
-                          .split("_")
-                          .map(
-                            (word) =>
-                              word.charAt(0).toUpperCase() + word.slice(1),
-                          )
-                          .join(" ");
+                        const formattedCategory = formatEnumValue(category);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between eaaa8fb and ea6d909.

📒 Files selected for processing (12)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-comment-form.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-form.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-sheet.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/inherent-risk-form.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/residual-risk-form.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/risk-overview.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/create-task-comment-form.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/create-task-form.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/update-task-form.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/update-task-overview-form.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/update-risk-form.tsx (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-sheet.tsx (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-form.tsx (1) (1)
  • CreateRisk (49-306)
🔇 Additional comments (15)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-sheet.tsx (1)

1-55: Well-structured responsive design pattern

The component effectively handles both desktop and mobile views using conditional rendering based on viewport size. The pattern of using Sheet for desktop and Drawer for mobile provides a consistent user experience across different devices.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/inherent-risk-form.tsx (1)

110-122: Good loading state implementation

The Button component properly shows a loading indicator during form submission, which is a good UX practice. This prevents users from submitting the form multiple times.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-form.tsx (1)

6-6:

Details

❓ Verification inconclusive

Inconsistent import path

The useRisks hook is imported from a path that seems different from where other dashboard components are located. This might indicate an inconsistency in the project structure.


🏁 Script executed:

#!/bin/bash
# Check for possible duplicated implementation of useRisks hooks
rg -A 3 "export function useRisks" --glob "*.ts" --glob "*.tsx"

Length of output: 65


Action: Please verify the import path for useRisks

It appears that the useRisks hook is imported from
@/app/[locale]/(app)/(dashboard)/risk/register/hooks/useRisks
while other dashboard components are located elsewhere (e.g., within the vendors folder). Our initial grep for duplicate implementations of useRisks returned no output, which suggests there isn’t an extra, hidden copy of the hook. However, this also means it’s unclear if the current path is intentional or if the project would benefit from a more consistent structure.

Next Steps:

  • Confirm that this separation (placing the risk hook under a “risk/register/hooks” directory) is intentional and intentional by design.
  • If the design requires a centralized location for hooks used across dashboard components, consider relocating or aliasing the hook so its path is consistent with similar components.
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/update-risk-form.tsx (1)

51-51: Default department value may not be appropriate

Defaulting to Departments.admin when risk.department is null could lead to unintended consequences if admin isn't the appropriate fallback.

Consider if this is intentional or if another default might be more appropriate. Alternatively, you could leave it undefined and handle the null case in the form validation or backend.

-      department: risk.department ?? Departments.admin,
+      department: risk.department ?? undefined, // Or choose a more appropriate default
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/update-task-form.tsx (3)

89-89: Check if submitting form on dropdown interaction is intended

The onOpenChange event handler on the Select component will trigger form submission whenever the dropdown opens or closes. This might lead to unexpected form submissions and potential UI confusion for users. Consider if this behavior is truly intended or if it should be removed.


171-171: Verify date selection logic

The Calendar component disables dates that are "less than or equal to today" (date <= new Date()), meaning users can only select future dates starting from tomorrow. If tasks can have due dates on the current day, this constraint might be too restrictive.


1-197: Good implementation of form for task updates

The overall implementation is well-structured with proper validation, internationalization, loading states, and error handling. The form fields are logically organized and the component provides good user feedback through toast notifications.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/residual-risk-form.tsx (2)

77-84: Potential inconsistency between slider minimum and default values

The Slider components have a minimum value of 1, but the form's default values can be 0 when the initial values are falsy. This could create a situation where the UI shows a value of 1 while the actual form value is 0. Consider setting the minimum value to 0 or ensuring that default values are always at least 1.


1-132: Well-implemented residual risk form with good UI components

The overall implementation of the residual risk form is well-structured with proper validation, slider components for intuitive input, and good error handling with toast notifications. The form provides clear visual feedback through the slider and description elements.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/risk-overview.tsx (2)

98-98: Check if submitting form on dropdown interaction is intended

The onOpenChange event handler on the Select component will trigger form submission whenever the dropdown opens or closes. This might lead to unexpected form submissions and potential UI confusion for users. Consider if this behavior is truly intended or if it should be removed.


1-240: Well-structured risk overview form implementation

The form is well-structured with proper validation, good field organization, and appropriate error handling. The component handles multiple form fields efficiently and provides good user feedback through toast notifications.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx (4)

31-41: Uncommented placeholder code

The page currently displays "Coming Soon" text with commented-out component code. If this PR is meant to fully implement the risk management functionality, consider uncommenting these components or creating a follow-up task to enable them.

Is the intention to enable these components now or in a future PR?


11-12: Clear rename from Vendor to Risk management

Good renaming of the component from VendorOverviewPage to RiskManagement, which matches the new functionality.


45-60: Good implementation of caching for risk overview data

The use of unstable_cache is appropriate for caching the risk overview data, improving performance by avoiding unnecessary database queries.


28-28:

Details

❓ Verification inconclusive

Verify redirect path consistency

The code redirects to "/risk/register" but the current file path is in "/vendors/". This suggests a potential inconsistency in the routing structure. Verify if this is intentional as part of transitioning from vendor management to risk management.


🏁 Script executed:

#!/bin/bash
# Check if there's a corresponding risk register route
fd -t d "register" apps/app/src/app/\[locale\]/\(app\)/\(dashboard\)

Length of output: 185


Action Required: Confirm Redirect Path Intent in Vendors Overview

The file apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx currently uses:

redirect("/risk/register");

Our investigation shows that the project includes both a risk-based route:

  • apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/
    and a vendors-based route:
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/

Please confirm whether redirecting to /risk/register from the vendors overview is intentional as part of a transition from vendor management to risk management. If not, consider using /vendors/register to maintain consistency with the vendors route. Additionally, if the current logic is deliberate, adding a comment to document the rationale would be helpful for future maintainability.


const updateTask = useAction(updateTaskAction, {
onSuccess: () => {
toast.success(t("risk.form.update_risk_success"));
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

Incorrect translation keys for task updates

The success and error toast messages are using translation keys for risk updates instead of task updates.

-      toast.success(t("risk.form.update_risk_success"));
+      toast.success(t("risk.tasks.form.update_task_success"));
-      toast.error(t("risk.form.update_risk_error"));
+      toast.error(t("risk.tasks.form.update_task_error"));

Also applies to: 40-40

Comment on lines +44 to +54
const form = useForm<z.infer<typeof updateTaskSchema>>({
resolver: zodResolver(updateTaskSchema),
defaultValues: {
id: task.id,
title: task.title,
description: task.description,
dueDate: task.dueDate ? new Date(task.dueDate) : undefined,
status: task.status,
ownerId: task.ownerId ?? undefined,
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Form contains fields not present in the UI

The form initialization and submission include fields such as dueDate, status, and ownerId that aren't represented in the UI. These fields are being updated without user input or visibility.

This could lead to unexpected behavior or data changes. Consider either:

  1. Adding these fields to the UI to allow user modification
  2. Removing them from the form if they shouldn't be user-editable

Run this script to check for usage of the missing form fields:


🏁 Script executed:

#!/bin/bash
# Check for fields defined in the form but not displayed in the UI
echo "Searching for dueDate, status, and ownerId fields in update-task-overview-form.tsx"
rg -A 3 "dueDate|status|ownerId" apps/app/src/app/[locale]/\(app\)/\(dashboard\)/vendors/components/task/update-task-overview-form.tsx

Length of output: 836


Action Required: Address Invisible Form Fields

The form initialization and submission logic include the fields dueDate, status, and ownerId (as shown on lines 44–54 and in the update handler) even though there are no corresponding UI elements to allow user input for these values. This discrepancy risks unintentional updates. Please either:

  • Add appropriate input components to the UI so that these values can be controlled by the user, or
  • Remove them from both the form’s default values and submission logic if they are not meant to be user-editable.

Comment on lines +44 to +54
const form = useForm<z.infer<typeof updateRiskSchema>>({
resolver: zodResolver(updateRiskSchema),
defaultValues: {
id: risk.id,
title: risk.title,
description: risk.description,
category: risk.category,
department: risk.department ?? Departments.admin,
status: risk.status,
ownerId: risk.ownerId ?? undefined,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Form contains fields not present in the UI

The form initialization and submission include fields such as category, department, status, and ownerId that aren't represented in the UI. These fields are being updated without user input or visibility.

This could lead to unexpected behavior or data changes. Consider either:

  1. Adding these fields to the UI to allow user modification
  2. Removing them from the form if they shouldn't be user-editable

Run this script to check for usage of the missing form fields:


🏁 Script executed:

#!/bin/bash
# Check for fields defined in the form but not displayed in the UI
echo "Searching for category, department, status, and ownerId fields in update-risk-form.tsx"
rg -A 3 "category|department|status|ownerId" apps/app/src/app/[locale]/\(app\)/\(dashboard\)/vendors/components/update-risk-form.tsx

Length of output: 863


Action Required: Hidden Form Fields Mismatch

The form in apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/update-risk-form.tsx initializes and submits the fields category, department, status, and ownerId. However, these fields are not exposed to the UI, and thus are updated without user intervention. This could lead to unexpected data changes.

Please review and address one of the following:

  • Expose the fields in the UI: Add the necessary input components if these fields should be user-editable.
  • Remove the fields from the form: If these fields are not meant to be modified by the user, eliminate them from the form initialization and submission logic.

… display

- Integrated authentication check to redirect users without an organization ID to onboarding.
- Fetched vendor data from the database and passed it to the RiskRegisterTable component.
- Updated RiskRegisterTable to display vendor information instead of risks.
- Removed unused getRisks action and related hooks to streamline the codebase.
- Added new localization entries for vendor registration.
@vercel vercel Bot temporarily deployed to Preview – comp-portal March 19, 2025 18:33 Inactive
Copy link
Copy Markdown

@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: 4

🧹 Nitpick comments (2)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/RiskRegisterTable.tsx (1)

10-10: Consider using a more local import path for organization admins hook.

The import useOrganizationAdmins is coming from a different feature area (evidence/[id]/hooks). This creates a cross-cutting dependency that could make maintenance more difficult. Consider moving this hook to a shared location or duplicating the functionality if it's significantly different.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (1)

16-23: Implement pagination for database query.

The current implementation fetches all vendors without pagination, which could lead to performance issues if there are many vendors in the database.

Consider implementing pagination at the database level:

  const vendors = await db.vendor.findMany({
    where: {
      organizationId: session.user.organizationId
    },
    include: {
      owner: true
    },
+   skip: (Number(searchParams.page || 1) - 1) * Number(searchParams.pageSize || 10),
+   take: Number(searchParams.pageSize || 10)
  });

+ const totalCount = await db.vendor.count({
+   where: {
+     organizationId: session.user.organizationId
+   }
+ });
  
- return <RiskRegisterTable vendors={vendors} />;
+ return <RiskRegisterTable vendors={vendors} totalCount={totalCount} />;

You'll also need to update the RiskRegisterTable component to accept the totalCount prop.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ea6d909 and 4f50adc.

⛔ Files ignored due to path filters (1)
  • apps/app/languine.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/RiskRegisterTable.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterColumns.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (1 hunks)
  • apps/app/src/locales/en.ts (2 hunks)
  • apps/app/src/locales/es.ts (2 hunks)
  • apps/app/src/locales/fr.ts (2 hunks)
  • apps/app/src/locales/no.ts (2 hunks)
  • apps/app/src/locales/pt.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterColumns.tsx
🧰 Additional context used
🧬 Code Definitions (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/RiskRegisterTable.tsx (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterColumns.tsx (1) (1)
  • columns (8-56)
🔇 Additional comments (12)
apps/app/src/locales/no.ts (2)

987-991: Consistent vendor management localization added.

The addition of vendor-related entries in the sub_pages section provides proper navigation structure for the Norwegian localization, ensuring a consistent user experience across languages.


1166-1167: Vendor register interface updated to dashboard approach.

Good update changing the register title from "Leverandørregister" to "Dashbord" and adding a create action. This aligns with a more action-oriented vendor management approach.

apps/app/src/locales/fr.ts (2)

987-991: Vendor navigation localization added for French users.

The addition of French translations for vendor navigation maintains consistency across the application's language options.


1166-1167: Vendor management UI terminology updated to dashboard-focused approach.

The change from "Enregistrement des fournisseurs" to "Tableau de Bord" along with adding the create action maintains consistency with changes in other language files.

apps/app/src/locales/en.ts (2)

274-277: Vendor navigation structure localization added.

The addition of vendor navigation entries in the sub_pages section provides clear structure for user navigation through the vendor management interfaces.


1097-1098: Dashboard-focused vendor management UI terminology implemented.

Changing the register title to "Dashboard" and adding a clear action for vendor creation reflects a more intuitive, action-oriented interface for vendor management.

apps/app/src/locales/es.ts (2)

988-991: Added new vendor navigation translations - LGTM!

These new translations for vendor-related navigation items have been properly added to the Spanish localization file, providing the necessary localizations for the vendor management interface.


1166-1167: Updated vendor register section translations - LGTM!

The vendor register title has been updated to "Tablero" and a new entry "create_new" has been added with the value "Crear Proveedor". These changes appropriately support the new vendor management interface.

apps/app/src/locales/pt.ts (2)

988-991: Added new vendor navigation translations - LGTM!

These new translations for vendor-related navigation items have been properly added to the Portuguese localization file, providing the necessary localizations for the vendor management interface.


1137-1138: Updated vendor register section translations - LGTM!

The vendor register title has been updated to "Painel de Controle" and a new entry "create_new" has been added with the value "Criar Fornecedor". These changes appropriately support the new vendor management interface.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/RiskRegisterTable.tsx (1)

96-99: Fix Server-Side Pagination Calculation.

The current implementation uses vendors.length for pagination, which only reflects the number of items on the current page. For proper server-side pagination, the response must include the total number of items (totalCount), and the pagination values should be computed from that.

Here's an example diff for the pagination configuration:

pagination={{
  page: Number(page),
  pageSize: Number(pageSize),
-  totalCount: vendors.length,
-  totalPages: Math.ceil(vendors.length / Number(pageSize)),
-  hasNextPage: Number(page) < Math.ceil(vendors.length / Number(pageSize)),
+  totalCount: vendors.totalCount || vendors.length, // Expecting totalCount from server
+  totalPages: Math.ceil((vendors.totalCount || vendors.length) / Number(pageSize)),
+  hasNextPage: Number(page) < Math.ceil((vendors.totalCount || vendors.length) / Number(pageSize)),
  hasPreviousPage: Number(page) > 1,
}}
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (1)

25-25:

Details

❓ Verification inconclusive

Add searchParams prop for pagination.

For server-side pagination to work correctly, you need to pass the searchParams to extract page and pageSize parameters.

Update the component to receive and use searchParams:

- export default async function RiskRegisterPage() {
+ export default async function RiskRegisterPage({
+   searchParams,
+ }: {
+   searchParams: { [key: string]: string | string[] | undefined };
+ }) {
  // ... existing code
  
  return <RiskRegisterTable vendors={vendors} />;
}

Run this script to check if DataTable or other components are handling searchParams:


🏁 Script executed:

#!/bin/bash
# Check if DataTable component or other pagination components use searchParams
rg -A 5 -B 5 "export.*function DataTable" --type tsx
rg -A 5 -B 5 "searchParams.*pagination" --type tsx

Length of output: 163


Action Required: Update Pagination Handling in RiskRegisterPage

Please update the RiskRegisterPage component to accept and pass along the searchParams prop for correct server-side pagination. The component should be updated as follows:

- export default async function RiskRegisterPage() {
+ export default async function RiskRegisterPage({
+   searchParams,
+ }: {
+   searchParams: { [key: string]: string | string[] | undefined };
+ }) {
  // ... existing code
  
  return <RiskRegisterTable vendors={vendors} />;
}

Note: The earlier search command did not yield results due to file-type recognition issues. Please manually verify that the dependent pagination components (such as RiskRegisterTable or DataTable) are correctly handling searchParams for pagination functionality.


🏁 Script executed:

#!/bin/bash
# Updated search to target .tsx files specifically to locate usage of searchParams in pagination-related components.
rg -g "*.tsx" -A 5 -B 5 "export.*function DataTable"
rg -g "*.tsx" -A 5 -B 5 "searchParams.*pagination"

Length of output: 18108


Update RiskRegisterPage to Accept the searchParams Prop for Pagination

Please update the component signature for RiskRegisterPage so that it accepts a searchParams prop. This change is necessary to pass along URL parameters (such as page and pageSize) for proper server-side pagination. Our recent search confirms that other related components (e.g., in the DataTablePagination files) are already using searchParams (via hooks like useSearchParams), so aligning the page component with this pattern is essential.

Required changes:

  • Update the function signature to accept searchParams with the following type:
    { [key: string]: string | string[] | undefined }.
  • Ensure that you pass or utilize searchParams in the pagination logic of RiskRegisterTable (if applicable), so that pagination parameters are handled correctly.

Below is the diff snippet reflecting the proposed revision:

- export default async function RiskRegisterPage() {
+ export default async function RiskRegisterPage({
+   searchParams,
+ }: {
+   searchParams: { [key: string]: string | string[] | undefined };
+ }) {
  // ... existing code
  
  return <RiskRegisterTable vendors={vendors} />;
}

Action: Update the component accordingly and manually verify that any pagination-related components (e.g., RiskRegisterTable or its children) correctly leverage the passed searchParams for server-side pagination.

Comment on lines +103 to +109
// filters={{
// categories: filterCategories,
// hasActiveFilters,
// onClearFilters: handleClearFilters,
// activeFilterCount: [status, department, assigneeId].filter(Boolean)
// .length,
// }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Commented filter UI needs implementation or removal.

Similar to the filter categories, the filter UI configuration is commented out but the state and handlers exist. This creates a disconnect between what the component can do and what it actually does.

Either uncomment and implement the filter UI:

- // filters={{
- // 	categories: filterCategories,
- // 	hasActiveFilters,
- // 	onClearFilters: handleClearFilters,
- // 	activeFilterCount: [status, department, assigneeId].filter(Boolean)
- // 		.length,
- // }}
+ filters={{
+ 	categories: filterCategories,
+ 	hasActiveFilters,
+ 	onClearFilters: handleClearFilters,
+ 	activeFilterCount: [status, department, assigneeId].filter(Boolean)
+ 		.length,
+ }}

Or remove the unused state and handlers to keep the code clean.

📝 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
// filters={{
// categories: filterCategories,
// hasActiveFilters,
// onClearFilters: handleClearFilters,
// activeFilterCount: [status, department, assigneeId].filter(Boolean)
// .length,
// }}
filters={{
categories: filterCategories,
hasActiveFilters,
onClearFilters: handleClearFilters,
activeFilterCount: [status, department, assigneeId].filter(Boolean)
.length,
}}

icon: <Plus className="h-4 w-4 mr-2" />,
}}
/>
<CreateRiskSheet />
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

CreateRiskSheet rendered without props.

CreateRiskSheet is being rendered without any props, but it's likely meant to receive the open state that's being managed by useQueryState("create-vendor-sheet").

Make sure to pass the necessary props to the CreateRiskSheet component:

- <CreateRiskSheet />
+ <CreateRiskSheet open={open === "true"} onOpenChange={(isOpen) => setOpen(isOpen ? "true" : "false")} />
📝 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
<CreateRiskSheet />
<CreateRiskSheet open={open === "true"} onOpenChange={(isOpen) => setOpen(isOpen ? "true" : "false")} />

Comment on lines +71 to +81
// const filterCategories = RiskRegisterFilters({
// setPage: (newPage: number) => setPage(newPage),
// departments: departments,
// assignees: admins || [],
// status,
// setStatus,
// department,
// setDepartment,
// assigneeId,
// setAssigneeId,
// });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Commented filter functionality needs attention.

There's commented-out filter functionality that appears to be partially implemented. The state management for filters exists (status, department, assigneeId), but the UI components to use them are commented out. This creates a confusing implementation where some filter code is active but not used.

Either complete the implementation by uncommenting and fixing the filter UI, or remove the unused filter state to keep the code clean:

- // const filterCategories = RiskRegisterFilters({
- // 	setPage: (newPage: number) => setPage(newPage),
- // 	departments: departments,
- // 	assignees: admins || [],
- // 	status,
- // 	setStatus,
- // 	department,
- // 	setDepartment,
- // 	assigneeId,
- // 	setAssigneeId,
- // });
📝 Committable suggestion

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

Suggested change
// const filterCategories = RiskRegisterFilters({
// setPage: (newPage: number) => setPage(newPage),
// departments: departments,
// assignees: admins || [],
// status,
// setStatus,
// department,
// setDepartment,
// assigneeId,
// setAssigneeId,
// });
// (Any surrounding code before the removed block remains unchanged)
// (The previously commented-out filter functionality was removed to clean up the implementation.
// If filtering functionality is intended to be implemented later, consider adding the necessary UI and logic then.)
// (Any surrounding code after the removed block remains unchanged)

Comment on lines +16 to +23
const vendors = await db.vendor.findMany({
where: {
organizationId: session.user.organizationId
},
include: {
owner: true
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for database queries.

The database query for fetching vendors lacks error handling. If the query fails, there's no graceful error handling or user feedback mechanism in place.

Consider implementing try/catch block for better error handling:

+ try {
  const vendors = await db.vendor.findMany({
    where: {
      organizationId: session.user.organizationId
    },
    include: {
      owner: true
    }
  });
+  return <RiskRegisterTable vendors={vendors} />;
+ } catch (error) {
+   console.error("Error fetching vendors:", error);
+   return <div>Failed to load vendors. Please try again later.</div>;
+ }
📝 Committable suggestion

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

Suggested change
const vendors = await db.vendor.findMany({
where: {
organizationId: session.user.organizationId
},
include: {
owner: true
}
});
try {
const vendors = await db.vendor.findMany({
where: {
organizationId: session.user.organizationId
},
include: {
owner: true
}
});
return <RiskRegisterTable vendors={vendors} />;
} catch (error) {
console.error("Error fetching vendors:", error);
return <div>Failed to load vendors. Please try again later.</div>;
}

@vercel vercel Bot temporarily deployed to Preview – comp-portal March 19, 2025 18:51 Inactive
@vercel vercel Bot temporarily deployed to Preview – comp-portal March 19, 2025 18:52 Inactive
Copy link
Copy Markdown

@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: 1

♻️ Duplicate comments (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (1)

49-61: ⚠️ Potential issue

Add error handling for database queries.

The database query for fetching vendors lacks error handling. If the query fails, there's no graceful error handling or user feedback mechanism in place.

Consider implementing a try/catch block for better error handling:

+ try {
  const vendors = await db.vendor.findMany({
    where: {
      organizationId: session.user.organizationId,
      ...(status && { status: status }),
      ...(department && { department: department }),
      ...(assigneeId && { ownerId: assigneeId })
    },
    include: {
      owner: true
    },
    skip: page ? (Number(page) - 1) * Number(pageSize || 10) : 0,
    take: Number(pageSize || 10)
  });
+  return <VendorRegisterTable data={vendors} />;
+ } catch (error) {
+   console.error("Error fetching vendors:", error);
+   return <div>Failed to load vendors. Please try again later.</div>;
+ }
🧹 Nitpick comments (1)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx (1)

71-81: Remove or implement commented-out filter code.

There's a significant block of commented-out code related to filtering functionality. This suggests incomplete implementation or temporary disabling of features.

Either implement the filtering functionality or remove the commented code to improve code cleanliness:

- // const filterCategories = RiskRegisterFilters({
- // 	setPage: (newPage: number) => setPage(newPage),
- // 	departments: departments,
- // 	assignees: admins || [],
- // 	status,
- // 	setStatus,
- // 	department,
- // 	setDepartment,
- // 	assigneeId,
- // 	setAssigneeId,
- // });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4f50adc and 5b6dc99.

⛔ Files ignored due to path filters (1)
  • apps/app/languine.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (1 hunks)
  • apps/app/src/locales/en.ts (2 hunks)
  • apps/app/src/locales/es.ts (2 hunks)
  • apps/app/src/locales/fr.ts (2 hunks)
  • apps/app/src/locales/no.ts (2 hunks)
  • apps/app/src/locales/pt.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/app/src/locales/pt.ts
  • apps/app/src/locales/en.ts
  • apps/app/src/locales/es.ts
  • apps/app/src/locales/no.ts
🔇 Additional comments (8)
apps/app/src/locales/fr.ts (3)

988-991: Good addition of localized vendor-related entries to the sidebar sub-pages.

These localization entries for vendor management are well-structured and consistent with the changes made in other language files according to the AI summary.


1166-1167: Appropriate title change and addition of create action.

The title change to "Inscription du fournisseur" and the new "Créer un Fournisseur" action entry provide a more refined user experience for the vendor registration workflow.


1170-1170: Dashboard title simplification is good.

Simplifying the dashboard title to just "Aperçu" (Overview) is a good design choice, keeping it concise and focused.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx (3)

25-32: Well-implemented schema validation using Zod.

The search parameters validation is thorough and uses appropriate validation rules for each parameter type. This ensures data integrity and helps prevent potential injection attacks.


34-39: Good error handling for invalid search parameters.

The error handling and redirection for invalid search parameters is properly implemented. Logging the error details before redirection is helpful for debugging purposes.


66-78: Well-implemented metadata generation for internationalization.

The generateMetadata function properly handles localization by setting the static params locale and fetching the appropriate translation for the page title. This ensures a consistent user experience across different languages.

apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx (2)

11-12:

Details

❓ Verification inconclusive

Verify component naming consistency.

The component imports CreateRiskSheet but later uses it for vendor creation. This naming inconsistency could lead to confusion for developers maintaining the code.

Please check if this component is correctly named given its purpose in this context (vendors vs. risks):


🏁 Script executed:

#!/bin/bash
# Check the implementation of CreateRiskSheet to verify if it's generic or risk-specific
grep -A 15 "export.*CreateRiskSheet" "../components/create-risk-sheet"

Length of output: 135


Component Import and Naming Verification Needed

  • The current import in VendorRegisterTable.tsx uses CreateRiskSheet, which appears inconsistent with the vendor creation context.
  • The verification command for locating the component (using the relative path ../components/create-risk-sheet) returned an error, indicating that this file or directory wasn’t found.
  • Action Required: Please verify manually whether:
    • The intended component exists at an alternative or updated path.
    • The naming of the component accurately reflects its functionality (i.e., vendor-related versus risk-related). If it’s meant for vendor creation, consider renaming it (and updating its import) to avoid confusion.

116-116:

Details

❓ Verification inconclusive

Ensure CreateRiskSheet is compatible with vendor creation.

The component uses <CreateRiskSheet /> without props, but the name suggests it's designed for risks rather than vendors. Ensure this component is properly adapted for vendor creation or rename it for clarity.


🏁 Script executed:

#!/bin/bash
# Check if CreateRiskSheet accepts the correct props for vendor creation
grep -r -A 5 "CreateRiskSheet" "../components/"

Length of output: 93


Verify CreateRiskSheet Component Usage

Please ensure that the <CreateRiskSheet /> component used in
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx
is truly compatible with vendor registration. The component is currently invoked
without props, yet its name implies it was initially designed for risk management.

Action Items:

  • Review the Component Definition: Run a repository-wide search (e.g., with the script above) to locate its implementation and verify whether it accepts vendor-specific configuration.
  • Adapt or Rename: If the component’s interface is solely oriented toward risk creation, either update it to support vendor creation or rename it for improved clarity.

Please manually verify the component’s behavior using the output of the script above.


🏁 Script executed:

#!/bin/bash
# Search for the definition and usages of CreateRiskSheet in the entire repository
rg -n "CreateRiskSheet" .

Length of output: 1871


Verify that CreateRiskSheet is tailored for vendor registration

The <CreateRiskSheet /> component imported in
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx
appears to originate from a vendor-specific directory (vendors/components/create-risk-sheet.tsx). However, given that its name and overall pattern mirror the risk-related version, please confirm that it either
• implements vendor-specific behavior, or
• if it was simply copied from the risk implementation, consider adapting its interface (or renaming it) to better reflect its role in vendor creation.

  • Next Steps:
    • Review the file at apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-sheet.tsx for vendor-specific logic.
    • Verify that the component’s behavior, even when used without props, meets all vendor registration requirements.

Comment on lines +83 to +115
return (
<>
<DataTable<Vendor>
columns={columns}
data={data}
// isLoading={isLoading}
search={{
value: search,
onChange: setSearch,
}}
pagination={{
page: Number(page),
pageSize: Number(pageSize),
totalCount: data.length,
totalPages: Math.ceil(data.length / Number(pageSize)),
hasNextPage: Number(page) < Math.ceil(data.length / Number(pageSize)),
hasPreviousPage: Number(page) > 1,
}}
onPageChange={(newPage) => setPage(newPage)}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
// filters={{
// categories: filterCategories,
// hasActiveFilters,
// onClearFilters: handleClearFilters,
// activeFilterCount: [status, department, assigneeId].filter(Boolean)
// .length,
// }}
ctaButton={{
label: t("vendors.register.create_new"),
onClick: () => setOpen("true"),
icon: <Plus className="h-4 w-4 mr-2" />,
}}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix pagination implementation and uncomment or remove filter code.

The current implementation has two issues:

  1. The pagination logic assumes that data.length represents the total count of vendors, which might not be accurate if the data is paginated on the server-side.
  2. There's commented-out filter functionality that should either be implemented or removed.

Consider implementing server-side pagination with total count:

<DataTable<Vendor>
  columns={columns}
  data={data}
- // isLoading={isLoading}
  search={{
    value: search,
    onChange: setSearch,
  }}
  pagination={{
    page: Number(page),
    pageSize: Number(pageSize),
-   totalCount: data.length,
-   totalPages: Math.ceil(data.length / Number(pageSize)),
-   hasNextPage: Number(page) < Math.ceil(data.length / Number(pageSize)),
+   totalCount: totalCount, // Should be provided from server
+   totalPages: totalPages, // Should be calculated on server
+   hasNextPage: hasNextPage, // Should be determined on server
    hasPreviousPage: Number(page) > 1,
  }}
  onPageChange={(newPage) => setPage(newPage)}
  onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
- // filters={{
- // 	categories: filterCategories,
- // 	hasActiveFilters,
- // 	onClearFilters: handleClearFilters,
- // 	activeFilterCount: [status, department, assigneeId].filter(Boolean)
- // 		.length,
- // }}
+ filters={{
+ 	categories: filterCategories,
+ 	hasActiveFilters,
+ 	onClearFilters: handleClearFilters,
+ 	activeFilterCount: [status, department, assigneeId].filter(Boolean)
+ 		.length,
+ }}
  ctaButton={{
    label: t("vendors.register.create_new"),
    onClick: () => setOpen("true"),
    icon: <Plus className="h-4 w-4 mr-2" />,
  }}
/>
📝 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
return (
<>
<DataTable<Vendor>
columns={columns}
data={data}
// isLoading={isLoading}
search={{
value: search,
onChange: setSearch,
}}
pagination={{
page: Number(page),
pageSize: Number(pageSize),
totalCount: data.length,
totalPages: Math.ceil(data.length / Number(pageSize)),
hasNextPage: Number(page) < Math.ceil(data.length / Number(pageSize)),
hasPreviousPage: Number(page) > 1,
}}
onPageChange={(newPage) => setPage(newPage)}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
// filters={{
// categories: filterCategories,
// hasActiveFilters,
// onClearFilters: handleClearFilters,
// activeFilterCount: [status, department, assigneeId].filter(Boolean)
// .length,
// }}
ctaButton={{
label: t("vendors.register.create_new"),
onClick: () => setOpen("true"),
icon: <Plus className="h-4 w-4 mr-2" />,
}}
/>
return (
<>
<DataTable<Vendor>
columns={columns}
data={data}
search={{
value: search,
onChange: setSearch,
}}
pagination={{
page: Number(page),
pageSize: Number(pageSize),
totalCount: totalCount, // Should be provided from server
totalPages: totalPages, // Should be calculated on server
hasNextPage: hasNextPage, // Should be determined on server
hasPreviousPage: Number(page) > 1,
}}
onPageChange={(newPage) => setPage(newPage)}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
filters={{
categories: filterCategories,
hasActiveFilters,
onClearFilters: handleClearFilters,
activeFilterCount: [status, department, assigneeId].filter(Boolean).length,
}}
ctaButton={{
label: t("vendors.register.create_new"),
onClick: () => setOpen("true"),
icon: <Plus className="h-4 w-4 mr-2" />,
}}
/>
</>
);

Copy link
Copy Markdown

@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

🧹 Nitpick comments (3)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-vendor-sheet.tsx (3)

1-12: Component imports are well-organized, but naming inconsistency noticed.

The imports are clean and well-organized, but there seems to be a naming inconsistency between the file name (create-vendor-sheet.tsx) and the imported component (CreateRisk from "./create-risk-form"). This suggests the file might have been renamed during refactoring from vendors to risks management.

Consider renaming this file to better reflect its purpose (e.g., create-risk-sheet.tsx) or updating the imported component if this is indeed meant to create vendors rather than risks.


23-45: Desktop UI implemented correctly with minor inconsistency in close actions.

The desktop version of the UI is well-structured with proper layout and scrolling capabilities. However, there's an inconsistency in how the component is closed:

  • The Sheet component uses onOpenChange={handleOpenChange}
  • The close button directly calls setOpen(null)

For consistency, consider using the same closing mechanism for both:

-            onClick={() => setOpen(null)}
+            onClick={() => handleOpenChange(false)}

11-11: Naming suggests possible refactoring from vendors to risks management.

The component is in a "vendors" directory and named CreateVendorSheet, but it renders CreateRisk and uses "risk.create" in the i18n text, indicating this might be part of a vendor-to-risk management refactoring.

If this is an intentional refactoring from vendors to risks, ensure all related components, routes, and documentation reflect this change consistently.

Also applies to: 28-28, 49-49

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6dc99 and 66dcbed.

📒 Files selected for processing (2)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-vendor-sheet.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx
🔇 Additional comments (3)
apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-vendor-sheet.tsx (3)

13-18: Well-structured state management with URL integration.

The use of useQueryState for managing the component's open state is an excellent approach that allows for shareable and bookmarkable URLs. The Boolean conversion of the query parameter is also handled cleanly.


19-22: Clean state handler with proper TypeScript typing.

The handleOpenChange function is well-typed and handles the state updates appropriately by converting boolean values to the expected string or null format for the URL parameter.


47-55: Mobile drawer implementation is accessible and minimal.

The mobile drawer implementation properly hides the title visually while keeping it accessible to screen readers. The simplified layout for mobile is appropriate.

@claudfuen claudfuen merged commit 339eaac into main Mar 19, 2025
5 of 7 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 3, 2025
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