Skip to content

Feature/fin 023#103

Merged
dmytroreshetylo merged 23 commits into
mainfrom
feature/fin-023
May 2, 2026
Merged

Feature/fin 023#103
dmytroreshetylo merged 23 commits into
mainfrom
feature/fin-023

Conversation

@IeuanZorander

@IeuanZorander IeuanZorander commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Title

Feature: Implement Lookups Data Management Forms and Soft Delete

Type

  • Feature
  • Bug

Description

This PR implements the UI forms and logic for adding, editing, and deleting lookup data, specifically Countries/Locales and Currencies, while following the project’s FSD architecture and existing code style.

Components Created/Updated

  • UiAdminModal: Created a reusable base modal wrapper (@frontend/shared/components/admin-modal/fin-admin-modal) that handles the standard admin modal layout, accessible form submission via formId, and loading states.

  • CountryFormModal & CurrencyFormModal: Implemented lookup-specific form modals using react-hook-form and shared zod validation schemas from common/domains/lookups.

  • CountriesLookup & CurrenciesLookup: Replaced page navigation for Add/Edit actions with inline modal forms.

  • useCountryMutations & useCurrencyMutations: Added dedicated mutation hooks for create, update, and delete lookup operations.

  • useAdminLookup: Extracted shared admin lookup table behavior, including pagination, selection, modal state, edit flow, and delete confirmation logic.

Key Tasks Completed

  • Added create and update flows for Countries/Locales and Currencies.
  • Added inline form population for Edit scenarios via initialData.
  • Added strict form validation:
    • required string fields are trimmed;
    • currency code must contain exactly 3 characters;
    • empty update payloads are rejected.
  • Implemented single and bulk soft deletion.
  • Improved bulk delete resilience with Promise.allSettled.
  • Added success and error toast notifications.
  • Added createdBy display for lookup tables by resolving the related admin from adminId.
  • Secured user lookup by preventing cross-user name access unless the requester is an admin.
  • Fixed lookup migration naming so the class timestamp matches the migration filename.
  • Removed duplicated ui-dialog implementation in favor of the existing modal system.
  • Cleaned up unused lookup/admin code from the affected files.

API / Backend Updates

  • Added admin relation loading for lookup list endpoints when the requester has the admin role.
  • Added adminId support for lookup records.
  • Updated create routes to store the current user as the creator.
  • Updated delete routes to perform soft delete through softDeleted: 1.
  • Added validation for no-op update requests.

Media

image image image image

Check list

  • Self-reviewed
  • Added unit tests
  • The code doesn't have new warnings and errors
  • Passed ESLint / Prettier checks

Addition

Implementation Note: To maintain the existing UiConfirmModal component's structure (which expects a trigger node) without modifying its source code, a hidden useRef trigger pattern was implemented in the lookup tables. This allows opening the confirmation modals programmatically upon table action clicks.

Summary by CodeRabbit

  • New Features

    • Added modal-based create and edit flows for countries and currencies management
    • Implemented confirmation dialogs for single and bulk delete operations
    • Added admin attribution tracking for created/modified countries and currencies
  • Improvements

    • Enhanced lookup status badge styling for better visual consistency
    • Refined row selection and deselection functionality in lookup tables

@IeuanZorander IeuanZorander self-assigned this Apr 29, 2026
@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors lookup management (countries/currencies) to use a centralized useAdminLookup hook, replaces navigation-based workflows with modal forms and confirmation dialogs, introduces new API endpoints for CRUD operations with admin tracking, extends database schema to store admin relationships, and adds supporting UI components and utilities.

Changes

Cohort / File(s) Summary
Lookup Hook & Selection
src/client/features/admin/hooks/use-admin-lookup.hook.ts, src/client/features/admin/hooks/use-lookup-selection.hook.ts
New useAdminLookup hook centralizes pagination, selection, form state, and delete workflows; use-lookup-selection adds deselect() method for individual row deselection.
Countries Lookup Feature
src/client/features/admin/countries-lookup/countries-lookup.tsx, src/client/features/admin/lookups/countries/country-form-modal.tsx, src/client/features/admin/lookups/hooks/use-country-mutations.hook.ts
CountriesLookup component refactored to use useAdminLookup hook; new CountryFormModal for create/edit with react-hook-form validation; new mutations hook for submit/delete operations.
Currencies Lookup Feature
src/client/features/admin/currencies-lookup/currencies-lookup.tsx, src/client/features/admin/lookups/currencies/currency-form-modal.tsx, src/client/features/admin/lookups/hooks/use-currency-mutations.hook.ts
CurrenciesLookup component refactored to use useAdminLookup hook; new CurrencyFormModal for create/edit with form validation; new mutations hook for API operations.
Admin Modal & Table UI
src/client/shared/components/admin-modal/fin-admin-modal.tsx, src/client/shared/components/admin-modal/fin-admin-modal.scss, src/client/shared/components/error/fin-error-table-widget.tsx, src/client/shared/components/screen-handlers/fin-table-screen-handler.tsx, src/client/shared/components/screen-handlers/props/table-screen-handler.props.ts
New UiAdminModal component for form wrapping with loading states; new FinTableScreenHandler for table layouts with skeleton/error handling; new error widget component; shared modal styling.
Lookup Table & Row Components
src/client/entities/lookups/lookup-table/lookup-table.tsx, src/client/entities/lookups/lookup-table/lookup-table-row.tsx, src/client/entities/lookups/lookup-table/lookup-row-skeleton.tsx, src/client/entities/lookups/lookup-row-actions/lookup-row-actions.tsx
LookupTable switches to FinTableScreenHandler, removes skeleton prop; LookupRowSkeleton deleted; lookup-table-row updates LookupCreatedByCell invocation; row-actions styling update (opacity prop).
Created-By Utilities
src/client/entities/lookups/lookup-created-by/lookup-created-by.model.ts, src/client/entities/lookups/lookup-created-by/lookup-created-by.util.ts, src/client/entities/lookups/lookup-created-by/lookup-created-by-cell.tsx
Consolidates LookupCreatedBy type into util file; simplifies getCreatedBy() to extract only admin?.name; updates LookupCreatedByCell to destructure props directly; status badge text color changes for deleted state.
Lookup Service & Test
src/client/entities/lookups/lookups.service.ts, src/client/entities/lookups/__tests__/lookups.service.spec.ts
Service refactored to use fetchClient instead of browser fetch; tests centralized with jsonResponse helper and relaxed URL matching.
Form Schemas & Validation
src/common/domains/lookups/schemas/lookups-form.schema.ts
New Zod schemas for currency and country forms with validation; exports inferred FormData and UpdateData types; update schemas include "at least one field required" refinement.
Country API Endpoints
src/app/api/lookups/countries-and-locales/create/route.ts, src/app/api/lookups/countries-and-locales/update/[id]/route.ts, src/app/api/lookups/countries-and-locales/remove/[id]/route.ts, src/app/api/lookups/countries-and-locales/get-items/route.ts
New authenticated POST/PUT/DELETE endpoints for country CRUD; GET updated to include role-based admin relation loading; all use form schemas and guards.
Currency API Endpoints
src/app/api/lookups/currencies/create/route.ts, src/app/api/lookups/currencies/update/[id]/route.ts, src/app/api/lookups/currencies/remove/[id]/route.ts, src/app/api/lookups/currencies/get-items/route.ts
New authenticated POST/PUT/DELETE endpoints for currency CRUD; GET updated with role-based context; all apply auth guards and schemas.
User & Admin API
src/app/api/lookups/user/get-by-id/[id]/route.ts, src/server/shared/transformers/get-user-role.transformer.ts
New GET endpoint for user name lookup with authorization checks; new transformer extracts user role from JWT for context injection.
API Guards
src/server/entities/country/application/exist-country.guard.ts, src/server/entities/currency/application/exist-currency.guard.ts
New guards validate entity existence and non-soft-deleted state; return 404 with Ukrainian error messages when checks fail.
ORM & Repository Updates
src/server/entities/country/infrastructure/country.orm.ts, src/server/entities/country/infrastructure/country.repository.ts, src/server/entities/currency/infrastructure/currency.orm.ts, src/server/entities/currency/infrastructure/currency.repository.ts, src/server/entities/user/infrastructure/user.repository.ts
ORM entities add nullable adminId column and admin relation to UserOrm; repositories override getItems() with pagination and conditional admin relation loading; UserApiRepository adds getUserNameById() query.
Data Records
src/common/records/countries.record.ts, src/common/records/currencies.record.ts
Extend CountryAndLocale and Currency interfaces with optional adminId and admin fields for tracking lookup creators.
Database Migration
src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts, src/server/database/migrations.ts
New TypeORM migration adds nullable adminId column to currency and country tables with reversibility; registry updated to include migration.
Screen Handlers & UI Utils
src/client/shared/components/screen-handlers/fin-list-screen-handler.tsx, src/client/shared/components/screen-handlers/fin-form-screen-handler.tsx, src/client/shared/ui/ui-table/ui-table.tsx, src/client/shared/services/fetch-client/fetch-client.service.ts, src/client/shared/services/fetch-client/models/request-options.model.ts
Import path updates for screen handlers; UiTable adds optional containerClassName; FetchClient adds optional throwErrorIfNotAuth flag to suppress 401 errors.
Tracking Operations API
src/client/entities/tracking-operations/tracking-operations.api.client.ts
New API client class implementing ICrudService interface for tracking operations with full CRUD endpoint integration.
Other Updates
src/app/(profile)/profile/budget/(regular-operations)/regular-operations/edit/[id]/page.tsx, src/client/features/regular-incomes-expenses/regular-incomes-expenses-screen.tsx
Screen handler import path corrections (screen-handlers module consolidation).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as CountriesLookup<br/>Component
    participant Hook as useAdminLookup<br/>Hook
    participant Service as LookupsService
    participant API as API<br/>Endpoints
    participant DB as Database

    User->>UI: Load component
    UI->>Hook: Initialize with mutations
    Hook->>Service: fetchItems (from, to, filters)
    Service->>API: POST /api/lookups/countries-and-locales/get-items
    API->>DB: Query with admin relation
    DB-->>API: Return paginated items + admin
    API-->>Service: Returns data
    Service-->>Hook: Items loaded
    Hook-->>UI: Render table

    User->>UI: Click edit row
    UI->>Hook: openEditForm(item)
    Hook-->>UI: Set isFormOpen=true, editingItem
    UI->>UI: Render CountryFormModal with initialData

    User->>UI: Submit form
    UI->>Hook: onSuccessCallback via mutation
    Hook->>Service: submitMutation({id, data})
    Service->>API: PUT /api/lookups/countries-and-locales/update/{id}
    API->>DB: Update country & locale
    DB-->>API: Success
    API-->>Service: Returns data
    Service-->>Hook: onSuccess triggered
    Hook->>Hook: reload(), toast
    Hook-->>UI: Close modal, refresh table
    UI-->>User: Updated table displayed

    User->>UI: Click delete row
    UI->>Hook: requestSingleDelete(item)
    Hook-->>UI: Open confirm modal
    User->>UI: Confirm delete
    UI->>Hook: confirmSingleDelete()
    Hook->>Service: deleteMutation(id)
    Service->>API: DELETE /api/lookups/countries-and-locales/remove/{id}
    API->>DB: Soft-delete country
    DB-->>API: Success
    API-->>Service: Returns data
    Service-->>Hook: onSuccess
    Hook->>Hook: deselect(id), reload(), toast
    Hook-->>UI: Refresh table
    UI-->>User: Item removed, table updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

