Conversation
WalkthroughAdds patentStatus across DB types, schema, service, helpers, form, and agreements table UI; wires missing getUserTypeLabel import in UserCard and adds a slide-left motion class to AgreementFilesBlock. Formats concludedAt into its own column and surfaces patent status in lists and forms. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Form as UpdatePartnerAgreement Form
participant Helpers as shared/utils/helpers
participant Service as partner.ts
participant API as Backend API
participant DB as Database
rect rgba(100,150,255,0.06)
User->>Form: Open form (current patentStatus loaded)
Form->>Helpers: getPatentStatusForSelect()
Helpers-->>Form: [{value,label}, ...]
User->>Form: Select new patent status
Form->>Form: watcher updates state.patentStatus
User->>Form: Submit
Form->>Service: onSubmit({ ..., patentStatus })
Service->>API: PATCH /partner-agreements/:id { patentStatus, ... }
API->>DB: UPDATE partner_agreements SET patent_status=...
DB-->>API: OK
API-->>Service: 200 OK
Service-->>Form: success
Form-->>User: confirmation
end
rect rgba(150,220,150,0.06)
participant Page as AgreementsPage
Page->>API: GET /partner-agreements
API-->>Page: [{ concludedAt, patentStatus, ... }]
Page->>Helpers: getPatentStatus(patentStatus)
Helpers-->>Page: "Russian label"
Page-->>User: Table with concludedAt and patentStatus columns
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (5)
apps/web-app/app/components/AgreementFilesBlock.vue (2)
2-2: Respect reduced-motion preferences for the new animationGate the motion preset so it won’t animate for users who prefer reduced motion.
- <div class="flex flex-row gap-1.5 items-center motion-preset-slide-left"> + <div class="flex flex-row gap-1.5 items-center motion-safe:motion-preset-slide-left">If
motion-safe:isn’t available in your CSS stack, add an equivalent@media (prefers-reduced-motion: reduce)guard.
13-21: Open in new tab: add rel for securityLinks opened with target="_blank" should also set rel="noopener noreferrer" to prevent tabnabbing. Verify ULink sets this automatically when external is true; if not, pass rel explicitly.
apps/web-app/shared/utils/helpers.ts (1)
45-51: Build select options from a single source of truthGenerate options from AGREEMENT_PATENT_STATUSES to avoid literal duplication.
-export function getPatentStatusForSelect(): { value: AgreementPatentStatus, label: string }[] { - return [ - { value: 'in_work', label: 'В работе' }, - { value: 'on_registration', label: 'На регистрации' }, - { value: 'registered', label: 'Зарегистрирован' }, - ] -} +export function getPatentStatusForSelect(): { value: AgreementPatentStatus, label: string }[] { + const map: Record<AgreementPatentStatus, string> = { + in_work: 'В работе', + on_registration: 'На регистрации', + registered: 'Зарегистрирован', + } + return AGREEMENT_PATENT_STATUSES.map((s) => ({ value: s, label: map[s] })) +}If AGREEMENT_PATENT_STATUSES isn’t exported from @roll-stack/database yet, see my types.ts comment.
apps/web-app/shared/services/partner.ts (1)
51-51: UI says “required”, schema says “optional” — pick oneThe Update schema makes patentStatus optional, but the form marks it required. Align them to avoid UX confusion and false validation expectations. I recommend keeping it optional in both places (DB has a default).
apps/web-app/app/components/form/UpdatePartnerAgreement.vue (1)
72-88: “Роспатент” field marked required but schema is optionalDrop the required flag to match updatePartnerAgreementSchema and DB default.
- <UFormField - label="Роспатент" - name="patentStatus" - required - > + <UFormField + label="Роспатент" + name="patentStatus" + >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (8)
apps/web-app/app/components/AgreementFilesBlock.vue(1 hunks)apps/web-app/app/components/UserCard.vue(1 hunks)apps/web-app/app/components/form/UpdatePartnerAgreement.vue(4 hunks)apps/web-app/app/pages/agreement/index.vue(6 hunks)apps/web-app/shared/services/partner.ts(2 hunks)apps/web-app/shared/utils/helpers.ts(2 hunks)packages/database/src/tables.ts(2 hunks)packages/database/src/types.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web-app/shared/utils/helpers.ts (1)
packages/database/src/types.ts (1)
AgreementPatentStatus(21-21)
packages/database/src/tables.ts (1)
packages/database/src/types.ts (1)
AgreementPatentStatus(21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
apps/web-app/app/components/UserCard.vue (1)
41-41: LGTM: helper import and usageImporting getUserTypeLabel and using it in the badge improves readability and consistency.
apps/web-app/shared/utils/helpers.ts (1)
1-1: LGTM: type importImport of AgreementPatentStatus keeps UI helpers aligned with DB types.
apps/web-app/app/pages/agreement/index.vue (3)
221-223: Sorting by string date can be wrong if non-ISO — verifyIf concludedAt isn’t ISO-8601, alphanumeric sort will misorder dates. Consider TanStack’s datetime/custom sorting fn or store concludedAt as ISO.
246-260: Nice: dedicated, sortable “Заключен” columnClean separation from internalId-cell improves readability and sorting UX.
295-298: New “Роспатент” column: ensure store/data is populatedConfirm PartnerAgreementWithAllData includes patentStatus and partnerStore.agreements supplies it after the DB migration; otherwise cells will be empty.
| const patentStatuses = computed(() => getPatentStatusForSelect()) | ||
|
|
||
| const selectedPatentStatus = ref<{ value: AgreementPatentStatus, label: string }>(patentStatuses.value.find((patentStatus) => patentStatus.value === agreement.value?.patentStatus) ?? { value: 'in_work', label: 'Не определено' }) | ||
|
|
||
| watch(selectedPatentStatus, (newValue) => { | ||
| state.value.patentStatus = newValue?.value | ||
| }) |
There was a problem hiding this comment.
Selected option must be one of items (object identity) — avoid custom fallback
Creating a new object for the fallback can break selection rendering in USelectMenu. Use the first item from patentStatuses instead.
-const selectedPatentStatus = ref<{ value: AgreementPatentStatus, label: string }>(patentStatuses.value.find((patentStatus) => patentStatus.value === agreement.value?.patentStatus) ?? { value: 'in_work', label: 'Не определено' })
+const selectedPatentStatus = ref<{ value: AgreementPatentStatus, label: string }>(
+ patentStatuses.value.find((s) => s.value === agreement.value?.patentStatus) ?? patentStatuses.value[0]
+)📝 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.
| const patentStatuses = computed(() => getPatentStatusForSelect()) | |
| const selectedPatentStatus = ref<{ value: AgreementPatentStatus, label: string }>(patentStatuses.value.find((patentStatus) => patentStatus.value === agreement.value?.patentStatus) ?? { value: 'in_work', label: 'Не определено' }) | |
| watch(selectedPatentStatus, (newValue) => { | |
| state.value.patentStatus = newValue?.value | |
| }) | |
| const patentStatuses = computed(() => getPatentStatusForSelect()) | |
| const selectedPatentStatus = ref<{ value: AgreementPatentStatus, label: string }>( | |
| patentStatuses.value.find((s) => s.value === agreement.value?.patentStatus) | |
| ?? patentStatuses.value[0] | |
| ) | |
| watch(selectedPatentStatus, (newValue) => { | |
| state.value.patentStatus = newValue?.value | |
| }) |
🤖 Prompt for AI Agents
In apps/web-app/app/components/form/UpdatePartnerAgreement.vue around lines 154
to 160, the selectedPatentStatus fallback creates a new object literal which
breaks USelectMenu selection identity; change the fallback to use the actual
first item from patentStatuses (e.g. patentStatuses.value.find(...) ??
patentStatuses.value[0]) so the selected option is one of the existing items by
reference, keeping the same typed shape.
| <template #concludedAt-cell="{ row }"> | ||
| {{ format(new Date(row.getValue('concludedAt')), 'd MMMM yyyy', { locale: ru }) }} | ||
| </template> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Guard date formatting against null/invalid values
If concludedAt is missing/invalid, format() will throw. Render a dash instead.
- <template #concludedAt-cell="{ row }">
- {{ format(new Date(row.getValue('concludedAt')), 'd MMMM yyyy', { locale: ru }) }}
- </template>
+ <template #concludedAt-cell="{ row }">
+ <span v-if="row.getValue('concludedAt')">
+ {{ format(new Date(row.getValue('concludedAt')), 'd MMMM yyyy', { locale: ru }) }}
+ </span>
+ <span v-else>-</span>
+ </template>📝 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.
| <template #concludedAt-cell="{ row }"> | |
| {{ format(new Date(row.getValue('concludedAt')), 'd MMMM yyyy', { locale: ru }) }} | |
| </template> | |
| <template #concludedAt-cell="{ row }"> | |
| <span v-if="row.getValue('concludedAt')"> | |
| {{ format(new Date(row.getValue('concludedAt')), 'd MMMM yyyy', { locale: ru }) }} | |
| </span> | |
| <span v-else>-</span> | |
| </template> |
🤖 Prompt for AI Agents
In apps/web-app/app/pages/agreement/index.vue around lines 92 to 94, guard the
date formatting so format() is not called with null/invalid values: check the
concludedAt value (row.getValue('concludedAt')) and if it's missing or not a
valid Date, render a dash (e.g. '—'); otherwise create the Date and call format
with the ru locale. Implement the check using a simple falsy/valid Date test (or
date-fns isValid) and return the dash early to avoid format() throwing.
| <template #patentStatus-cell="{ row }"> | ||
| <div class="text-sm/4 whitespace-pre-wrap max-w-56"> | ||
| {{ getPatentStatus(row.getValue('patentStatus')) }} | ||
| </div> | ||
| </template> |
There was a problem hiding this comment.
Missing import for getPatentStatus
The template calls getPatentStatus but it isn’t imported.
<script setup lang="ts">
import type { DropdownMenuItem, TableColumn } from '@nuxt/ui'
import type { PartnerAgreement } from '@roll-stack/database'
import type { PartnerAgreementWithAllData } from '~/stores/partner'
+import { getPatentStatus } from '~~/shared/utils/helpers'📝 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.
| <template #patentStatus-cell="{ row }"> | |
| <div class="text-sm/4 whitespace-pre-wrap max-w-56"> | |
| {{ getPatentStatus(row.getValue('patentStatus')) }} | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import type { DropdownMenuItem, TableColumn } from '@nuxt/ui' | |
| import type { PartnerAgreement } from '@roll-stack/database' | |
| import type { PartnerAgreementWithAllData } from '~/stores/partner' | |
| import { getPatentStatus } from '~~/shared/utils/helpers' | |
| // ...rest of your script setup code | |
| </script> |
🤖 Prompt for AI Agents
In apps/web-app/app/pages/agreement/index.vue around lines 133 to 137, the
template calls getPatentStatus but it is not imported; add an import for
getPatentStatus at the top of the component script (or <script setup>) from the
module that defines it (e.g., the utility/composable where you implemented it),
ensuring you use the correct named/default import syntax and that the function
is exported from that module; if the helper doesn't exist yet, create and export
getPatentStatus from an appropriate utils/composables file and then import it
into this component so the template can call it without runtime errors.
| const patentStatus = type('"in_work" | "on_registration" | "registered"') | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid type drift: reuse the canonical AgreementPatentStatus union
You're re-declaring the union inline. Import the DB type to keep runtime validation and TS in sync.
+import type { AgreementPatentStatus } from '@roll-stack/database'
...
-const patentStatus = type('"in_work" | "on_registration" | "registered"')
+const patentStatus = type<AgreementPatentStatus>('"in_work" | "on_registration" | "registered"')📝 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.
| const patentStatus = type('"in_work" | "on_registration" | "registered"') | |
| // at the top of the file, alongside your other imports | |
| import type { AgreementPatentStatus } from '@roll-stack/database' | |
| // …later, replace the inline union with the imported type | |
| const patentStatus = type<AgreementPatentStatus>("in_work" | "on_registration" | "registered") |
🤖 Prompt for AI Agents
In apps/web-app/shared/services/partner.ts around lines 38-39, the code
re-declares the patent status union inline as type('"in_work" |
"on_registration" | "registered"'), which causes type drift; replace this inline
declaration by importing the canonical AgreementPatentStatus type/union from the
DB/types module and use that exported union for the runtime validator (or derive
the validator from the imported union), so TypeScript and runtime validation
stay in sync—update the import, swap the inline string-union with the imported
AgreementPatentStatus reference, and adjust any typing/validator creation
accordingly.
| export function getPatentStatus(status: AgreementPatentStatus) { | ||
| switch (status) { | ||
| case 'in_work': | ||
| return 'В работе' | ||
| case 'on_registration': | ||
| return 'На регистрации' | ||
| case 'registered': | ||
| return 'Зарегистрирован' | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add explicit return type and a safe default
At runtime an unexpected value could still slip in; also make the function signature explicit.
-export function getPatentStatus(status: AgreementPatentStatus) {
+export function getPatentStatus(status: AgreementPatentStatus): string {
switch (status) {
case 'in_work':
return 'В работе'
case 'on_registration':
return 'На регистрации'
case 'registered':
return 'Зарегистрирован'
}
+ return '—'
}📝 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.
| export function getPatentStatus(status: AgreementPatentStatus) { | |
| switch (status) { | |
| case 'in_work': | |
| return 'В работе' | |
| case 'on_registration': | |
| return 'На регистрации' | |
| case 'registered': | |
| return 'Зарегистрирован' | |
| } | |
| } | |
| export function getPatentStatus(status: AgreementPatentStatus): string { | |
| switch (status) { | |
| case 'in_work': | |
| return 'В работе' | |
| case 'on_registration': | |
| return 'На регистрации' | |
| case 'registered': | |
| return 'Зарегистрирован' | |
| } | |
| return '—' | |
| } |
🤖 Prompt for AI Agents
In apps/web-app/shared/utils/helpers.ts around lines 34 to 43, the
getPatentStatus function lacks an explicit return type and a safe default
branch; update its signature to declare a return type (e.g., string or a
specific localized string union), and add a default/fallback case (or a final
return) to handle unexpected status values so the function never returns
undefined—for example return a sensible default label like 'Неизвестно' or an
empty string and/or log/throw if you prefer strictness.
| @@ -1,4 +1,4 @@ | |||
| import type { NotificationOption, UserGender, UserType } from './types' | |||
| import type { AgreementPatentStatus, NotificationOption, UserGender, UserType } from './types' | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
Prefer pgEnum for strong DB-level constraints
Import pgEnum; enum types prevent invalid patent_status values at the database level.
-import { boolean, date, integer, jsonb, numeric, pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core'
+import { boolean, date, integer, jsonb, numeric, pgEnum, pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core'📝 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.
| import type { AgreementPatentStatus, NotificationOption, UserGender, UserType } from './types' | |
| import type { AgreementPatentStatus, NotificationOption, UserGender, UserType } from './types' | |
| import { boolean, date, integer, jsonb, numeric, pgEnum, pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core' |
🤖 Prompt for AI Agents
In packages/database/src/tables.ts around line 1, the file currently imports
TypeScript union types but doesn't define DB-level enums; import pgEnum from
'drizzle-orm/pg-core' and create pgEnum values for AgreementPatentStatus,
NotificationOption, UserGender, and UserType (e.g. const
agreementPatentStatusEnum = pgEnum('agreement_patent_status', ['...'])), then
use those enums in your table column definitions instead of plain text/enum
strings so the database enforces valid values; export the enum constants for
reuse and update/emit a migration to create the enums in Postgres.
| patentStatus: varchar('patent_status').notNull().default('in_work').$type<AgreementPatentStatus>(), | ||
| legalEntityId: cuid2('legal_entity_id').references(() => partnerLegalEntities.id), |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Define a PostgreSQL enum for patent_status and use it
Varchar + $type doesn’t enforce allowed values. Use pgEnum to guarantee data integrity and make migrations explicit.
+// Place near other type/const defs
+export const patentStatusEnum = pgEnum('agreement_patent_status', ['in_work', 'on_registration', 'registered'])
+
export const partnerAgreements = pgTable('partner_agreements', {
@@
- patentStatus: varchar('patent_status').notNull().default('in_work').$type<AgreementPatentStatus>(),
+ patentStatus: patentStatusEnum('patent_status').notNull().default('in_work'),Run this to confirm a migration exists that adds the column and (if applied) creates the enum:
If you prefer to avoid enums, at least add a CHECK constraint in the migration.
🏁 Script executed:
#!/bin/bash
# Verify migrations mention the new column / enum
rg -n --hidden -S "(agreement_patent_status|patent_status)" -g "!**/node_modules/**" -g "!**/dist/**"Length of output: 241
Define a Postgres enum for patent_status and add the corresponding migration
The table still uses varchar with a TS‐only type and there’s no migration creating an enum or altering the column, so data‐integrity isn’t enforced. Update your schema and add a migration:
// packages/database/src/tables.ts (near other type defs)
export const patentStatusEnum = pgEnum('agreement_patent_status', ['in_work', 'on_registration', 'registered'])
export const partnerAgreements = pgTable('partner_agreements', {
// …
- patentStatus: varchar('patent_status').notNull().default('in_work').$type<AgreementPatentStatus>(),
+ patentStatus: patentStatusEnum('patent_status').notNull().default('in_work'),
// …
})Then create a migration that:
CREATE TYPE agreement_patent_status AS ENUM ('in_work','on_registration','registered');ALTER TABLE partner_agreements ALTER COLUMN patent_status TYPE agreement_patent_status USING patent_status::agreement_patent_status;
🤖 Prompt for AI Agents
In packages/database/src/tables.ts around lines 130-131 the column patentStatus
is declared as varchar with only a TS type which doesn’t enforce a Postgres
enum; replace it by declaring a pgEnum export (e.g. export const
patentStatusEnum = pgEnum('agreement_patent_status',
['in_work','on_registration','registered'])) and change the partnerAgreements
patentStatus column to use
patentStatusEnum('patent_status').notNull().default('in_work'); then add a DB
migration that runs: CREATE TYPE agreement_patent_status AS ENUM
('in_work','on_registration','registered'); and ALTER TABLE partner_agreements
ALTER COLUMN patent_status TYPE agreement_patent_status USING
patent_status::agreement_patent_status; (and run the migration).
| export type AgreementPatentStatus = 'in_work' | 'on_registration' | 'registered' | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
De-duplicate status literals and make them reusable
Export a canonical list to avoid drift across DB, helpers, and forms. Also derive the type from it.
-export type AgreementPatentStatus = 'in_work' | 'on_registration' | 'registered'
+export const AGREEMENT_PATENT_STATUSES = ['in_work', 'on_registration', 'registered'] as const
+export type AgreementPatentStatus = typeof AGREEMENT_PATENT_STATUSES[number]I can follow up by wiring helpers/selects to consume AGREEMENT_PATENT_STATUSES.
📝 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.
| export type AgreementPatentStatus = 'in_work' | 'on_registration' | 'registered' | |
| export const AGREEMENT_PATENT_STATUSES = ['in_work', 'on_registration', 'registered'] as const | |
| export type AgreementPatentStatus = typeof AGREEMENT_PATENT_STATUSES[number] |
🤖 Prompt for AI Agents
In packages/database/src/types.ts around lines 21-22, the status literals are
duplicated; export a single canonical readonly array/tuple (e.g.
AGREEMENT_PATENT_STATUSES) containing 'in_work', 'on_registration', 'registered'
and then derive the AgreementPatentStatus type from that array
(AgreementPatentStatus = typeof AGREEMENT_PATENT_STATUSES[number]); export both
so helpers/forms/DB can import the canonical list. Ensure the array is readonly
so the derived type stays literal.
|


Summary by CodeRabbit
New Features
Bug Fixes
Style