Перейти до змісту

Frontend DOP — Документація імплементації

Локація: c:\eswf\frontend\erp\ Домен: erp.eswf.dev Дата початку: 2026-02-17


1. Планові етапи імплементації

Етап Назва Статус
1 Архітектура & Setup ✅ Завершено
2 Auth (Login / JWT refresh) ✅ Завершено
3 MasterData — список + форма ✅ Завершено
4 TransactionData — список + форма ✅ Завершено
5 Journals & Ledgers — перегляд ✅ Завершено
6 Reports — placeholder + charts ✅ Завершено
7 Applications — App Store сторінка ✅ Завершено
8 Tabbed navigation (відкриті вкладки) ✅ Завершено
9 i18n — повний переклад всіх рядків ✅ Завершено
10 Полірування & Production build ⏳ Заплановано

2. Етап 1 — Архітектура & Setup (завершено)

Що реалізовано і навіщо


2.1 Scaffold Vite + React 19 + TypeScript

npm create vite@latest . -- --template react-ts

Навіщо: Vite забезпечує миттєвий HMR і швидкий production-build. React 19 — актуальна версія, сумісна з Mantine v7 та React Router v7. TypeScript обов'язковий для config-driven архітектури (типізовані секції/групи/items).

Видалено boilerplate: App.css, index.css, assets/react.svg.


2.2 Встановлення залежностей

Runtime:

@mantine/core @mantine/hooks @mantine/notifications @mantine/charts
@tabler/icons-react
zustand
@tanstack/react-query
react-router-dom
axios
i18next react-i18next
recharts

Dev:

@types/node
postcss postcss-preset-mantine postcss-simple-vars