The changes introduce substantial new logic across multiple interconnected areas: a centralized lookup hook managing pagination/selection/deletion/forms, new API endpoints with authentication and validation, ORM relation updates, and UI component refactoring. While the implementation follows consistent patterns, the breadth of affected files and the need to verify the complete flow from UI through mutations to API to database requires careful review.

Possibly related PRs

  • Feature/fin 022 completed #101: Introduced the initial LookupTable, lookup pages, and lookup-related components that this PR refactors and extends with new modals, mutations, and centralized state management.
  • Feature/fin 022 parts/sidebar and lookups #98: Extended the lookup/sidebar feature set; this PR refactors the same lookup pages, selection hooks, and created-by utilities with new admin tracking and form flows.
  • Feature/fin 033 #84: Modified the same ORM entities (CountryOrm, CurrencyOrm) to add user/admin relationship columns that this PR now populates and loads via repositories.

Suggested labels

Highest priority, Code review

Suggested reviewers

  • dmytroreshetylo
  • Qunitee

Poem

🐰 A rabbit hops through admin lands,
With modals, forms, and steady hands,
Delete confirms and lookups clean,
Admin tracking in between!
Schemas validated, mutations flow—
The refactor reaps what devs will sow! 🌾✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Feature/fin 023" is vague and does not convey meaningful information about the specific changes; it references a ticket number without describing the actual feature or main change. Use a descriptive title like "Add lookup data management forms for countries and currencies" to clearly summarize the primary feature being implemented.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is well-structured and comprehensive, following the template with all required sections completed.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/fin-023

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (3)
src/client/shared/ui/ui-dialog/ui-dialog.tsx (1)

81-83: Close-button utility selectors target <svg>, but the icon renders as <i>.

The _svg utility selectors on Line 81 do not apply to UiSvgIcon (it renders <i>), so those styles are effectively dead here.

♻️ Suggested fix
-            className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
+            className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_i]:pointer-events-none [&_i]:shrink-0 [&_i:not([class*='size-'])]:size-4"
           >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/shared/ui/ui-dialog/ui-dialog.tsx` around lines 81 - 83, The
close-button's utility selectors target SVG elements but UiSvgIcon renders an
<i>, so those styles never apply; update the close button's className on the
close element (where UiSvgIcon is used) to include matching nested selectors for
<i> as well as <svg> (e.g. add [&_i]:pointer-events-none [&_i]:shrink-0
[&_i:not([class*='size-'])]:size-4 or similar equivalents) so the
pointer-events, sizing and shrink rules apply to UiSvgIcon, or alternatively
change UiSvgIcon to render an actual <svg> element if you prefer that
component-level fix (referencing UiSvgIcon and the close button className).
src/client/shared/components/admin-modal/fin-admin-modal.tsx (1)

13-23: Consider aligning the interface name with the component export.

The interface is named FinAdminModalProps but the exported component is UiAdminModal. For consistency with the Ui* naming convention used throughout the UI layer, consider renaming to UiAdminModalProps.

♻️ Suggested rename
-interface FinAdminModalProps {
+interface UiAdminModalProps {
   isOpen: boolean;
   onClose: () => void;
   title: string;
   description?: string;
   children: React.ReactNode;
   formId: string;
   isLoading?: boolean;
 }

-export function UiAdminModal({ isOpen, onClose, title, description, children, formId, isLoading }: FinAdminModalProps) {
+export function UiAdminModal({ isOpen, onClose, title, description, children, formId, isLoading }: UiAdminModalProps) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/shared/components/admin-modal/fin-admin-modal.tsx` around lines 13
- 23, Rename the props interface to match the exported component's Ui* naming:
change FinAdminModalProps to UiAdminModalProps and update its usage in the
UiAdminModal function signature so the parameter type references
UiAdminModalProps; ensure any other references or imports (e.g., tests,
consumers) that used FinAdminModalProps are updated to the new name to keep
naming consistent with the UI layer convention.
src/client/features/admin/countries-lookup/countries-lookup.tsx (1)

140-152: Hidden modal pattern with ref triggers works but is fragile.

The PR description mentions this is intentional to avoid modifying UiConfirmModal. While functional, this approach has drawbacks: timing-dependent (setTimeout), hidden DOM elements, and accessibility concerns with aria-hidden on interactive elements.

Consider refactoring UiConfirmModal to support controlled open state in a future iteration for cleaner integration.

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

