Skip to content

Design: Car Modification — 4th Level of Car Hierarchy

Date: 2026-02-14 Status: Approved

Problem

Backend added a 4th level to the car reference hierarchy — Modifications (30,066 records with engine/transmission specs). The frontend currently supports only 3 levels: Make → Model → Generation. All cascade selectors, schemas, admin CRUD, and catalog filters need to be extended to support the new level.

Decisions

  • modification_id is optional (nullable) everywhere — users are never required to select a modification
  • Modification dropdown label uses name field as-is from backend (already formatted: "1.8L 5MT FWD (90 HP)")
  • Homepage hero: 4 dropdowns in a row (ужать), expand max-width
  • Admin references: 4-column grid
  • Admin CRUD: full create/update/delete for modifications

Data Model

New Entity: CarModification

entities/car/model/car.schema.ts

CarModification {
  id: number
  generation_id: number
  name: string                      // "1.8L 5MT FWD (90 HP)"
  engine_volume: string | null      // "1.8", "2.0"
  fuel_type: FuelType | null        // petrol|diesel|hybrid|electric|gas
  power: number | null              // HP
  transmission: Transmission | null // mt|at|cvt|amt|robot
  drivetrain: Drivetrain | null     // fwd|rwd|awd|4wd
}

New enum schemas: fuelTypeSchema, transmissionSchema, drivetrainSchema. Update carSteeringSchema to add 'universal' value.

Compatibility Extension

entities/product/model/product.schema.ts

compatibilitySchema += modification_id: z.number().nullable()

API Endpoint

GET /store/cars/generations/{generationId}/modifications → { data: CarModification[] }

Admin CRUD:

POST   /admin/cars/modifications
PUT    /admin/cars/modifications/{id}
DELETE /admin/cars/modifications/{id}

Cascade Logic

useYmmCascade.ts (public cascade)

  • New refs: modifications, selectedModificationId, loadingModifications
  • fetchModifications(generationId) → GET endpoint
  • YmmSelection interface += modification_id
  • Watcher: watch(selectedGenerationId) → fetch modifications or clear
  • All upstream watchers clear modifications downstream
  • hydrate() handles modification_id
  • reset() clears modifications

useAdminCars.ts (admin CRUD)

  • New refs: modifications, selectedGenerationId
  • fetchModifications(generationId)
  • Watcher: watch(selectedGenerationId) → fetch/clear
  • selectedModelId watcher also clears modifications + selectedGenerationId
  • CRUD: createModification, updateModification, deleteModification

useProductForm.ts

No changes needed. Code is generic — compatibility array passes through, buildRequestBody() filters by make_id > 0. New modification_id flows automatically via updated Zod type.

UI Changes

YmmSelect.vue (single cascade row)

4th USelectMenu for modification:

  • Props: modifications[], loadingModifications
  • Model: modificationId
  • Disabled until generationId selected
  • Label: modification.name (as-is)

YmmMultiSelect.vue (multi-row in product form)

  • addRow() creates entry with modification_id: null
  • updateRow() accepts 'modification_id' field
  • Passes modifications data + events to YmmSelect

Homepage Hero (pages/index.vue)

  • 4 dropdowns + "Подобрать" button in single row
  • max-w-3xlmax-w-4xl
  • Add modification USelectMenu (disabled without generationId)
  • submitYmm() includes modification_id in query

Catalog Sidebar (pages/catalog/index.vue)

  • Add missing Generation filter (v-if selectedModelId)
  • Add Modification filter (v-if selectedGenerationId)
  • filters.modification_id in reactive state
  • Cascade reset downstream on upper-level change
  • Hydration from URL query params

Admin References (pages/admin/references.vue)

  • Grid: md:grid-cols-3md:grid-cols-4
  • Generations ReferenceList becomes selectable (selectedGenerationId)
  • 4th ReferenceList for modifications
  • modificationFields with select options for fuel_type, transmission, drivetrain

Product Detail Pages

No changes. Compatibility displays via compat.note (backend formats text). Fallback is dev-only.

i18n (ru.json)

json
ymm: {
  + "modification": "Модификация",
  + "selectModification": "Выберите модификацию"
}

car: {
  "fuelType": "Тип топлива",
  "fuelPetrol": "Бензин", "fuelDiesel": "Дизель", "fuelHybrid": "Гибрид",
  "fuelElectric": "Электро", "fuelGas": "Газ",
  "transmission": "КПП",
  "transmissionMt": "МКПП", "transmissionAt": "АКПП", "transmissionCvt": "Вариатор",
  "transmissionAmt": "АМТ", "transmissionRobot": "Робот",
  "drivetrain": "Привод",
  "drivetrainFwd": "Передний", "drivetrainRwd": "Задний",
  "drivetrainAwd": "Полный (AWD)", "drivetrain4wd": "Полный (4WD)",
  "engineVolume": "Объём двигателя", "power": "Мощность"
}

admin: {
  + "modifications": "Модификации"
}

Files to Modify

#FileChange
1app/entities/car/model/car.schema.tsAdd CarModification + enums
2app/entities/product/model/product.schema.tsAdd modification_id to compatibility
3i18n/locales/ru.jsonNew i18n keys
4app/features/ymm-select/composables/useYmmCascade.ts4th cascade level
5app/features/admin-references/composables/useAdminCars.tsAdmin CRUD + cascade
6app/features/ymm-select/ui/YmmSelect.vue4th dropdown
7app/features/ymm-select/ui/YmmMultiSelect.vuePass modification data
8app/pages/index.vueHero 4-dropdown layout
9app/pages/catalog/index.vueGeneration + modification filters
10app/pages/admin/references.vue4-column grid + modification CRUD
11tests/entities/product/model/product.schema.test.tsUpdate compatibility tests
12app/features/admin-references/composables/useAdminCars.test.tsAdd modification CRUD tests
13CLAUDE.mdDocumentation update

Testing

  1. npm run typecheck — no TypeScript errors
  2. npm run test:run — all tests pass
  3. npm run lint — no ESLint/FSD violations
  4. Manual verification on dev server: hero, catalog, product form, admin references