Entities are the core business units of the project. This section describes the unified structure of any entity based on strict architectural rules.
Each business entity inside src/entities/* must follow a strict directory structure for logic and UI isolation.
A complete entity consists of these required modules:
RTK Query endpoints for the entity. Converters run inside the API layer: transformResponse maps backend T*Backend to frontend models, and query maps params/body via map*ToBackend. Hooks already return domain models — UI must not re-map DTOs.
1import { baseApi } from "@/shared/api/backend";
2import type { IPaginationRequest, IPaginationResponse } from "@/shared/types";
3import {
4 mapEntitiesQueryToBackend,
5 mapEntitiesToFrontend
6} from "../converters";
7import type { TEntitiesBackend, IEntity } from "../types";
8
9/** Converters run here — hooks return frontend models, not raw DTOs */
10export const entityApi = baseApi.injectEndpoints({
11 endpoints: (builder) => ({
12 getEntities: builder.query<
13 IPaginationResponse<IEntity>,
14 IPaginationRequest
15 >({
16 query: (filters) => ({
17 url: "/entities",
18 params: mapEntitiesQueryToBackend(filters)
19 }),
20 transformResponse: (response: TEntitiesBackend) =>
21 mapEntitiesToFrontend(response),
22 providesTags: [ENUM_API_TAGS.ENTITIES]
23 })
24 })
25});Data mappers. They transform a raw server DTO into a convenient Domain Model. This isolates UI components from sudden changes in backend contracts.
1export const mapEntityToFrontend = (data: TEntityBackend): IEntity => ({
2 id: data.id,
3 type: data.type as ENUM_ENTITY_TYPE,
4 dateCreated: formatDate(data.date_created),
5 client: data.client,
6 status: data.status as ENUM_ENTITY_STATUS
7});
8
9export const mapEntitiesToFrontend = (
10 response: TEntitiesBackend
11): IPaginationResponse<IEntity> => ({
12 data: response.items.map(mapEntityToFrontend),
13 total: response.total_count
14});
15
16export const mapEntityToBackend = (
17 data: Partial<IEntityDetail>
18): Partial<TEntityDetailBackend> => ({
19 id: data.id,
20 type: data.type,
21 date_created: data.dateCreated,
22 client: data.client,
23 status: data.status
24});
25MSW (Mock Service Worker) handlers. Used to intercept network calls during development and in integration tests (Vitest / RTL).
1import { HttpResponse } from "msw";
2import { createMockHandler } from "@/shared/api/msw/utils";
3import { listEntitiesBackend } from "../mock";
4
5/** Backend DTO responses — converters run in RTK transformResponse */
6export const entityHandlers = [
7 createMockHandler(
8 { url: "/entities", method: "GET", _types: { body: undefined } },
9 () => HttpResponse.json(listEntitiesBackend())
10 ),
11 createMockHandler(
12 { url: "/entities/:id", method: "GET", _types: { body: undefined } },
13 ({ params }) => {
14 const { items } = listEntitiesBackend();
15 const entity = items.find((e) => e.id === params.id);
16 if (!entity) return new HttpResponse(null, { status: 404 });
17 return HttpResponse.json(entity);
18 }
19 )
20];Static fixtures and fake data (mock data) used to emulate server responses in conjunction with MSW handlers or for Storybook.
1import type { TEntityBackend } from "../types";
2
3export const ENTITIES_MOCK: TEntityBackend[] = [
4 {
5 id: "ENT-001",
6 type: "individual",
7 date_created: "2024-03-15T12:00:00Z",
8 client: "John Doe",
9 pax: 2,
10 status_code: "CONFIRMED"
11 }
12];Single Source of Truth for entity data types. Contains Zod objects with built-in internationalization (i18n) of errors.
1import { z } from "zod";
2import { i18nKey } from "@/shared/config";
3// прямой путь к form.types — не barrel "../types"
4import { ENUM_FORM_ENTITY_CREATE } from "../types/entity-create-form.types";
5
6const msg = i18nKey<TEntityKeys>();
7const F = ENUM_FORM_ENTITY_CREATE;
8
9/** Runtime only — inferred form types belong in types/ (одна пара на операцию) */
10export const ENTITY_CREATE_SCHEMA = z.object({
11 [F.CLIENT]: z.string().min(1, { message: msg("errors.client.required") }),
12 [F.STATUS]: z.enum(["PENDING", "CONFIRMED"]),
13 [F.PAX]: z.number().min(1)
14});1import type { FieldValues } from "react-hook-form";
2import { z } from "zod";
3// только import type — в рантайме зависимость schema → form.types односторонняя
4import type { ENTITY_CREATE_SCHEMA } from "../schema";
5
6export const ENUM_FORM_ENTITY_CREATE = {
7 CLIENT: "client",
8 STATUS: "status",
9 PAX: "pax"
10} as const;
11
12export type ENUM_FORM_ENTITY_CREATE_TYPE =
13 (typeof ENUM_FORM_ENTITY_CREATE)[keyof typeof ENUM_FORM_ENTITY_CREATE];
14
15export type TEntityCreateSchema = z.infer<typeof ENTITY_CREATE_SCHEMA> &
16 FieldValues;Redux state of the entity (if it stores global data). Selectors and actions often reside here as well. Note: state should depend solely on the Domain Model, not on DTOs.
1import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2
3interface IEntitySliceState {
4 selectedId: string | null;
5}
6
7const initialState: IEntitySliceState = {
8 selectedId: null
9};
10
11export const entitySlice = createSlice({
12 name: "entity",
13 initialState,
14 reducers: {
15 setSelectedEntity: (state, action: PayloadAction<string>) => {
16 state.selectedId = action.payload;
17 }
18 }
19});Dump or Smart components at the entity level. For example: UserCard, UserAvatar. Can be reused across different features and widgets.
1interface IEntityCardProps {
2 entity: IEntity;
3 onSelect: (id: string) => void;
4}
5
6export const EntityCard = ({ entity, onSelect }: IEntityCardProps) => (
7 <Card onClick={() => onSelect(entity.id)}>
8 <CardHeader>{entity.client}</CardHeader>
9 <CardContent>Pax: {entity.pax}</CardContent>
10 </Card>
11);Global TS interfaces and types for a specific entity (e.g., Domain Model interface if Zod doesn't cover all logic). Type inference from Zod schemas is recommended, but when not possible, types are defined here.
1import type { IPaginationRequest, IPaginationResponse } from "@/shared/types";
2
3export interface IEntityDates {
4 from: string;
5 to: string;
6}
7
8export interface IEntity {
9 id: string;
10 type: ENUM_ENTITY_TYPE;
11 dateCreated: string;
12 client: string;
13 pax: number;
14 dates: IEntityDates;
15 status: ENUM_ENTITY_STATUS;
16}
17
18export type TEntitiesPage = IPaginationResponse<IEntity>;
19export interface IEntitiesQuery extends IPaginationRequest {}
20