In `@src/client/features/admin/countries-lookup/countries-lookup.tsx` around lines
140 - 152, The hidden-trigger pattern using UiConfirmModal +
singleDeleteTriggerRef is fragile and should be replaced with a controlled open
prop: update UiConfirmModal to accept an isOpen (boolean) and onClose callback
(and keep onConfirm), then in this file replace the hidden button/ref logic with
a local state (e.g. isSingleDeleteOpen) and setIsSingleDeleteOpen(true) to open
and setIsSingleDeleteOpen(false) in onClose; wire isSingleDeleteOpen to the new
isOpen prop and confirmSingleDelete to onConfirm (also close after confirm).
Update occurrences of singleDeleteTriggerRef, the hidden button, and any
setTimeout click hacks to use this new controlled open state and onClose
instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/features/admin/countries-lookup/countries-lookup.tsx`:
- Around line 154-165: The bulk-delete path currently calls confirmBulkDelete
directly and never opens the UiConfirmModal; update the delete handler to
trigger the modal by programmatically clicking the ref instead. Replace the
direct onDelete={confirmBulkDelete} usage with a small handler (e.g.,
onBulkDeleteClick or a lambda) that calls bulkDeleteTriggerRef.current?.click(),
and move the actual deletion logic into the confirmBulkDelete callback (the
onConfirm prop of UiConfirmModal). Ensure you reference bulkDeleteTriggerRef,
confirmBulkDelete, and UiConfirmModal so the modal is triggered before deletion.
- Around line 59-67: confirmBulkDelete currently just shows a success toast and
clears selection with no API call; replace the no-op with real bulk-delete logic
by invoking the backend delete function (or fetch/axios call) for the selected
IDs, await its completion inside confirmBulkDelete, handle errors by showing
showToast({ title: 'Error', ... }) and rethrow or return, and on success call
showToast({ title: 'Success', ... }), call clearSelection(), and refresh the
list/state (e.g., refetch or call the existing load/refresh function) so the UI
reflects deletions; locate confirmBulkDelete, clearSelection, and showToast in
this file and use the same ID array/state used to drive selection.
- Around line 74-87: The delete handler confirmSingleDelete currently patches
the server but doesn't refresh the list; obtain React Query's QueryClient (via
useQueryClient) in this component and call queryClient.invalidateQueries with
the countries list query key (the same key used to fetch the countries list)
after the successful patch inside confirmSingleDelete (before or after
showToast) so the UI refetches and the deleted item disappears; ensure you
import/useQueryClient and reference the same query key string/array used by the
countries fetch.

In `@src/client/features/admin/currencies-lookup/currencies-lookup.tsx`:
- Around line 54-55: The bulk-delete flow currently calls confirmBulkDelete
directly, bypassing the confirmation modal; instead, wire the onDelete handler
to programmatically click the hidden modal trigger so the modal opens (use
bulkDeleteTriggerRef.current?.click()), and keep confirmBulkDelete as the
callback invoked by the modal confirm action; update any places where
onDelete={confirmBulkDelete} to onDelete={() =>
bulkDeleteTriggerRef.current?.click()} and ensure the confirmation modal
component uses confirmBulkDelete as its confirm callback.
- Around line 80-93: confirmSingleDelete performs the soft-delete but doesn't
refresh the currencies list; after the successful fetchClient.patch call in
confirmSingleDelete (and before clearing itemToDelete), call your query cache
updater (e.g., queryClient.invalidateQueries or refetchQueries) for the
currencies lookup query key used by the component's useQuery hook so the list is
refetched and UI updates; keep the existing success toast and error handling
intact.
- Around line 65-73: confirmBulkDelete currently performs no deletion — it only
shows a success toast and clears selection; modify confirmBulkDelete to await
the real bulk-delete API call for the selected currency IDs, handle the
response/errors, then on success call clearSelection() and showToast({ title:
'Success', ... }) and on failure show the error message via showToast. Locate
confirmBulkDelete in currencies-lookup.tsx and replace the empty try block with
an awaited API call (e.g., your existing bulk-delete client method or a loop of
deleteCurrency calls using the selected IDs), update local state to remove
deleted items from the list, and ensure errors are caught and their messages
shown (use error instanceof Error ? error.message : fallback).

In `@src/client/features/admin/lookups/countries/country-form-modal.tsx`:
- Around line 25-28: The form is not reset when switching edit items because
useForm's defaultValues only apply on mount; update the component to call
methods.reset(initialData ?? { countryName: '', localeName: '' }) whenever
initialData changes (useEffect that depends on initialData) so the form state
(methods from useForm<CountryFormData>) is synchronized with the new data
validated by countrySchema; ensure you reference the existing methods.reset(...)
call rather than recreating the form instance.
- Around line 30-40: The onSuccess behavior is missing from useSendDataFetch so
the modal (country-form-modal) never closes and the country list isn't
refreshed; add an onSuccess callback in the options passed to useSendDataFetch
that (1) closes the modal (call the component's close handler or set the modal
open state to false—wherever the modal is controlled in this component) and (2)
invalidates the countries lookup query via your react-query QueryClient (e.g.,
call queryClient.invalidateQueries with the countries list query key used by the
list component). Tie this onSuccess to the existing mutate/isPending usage so
both create (when initialData is falsy) and update (when initialData is present)
trigger the close + invalidate sequence.

In `@src/client/features/admin/lookups/currencies/currency-form-modal.tsx`:
- Around line 25-29: CurrencyFormModal's form doesn't update when initialData
changes because useForm's defaultValues only apply on mount; modify the
component to call methods.reset(initialData || { name: '', code: '', symbol: ''
}) inside a useEffect that watches initialData so the form syncs when switching
edit items. Locate the useForm invocation (methods) and add a useEffect
referencing methods.reset and initialData to reinitialize the form whenever
initialData changes.
- Around line 31-41: The mutation created with useSendDataFetch (the const {
mutate, isPending } = useSendDataFetch(...)) lacks an onSuccess handler, so the
modal never closes and list data isn't refreshed; add an onSuccess callback in
the options passed to useSendDataFetch that (1) calls the modal close function
used in this component (e.g., closeModal or closeCurrencyModal) and (2) calls
your React Query client to refresh the list (e.g., queryClient.invalidateQueries
with the lookups/currencies key or the appropriate query key used elsewhere);
ensure the onSuccess receives the response and still preserves any existing
success handling in the options object.

In `@src/client/shared/ui/ui-dialog/ui-dialog.tsx`:
- Around line 84-85: The hardcoded "Close" screen-reader label inside the shared
UI primitive should be made localizable: replace the literal text in the
DialogPrimitive.Close children (the <span className="sr-only">Close</span>
instances) by sourcing the string from your i18n layer or a passed-in prop
(e.g., add an optional closeLabel prop to the UiDialog component and fall back
to an i18n key like t('dialog.close')); update both occurrences that render
DialogPrimitive.Close to use that prop or t(...) value so the SR-only label is
configurable/localized.

---

Nitpick comments:
In `@src/client/features/admin/countries-lookup/countries-lookup.tsx`:
- Around line 140-152: The hidden-trigger pattern using UiConfirmModal +
singleDeleteTriggerRef is fragile and should be replaced with a controlled open
prop: update UiConfirmModal to accept an isOpen (boolean) and onClose callback
(and keep onConfirm), then in this file replace the hidden button/ref logic with
a local state (e.g. isSingleDeleteOpen) and setIsSingleDeleteOpen(true) to open
and setIsSingleDeleteOpen(false) in onClose; wire isSingleDeleteOpen to the new
isOpen prop and confirmSingleDelete to onConfirm (also close after confirm).
Update occurrences of singleDeleteTriggerRef, the hidden button, and any
setTimeout click hacks to use this new controlled open state and onClose
instead.

In `@src/client/shared/components/admin-modal/fin-admin-modal.tsx`:
- Around line 13-23: Rename the props interface to match the exported
component's Ui* naming: change FinAdminModalProps to UiAdminModalProps and
update its usage in the UiAdminModal function signature so the parameter type
references UiAdminModalProps; ensure any other references or imports (e.g.,
tests, consumers) that used FinAdminModalProps are updated to the new name to
keep naming consistent with the UI layer convention.

In `@src/client/shared/ui/ui-dialog/ui-dialog.tsx`:
- Around line 81-83: The close-button's utility selectors target SVG elements
but UiSvgIcon renders an <i>, so those styles never apply; update the close
button's className on the close element (where UiSvgIcon is used) to include
matching nested selectors for <i> as well as <svg> (e.g. add
[&_i]:pointer-events-none [&_i]:shrink-0 [&_i:not([class*='size-'])]:size-4 or
similar equivalents) so the pointer-events, sizing and shrink rules apply to
UiSvgIcon, or alternatively change UiSvgIcon to render an actual <svg> element
if you prefer that component-level fix (referencing UiSvgIcon and the close
button className).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 39552ee9-5008-42a7-897d-b15ab8f434a1

📥 Commits

Reviewing files that changed from the base of the PR and between a1b4706 and 76c0437.

📒 Files selected for processing (6)
  • src/client/features/admin/countries-lookup/countries-lookup.tsx
  • src/client/features/admin/currencies-lookup/currencies-lookup.tsx
  • src/client/features/admin/lookups/countries/country-form-modal.tsx
  • src/client/features/admin/lookups/currencies/currency-form-modal.tsx
  • src/client/shared/components/admin-modal/fin-admin-modal.tsx
  • src/client/shared/ui/ui-dialog/ui-dialog.tsx

Comment thread src/client/features/admin/countries-lookup/countries-lookup.tsx Outdated
Comment thread src/client/features/admin/countries-lookup/countries-lookup.tsx
Comment thread src/client/features/admin/currencies-lookup/currencies-lookup.tsx Outdated
Comment thread src/client/features/admin/currencies-lookup/currencies-lookup.tsx Outdated
Comment thread src/client/features/admin/currencies-lookup/currencies-lookup.tsx Outdated
Comment thread src/client/features/admin/lookups/countries/country-form-modal.tsx
Comment thread src/client/features/admin/lookups/countries/country-form-modal.tsx Outdated
Comment thread src/client/features/admin/lookups/currencies/currency-form-modal.tsx Outdated
Comment thread src/client/features/admin/lookups/currencies/currency-form-modal.tsx Outdated
Comment thread src/client/shared/ui/ui-dialog/ui-dialog.tsx Outdated
Comment thread src/client/shared/ui/ui-dialog/ui-dialog.tsx Outdated
Comment thread src/client/shared/components/admin-modal/fin-admin-modal.tsx Outdated
Comment thread src/client/features/admin/lookups/countries/country-form-modal.tsx Outdated
Comment thread src/client/features/admin/lookups/countries/country-form-modal.tsx Outdated
Comment thread src/client/features/admin/currencies-lookup/currencies-lookup.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/server/database/migrations.ts (1)

3-3: ⚡ Quick win

Normalize migration filename/import path (remove leading whitespace).

Line 3 imports from migrations/ 1746000000000... (note the leading space). This is brittle and easy to break in tooling and future refs.

♻️ Suggested change
-import { AddAdminNameToLookups1746000000000 } from '@backend/database/migrations/ 1746000000000-add-admin-name-to-lookups.migration';
+import { AddAdminNameToLookups1746000000000 } from '@backend/database/migrations/1746000000000-add-admin-name-to-lookups.migration';

Also rename the migration file to remove the leading space from its filename.

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

In `@src/server/database/migrations.ts` at line 3, The import path for
AddAdminNameToLookups1746000000000 in migrations.ts contains a leading space
("migrations/ 1746000000000...") which breaks tooling; update the import to the
normalized path without the space and also rename the physical migration file to
remove the leading space in its filename so the module path and filename match
(ensure the exported class AddAdminNameToLookups1746000000000 remains
unchanged).
src/common/domains/lookups/schemas/lookups-form.schema.ts (1)

11-13: ⚡ Quick win

Disallow empty update payloads for PATCH schemas.

Both update schemas currently accept {}, which makes no-op updates valid.

Proposed fix
+const hasAtLeastOneDefinedField = (data: Record<string, unknown>): boolean =>
+  Object.values(data).some((value) => value !== undefined);
+
 export const UpdateCurrencySchema = CurrencyFormSchema.partial().extend({
   softDeleted: z.union([z.literal(0), z.literal(1)]).optional(),
-});
+}).refine(hasAtLeastOneDefinedField, { message: 'Потрібно передати хоча б одне поле для оновлення' });
@@
 export const UpdateCountrySchema = CountryFormSchema.partial().extend({
   softDeleted: z.union([z.literal(0), z.literal(1)]).optional(),
-});
+}).refine(hasAtLeastOneDefinedField, { message: 'Потрібно передати хоча б одне поле для оновлення' });

Also applies to: 24-26

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

In `@src/common/domains/lookups/schemas/lookups-form.schema.ts` around lines 11 -
13, The UpdateCurrencySchema (built from
CurrencyFormSchema.partial().extend(...)) currently allows an empty object for
PATCH; update it to forbid empty payloads by adding a post-schema check (e.g., a
.refine on the resulting schema) that ensures Object.keys(value).length > 0 and
returns a clear error message when empty; apply the same non-empty-payload check
to the other update schemas referenced around lines 24-26 so all PATCH schemas
reject {}.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/features/admin/currencies-lookup/currencies-lookup.tsx`:
- Around line 64-68: The success side-effects (showToast, clearSelection,
reload) are being run once per item because you call
bulkDeleteMutation.mutateAsync for each selected id; change the flow so you call
the mutation once for all IDs (or await all deletions and run side-effects once
afterwards). Specifically, update the code that iterates over selectedIds and
calls mutateAsync to instead call bulkDeleteMutation.mutateAsync(selectedIds)
(or Promise.all on individual mutateAsync calls) and move the onSuccess logic
(showToast, clearSelection, reload) to a single follow-up await or the
mutation’s onSuccess handler so it executes only once after all deletions
complete.

