---
description: Code patterns and examples for each file inside a dashboard module (except hooks — see module-architecture-hooks.md). Consult when you need to know how to implement services.ts, queries.ts, schemas.ts, routes.ts, etc.
paths:
  - "src/pages/dashboard/**"
---

# Module Architecture — File Conventions

Implementation detail for each module file.
For the overall structure, see `module-architecture.md`.
For invariants (rules, principles), see `module-architecture-conventions.md`.
For hooks, see `module-architecture-hooks.md`.

---

## `index.tsx` (Optional layout wrapper)

A module **may** have an `index.tsx` at its root as a layout wrapper that wraps all pages
with shared providers, guards, react-helmet, etc. It uses `<Outlet />` from `react-router-dom`
so child pages render inside it. It is NOT a barrel re-exporter.

Most modules don't need this — each page is independent. Use it when the module needs to
control something across all its pages from a single place.

```typescript
// Goals/index.tsx — wraps all Goals pages
import { Outlet } from 'react-router-dom';

const GoalsLayout = () => (
  <GoalsProvider>
    <Outlet />
  </GoalsProvider>
);

export default GoalsLayout;
```

When present, wire it in `routes.ts` as the parent route (see `src/components/DashboardLayout/index.tsx` for reference):

```typescript
const GoalsLayout = lazy(() => import('.'));
const MyGoals = lazy(() => import('./MyGoals'));

export const goalsRouteConfig = [
  {
    path: goalsRoutes.root(),
    element: <GoalsLayout />,
    children: [
      { index: true, element: <MyGoals /> },
      { path: ':id', element: <GoalDetail /> },
    ],
  },
];
```

**Do NOT use `index.ts`** (TypeScript only, no JSX) as a barrel re-exporter. Barrel exports provide little value and can break tree-shaking given how screens are structured in this codebase. Import directly from the specific file you need:

```typescript
// ✅ Correct
import { goalsRoutes } from 'src/pages/dashboard/Goals/routes';
import type { Goal } from 'src/pages/dashboard/Goals/types';

// ❌ Avoid — no index.ts barrels
import { goalsRoutes } from 'src/pages/dashboard/Goals';
```

---

## `routes.ts`

Defines the module's routes: **path factories** and **route configuration with lazy loading per page**.
Each page loads independently — the user only downloads the JS for the page they visit.

```typescript
import { lazy } from 'react';

// --- Path factories ---
export const goalsRoutes = {
  root: () => '/goals',
  detail: (id: number | string) => `/goals/${id}`,
  create: () => '/goals/new',
  edit: (id: number | string) => `/goals/${id}/edit`,
};

// --- Lazy loaded pages (each page = an independent Vite chunk) ---
const Goals = lazy(() => import('./Goals'));
const NewGoal = lazy(() => import('./NewGoal'));
const GoalDetail = lazy(() => import('./GoalDetail'));

// --- Route config (consumed by src/routes.tsx) ---
export const goalsRouteConfig = [
  { path: goalsRoutes.root(), element: <Goals /> },
  { path: goalsRoutes.create(), element: <NewGoal /> },
  { path: goalsRoutes.detail(':id'), element: <GoalDetail /> },
];
```

**Rule:** Each page's `index.tsx` MUST have `export default` for
`lazy(() => import(...))` to work. Without a default export, the dynamic import won't resolve the component.

---

## `services.ts` or `services/`

Contains ALL API calls for the module. Each function uses `src/config/api`.

```typescript
import api from 'src/config/api';

export const getItems = (params: GetItemsParams) =>
  api.get<Pagination<Item[]>>('/items', { params });

export const getItemById = (id: number) =>
  api.get<ItemDetail>(`/items/${id}`);

export const createItem = (data: CreateItemPayload) =>
  api.post('/items', data);

export const updateItem = (id: number, data: UpdateItemPayload) =>
  api.put(`/items/${id}`, data);

export const deleteItem = (id: number) =>
  api.delete(`/items/${id}`);
```

**IMPORTANT:** Always use `import api from 'src/config/api'` — it is the axios instance
configured with interceptors, base URL, and headers. Do NOT use `src/utils/axios`.

---

## `queries.ts`

Query key factory for react-query:

```typescript
export const goalsKeys = {
  all: () => ['Goals'] as const,
  lists: () => [...goalsKeys.all(), 'list'] as const,
  list: (params?: unknown) => [...goalsKeys.lists(), params] as const,
  details: () => [...goalsKeys.all(), 'detail'] as const,
  detail: (id: number | string) => [...goalsKeys.details(), id] as const,
};
```

---

## `types.ts` or `types/`

Use `types.ts` (single file) for modules with few types.
Use `types/` (folder) when there are clearly separable domains:

```
types/
├── entities.ts       # Domain entity types (Item, ItemDetail, etc.)
├── api.ts            # API request/response types
└── forms.ts          # Form types
```

Import directly from the sub-file (`import type { Item } from '../types/entities'`).
**No `types/index.ts` barrel.**

---

## `utils.ts` or `utils/`

Pure utility functions **exclusive** to the module. If a util is used by 2+ modules,
move it to `src/utils/`. Split into a `utils/` folder when there are clearly separable
responsibilities — import directly from the sub-file, no barrel.

---

## `schemas.ts`

Validation schemas live **inside the Form folder** that uses them, not at the page or module root:

```
{PageName}/
└── forms/
    └── {FormName}/
        └── schemas.ts    ← schema lives here, next to the form that uses it
```

If a schema is shared across multiple forms within the same module, place it in a shared folder:

```
{PageName}/
└── forms/
    └── shared/
        └── schemas.ts
```

For zod patterns and React Hook Form integration, see the `forms.md` rule.

---

## `components/`

**Always create a `components/` folder** for shared components within the module (used by 2+ pages of the same module). Single-page components go in `{PageName}/components/` — also always use a `components/` subfolder rather than putting component files directly in the page folder.

---

## `{PageName}/` (page subfolders)

Each significant view/page of the module has its own folder with `index.tsx` as the entry point.
Inside it can have its own `components/`, `hooks/`, `schemas.ts`, `types.ts`, `utils.ts`.

```typescript
// Goals/index.tsx
const Goals = () => {
  // ...
};

export default Goals; // export default required for lazy()
```

---

## Full example: `Goals` module (migrated)

```
src/pages/dashboard/Goals/
├── README.md
├── routes.ts                    # Path factories + lazy loading per page
├── services.ts                  # (migrated from src/services/goals.ts)
├── queries.ts
├── types.ts                     # (migrated from src/types/goals.ts)
├── constants.ts
├── utils.ts
│
├── hooks/
│   ├── useGetGoals.ts
│   ├── useGetGoalDetail.ts
│   ├── useCreateGoalMutation.ts
│   ├── useGoalFeaturesConfig.ts
│   └── useSubordinates.ts
│
├── components/
│   ├── GoalCard.tsx
│   ├── GoalWeightsDrawer/
│   │   ├── index.tsx
│   │   ├── hooks/
│   │   └── utils.ts
│   └── ResponsibleAutocomplete/
│
├── MyGoals/
│   ├── index.tsx
│   └── components/
├── TeamGoals/
│   ├── index.tsx
│   └── components/
├── NewGoal/
│   ├── index.tsx
│   ├── components/
│   ├── schemas.ts
│   └── types.ts
└── GoalDetail/
    ├── index.tsx
    ├── components/
    │   ├── ProgressSection.tsx
    │   └── HistoryTab.tsx
    └── hooks/
```