Навіщо кожна: - @mantine/* — UI-компоненти (AppShell, NavLink, Card, etc.), повністю підходить для enterprise DOP-інтерфейсу - @tabler/icons-react — використовується у всіх config-файлах (icon: "CarOutlined"IconCar) - zustand — легкий state manager для auth, UI-налаштувань, відкритих вкладок (не потребує Provider-обгортки) - @tanstack/react-query — кешування запитів до API, фонові refetch-и, error/loading стани - react-router-dom — routing між секціями/групами/items DOP - axios — HTTP-клієнт з interceptors для JWT - i18next — інтернаціоналізація EN/UA з persistence в localStorage - recharts — графіки для Mantine Charts (бекенд пакету) - postcss-preset-mantine — обов'язковий для CSS-змінних Mantine (breakpoints, кольори)


2.3 Конфігурація Vite (vite.config.ts)

server: { port: 5173 }                  // відокремлено від News (5174)
resolve.alias: { '@': './src' }         // абсолютні імпорти @/components/...
css.postcss: './postcss.config.cjs'     // Mantine CSS-змінні
proxy: { '/api': 'http://localhost:8000' } // проксі на Django backend

Навіщо: Єдиний порт для всього DOP (5173). Проксі дозволяє не налаштовувати CORS у dev-режимі — всі запити /api/* прозоро йдуть до Django.

tsconfig.app.json доповнено: - baseUrl: "." + paths: { "@/*": ["src/*"] } — підтримка аліасу @/ у TypeScript - Видалено erasableSyntaxOnly: true — забороняло enum, який використовується в config-файлах


2.4 PostCSS (postcss.config.cjs)

plugins: {
  'postcss-preset-mantine': {},
  'postcss-simple-vars': { variables: { 'mantine-breakpoint-xs': '36em', ... } }
}

Навіщо: Mantine v7 вимагає PostCSS-плагіни для обробки CSS-змінних breakpoints. Без цього стилі AppShell (collapse sidebar) не працюватимуть коректно.


2.5 Config: src/config/itemTypes.ts

export enum ItemType {
  MASTERDATA = 'MASTERDATA',
  TRANSACTIONDATA = 'TRANSACTIONDATA',
  JOURNAL = 'JOURNAL',
  LEDGER = 'LEDGER',
  REPORT = 'REPORT',
  PROCESS = 'PROCESS',
  APPLICATION = 'APPLICATION',
}

Навіщо: Центральний enum для класифікації всіх елементів DOP-навігації. Визначає який React-компонент рендерити для кожного item (MasterDataList, TransactionList, JournalViewer, etc.).


2.6 Config: src/config/types.ts

TypeScript-інтерфейси для всієї ієрархії конфігурації:

Section → Group → Subgroup → SectionItem

Основні типи: - Section — модуль DOP (Essentials, Fleet, Logistic, Registers, Applications) - Group — група документів всередині модуля (Master Data, Transactions, Journals, Reports) - Subgroup — підгрупа (Goods Accounting, Cash Accounting, etc.) - SectionItem — конкретний елемент (Items, Vehicles, Waybills, etc.) з полями componentPath, componentProps - Subtable — вбудована таблиця в формі (OdometerReadings, RouteWaypoints, etc.) - Props-інтерфейси: MasterDataListProps, TransactionListProps, JournalViewerProps, LedgerViewerProps, ReportViewProps

Навіщо: Без типізації неможливо безпечно ітерувати конфіг і рендерити динамічні компоненти. Типи забезпечують autocomplete при розробці нових секцій.


2.7 Config-файли (src/config/essentials.ts і т.д.)

Скопійовано з docs/ у src/config/: - essentials.ts — 22+ items (Items, Units, Warehouses, Invoices, etc.) - fleet.ts — 13+ items (Vehicles, Drivers, Routes, Waybills, etc.) - logistic.ts — Logistic Orders, Route Sheets, Plan vs Fact - registers.ts — Cash/Inventory Ledgers & Journals - applications.ts — App Store з 16 додатками

Файли вже були розроблені з коментарем // frontend/src/config/... зверху — вони одразу призначались для цієї локації.

src/config/index.ts — barrel export:

export const sections: Section[] = [
  essentialsSection, fleetSection, logisticSection, registersSection, applicationsSection
];

Навіщо: Єдина точка імпорту всіх секцій. Sidebar і Dashboard ітерують саме цей масив.


2.8 Zustand Stores (src/store/)

authStore.ts

{ user, accessToken, refreshToken, isAuthenticated, login(), logout(), setTokens() }
- persist middleware → зберігається в localStorage (eswf-auth) - login() — зберігає user + tokens, ставить isAuthenticated: true - logout() — очищає все

uiStore.ts

{ colorScheme, language, sidebarOpened, toggleColorScheme(), setLanguage(), toggleSidebar() }
- persist middleware → зберігає тему і мову між сесіями - colorScheme → передається в MantineProvider

tabsStore.ts

{ tabs, activeTabId, openTab(), closeTab(), setActiveTab() }
- Без persist (tabs скидаються при перезавантаженні) - openTab() → якщо tab вже відкрита — просто активує її (не дублює)

Навіщо Zustand замість Redux/Context: Мінімум boilerplate, підтримка persist з коробки, доступ до стану поза React (useAuthStore.getState() в axios interceptor).


2.9 API Client (src/api/client.ts)

const apiClient = axios.create({ baseURL: '/api/v1' });

// Request: додає Authorization: Bearer <token>
apiClient.interceptors.request.use(config => {
  const token = useAuthStore.getState().accessToken;
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// Response: на 401 → logout + redirect /login
apiClient.interceptors.response.use(ok => ok, err => {
  if (err.response?.status === 401) {
    useAuthStore.getState().logout();
    window.location.href = '/login';
  }
  return Promise.reject(err);
});

Навіщо: Централізована обробка JWT. Всі компоненти імпортують apiClient замість голого axios — не потрібно в кожному місці додавати header вручну. Автоматичний logout на expired token.


2.10 i18n (src/i18n/)

locales/en.json  — англійські рядки
locales/ua.json  — українські рядки
index.ts         — ініціалізація i18next з читанням мови з localStorage

Ключі: auth.*, common.*, ui.*, nav.*

Навіщо: Інтерфейс двомовний (EN/UA) згідно з вимогами проєкту. Мова зберігається в uiStore (persist) і синхронізується з i18next при перемиканні в Header.

Примітка: Назви секцій/items беруться напряму з config (name / name_ua) — не через i18n. i18n покриває лише системні рядки UI (кнопки, лейбли, повідомлення).


2.11 Layout Components (src/components/Layout/)

AppLayout.tsx - Обгортає весь DOP в Mantine AppShell - Header: висота 56px - Navbar: ширина 260px, collapsible на mobile і desktop - <Outlet /> — React Router рендерить поточну сторінку

Header.tsx - Burger (toggle sidebar) - Логотип "ESWF DOP" - Кнопка EN/UA (перемикає i18n + uiStore) - Кнопка dark/light theme (перемикає MantineProvider + uiStore) - User menu (ім'я, Logout)

Sidebar.tsx - Ітерує sections з config - Показує лише showInSidebar: true секції та групи - Активний стан визначається за location.pathname - 3-рівнева навігація: Section → Group → Item

SectionDashboardPage.tsx - Огляд секції: заголовок + опис + картки всіх items - Кожна картка: badge з типом (MASTERDATA, REPORT, etc.), назва, опис - Адаптивна сітка: 1/2/3 колонки

Навіщо окремі файли: Мantine AppShell вимагає явного розділення Header/Navbar/Main. Компоненти будуть рости (Header отримає Search, Notifications; Sidebar — drag-and-drop для вкладок).


2.12 Pages (src/pages/)

LoginPage.tsx — форма входу (username/password), POST /api/v1/token/, зберігає токени через authStore.login()

DashboardPage.tsx — головна сторінка: картки всіх активних секцій з кількістю items

SectionPage.tsx — знаходить секцію за sectionCode з URL параметра → рендерить SectionDashboardPage

NotFoundPage.tsx — 404 з кнопкою повернення


2.13 Routing (App.tsx)

/login                              → LoginPage
/                                   → redirect → /essentials
/:sectionCode                       → SectionPage (essentials, fleet, logistic...)
/:sectionCode/:groupCode            → SectionPage (той самий, але для навігації)
/:sectionCode/:groupCode/:itemCode  → SectionPage (placeholder до реального CRUD)
/dashboard                          → DashboardPage
*                                   → NotFoundPage

Всі DOP-маршрути захищені ProtectedRoute (redirect → /login якщо не авторизовано).

MantineProvider + QueryClientProvider + BrowserRouter — всі на верхньому рівні App.tsx.


2.14 launcher.js — додано DOP

{
  name: "DOP",
  cwd: path.join(ROOT, "frontend", "erp"),
  command: "npm.cmd",
  args: ["run", "dev"],
  url: "http://localhost:5173",
  color: "\x1b[34m", // blue
}

Навіщо: DOP тепер стартує разом з усіма сервісами командою node launcher.js з кореня проєкту.


3. Дальніші плановані кроки


Етап 2 — Auth (повна авторизація)

Що потрібно зробити:

  • [ ] POST /api/v1/token/ — login з username/password (вже частково є в LoginPage)
  • [ ] GET /api/v1/auth/me/ — завантаження CurrentUser після логіну
  • [ ] POST /api/v1/token/refresh/ — автоматичний refresh access-токена (axios interceptor на 401)
  • [ ] POST /api/v1/auth/logout/ — blacklist refresh-токена на бекенді
  • [ ] useCurrentUser hook (React Query) — завантаження профілю
  • [ ] Сторінка профілю (Settings)

Файли для зміни: src/api/client.ts, src/store/authStore.ts, src/pages/LoginPage.tsx


Етап 3 — MasterData (список + форма) ✅ Завершено

Реалізовано: - [x] src/api/entities.tsentitiesApi (list/detail/fields/create/update/remove) - [x] src/hooks/useEntityList.ts — React Query, keepPreviousData - [x] src/hooks/useEntityDetail.ts — для форми редагування - [x] src/hooks/useEntityFields.ts — OPTIONS → field definitions (тип, label, required, choices) - [x] src/components/MasterData/MasterDataList.tsx — таблиця + пошук + пагінація + delete confirm - [x] src/components/MasterData/MasterDataForm.tsx — @mantine/form, dynamic fields від OPTIONS - [x] src/components/MasterData/MasterDataPage.tsx — router: list vs form за :recordId - [x] src/pages/SectionPage.tsx — знаходить item у конфізі, рендерить MasterDataPage для MASTERDATA - [x] src/App.tsx — маршрут /:sectionCode/:groupCode/:itemCode/:recordId - [x] i18n — додано ключі masterdata.* та common.refresh - [x] @mantine/form встановлено

Маршрутизація:

/:sectionCode/:groupCode/:itemCode         → MasterDataList (список)
/:sectionCode/:groupCode/:itemCode/new     → MasterDataForm (новий запис)
/:sectionCode/:groupCode/:itemCode/:id     → MasterDataForm (редагування)

Динамічна форма: - Поля отримуються через OPTIONS /api/v1/{module}/{entityCode}/actions.POST - Рендеринг: TextInput / Textarea / NumberInput / Switch / Select по типу поля - Булеві поля відображаються окремим рядком знизу

Що не реалізовано (для наступних етапів): - Підтримка hierarchy: true — деревоподібний список (TableTree) - Subtables у формі — вкладені таблиці - Інтеграція з tabsStore (Етап 8)


Етап 4 — TransactionData (список + форма) ✅ Завершено

Компоненти:

src/components/TransactionData/
├── TransactionList.tsx       ← таблиця з фільтром по статусу
├── TransactionForm.tsx       ← форма документа (з header + lines)
└── TransactionPage.tsx

Реалізовано: - [x] src/api/entities.tsaction() метод + гнучкий ListParams (підтримка status та інших фільтрів) - [x] TransactionList.tsx — таблиця з пошуком, фільтром по статусу (Select), пагінацією, підтвердженням видалення - [x] TransactionForm.tsx — динамічна форма (header fields від OPTIONS), статус-badge + workflow кнопки - [x] Status workflow: draft → approved → posted → cancelled (через PATCH) - [x] Кнопки дій: "Узгодити" (approved), "Провести" (posted), "Скасувати" (cancelled) - [x] "Копіювати" — клонує документ зі статусом draft - [x] "Друк" — placeholder (показує notification) - [x] Subtable lines — для invoicesinvoicelines (перегляд, додавання, видалення рядків) - [x] TransactionPage.tsx — роутер list vs form за :recordId - [x] SectionPage.tsxTRANSACTIONDATA кейс - [x] i18n — ключі transaction.* в en.json та ua.json

Маршрутизація:

/:sectionCode/:groupCode/:itemCode         → TransactionList (список)
/:sectionCode/:groupCode/:itemCode/new     → TransactionForm (новий)
/:sectionCode/:groupCode/:itemCode/:id     → TransactionForm (редагування)

Subtable логіка: - LINES_CONFIG map: invoices{ linesCode: 'invoicelines', parentField: 'invoice' } - Лінії завантажуються з record.lines (nested у відповіді API) - Додавання рядка: POST /essentials/invoicelines/ з {invoice: id, ...fields} - Видалення рядка: DELETE /essentials/invoicelines/{lineId}/ - Поля для форми рядка: від OPTIONS /essentials/invoicelines/

Що не реалізовано (для наступних етапів): - Реальні друковані форми (PDF) - Inline редагування рядків (зараз тільки add/delete) - Subtable для Fleet (Waybill lines, Route waypoints) - Інтеграція з tabsStore (Етап 8)

Пріоритетні: Invoices ✅, IncomingPayments ✅, DocumentOperations ✅


Етап 5 — Journals & Ledgers ✅ Завершено

Компоненти:

src/components/Accounting/
├── JournalViewer.tsx         ← хронологічний журнал (тільки читання)
└── LedgerViewer.tsx          ← регістр з фільтрами по датах/рахунках

Реалізовано: - [x] JournalViewer.tsx — читання журналу (CashJournal, InventoryJournal) - Пошук по тексту - Фільтрація по датах (date__gte / date__lte) через два TextInput type="date" - Badge для operation_type (income=green / expense=red) - Динамічні колонки з пріоритетом (date, number, operation_type, cashbox, client, amount...) - Пагінація (30 записів на сторінку) - Експорт CSV (BOM UTF-8 для Excel) — поточна сторінка - Read-only (без Edit/Delete) - [x] LedgerViewer.tsx — перегляд леджера (CashLedger, InventoryLedger) - Аналогічна фільтрація по датах та пошук - Числові колонки (debit, credit, balance, quantity, amount) — right-align + monospace - Totals summary cards — сума по кожній числовій колонці на поточній сторінці - Динамічні колонки з пріоритетом (date, cashbox, item, warehouse, debit, credit, balance...) - Пагінація та Експорт CSV - [x] src/api/entities.ts — доданий listByPath(apiPath, params) для нестандартних URL - [x] src/config/types.ts — оновлено JournalViewerProps і LedgerViewerProps (+ поле apiPath) - [x] src/config/registers.ts — додано componentProps до всіх 4 items: - cashJournal/registers/journals/cash/ - inventoryJournal/registers/journals/inventory/ - cashLedger/registers/ledgers/cash/ - inventoryLedger/registers/ledgers/inventory/ - [x] src/pages/SectionPage.tsx — додано кейси ItemType.JOURNAL та ItemType.LEDGER - [x] src/i18n/locales/en.json та ua.json — додано ключі journal.* та ledger.* - [x] backend/registers/views.py — оновлено filterset_fields до dict-формату з підтримкою date__gte / date__lte (django-filter)

Маршрутизація (успадкована від Етап 1-4):

/registers/ledgers/cashLedger       → LedgerViewer (Cash Ledger)
/registers/ledgers/inventoryLedger  → LedgerViewer (Inventory Ledger)
/registers/journals/cashJournal     → JournalViewer (Cash Journal)
/registers/journals/inventoryJournal → JournalViewer (Inventory Journal)

Backend API endpoints:

GET /api/v1/registers/ledgers/cash/?date__gte=2024-01-01&date__lte=2024-12-31
GET /api/v1/registers/ledgers/inventory/?date__gte=...
GET /api/v1/registers/journals/cash/?date__gte=...&operation_type=income
GET /api/v1/registers/journals/inventory/?date__gte=...

Що не реалізовано (для наступних етапів): - Фільтрація по конкретному cashbox/account/item (потребує Select з lazy-load) - Export всього набору (зараз тільки поточна сторінка) - Freeze header при скролі великих таблиць


Етап 6 — Reports ✅ Завершено

Компоненти:

src/components/Reports/
└── ReportViewer.tsx          ← повноцінний перегляд звітів з Mantine Charts

Реалізовано: - [x] ReportViewer.tsx — єдиний компонент для всіх звітів, перемикається по reportCode - [x] Фільтр дат (dateFrom / dateTo) + кнопка "Сформувати" - [x] 4 Summary cards (ключові метрики кожного звіту) - [x] Mantine Charts інтеграція: BarChart, LineChart, AreaChart, DonutChart - [x] Badge "DEMO" — позначає mock-дані - [x] Mock-дані — детерміновані wave() функцією (sin/cos), реалістично виглядають - [x] Повна підтримка EN/UA (i18n.language) - [x] SectionPage.tsx — доданий кейс ItemType.REPORT - [x] i18n — ключі report.* в en.json та ua.json

Звіти: | reportCode | Модуль | Тип графіка | Опис | |---|---|---|---| | profitAndLoss | Essentials | BarChart | Виручка / Витрати / Прибуток по місяцях | | cashFlow | Essentials | AreaChart | Надходження / Виплати / Чистий потік | | balanceSheet | Essentials | DonutChart × 2 | Структура активів + Структура капіталу | | fuelMovement | Fleet | BarChart | Отримано / Витрачено / Залишок по ТЗ | | transportStatistics | Fleet | LineChart | Рейси / Пробіг / Завантаженість по місяцях | | logisticsPlanFact | Logistic | BarChart | Замовлення план vs факт по місяцях |

Маршрутизація (успадкована):

/essentials/essentialsreports/profitAndLoss    → ReportViewer (P&L)
/essentials/essentialsreports/cashFlow         → ReportViewer (Cash Flow)
/essentials/essentialsreports/balanceSheet     → ReportViewer (Balance Sheet)
/fleet/.../fuelMovement                        → ReportViewer (Fuel Movement)
/fleet/.../transportStatistics                 → ReportViewer (Transport Statistics)
/logistic/.../logisticsPlanFact                → ReportViewer (Plan vs Fact)

Що не реалізовано (для наступних етапів): - Реальні API-ендпоінти для звітів на бекенді (зараз mock-дані) - Експорт звіту в PDF / Excel - Додаткові фільтри (по підрозділу, організації, ТЗ тощо)


Етап 7 — Applications (App Store) ✅ Завершено

Компоненти:

src/components/Applications/
└── ApplicationsPage.tsx     ← App Store сторінка

Реалізовано: - [x] ApplicationsPage.tsx — повноцінна App Store сторінка - [x] SegmentedControl фільтр: Всі / Встановлені (N) / Доступні (N) - [x] Лічильник у підзаголовку: "X встановлено · Y доступно" - [x] SimpleGrid карток (1/2/3 колонки адаптивно) - [x] Кожна картка: ThemeIcon + назва, опис (lineClamp=2), badge статусу, badge ціни - [x] Статус: installed (зелений) / available (синій) - [x] Ціна: free (сірий) / paid (помаранчевий) - [x] Кнопки дій: "Встановити" (install) / "Видалити" (uninstall outline red) / "Налаштування" (light) - [x] Локальний стан встановлення (useState, ініціалізується з config status) - [x] Модальне вікно: клік на назву → Modal з ThemeIcon + повний опис + badges + кнопки - [x] notifications.show на Install / Uninstall / Settings - [x] Іконки: маппінг string → Tabler icon (IconCar, IconPackage, IconCalculator, etc.) - [x] Повна EN/UA локалізація (ключі app.*) - [x] SectionPage.tsx — доданий кейс ItemType.APPLICATION<ApplicationsPage /> - [x] i18n/locales/en.json та ua.json — додано секцію app.*

Маршрутизація:

/applications/groupsapplications/appstore → ApplicationsPage (App Store)

Джерело даних: applicationsSection з src/config/applications.ts — 16 додатків (виключений сам "appstore" item).

Що не реалізовано (для наступних етапів): - Backend API для збереження встановлених додатків (зараз локальний стан) - Реальне завантаження/видалення модулів - Фільтр за категорією (Business, Integration, Mobile, etc.)


Етап 8 — Tabbed Navigation ✅ Завершено

Концепція: відкривати кожен item у своїй вкладці (як у 1С або SAP)

src/components/Tabs/
└── TabBar.tsx               ← горизонтальна панель вкладок під Header

Реалізовано: - [x] src/store/tabsStore.ts — оновлено: MAX_TABS=10 (закривати найстарішу), updateTabTitle() - [x] src/components/Tabs/TabBar.tsx — горизонтальна панель вкладок з ScrollArea - Кожна вкладка: назва (EN/UA) + × кнопка закриття - Активна вкладка підсвічена синім кольором - Горизонтальний скрол при переповненні - Клік → navigate + setActiveTab - Закриття → closeTab + автоматичний перехід на сусідню вкладку - [x] src/components/Layout/AppLayout.tsx — динамічна висота Header (56px без вкладок / 92px з вкладками) - [x] src/pages/SectionPage.tsxuseEffect auto-реєструє вкладку при відкритті будь-якого item - Tab ID = location.pathname (унікальний ключ) - Заголовок: Item Name / Item Name — New / Item Name #ID - EN/UA назви зберігаються в Tab-об'єкті - [x] i18n — ключ tabs.close в en.json та ua.json

Архітектурні рішення: - Tab ID = URL pathname → кожна URL = одна вкладка, без дублікатів - openTab() при існуючому ID → просто активує (не дублює) - Вкладки скидаються при перезавантаженні (без persist) — очікувана поведінка - Стан форми при перемиканні вкладок не зберігається (обмеження: форма перемонтується)

Що не реалізовано: - Drag-and-drop для перетягування вкладок - Збереження стану форми між вкладками (потребує окремого store per-tab)


Етап 9 — i18n (повний переклад) ✅ Завершено

Реалізовано: - [x] Додано нові ключі у locales/en.json та ua.json: - common.id, common.active, common.inactive, common.items - tabs.suffixNew — суфікс вкладки для нових записів - ui.sectionNotFound, ui.itemNotFound, ui.typeNotImplemented - report.months — масив назв місяців (EN: Jan–Dec / UA: Січ–Гру) - report.* — всі рядки звітів: назви, підписи карток, серії графіків (37 нових ключів) - [x] MasterDataList.tsxt('common.id'), t('common.active') / t('common.inactive') для is_active - [x] TransactionList.tsxt('common.id') замість hardcoded "ID" - [x] JournalViewer.tsxt('common.id') замість hardcoded "ID" - [x] LedgerViewer.tsxt('common.id') замість hardcoded "ID" - [x] DashboardPage.tsxt('ui.dashboard') та t('common.items') - [x] SectionPage.tsxt('ui.sectionNotFound'), t('ui.itemNotFound'), t('ui.typeNotImplemented') з параметрами; суфікси вкладок через i18n.getFixedT('en'/'ua')('tabs.suffixNew') - [x] ReportViewer.tsx — повний рефакторинг: всі isUA ? 'UA' : 'EN' замінені на t('report.*'); buildReport отримує t: TFunction; місяці через t('report.months', { returnObjects: true }); t('report.demo') для DEMO-бейджа

Що залишено без змін (відповідно до архітектурного рішення): - Назви секцій/груп/items — беруться з config (name / name_ua) - Назви вкладок — зберігають обидва переклади з config - Числовий формат uk-UA — стандарт для DOP в Україні


Етап 10 — Полірування & Production

  • [ ] Code splitting (dynamic import() для важких сторінок)
  • [ ] Error Boundary (глобальний + per-page)
  • [ ] Loading skeleton замість спінерів
  • [ ] vite.config.tsbuild.rollupOptions.output.manualChunks для vendor splitting
  • [ ] index.html — favicon ESWF, <title>ESWF DOP</title>
  • [ ] Environment variables: VITE_API_URL, VITE_APP_ENV
  • [ ] Nginx config для erp.eswf.dev (static files + proxy /api)

Структура файлів (поточна)

frontend/erp/
├── src/
│   ├── api/
│   │   └── client.ts
│   ├── auth/
│   │   └── ProtectedRoute.tsx
│   ├── api/
│   │   ├── client.ts
│   │   ├── auth.ts
│   │   └── entities.ts              ← NEW (Етап 3)
│   ├── hooks/
│   │   ├── useCurrentUser.ts
│   │   ├── useEntityList.ts         ← NEW (Етап 3)
│   │   ├── useEntityDetail.ts       ← NEW (Етап 3)
│   │   └── useEntityFields.ts       ← NEW (Етап 3)
│   ├── components/
│   │   ├── Layout/
│   │   │   ├── AppLayout.tsx
│   │   │   ├── Header.tsx
│   │   │   ├── Sidebar.tsx
│   │   │   └── SectionDashboardPage.tsx
│   │   ├── MasterData/              ← NEW (Етап 3)
│   │   │   ├── MasterDataList.tsx
│   │   │   ├── MasterDataForm.tsx
│   │   │   └── MasterDataPage.tsx
│   │   └── Reports/                 ← NEW (Етап 6)
│   │       └── ReportViewer.tsx
│   ├── config/
│   │   ├── itemTypes.ts
│   │   ├── types.ts
│   │   ├── index.ts
│   │   ├── essentials.ts
│   │   ├── fleet.ts
│   │   ├── logistic.ts
│   │   ├── registers.ts
│   │   └── applications.ts
│   ├── i18n/
│   │   ├── index.ts
│   │   └── locales/
│   │       ├── en.json
│   │       └── ua.json
│   ├── pages/
│   │   ├── LoginPage.tsx
│   │   ├── DashboardPage.tsx
│   │   ├── SectionPage.tsx          ← UPDATED (Етап 3)
│   │   ├── ProfilePage.tsx
│   │   └── NotFoundPage.tsx
│   ├── store/
│   │   ├── authStore.ts
│   │   ├── uiStore.ts
│   │   └── tabsStore.ts
│   ├── App.tsx
│   └── main.tsx
├── postcss.config.cjs
├── vite.config.ts
├── tsconfig.app.json
└── package.json

Технічний стек (підсумок)

Категорія Технологія Версія
Build Vite 7.x
UI Framework React 19.x
Мова TypeScript ~5.9
UI Components Mantine 7.x
State Zustand latest
Data Fetching TanStack React Query v5
Routing React Router v7
HTTP Axios latest
i18n i18next + react-i18next latest
Charts Mantine Charts (recharts) latest
Icons Tabler Icons latest
Forms (план) React Hook Form + Zod
Dev port 5173
Prod domain erp.eswf.dev