---

Nitpick comments:
In `@src/common/domains/lookups/schemas/lookups-form.schema.ts`:
- Around line 11-13: The UpdateCurrencySchema (built from
CurrencyFormSchema.partial().extend(...)) currently allows an empty object for
PATCH; update it to forbid empty payloads by adding a post-schema check (e.g., a
.refine on the resulting schema) that ensures Object.keys(value).length > 0 and
returns a clear error message when empty; apply the same non-empty-payload check
to the other update schemas referenced around lines 24-26 so all PATCH schemas
reject {}.

In `@src/server/database/migrations.ts`:
- Line 3: The import path for AddAdminNameToLookups1746000000000 in
migrations.ts contains a leading space ("migrations/ 1746000000000...") which
breaks tooling; update the import to the normalized path without the space and
also rename the physical migration file to remove the leading space in its
filename so the module path and filename match (ensure the exported class
AddAdminNameToLookups1746000000000 remains unchanged).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fbc2546-bf31-4ded-8510-0707c1e08f74

📥 Commits

Reviewing files that changed from the base of the PR and between 76c0437 and f0d2f85.

📒 Files selected for processing (20)
  • src/app/api/lookups/countries-and-locales/create/route.ts
  • src/app/api/lookups/countries-and-locales/update/[id]/route.ts
  • src/app/api/lookups/currencies/create/route.ts
  • src/app/api/lookups/currencies/update/[id]/route.ts
  • src/client/entities/lookups/lookup-row-actions/lookup-row-actions.tsx
  • src/client/features/admin/countries-lookup/countries-lookup.tsx
  • src/client/features/admin/currencies-lookup/currencies-lookup.tsx
  • src/client/features/admin/lookups/countries/country-form-modal.tsx
  • src/client/features/admin/lookups/currencies/currency-form-modal.tsx
  • src/client/features/admin/lookups/hooks/use-country-mutations.hook.ts
  • src/client/features/admin/lookups/hooks/use-currency-mutations.hook.ts
  • src/client/shared/components/admin-modal/fin-admin-modal.scss
  • src/client/shared/components/admin-modal/fin-admin-modal.tsx
  • src/common/domains/lookups/schemas/lookups-form.schema.ts
  • src/common/records/countries.record.ts
  • src/common/records/currencies.record.ts
  • src/server/database/migrations.ts
  • src/server/database/migrations/ 1746000000000-add-admin-name-to-lookups.migration.ts
  • src/server/entities/country/infrastructure/country.orm.ts
  • src/server/entities/currency/infrastructure/currency.orm.ts
