init: project docs — requirements, architecture, deploy config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
320
ARCHITECTURE.md
Normal file
320
ARCHITECTURE.md
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
# Архитектура Calculator
|
||||||
|
|
||||||
|
## Дерево файлов
|
||||||
|
|
||||||
|
```
|
||||||
|
calculator/
|
||||||
|
├── service.yaml # Конфиг для ci-templates (тип, домен, ресурсы)
|
||||||
|
├── .drone.yml # CI/CD пайплайн (копия base.drone.yml)
|
||||||
|
├── index.html
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── vite.config.ts
|
||||||
|
├── public/
|
||||||
|
│ └── favicon.svg
|
||||||
|
└── src/
|
||||||
|
├── main.tsx # Точка входа, рендер <App />
|
||||||
|
├── App.tsx # Роутинг: /login → /calculator
|
||||||
|
├── App.css # Глобальные стили (reset, body, центрирование)
|
||||||
|
│
|
||||||
|
├── context/
|
||||||
|
│ └── AuthContext.tsx # React Context + Provider для псевдоавторизации
|
||||||
|
│
|
||||||
|
├── hooks/
|
||||||
|
│ ├── useCalculator.ts # Стейт-машина калькулятора (вся логика)
|
||||||
|
│ └── useKeyboard.ts # Слушатель клавиатуры → маппинг на действия
|
||||||
|
│
|
||||||
|
├── utils/
|
||||||
|
│ └── calculate.ts # Чистые функции вычислений
|
||||||
|
│
|
||||||
|
├── pages/
|
||||||
|
│ ├── LoginPage.tsx # Страница входа
|
||||||
|
│ ├── LoginPage.module.css
|
||||||
|
│ ├── CalculatorPage.tsx # Страница калькулятора (шапка + калькулятор)
|
||||||
|
│ └── CalculatorPage.module.css
|
||||||
|
│
|
||||||
|
└── components/
|
||||||
|
├── Calculator/
|
||||||
|
│ ├── Calculator.tsx # Обёртка: Display + Keypad
|
||||||
|
│ └── Calculator.module.css
|
||||||
|
├── Display/
|
||||||
|
│ ├── Display.tsx # Выражение + результат
|
||||||
|
│ └── Display.module.css
|
||||||
|
├── Keypad/
|
||||||
|
│ ├── Keypad.tsx # Сетка кнопок
|
||||||
|
│ └── Keypad.module.css
|
||||||
|
└── Button/
|
||||||
|
├── Button.tsx # Кнопка калькулятора
|
||||||
|
└── Button.module.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## Схема компонентов
|
||||||
|
|
||||||
|
```
|
||||||
|
<App>
|
||||||
|
<AuthProvider> ← context/AuthContext.tsx
|
||||||
|
<Routes>
|
||||||
|
/login → <LoginPage /> ← Форма логина
|
||||||
|
/ → <ProtectedRoute> ← Редирект на /login если нет юзера
|
||||||
|
<CalculatorPage />
|
||||||
|
├── Header (логин + выход)
|
||||||
|
└── <Calculator />
|
||||||
|
├── <Display /> ← expression + result
|
||||||
|
└── <Keypad /> ← сетка <Button />
|
||||||
|
</ProtectedRoute>
|
||||||
|
</Routes>
|
||||||
|
</AuthProvider>
|
||||||
|
</App>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Слои приложения
|
||||||
|
|
||||||
|
### 1. AuthContext — псевдоавторизация
|
||||||
|
|
||||||
|
```
|
||||||
|
AuthContext {
|
||||||
|
user: string | null // текущий логин
|
||||||
|
login(name: string): void // сохранить в localStorage, установить user
|
||||||
|
logout(): void // очистить localStorage, user = null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- При инициализации читает `localStorage.getItem('calculator_user')`
|
||||||
|
- `login()` — записывает в localStorage, обновляет стейт
|
||||||
|
- `logout()` — удаляет из localStorage, обновляет стейт
|
||||||
|
- `ProtectedRoute` — обёртка, редиректит на `/login` если `user === null`
|
||||||
|
|
||||||
|
### 2. useCalculator — стейт-машина
|
||||||
|
|
||||||
|
Управляет всем состоянием калькулятора. Чистая логика, без побочных эффектов.
|
||||||
|
|
||||||
|
```
|
||||||
|
State {
|
||||||
|
display: string // текущее отображаемое число ("0")
|
||||||
|
expression: string // строка выражения ("12 + 3")
|
||||||
|
operator: string | null // текущий оператор
|
||||||
|
prevValue: number | null // предыдущее значение
|
||||||
|
waitingForOperand: boolean
|
||||||
|
lastResult: boolean // только что получили результат
|
||||||
|
}
|
||||||
|
|
||||||
|
Actions (dispatch):
|
||||||
|
inputDigit(digit: string)
|
||||||
|
inputDecimal()
|
||||||
|
inputOperator(op: string)
|
||||||
|
calculate()
|
||||||
|
clear()
|
||||||
|
backspace()
|
||||||
|
toggleSign()
|
||||||
|
percent()
|
||||||
|
```
|
||||||
|
|
||||||
|
Логика переходов:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Ввод цифры]
|
||||||
|
waitingForOperand=true → display = digit, waiting = false
|
||||||
|
waitingForOperand=false → display = display + digit (макс 16 символов)
|
||||||
|
|
||||||
|
[Оператор (+, -, *, /)]
|
||||||
|
prevValue=null → сохранить display как prevValue, запомнить оператор
|
||||||
|
prevValue!=null → вычислить промежуточный результат, показать его
|
||||||
|
|
||||||
|
[Равно]
|
||||||
|
Вычислить prevValue {operator} display, показать результат
|
||||||
|
|
||||||
|
[C] → полный сброс к начальному состоянию
|
||||||
|
[Backspace] → удалить последний символ из display (минимум "0")
|
||||||
|
[+/-] → сменить знак display
|
||||||
|
[%] → display = display / 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. useKeyboard — слушатель клавиатуры
|
||||||
|
|
||||||
|
```
|
||||||
|
useKeyboard(actions: CalculatorActions): Map<string, string>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Вешает `keydown` listener на `window`
|
||||||
|
- Маппинг клавиш → действий:
|
||||||
|
|
||||||
|
| key | action |
|
||||||
|
|-----|--------|
|
||||||
|
| `0`-`9` | `inputDigit(key)` |
|
||||||
|
| `.` | `inputDecimal()` |
|
||||||
|
| `+` `-` `*` `/` | `inputOperator(key)` |
|
||||||
|
| `Enter`, `=` | `calculate()` |
|
||||||
|
| `Escape` | `clear()` |
|
||||||
|
| `Backspace` | `backspace()` |
|
||||||
|
| `%` | `percent()` |
|
||||||
|
|
||||||
|
- Возвращает `activeKey` (текущая нажатая клавиша) для подсветки кнопки
|
||||||
|
- `preventDefault()` для `/` (чтобы не открывался поиск в браузере)
|
||||||
|
|
||||||
|
### 4. calculate.ts — вычисления
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function calculate(a: number, operator: string, b: number): number
|
||||||
|
```
|
||||||
|
|
||||||
|
- Чистая функция, без стейта
|
||||||
|
- Обработка деления на ноль → возвращает `Infinity` (дисплей покажет "Error")
|
||||||
|
- Округление для борьбы с погрешностью float (0.1 + 0.2)
|
||||||
|
|
||||||
|
### 5. Компоненты
|
||||||
|
|
||||||
|
**Display** — два блока:
|
||||||
|
- `expression` — мелкий шрифт, выражение сверху
|
||||||
|
- `result` — крупный шрифт, текущее число/результат снизу
|
||||||
|
- Auto-fit: `font-size` уменьшается при длине > 10 символов
|
||||||
|
|
||||||
|
**Keypad** — CSS Grid 4x5:
|
||||||
|
|
||||||
|
```
|
||||||
|
| C | +/- | % | ÷ |
|
||||||
|
| 7 | 8 | 9 | × |
|
||||||
|
| 4 | 5 | 6 | − |
|
||||||
|
| 1 | 2 | 3 | + |
|
||||||
|
| 0 (span 2) | . | = |
|
||||||
|
```
|
||||||
|
|
||||||
|
**Button** — три варианта стиля:
|
||||||
|
- `default` — цифры (тёмно-серый фон)
|
||||||
|
- `function` — C, +/-, % (светло-серый фон)
|
||||||
|
- `operator` — +, -, ×, ÷, = (акцентный цвет)
|
||||||
|
- Пропс `active` — подсветка при нажатии с клавиатуры
|
||||||
|
|
||||||
|
## Адаптивность
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Калькулятор */
|
||||||
|
max-width: 360px; /* ограничение на больших экранах */
|
||||||
|
width: calc(100vw - 32px); /* на маленьких — почти вся ширина */
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
/* Центрирование на экране */
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
/* Кнопки */
|
||||||
|
min-height: 56px; /* тач-friendly */
|
||||||
|
font-size: clamp(16px, 4vw, 20px);
|
||||||
|
|
||||||
|
/* Дисплей результата */
|
||||||
|
font-size: clamp(28px, 8vw, 40px);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Поток данных
|
||||||
|
|
||||||
|
```
|
||||||
|
[Keyboard Event] [Button Click]
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
useKeyboard ──── action ────→ useCalculator (useReducer)
|
||||||
|
│ │
|
||||||
|
│ activeKey │ state
|
||||||
|
▼ ▼
|
||||||
|
<Keypad> ◄──── props ──── <Calculator>
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
<Button active?> <Display expression, result>
|
||||||
|
```
|
||||||
|
|
||||||
|
Клавиатура и клики вызывают одни и те же actions хука `useCalculator`. Хук `useKeyboard` дополнительно пробрасывает `activeKey` для визуальной подсветки.
|
||||||
|
|
||||||
|
## Деплой и CI/CD
|
||||||
|
|
||||||
|
### Инфраструктура
|
||||||
|
|
||||||
|
```
|
||||||
|
Домен: test-calculator.vigdorov.ru
|
||||||
|
Namespace: test-calculator
|
||||||
|
Тип: web-frontend (SPA → nginx)
|
||||||
|
Registry: registry.vigdorov.ru/library/test-calculator
|
||||||
|
Ingress: Traefik + wildcard-cert
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пайплайн (Drone CI)
|
||||||
|
|
||||||
|
```
|
||||||
|
git push (master)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[prepare] Клонирует ci-templates → .ci/
|
||||||
|
Парсит service.yaml
|
||||||
|
Копирует react.Dockerfile + spa.conf
|
||||||
|
Генерирует .ci/env
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[build] Kaniko собирает образ:
|
||||||
|
- Stage 1: node:20-alpine → npm ci → npm run build
|
||||||
|
- Stage 2: nginx:alpine + dist + spa.conf
|
||||||
|
Push в Harbor: registry.vigdorov.ru/library/test-calculator:<sha>
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[deploy] Helm upgrade --install в namespace test-calculator
|
||||||
|
Chart: ci-templates/helm-charts/frontend/
|
||||||
|
Ingress: test-calculator.vigdorov.ru → Service:80
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[notify] Telegram уведомление
|
||||||
|
```
|
||||||
|
|
||||||
|
### service.yaml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service:
|
||||||
|
name: test-calculator
|
||||||
|
type: web-frontend
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
namespace: test-calculator
|
||||||
|
domain: test-calculator.vigdorov.ru
|
||||||
|
```
|
||||||
|
|
||||||
|
Минимальная конфигурация. ci-templates применит дефолты:
|
||||||
|
- `frontend.framework: react`
|
||||||
|
- `frontend.resources.cpu: 100m`
|
||||||
|
- `frontend.resources.memory: 128Mi`
|
||||||
|
- `frontend.replicas: 1`
|
||||||
|
- Dockerfile: `react.Dockerfile` (multi-stage: node → nginx)
|
||||||
|
- Nginx: `spa.conf` (gzip, кэш статики, SPA fallback, /health)
|
||||||
|
|
||||||
|
### Helm-чарт (frontend/)
|
||||||
|
|
||||||
|
```
|
||||||
|
Deployment
|
||||||
|
└── Pod (nginx:alpine)
|
||||||
|
├── /usr/share/nginx/html ← собранный SPA
|
||||||
|
└── /etc/nginx/nginx.conf ← spa.conf
|
||||||
|
Service (ClusterIP:80)
|
||||||
|
└── → Pod:80
|
||||||
|
Ingress (Traefik)
|
||||||
|
└── test-calculator.vigdorov.ru → Service:80
|
||||||
|
TLS: wildcard-cert (автоматически отражён в namespace)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Схема сети
|
||||||
|
|
||||||
|
```
|
||||||
|
Браузер
|
||||||
|
│
|
||||||
|
│ HTTPS
|
||||||
|
▼
|
||||||
|
DNS (cishost.ru) → VDS Nginx (reg.ru) → Keenetic Router
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
K3s Traefik
|
||||||
|
│
|
||||||
|
┌───────────────┘
|
||||||
|
▼
|
||||||
|
Ingress: test-calculator.vigdorov.ru
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Service: test-calculator (ClusterIP:80)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Pod: nginx (SPA)
|
||||||
|
```
|
||||||
53
CLAUDE.md
Normal file
53
CLAUDE.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Calculator
|
||||||
|
|
||||||
|
Frontend-калькулятор с псевдоавторизацией.
|
||||||
|
|
||||||
|
## Стек
|
||||||
|
|
||||||
|
- **Framework:** React 18 + TypeScript
|
||||||
|
- **UI:** Ant Design 5
|
||||||
|
- **Сборка:** Vite
|
||||||
|
- **Стили:** CSS Modules
|
||||||
|
- **Роутинг:** React Router 6
|
||||||
|
- **Домен:** test-calculator.vigdorov.ru
|
||||||
|
|
||||||
|
## Деплой
|
||||||
|
|
||||||
|
- **Namespace:** test-calculator (отдельный)
|
||||||
|
- **CI/CD:** Drone CI через ci-templates (service.yaml + .drone.yml)
|
||||||
|
- **Тип:** web-frontend (SPA → nginx)
|
||||||
|
- **Registry:** registry.vigdorov.ru/library/test-calculator
|
||||||
|
- **Trigger:** push в master
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
calculator/
|
||||||
|
service.yaml # Конфиг ci-templates
|
||||||
|
.drone.yml # CI/CD пайплайн
|
||||||
|
src/
|
||||||
|
components/ # UI-компоненты (Calculator, Display, Keypad, LoginForm)
|
||||||
|
pages/ # Страницы (LoginPage, CalculatorPage)
|
||||||
|
context/ # AuthContext для псевдоавторизации
|
||||||
|
hooks/ # Кастомные хуки (useCalculator, useKeyboard)
|
||||||
|
utils/ # Логика вычислений
|
||||||
|
App.tsx
|
||||||
|
main.tsx
|
||||||
|
public/
|
||||||
|
index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # Запуск dev-сервера
|
||||||
|
npm run build # Сборка для продакшена
|
||||||
|
npm run preview # Превью билда
|
||||||
|
npm run lint # Линтинг
|
||||||
|
```
|
||||||
|
|
||||||
|
## Архитектурные решения
|
||||||
|
|
||||||
|
- **Псевдоавторизация:** Логин сохраняется в localStorage, любой логин принимается. Нет пароля.
|
||||||
|
- **Клавиатура:** Калькулятор полностью управляется с клавиатуры (цифры, операторы, Enter=равно, Escape=сброс, Backspace=удаление).
|
||||||
|
- **Адаптивность:** Минимальная ширина экрана 320px. Интерфейс центрирован.
|
||||||
67
REQUIREMENTS.md
Normal file
67
REQUIREMENTS.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Требования к проекту Calculator
|
||||||
|
|
||||||
|
## 1. Псевдоавторизация
|
||||||
|
|
||||||
|
- Экран входа с полем "Логин" и кнопкой "Войти"
|
||||||
|
- Принимается любой непустой логин (без пароля)
|
||||||
|
- Логин сохраняется в localStorage
|
||||||
|
- При повторном входе пользователь попадает сразу в калькулятор
|
||||||
|
- Кнопка "Выйти" в интерфейсе калькулятора (очищает localStorage)
|
||||||
|
- Отображение текущего логина в шапке
|
||||||
|
|
||||||
|
## 2. Калькулятор
|
||||||
|
|
||||||
|
### Операции
|
||||||
|
- Сложение (+)
|
||||||
|
- Вычитание (-)
|
||||||
|
- Умножение (*)
|
||||||
|
- Деление (/)
|
||||||
|
- Процент (%)
|
||||||
|
- Смена знака (+/-)
|
||||||
|
- Десятичная точка
|
||||||
|
- Равно (=)
|
||||||
|
- Очистка (C) - полный сброс
|
||||||
|
- Удаление последнего символа (Backspace)
|
||||||
|
|
||||||
|
### Дисплей
|
||||||
|
- Поле ввода/результата с крупным шрифтом
|
||||||
|
- Отображение текущего выражения (например "12 + 3")
|
||||||
|
- Отображение результата
|
||||||
|
- Текст уменьшается при длинных числах (auto-fit)
|
||||||
|
|
||||||
|
### Клавиатурная панель
|
||||||
|
- Кнопки расположены сеткой (стандартная раскладка калькулятора)
|
||||||
|
- Визуальная обратная связь при нажатии (hover, active состояния)
|
||||||
|
|
||||||
|
## 3. Управление с клавиатуры
|
||||||
|
|
||||||
|
| Клавиша | Действие |
|
||||||
|
|---------|----------|
|
||||||
|
| 0-9 | Ввод цифры |
|
||||||
|
| . | Десятичная точка |
|
||||||
|
| + | Сложение |
|
||||||
|
| - | Вычитание |
|
||||||
|
| * | Умножение |
|
||||||
|
| / | Деление |
|
||||||
|
| Enter или = | Вычислить результат |
|
||||||
|
| Escape | Полный сброс (C) |
|
||||||
|
| Backspace | Удалить последний символ |
|
||||||
|
| % | Процент |
|
||||||
|
|
||||||
|
- Нажатие клавиши визуально подсвечивает соответствующую кнопку на экране
|
||||||
|
- Фокус не должен уходить с калькулятора при работе с клавиатуры
|
||||||
|
|
||||||
|
## 4. Адаптивность
|
||||||
|
|
||||||
|
- Минимальная поддерживаемая ширина: 320px
|
||||||
|
- Калькулятор центрирован на экране (по горизонтали и вертикали)
|
||||||
|
- На маленьких экранах калькулятор занимает почти всю ширину
|
||||||
|
- На больших экранах калькулятор имеет фиксированную максимальную ширину
|
||||||
|
- Кнопки достаточно крупные для тач-устройств (минимум 48px)
|
||||||
|
|
||||||
|
## 5. UI/UX
|
||||||
|
|
||||||
|
- Чистый минималистичный дизайн
|
||||||
|
- Цветовое разделение: цифры / операторы / функции
|
||||||
|
- Плавные анимации при нажатии кнопок
|
||||||
|
- Отображение имени пользователя и кнопки выхода в шапке
|
||||||
Reference in New Issue
Block a user