✅ Files skipped from review due to trivial changes (5)
  • src/client/shared/components/admin-modal/fin-admin-modal.scss
  • src/common/records/currencies.record.ts
  • src/server/entities/country/infrastructure/country.orm.ts
  • src/common/records/countries.record.ts
  • src/client/features/admin/lookups/countries/country-form-modal.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/client/features/admin/lookups/currencies/currency-form-modal.tsx
  • src/client/features/admin/countries-lookup/countries-lookup.tsx

Comment thread src/client/features/admin/currencies-lookup/currencies-lookup.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

Inline comments:
In `@src/client/features/admin/countries-lookup/countries-lookup.tsx`:
- Around line 87-90: The confirmSingleDelete function currently calls
singleDeleteMutation.mutateAsync(itemToDelete.id) but doesn't remove that id
from the bulk selection state, leaving stale ids in selected; update
confirmSingleDelete (and/or the singleDeleteMutation success handler) to remove
the deleted item id from the selected collection (e.g., call the setter that
manages selected to filter out itemToDelete.id or delete it from the Set) after
the deletion resolves so bulk actions no longer include the deleted row.
- Around line 75-79: In confirmBulkDelete replace Promise.all(...) with
Promise.allSettled(...) so that all deletion attempts are awaited without
short-circuiting on the first rejection; inspect the returned results array from
Promise.allSettled to optionally log or aggregate failures (using
bulkDeleteMutation.mutateAsync for each id), then always call showToast,
clearSelection(), and reload() regardless of individual failures to keep UI in
sync; reference the confirmBulkDelete function and the selected set plus
bulkDeleteMutation to locate where to change the logic.

In `@src/client/features/admin/currencies-lookup/currencies-lookup.tsx`:
- Around line 90-93: When deleting a single row in confirmSingleDelete, ensure
you also remove that id from the bulk selection state so stale ids remain
selected; after awaiting singleDeleteMutation.mutateAsync(itemToDelete.id)
update the selection state (e.g., call setSelected(prev => prev.filter(id => id
!== itemToDelete.id)) or equivalent) and then clear itemToDelete as currently
done. This uses confirmSingleDelete, singleDeleteMutation and the component's
selected/setSelected state to keep bulk selection in sync after a single-row
delete.
- Around line 78-82: The confirmBulkDelete implementation currently uses
Promise.all which aborts on the first rejection; change it to use
Promise.allSettled to ensure all bulkDeleteMutation.mutateAsync(id) calls are
attempted, then inspect the results to determine if any deletions failed—if any
failed call show a destructive/error toast, otherwise show the success toast;
regardless of outcome always call clearSelection() and reload() afterwards.
Reference confirmBulkDelete, bulkDeleteMutation.mutateAsync, clearSelection,
reload, and showToast when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 11517d8e-c62a-469a-9662-c3b92a20081d

📥 Commits

Reviewing files that changed from the base of the PR and between f0d2f85 and e4af1c6.

📒 Files selected for processing (9)
  • src/app/api/lookups/countries-and-locales/update/[id]/route.ts
  • src/app/api/lookups/currencies/update/[id]/route.ts
  • src/client/entities/lookups/lookup-created-by/lookup-created-by.model.ts
  • src/client/entities/lookups/lookup-created-by/lookup-created-by.util.ts
  • src/client/entities/lookups/lookup-status-badge/lookup-status-badge.tsx
  • src/client/features/admin/countries-lookup/countries-lookup.tsx
  • src/client/features/admin/currencies-lookup/currencies-lookup.tsx
  • src/client/features/admin/lookups/hooks/use-country-mutations.hook.ts
  • src/client/features/admin/lookups/hooks/use-currency-mutations.hook.ts
✅ Files skipped from review due to trivial changes (2)
  • src/client/entities/lookups/lookup-created-by/lookup-created-by.model.ts
  • src/client/entities/lookups/lookup-created-by/lookup-created-by.util.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/client/features/admin/lookups/hooks/use-currency-mutations.hook.ts
  • src/app/api/lookups/currencies/update/[id]/route.ts

Comment thread src/client/features/admin/countries-lookup/countries-lookup.tsx Outdated
Comment thread src/client/features/admin/currencies-lookup/currencies-lookup.tsx Outdated
Comment thread src/client/features/admin/currencies-lookup/currencies-lookup.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@src/app/api/lookups/countries-and-locales/remove/`[i]/route.ts:
- Line 10: The paramsFn in remove/[i]/route.ts is reading context.id which is
undefined because Next.js provides the dynamic param as i; update the paramsFn
to use context.i when calling GetIntegerParamPipe (e.g., paramsFn: (context) =>
({ id: GetIntegerParamPipe(context.i, 1) })), or alternatively rename the route
segment from [i] to [id] so context.id exists; apply the same fix to the
currencies remove route to ensure the dynamic param is read correctly.

In `@src/app/api/lookups/currencies/remove/`[i]/route.ts:
- Line 10: The params accessor in the route is using the wrong key: paramsFn
currently reads context.id but the dynamic segment is named [i], so Next.js will
provide params.i; update the paramsFn to use context.i (e.g., paramsFn:
(context) => ({ id: GetIntegerParamPipe(context.i, 1) })) or alternatively
rename the route segment to [id] so context.id is valid; ensure the change is
applied where paramsFn and GetIntegerParamPipe are referenced to avoid undefined
IDs.

In `@src/common/domains/lookups/schemas/lookups-form.schema.ts`:
- Around line 3-24: The current schemas accept whitespace-only strings because
they use z.string().min(1) and length(3) without trimming; update
CurrencyFormSchema and CountryFormSchema to trim input before validating (e.g.,
use z.string().trim().min(1, ...) for name, symbol, countryName, localeName and
z.string().trim().length(3, ...) for code) so whitespace-only values are
rejected; the existing UpdateCurrencySchema and UpdateCountrySchema (which use
.partial()) will inherit the fixed validators automatically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: adb10a9a-2f42-4380-84ee-e3fa801ca0c8

📥 Commits

Reviewing files that changed from the base of the PR and between e4af1c6 and b661664.

📒 Files selected for processing (12)
  • src/app/api/lookups/countries-and-locales/remove/[i]/route.ts
  • src/app/api/lookups/currencies/remove/[i]/route.ts
  • src/client/features/admin/countries-lookup/countries-lookup.tsx
  • src/client/features/admin/currencies-lookup/currencies-lookup.tsx
  • src/client/features/admin/hooks/use-lookup-selection.hook.ts
  • src/client/features/admin/lookups/hooks/use-country-mutations.hook.ts
  • src/client/features/admin/lookups/hooks/use-currency-mutations.hook.ts
  • src/common/domains/lookups/schemas/lookups-form.schema.ts
  • src/server/entities/country/application/exist-country.guard.ts
  • src/server/entities/country/infrastructure/country.orm.ts
  • src/server/entities/currency/application/exist-currency.guard.ts
  • src/server/entities/currency/infrastructure/currency.orm.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/server/entities/currency/infrastructure/currency.orm.ts
  • src/client/features/admin/lookups/hooks/use-country-mutations.hook.ts
  • src/server/entities/country/infrastructure/country.orm.ts
  • src/client/features/admin/lookups/hooks/use-currency-mutations.hook.ts
  • src/client/features/admin/countries-lookup/countries-lookup.tsx

Comment thread src/app/api/lookups/countries-and-locales/remove/[id]/route.ts
Comment thread src/app/api/lookups/currencies/remove/[id]/route.ts
Comment thread src/common/domains/lookups/schemas/lookups-form.schema.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts (1)

3-3: 💤 Low value

Minor: Class timestamp differs from filename timestamp.

The class is named AddAdminIdToLookups1746100000000 but the file is 1746000000000-add-admin-id-to-lookups.migration.ts. While this works (the import uses the filename), aligning them improves maintainability.

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

In
`@src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts`
at line 3, Rename the migration class so its timestamp matches the filename:
change the class name AddAdminIdToLookups1746100000000 to
AddAdminIdToLookups1746000000000 (or alternatively rename the file to match the
class), ensuring the exported class name and any references to it (the class
declaration in
src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts)
are consistent for easier maintainability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/api/lookups/currencies/update/`[id]/route.ts:
- Around line 18-25: The handler currently allows an empty object (validated by
UpdateCurrencySchema.partial()) and performs a no-op update; modify the execute
function to detect when the request body contains no updatable fields (e.g. no
name, code, or symbol keys or all values are undefined) before calling
currencyRepository.updateItem, and return a 400 Bad Request with an explanatory
message instead of proceeding; add this check at the start of execute (using the
same param names: body, context, params: { id }, and context.currency) so you
only call currencyRepository.updateItem when at least one of body.name,
body.code, or body.symbol is provided.

In `@src/server/entities/currency/infrastructure/currency.orm.ts`:
- Around line 1-24: The file wrongly defines and exports CountryOrm for the
country table; replace it with a CurrencyOrm entity mapped to
`@Entity`('currency') and export CurrencyOrm instead of CountryOrm, implement
DefaultTableColumnsOrm, include the adminId : number | null and
`@ManyToOne`('UserOrm', { nullable: true, onDelete: 'SET NULL' }) with
`@JoinColumn`({ name: 'adminId' }) admin?: UserOrm, and ensure the entity exposes
the currencyCode field expected by UserOrm.currency (and any other
currency-specific columns like code/name as in other repos); keep the
OneToMany/relationships consistent with UserOrm.currency and update any unique
constraints or lengths to match currency requirements.

---

Nitpick comments:
In
`@src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts`:
- Line 3: Rename the migration class so its timestamp matches the filename:
change the class name AddAdminIdToLookups1746100000000 to
AddAdminIdToLookups1746000000000 (or alternatively rename the file to match the
class), ensuring the exported class name and any references to it (the class
declaration in
src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts)
are consistent for easier maintainability.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f60e5f3-e9ce-4c5c-b415-729296841a7a

📥 Commits

Reviewing files that changed from the base of the PR and between b661664 and e7a12ea.

📒 Files selected for processing (11)
  • src/app/api/lookups/countries-and-locales/create/route.ts
  • src/app/api/lookups/countries-and-locales/update/[id]/route.ts
  • src/app/api/lookups/currencies/create/route.ts
  • src/app/api/lookups/currencies/update/[id]/route.ts
  • src/common/records/countries.record.ts
  • src/common/records/currencies.record.ts
  • src/server/database/migrations.ts
  • src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts
  • src/server/entities/country/infrastructure/country.orm.ts
  • src/server/entities/currency/infrastructure/currency.orm.ts
  • src/server/entities/currency/infrastructure/currency.repository.ts
✅ Files skipped from review due to trivial changes (5)
  • src/common/records/currencies.record.ts
  • src/common/records/countries.record.ts
  • src/server/entities/currency/infrastructure/currency.repository.ts
  • src/server/database/migrations.ts
  • src/app/api/lookups/countries-and-locales/create/route.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/api/lookups/currencies/create/route.ts
  • src/app/api/lookups/countries-and-locales/update/[id]/route.ts

Comment thread src/app/api/lookups/currencies/update/[id]/route.ts
Comment thread src/server/entities/currency/infrastructure/currency.orm.ts
Comment thread src/client/entities/lookups/lookup-table/lookup-table-row.tsx Fixed
Comment thread src/client/entities/lookups/lookup-table/lookup-table-row.tsx Fixed
@IeuanZorander

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/client/features/admin/countries-lookup/countries-lookup.tsx (1)

71-76: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use Promise.allSettled to prevent partial failure from breaking UI sync.

If any deletion in Promise.all fails, the promise rejects immediately. Earlier successful deletions persist, but clearSelection() and reload() never run, leaving the UI inconsistent with the server. Replace with Promise.allSettled to ensure cleanup always occurs.

🐛 Suggested fix
 const confirmBulkDelete = async () => {
-  await Promise.all(Array.from(selected).map((id) => bulkDeleteMutation.mutateAsync(id)));
-  showToast({ title: 'Успішно', description: 'Вибрані записи видалено', variant: 'default' });
-  clearSelection();
-  reload();
+  const results = await Promise.allSettled(
+    Array.from(selected).map((id) => deleteMutation.mutateAsync(id)),
+  );
+  const failedCount = results.filter((r) => r.status === 'rejected').length;
+  clearSelection();
+  reload();
+  showToast(
+    failedCount === 0
+      ? { title: 'Успішно', description: 'Вибрані записи видалено', variant: 'default' }
+      : { title: 'Помилка', description: `Не вдалося видалити ${failedCount} запис(ів)`, variant: 'destructive' },
+  );
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/features/admin/countries-lookup/countries-lookup.tsx` around lines
71 - 76, In confirmBulkDelete replace Promise.all with Promise.allSettled so one
failing mutateAsync doesn't prevent cleanup; call
Promise.allSettled(Array.from(selected).map(id =>
bulkDeleteMutation.mutateAsync(id))), then inspect results to optionally show
failure toasts, but always run clearSelection() and reload() afterwards;
reference confirmBulkDelete, bulkDeleteMutation.mutateAsync, clearSelection, and
reload when making the change.
🧹 Nitpick comments (3)
src/client/features/admin/countries-lookup/countries-lookup.tsx (1)

61-63: ⚡ Quick win

Duplicate mutation hook instances are unnecessary.

Two separate useCountryMutations() calls create redundant mutation instances. A single call can provide the same deleteMutation for both bulk and single delete operations.

♻️ Consolidate to single hook call
-const { deleteMutation: bulkDeleteMutation } = useCountryMutations();
-
-const { deleteMutation: singleDeleteMutation } = useCountryMutations();
+const { deleteMutation } = useCountryMutations();

Then use deleteMutation in both confirmBulkDelete and confirmSingleDelete.

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

In `@src/client/features/admin/countries-lookup/countries-lookup.tsx` around lines
61 - 63, Two calls to useCountryMutations() create duplicate mutation instances;
consolidate by replacing both declarations with a single call like const {
deleteMutation } = useCountryMutations() and then use that deleteMutation in
both confirmBulkDelete and confirmSingleDelete (remove bulkDeleteMutation and
singleDeleteMutation variables and update their usages to deleteMutation).
src/app/api/lookups/countries-and-locales/remove/[id]/route.ts (1)

11-12: 💤 Low value

userId is loaded but never used.

The userId is extracted via GetUserIdTransformer in contextFn but isn't referenced in execute or passed for audit/logging. If audit tracking isn't needed here, remove it to avoid unnecessary async work. If it should be used (e.g., to record who deleted), wire it into the delete call.

♻️ Remove unused context if not needed
 contextFn: async (request, params) => ({
-  userId: await GetUserIdTransformer(request),
   country: await countryRepository.getItemById(params.id),
 }),

Or, if audit is intended, pass userId to the delete operation.

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

In `@src/app/api/lookups/countries-and-locales/remove/`[id]/route.ts around lines
11 - 12, The contextFn currently awaits GetUserIdTransformer(request) and sets
userId but that value is never used; either remove the userId extraction from
contextFn to avoid unnecessary async work, or if audit is required, thread the
ID into the delete flow by passing the userId from context into the execute
handler and into the repository/service delete method (e.g., update the delete
call in execute to accept an actor or auditUserId and forward userId from
contextFn), and ensure GetUserIdTransformer stays only when the execute/delete
paths consume it.
src/server/entities/country/infrastructure/country.repository.ts (1)

47-50: ⚡ Quick win

Type assertion masks a shape mismatch.

The .map() produces plain objects with an adminName property that doesn't exist on CountryOrm. The as CountryOrm[] assertion hides this discrepancy and strips the ORM class prototype. If callers rely on ORM instance methods or the actual type contract, this will fail silently.

Consider returning a proper response type that includes adminName, or using a DTO/view model that matches the actual shape.

♻️ Suggested approach

Define a return type that accurately reflects the shape:

+type CountryWithAdmin = CountryOrm & { adminName: string | null };

 override async getItems(
   from: number,
   to: number,
   filters?: DeepPartial<CountriesAndLocalesFilter>,
-): Promise<CountryOrm[]> {
+): Promise<CountryWithAdmin[]> {
   // ...
   return results.map((item) => ({
     ...item,
     adminName: item.admin?.name ?? null,
-  })) as CountryOrm[];
+  })) as CountryWithAdmin[];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/entities/country/infrastructure/country.repository.ts` around
lines 47 - 50, The map currently creates plain objects with an extra adminName
property and then coerces them to CountryOrm via "as CountryOrm[]", which hides
a shape mismatch; instead, remove the unsafe cast and return a correctly typed
structure: define a new DTO/interface (e.g., CountryWithAdminName) or view model
that includes adminName, change the function return type to
CountryWithAdminName[] and map results into that DTO (or construct proper
CountryOrm instances and set adminName via a dedicated serializer if ORM methods
are required), updating usages to consume the new type rather than forcing
CountryOrm.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/api/lookups/user/get-by-id/`[id]/route.ts:
- Around line 15-24: The route currently only uses AuthGuard and allows any
authenticated user to fetch arbitrary user names in the execute function
(params.id); add an authorization check to prevent enumeration by enforcing
either a self-only policy (compare context.userId or context.user?.id to
params.id inside execute) or require an admin role before returning data (check
context.user?.role === 'admin'), and if the check fails return a 403 response.
Update the guards array or add the authorization logic at the top of execute
(referencing AuthGuard, execute, params.id, and context.userId/context.user) so
cross-user lookups are blocked unless the caller is the same user or has admin
privileges.

In `@src/client/entities/lookups/lookup-created-by/lookup-created-by-cell.tsx`:
- Around line 11-23: The avatar image and the fallback initial are currently
announcing the author's name twice (alt={name}) while the full name is rendered
in text; change the avatar's alt to an empty string (alt="") and add
aria-hidden="true" to the UiGraphic to make it decorative, and add
aria-hidden="true" to the fallback initial span (the inline-flex span) so only
the visible name span is exposed to assistive tech; update in
LookupCreatedByCell (UiGraphic usage and the fallback span) accordingly.

In `@src/client/features/admin/countries-lookup/countries-lookup.tsx`:
- Around line 83-90: The confirmSingleDelete function lacks error handling
around singleDeleteMutation.mutateAsync so an exception will leave itemToDelete
set and no error toast shown; wrap the mutateAsync call in try/catch and in the
catch call showToast with an error title/description (include the caught error
message if available), and ensure cleanup (deselect(itemToDelete.id),
setItemToDelete(null), and reload()) runs in a finally block so modal/state is
cleared regardless of success or failure.

---

Duplicate comments:
In `@src/client/features/admin/countries-lookup/countries-lookup.tsx`:
- Around line 71-76: In confirmBulkDelete replace Promise.all with
Promise.allSettled so one failing mutateAsync doesn't prevent cleanup; call
Promise.allSettled(Array.from(selected).map(id =>
bulkDeleteMutation.mutateAsync(id))), then inspect results to optionally show
failure toasts, but always run clearSelection() and reload() afterwards;
reference confirmBulkDelete, bulkDeleteMutation.mutateAsync, clearSelection, and
reload when making the change.

---

Nitpick comments:
In `@src/app/api/lookups/countries-and-locales/remove/`[id]/route.ts:
- Around line 11-12: The contextFn currently awaits
GetUserIdTransformer(request) and sets userId but that value is never used;
either remove the userId extraction from contextFn to avoid unnecessary async
work, or if audit is required, thread the ID into the delete flow by passing the
userId from context into the execute handler and into the repository/service
delete method (e.g., update the delete call in execute to accept an actor or
auditUserId and forward userId from contextFn), and ensure GetUserIdTransformer
stays only when the execute/delete paths consume it.

In `@src/client/features/admin/countries-lookup/countries-lookup.tsx`:
- Around line 61-63: Two calls to useCountryMutations() create duplicate
mutation instances; consolidate by replacing both declarations with a single
call like const { deleteMutation } = useCountryMutations() and then use that
deleteMutation in both confirmBulkDelete and confirmSingleDelete (remove
bulkDeleteMutation and singleDeleteMutation variables and update their usages to
deleteMutation).

In `@src/server/entities/country/infrastructure/country.repository.ts`:
- Around line 47-50: The map currently creates plain objects with an extra
adminName property and then coerces them to CountryOrm via "as CountryOrm[]",
which hides a shape mismatch; instead, remove the unsafe cast and return a
correctly typed structure: define a new DTO/interface (e.g.,
CountryWithAdminName) or view model that includes adminName, change the function
return type to CountryWithAdminName[] and map results into that DTO (or
construct proper CountryOrm instances and set adminName via a dedicated
serializer if ORM methods are required), updating usages to consume the new type
rather than forcing CountryOrm.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 419e2cbb-736f-48ac-92ca-cb1d3629c916

📥 Commits

Reviewing files that changed from the base of the PR and between b661664 and db56ea6.

📒 Files selected for processing (21)
  • src/app/api/lookups/countries-and-locales/create/route.ts
  • src/app/api/lookups/countries-and-locales/remove/[id]/route.ts
  • src/app/api/lookups/countries-and-locales/update/[id]/route.ts
  • src/app/api/lookups/currencies/create/route.ts
  • src/app/api/lookups/currencies/remove/[id]/route.ts
  • src/app/api/lookups/currencies/update/[id]/route.ts
  • src/app/api/lookups/user/get-by-id/[id]/route.ts
  • src/client/entities/lookups/lookup-created-by/lookup-created-by-cell.tsx
  • src/client/entities/lookups/lookup-created-by/lookup-created-by.model.ts
  • src/client/entities/lookups/lookup-created-by/lookup-created-by.util.ts
  • src/client/entities/lookups/lookup-table/lookup-table-row.tsx
  • src/client/features/admin/countries-lookup/countries-lookup.tsx
  • src/common/records/countries.record.ts
  • src/common/records/currencies.record.ts
  • src/server/database/migrations.ts
  • src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts
  • src/server/entities/country/infrastructure/country.orm.ts
  • src/server/entities/country/infrastructure/country.repository.ts
  • src/server/entities/currency/infrastructure/currency.orm.ts
  • src/server/entities/currency/infrastructure/currency.repository.ts
  • src/server/entities/user/infrastructure/user.repository.ts
💤 Files with no reviewable changes (1)
  • src/client/entities/lookups/lookup-created-by/lookup-created-by.model.ts
✅ Files skipped from review due to trivial changes (1)
  • src/common/records/currencies.record.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/common/records/countries.record.ts
  • src/app/api/lookups/currencies/create/route.ts
  • src/server/database/migrations.ts
  • src/app/api/lookups/countries-and-locales/create/route.ts
  • src/server/entities/currency/infrastructure/currency.repository.ts
  • src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts
  • src/app/api/lookups/countries-and-locales/update/[id]/route.ts
  • src/app/api/lookups/currencies/update/[id]/route.ts
  • src/server/entities/currency/infrastructure/currency.orm.ts

Comment thread src/app/api/lookups/user/get-by-id/[id]/route.ts Outdated
Comment thread src/client/features/admin/countries-lookup/countries-lookup.tsx Outdated
@IeuanZorander

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/client/entities/lookups/lookups.service.ts (1)

60-65: 💤 Low value

Minor: skipAuth: false is redundant.

Since skipAuth defaults to false in RequestOptions, this explicit assignment is unnecessary. Consider removing it for cleaner code, keeping only throwErrorIfNotAuth: false.

         {
           signal: abortSignal ?? null,
-          skipAuth: false,
           throwErrorIfNotAuth: false,
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/entities/lookups/lookups.service.ts` around lines 60 - 65, The
options object passed to the request includes a redundant skipAuth: false;
remove the skipAuth property and leave the remaining options (signal:
abortSignal ?? null and throwErrorIfNotAuth: false) so the RequestOptions
default behavior is used. Locate the options object in
src/client/entities/lookups/lookups.service.ts (around the request call that
references abortSignal and throwErrorIfNotAuth) and delete the skipAuth: false
entry only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/api/lookups/countries-and-locales/remove/`[id]/route.ts:
- Around line 17-24: The route's guards currently only verify authentication via
GetUserIdTransformer and existence via ExistCountryGuard but do not check admin
privileges; update the guards array to include an authorization guard that
enforces admin role before allowing deletion (e.g., add or create an AdminGuard
or RoleGuard and place it before calling countryRepository.deleteItem), ensure
the guard inspects the authenticated user object from GetUserIdTransformer (or
request/context) and returns { status: 403, message: 'Недостатньо прав' } when
the user is not an admin, and keep ExistCountryGuard to validate the target
exists before executing deleteItem.

In `@src/client/features/admin/hooks/use-admin-lookup.hook.ts`:
- Around line 75-88: The confirmSingleDelete function currently swallows errors
in its catch block; update the catch to display a destructive error toast
(matching confirmBulkDelete) when deleteMutation.mutateAsync fails — include a
clear title (e.g., 'Помилка') and description (e.g., 'Не вдалося видалити
запис') and optionally log or include the caught error message; keep the
existing finally behavior (setItemToDelete(null) and pagination.reload())
intact.

In `@src/client/shared/components/screen-handlers/fin-table-screen-handler.tsx`:
- Around line 48-54: FinTableScreenHandler currently delegates the no-data
rendering to FinListScreenHandler which returns a bare <span>, causing invalid
markup inside a <tbody>; update FinTableScreenHandler to override the
empty/no-data state by passing a table-safe emptyState (a <tr><td
colSpan="...">...</td></tr> or similar) into FinListScreenHandler props (or
implement an override in FinTableScreenHandler's render path) so that when
tableSkeleton/tableError are not used the no-data UI is wrapped in a table
row/cell and produces valid tbody markup.

In `@src/server/entities/country/infrastructure/country.orm.ts`:
- Around line 18-20: The relation on CountryOrm using admin?: UserOrm | null
with `@JoinColumn`(name: 'adminId') declares onDelete: 'SET NULL' but the
migration only creates a plain adminId column without a foreign key; update the
migration that creates/updates the countries table to add a proper FOREIGN KEY
constraint on adminId referencing the users (or UserOrm backing) primary key
with ON DELETE SET NULL (and keep adminId nullable), or add an ALTER TABLE ...
ADD CONSTRAINT step to create that FK if the table already exists so the DB
enforces referential integrity and SET NULL behavior when a user is deleted.

In `@src/server/shared/transformers/get-user-role.transformer.ts`:
- Around line 4-15: GetUserRoleTransformer currently returns payload.role cast
to RoleEnum without runtime validation, so malformed strings can leak through;
update GetUserRoleTransformer to validate payload.role at runtime against the
RoleEnum set (e.g., check that typeof payload.role === 'string' and that it
matches one of Object.values(RoleEnum) or use a dedicated isValidRole(type)
guard) and only return the role when it is a known RoleEnum value, otherwise
return null; reference the jwtVerify call and payload.role, JwtSecretConstant,
RoleEnum and isEmpty to locate and modify the transformer logic.

---

Nitpick comments:
In `@src/client/entities/lookups/lookups.service.ts`:
- Around line 60-65: The options object passed to the request includes a
redundant skipAuth: false; remove the skipAuth property and leave the remaining
options (signal: abortSignal ?? null and throwErrorIfNotAuth: false) so the
RequestOptions default behavior is used. Locate the options object in
src/client/entities/lookups/lookups.service.ts (around the request call that
references abortSignal and throwErrorIfNotAuth) and delete the skipAuth: false
entry only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e180dac-3e27-402c-ac33-750cc164b263

📥 Commits

Reviewing files that changed from the base of the PR and between db56ea6 and c1470cc.

📒 Files selected for processing (36)
  • src/app/(profile)/profile/budget/(regular-operations)/regular-operations/edit/[id]/page.tsx
  • src/app/api/lookups/countries-and-locales/get-items/route.ts
  • src/app/api/lookups/countries-and-locales/remove/[id]/route.ts
  • src/app/api/lookups/currencies/get-items/route.ts
  • src/app/api/lookups/currencies/update/[id]/route.ts
  • src/app/api/lookups/user/get-by-id/[id]/route.ts
  • src/client/entities/lookups/__tests__/lookups.service.spec.ts
  • src/client/entities/lookups/lookup-created-by/lookup-created-by-cell.tsx
  • src/client/entities/lookups/lookup-created-by/lookup-created-by.util.ts
  • src/client/entities/lookups/lookup-table/lookup-row-skeleton.tsx
  • src/client/entities/lookups/lookup-table/lookup-table.tsx
  • src/client/entities/lookups/lookups.service.ts
  • src/client/entities/tracking-operations/tracking-operations.api.client.ts
  • src/client/features/admin/countries-lookup/countries-lookup.tsx
  • src/client/features/admin/currencies-lookup/currencies-lookup.tsx
  • src/client/features/admin/hooks/use-admin-lookup.hook.ts
  • src/client/features/regular-incomes-expenses/regular-incomes-expenses-screen.tsx
  • src/client/shared/components/error/fin-error-table-widget.tsx
  • src/client/shared/components/screen-handlers/fin-form-screen-handler.tsx
  • src/client/shared/components/screen-handlers/fin-list-screen-handler.tsx
  • src/client/shared/components/screen-handlers/fin-table-screen-handler.tsx
  • src/client/shared/components/screen-handlers/props/form-screen-handler.props.ts
  • src/client/shared/components/screen-handlers/props/list-screen-handler.props.ts
  • src/client/shared/components/screen-handlers/props/table-screen-handler.props.ts
  • src/client/shared/services/fetch-client/fetch-client.service.ts
  • src/client/shared/services/fetch-client/models/request-options.model.ts
  • src/client/shared/ui/ui-table/ui-table.tsx
  • src/common/domains/lookups/schemas/lookups-form.schema.ts
  • src/common/records/countries.record.ts
  • src/common/records/currencies.record.ts
  • src/server/database/migrations.ts
  • src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts
  • src/server/entities/country/infrastructure/country.orm.ts
  • src/server/entities/country/infrastructure/country.repository.ts
  • src/server/entities/currency/infrastructure/currency.repository.ts
  • src/server/shared/transformers/get-user-role.transformer.ts
💤 Files with no reviewable changes (1)
  • src/client/entities/lookups/lookup-table/lookup-row-skeleton.tsx
✅ Files skipped from review due to trivial changes (8)
  • src/client/shared/components/screen-handlers/fin-form-screen-handler.tsx
  • src/app/(profile)/profile/budget/(regular-operations)/regular-operations/edit/[id]/page.tsx
  • src/client/shared/components/screen-handlers/fin-list-screen-handler.tsx
  • src/client/shared/components/error/fin-error-table-widget.tsx
  • src/server/database/migrations.ts
  • src/client/shared/components/screen-handlers/props/table-screen-handler.props.ts
  • src/client/features/regular-incomes-expenses/regular-incomes-expenses-screen.tsx
  • src/common/domains/lookups/schemas/lookups-form.schema.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/common/records/countries.record.ts
  • src/server/entities/country/infrastructure/country.repository.ts
  • src/server/database/migrations/1746000000000-add-admin-id-to-lookups.migration.ts
  • src/common/records/currencies.record.ts
  • src/app/api/lookups/currencies/update/[id]/route.ts

Comment thread src/app/api/lookups/countries-and-locales/remove/[id]/route.ts Outdated
Comment thread src/client/features/admin/hooks/use-admin-lookup.hook.ts
Comment thread src/server/entities/country/infrastructure/country.orm.ts
Comment thread src/server/shared/transformers/get-user-role.transformer.ts Outdated
@IeuanZorander

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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

Comment thread src/app/api/lookups/user/get-by-id/[id]/route.ts Outdated
Comment thread src/client/entities/lookups/lookup-created-by/lookup-created-by.util.ts Outdated
@dmytroreshetylo dmytroreshetylo linked an issue May 2, 2026 that may be closed by this pull request
@dmytroreshetylo dmytroreshetylo merged commit c11c3a4 into main May 2, 2026
5 checks passed
@dmytroreshetylo dmytroreshetylo deleted the feature/fin-023 branch May 2, 2026 12:30
@coderabbitai coderabbitai Bot mentioned this pull request May 15, 2026
6 tasks
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.

[FEATURE] FIN-023: Lookups' data forms

2 participants