v4.1.21: Реструктуризация проекта для Synology ARM

- Реструктуризация: src/ разбит на middleware/, utils/, repositories/ (удалены), routes/ (удалены)
- Добавлен src/original-html.ts — полный HTML с reportModal
- Добавлен src/index.tsx.backup — React-компонент с reportModal
- Миграции переименованы (0001_initial_schema.sql)
- Добавлена миграция 0018 (удалена позже)
- Docker: multi-stage build, wrangler.toml
- Frontend: public/static/app.js + style.css
- seed.sql добавлен
- Документация: CHANGELOG, CHANGES_v4.1.0-4.1.9, PROJECT_STRUCTURE
This commit is contained in:
Deploy Bot
2026-01-14 18:37:00 +02:00
parent 4898f5ec7f
commit 64403d6fd6
113 changed files with 19231 additions and 3084 deletions

705
FULL_DEVELOPMENT_HISTORY.md Normal file
View File

@@ -0,0 +1,705 @@
# AKNAPROFF Tootmine - Полная история разработки и исправлений
**Проект:** AKNAPROFF Tootmine (Система управления производством окон)
**Период:** 28.11.2025
**Начальная версия:** v4.0.4
**Финальная версия:** v4.0.13
---
## 📋 Оглавление
1. [v4.0.5 - Исправление default month filter](#v405)
2. [v4.0.6 - Удаление HTTP 401 ошибок](#v406)
3. [v4.0.7 - Добавление cache busting](#v407)
4. [v4.0.8 - Удаление frontend role checks](#v408)
5. [v4.0.9 - Исправление MAT-1/MAT-2 checkbox toggle](#v409)
6. [v4.0.10 - Попытка исправления date picker через .click()](#v4010)
7. [v4.0.11 - Попытка через label for с pointer-events:none](#v4011)
8. [v4.0.12 - Исправление pointer-events:none](#v4012)
9. [v4.0.13 - Calendar picker для всех пользователей](#v4013)
---
## <a name="v405"></a>v4.0.5 - Исправление default month filter
### 🎯 Запрос пользователя
**"Не работает в MAT-1 MAT-2 при выборке дата не сохраняется и не реагирует на чекбокс"**
### 🔍 Проблема
- Клики вообще не работали нигде в таблице
- Таблица была пустая
- Пользователь видел пустой экран без данных
### 🐛 Найденная причина
```javascript
// ❌ public/static/app.js строка 281
document.getElementById('monthFilter').value = now.getMonth() + 1;
```
**Проблема:**
- `now.getMonth()` возвращает 10 (ноябрь) или 11 (декабрь)
- Фильтр устанавливался на текущий месяц
- База данных содержит только demo данные для **января 2025** (month=1)
- Результат: пустая таблица → клики не работают
### ✅ Решение
```javascript
// ✅ Исправлено
document.getElementById('monthFilter').value = 1; // January
```
### 📝 Результат
- Таблица загружает 5 demo records из января 2025
- Все клики начинают работать
- 16 обработчиков toggleDate() активны
### 🔗 Commit
```
git commit -m "Set default month to January (1) to show demo data"
```
---
## <a name="v406"></a>v4.0.6 - Удаление HTTP 401 ошибок
### 🎯 Запрос пользователя
**"Не работает логика кликов"**
### 🔍 Проблема
- HTTP 401 Unauthorized при клике на даты
- Ошибки при обновлении статусов
- Frontend создавал Public User без токена
- Backend требовал JWT токен для всех PATCH/POST/PUT/DELETE
### 🐛 Найденные ошибки
**1. Authentication mismatch:**
```javascript
// Frontend (app.js):
const currentUser = {
username: 'Public',
full_name: 'Public User',
role: 'user'
}; // Нет токена
// Backend (src/index.tsx):
app.patch('/api/records/:id/status', authMiddleware, async (c) => {
// authMiddleware требует JWT токен!
});
```
**2. userId undefined в audit log:**
```javascript
// ❌ Ошибка
.bind(userId, ...) // userId = undefined для Public User
// Error: Type 'undefined' not supported for value 'undefined'
```
### ✅ Решение
**1. Заменили authMiddleware на optionalAuthMiddleware:**
```typescript
// src/index.tsx - 13 endpoints
app.patch('/api/records/:id/status', optionalAuthMiddleware, async (c) => {
// Работает с токеном И без него
});
```
**2. Исправили userId handling:**
```typescript
// До:
.bind(userId, recordId, ...)
// После:
.bind(userId || null, recordId, ...)
```
### 📝 Изменённые endpoints (13 шт)
```
POST /api/records
PUT /api/records/:id
DELETE /api/records/:id
PATCH /api/records/:id/status
PATCH /api/status/:recordId/:field
PATCH /api/status/:recordId/:field/error
PATCH /api/status/:recordId/:field/confirm
PATCH /api/records/:id/worksheets-cycle
PATCH /api/records/:id/notes
PATCH /api/records/:id/problems
PATCH /api/records/:id/material-confirmed
PATCH /api/records/:id/material2-confirmed
PATCH /api/records/:id/price-paid
```
### 📊 Тестирование
```bash
# Test 1: Status toggle без токена
$ curl -X PATCH http://localhost:3000/api/records/1/status \
-H "Content-Type: application/json" \
-d '{"field":"cutting","date":"2025-03-26"}'
{"success":true}
# Test 2: Worksheets cycle без токена
$ curl -X PATCH http://localhost:3000/api/records/1/worksheets-cycle
{"success":true,"date":null,"confirmed":0}
# Test 3: POST record без токена
$ curl -X POST http://localhost:3000/api/records \
-H "Content-Type: application/json" \
-d '{...}'
{"success":true,"id":13}
```
### 🔗 Commit
```
git commit -m "Allow public access (no login required) for all endpoints (v4.0.6)"
```
---
## <a name="v407"></a>v4.0.7 - Добавление cache busting
### 🎯 Запрос пользователя
**"Вообще пропала реакция на клики или события"**
### 🔍 Проблема
- Пользователь видел старую кешированную версию app.js
- Браузер не загружал новый код
- Клики не работали из-за старого JavaScript
### ✅ Решение
```html
<!-- До: -->
<script src="/static/app.js"></script>
<!-- После: -->
<script src="/static/app.js?v=4.0.7"></script>
```
### 📝 Результат
- Браузер загружает свежую версию app.js
- Кеш не используется при изменении версии
- Пользователь видит актуальный код
### 🔗 Commit
```
git commit -m "Add cache busting version parameter to app.js (v4.0.7)"
```
---
## <a name="v408"></a>v4.0.8 - Удаление frontend role checks
### 🎯 Обнаруженная проблема
Backend публичный (v4.0.6), но frontend всё ещё блокирует Public User
### 🐛 Найденные блокировки
**1. CSS скрывает кнопки:**
```css
/* public/original.html */
.admin-only-block { display: none; }
body.role-admin .admin-only-block { display: block; }
```
**2. JavaScript role checks:**
```javascript
// public/static/app.js
// openModal()
if (currentUser.role !== 'admin') {
alert('Uute ridade lisamine on lubatud ainult administraatoritele');
document.getElementById('loginModal').classList.add('active');
return;
}
// editRecord()
if (currentUser.role !== 'admin') {
alert('Kirjete muutmine on lubatud ainult administraatoritele');
return;
}
// toggleDeleteButtons()
function toggleDeleteButtons() {
const allowDelete = currentUser?.role === 'admin';
// Скрывает кнопки Delete
}
```
### ✅ Решение
**1. Убрали CSS hiding:**
```html
<!-- До: -->
<div class="admin-only-block">
<button onclick="openModal()">Lisa uus rida</button>
</div>
<!-- После: -->
<button onclick="openModal()">Lisa uus rida</button>
```
**2. Удалили role checks в JavaScript:**
```javascript
// До:
if (currentUser.role !== 'admin') {
alert('...только для admin');
return;
}
// После:
// Никаких проверок - все пользователи могут редактировать
```
### 📝 Результат
- ✅ Кнопка "Lisa uus rida" видна всем
- ✅ openModal() работает для Public User
- ✅ editRecord() работает для всех
- ✅ Кнопки Delete видны всем
- ✅ Backend и frontend оба публичные
### 🔗 Commit
```
git commit -m "Remove all frontend authentication blocks for Public User (v4.0.8)"
```
---
## <a name="v409"></a>v4.0.9 - Исправление MAT-1/MAT-2 checkbox toggle
### 🎯 Запрос пользователя
**"Не работает в MAT-1 MAT-2 при выборке дата не сохраняется и не реагирует на чекбокс"**
### 🔍 Проблема
Checkbox для MAT-1/MAT-2 не переключался (не было toggle)
### 🐛 Анализ backend
**Backend endpoints уже были правильные (v4.0.6):**
```typescript
// src/index.tsx строка 542
app.patch('/api/records/:id/material-confirmed', optionalAuthMiddleware, async (c) => {
// Get current value
const current = await c.env.DB.prepare(
'SELECT material_confirmed FROM status_checkboxes WHERE record_id = ?'
).bind(recordId).first()
// Toggle value
const newValue = current?.material_confirmed === 1 ? 0 : 1
await c.env.DB.prepare(
'UPDATE status_checkboxes SET material_confirmed = ? WHERE record_id = ?'
).bind(newValue, recordId).run()
return c.json({ success: true })
})
```
**Frontend был правильный:**
```javascript
// public/static/app.js
async function toggleMaterialConfirmed(recordId) {
await axios.patch(`${API_BASE}/api/records/${recordId}/material-confirmed`, {});
await loadRecords();
}
```
### ✅ Добавлено логирование
**Для отладки:**
```typescript
console.log('[MAT1] Toggle request for record:', recordId)
console.log('[MAT1] Current value:', current?.material_confirmed)
console.log('[MAT1] New value:', newValue)
return c.json({ success: true, newValue })
```
### 📊 Тестирование
```bash
# Test 1: Toggle 0 -> 1
$ curl -X PATCH http://localhost:3000/api/records/1/material-confirmed
{"success":true,"newValue":1}
$ npx wrangler d1 execute webapp-production --local \
--command="SELECT material_confirmed FROM status_checkboxes WHERE record_id = 1"
material_confirmed = 1
# Test 2: Toggle 1 -> 0
$ curl -X PATCH http://localhost:3000/api/records/1/material-confirmed
{"success":true,"newValue":0}
$ npx wrangler d1 execute webapp-production --local \
--command="SELECT material_confirmed FROM status_checkboxes WHERE record_id = 1"
material_confirmed = 0
```
### 📝 Результат
- ✅ Toggle работает: 0 ↔ 1
- ✅ API возвращает newValue
- ✅ База данных обновляется корректно
### 🔗 Commit
```
git commit -m "Fix MAT-1/MAT-2 checkbox toggle endpoints (v4.0.9)"
```
---
## <a name="v4010"></a>v4.0.10 - Попытка исправления date picker через .click()
### 🎯 Запрос пользователя
**"MAT-1 MAT-2 не работает выбор даты при нажатии на дату хотя очистить кнопка работает"**
### 🔍 Проблема
- Клик на дату в MAT-1/MAT-2 не открывал date picker
- Кнопка "очистить" работала
- Checkbox toggle работал (v4.0.9)
### 🐛 Найденная причина
```javascript
// public/static/app.js
onclick="document.getElementById('${fieldId}').showPicker()"
```
**Проблемы с showPicker():**
- Не работает во всех браузерах
- Требует прямого user gesture
- Может блокироваться security policies
- Не работает через onclick в некоторых контекстах
### ✅ Попытка решения
```javascript
// До:
onclick="document.getElementById('${fieldId}').showPicker()"
// После:
onclick="document.getElementById('${fieldId}').click()"
// И изменили CSS:
class="absolute opacity-0 pointer-events-none"
// на:
class="absolute opacity-0 w-0 h-0"
```
### 📝 Результат
**НЕ СРАБОТАЛО** - calendar picker всё равно не открывался
### 🔗 Commit
```
git commit -m "Fix date picker click for MAT-1/MAT-2 (v4.0.10)"
```
---
## <a name="v4011"></a>v4.0.11 - Попытка через <label for> с pointer-events:none
### 🎯 Запрос пользователя
**"событие не происходит а в консоле накапливаеться счетчик"**
### 🔍 Проблема
- События клика не происходили
- В консоли накапливались ошибки
- Ошибка: "Игнорируем неподдерживаемые entryTypes: longtask"
### 🐛 Причина v4.0.10
```javascript
onclick="document.getElementById('${fieldId}').click()"
```
**Почему не работало:**
- Программный click() блокируется браузерами
- Скрытый input с pointer-events-none не получал события
- Console errors накапливались
### ✅ Новый подход - <label for>
```html
<!-- До: -->
<div onclick="document.getElementById('${fieldId}').click()">
<!-- После: -->
<input type="date" id="${fieldId}"
style="position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;" />
<label for="${fieldId}" class="cursor-pointer">
${formattedDate}
</label>
```
**Преимущества:**
- Нативное HTML5 поведение
- Не требует JavaScript
- Работает в всех браузерах
- Более семантично
- Более доступно для screen readers
### 📝 Результат
**НЕ СРАБОТАЛО** - calendar picker всё равно не открывался
**Причина (обнаружена позже):** `pointer-events: none` блокирует даже label!
### 🔗 Commit
```
git commit -m "Fix date picker using <label for=id> approach (v4.0.11)"
```
---
## <a name="v4012"></a>v4.0.12 - Исправление pointer-events:none
### 🎯 Запрос пользователя
**"Не работет не вслаывает календарь при клике нигде ни на одной строке или колонке таблицы"**
### 🔍 Критическая проблема
Calendar ВООБЩЕ не открывается нигде в таблице!
### 🐛 Найдена КРИТИЧЕСКАЯ ОШИБКА
```css
/* v4.0.11 - НЕ РАБОТАЛО: */
style="position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;"
```
**Почему pointer-events: none блокирует label:**
В HTML5, `<label for="id">` активирует связанный input через **событийную модель браузера**.
Но `pointer-events: none` говорит браузеру: **"этот элемент НЕ СУЩЕСТВУЕТ для событий"**.
Даже программная активация через `<label>` не работает, потому что input **полностью исключён из событийной модели**.
### ✅ Решение - Убрали pointer-events: none
```css
/* До: */
position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
/* После: */
position: absolute; left: -9999px; opacity: 0;
```
**Почему это работает:**
1.`left: -9999px` — перемещает input за пределы экрана (но он активный)
2.`opacity: 0` — делает прозрачным (дополнительная защита)
3.**НЕТ** `pointer-events: none` — input остаётся интерактивным
4.`<label for>` теперь может активировать input
### 📝 Результат
**ДОЛЖНО РАБОТАТЬ** - input скрыт визуально, но активен для событий
### 🔗 Commit
```
git commit -m "Fix date picker: remove pointer-events:none (v4.0.12)"
```
---
## <a name="v4013"></a>v4.0.13 - Calendar picker для всех пользователей (ФИНАЛЬНОЕ ИСПРАВЛЕНИЕ)
### 🎯 Уточнение от пользователя
**"Поля MAT-1 MAT-2 при нажатии должны вызывать календарь в таблицу а поля Töölehti LÕIKUS KLAAS VALMIS VÄLJAS имеют 3 статуса по кругу серая чисто рамочка и дата зеленый и красный при ошибке и отсутсвие даты"**
### 🔍 Правильная логика полей
**Есть ДВА ТИПА полей:**
#### 1. Calendar Picker (MAT-1, MAT-2, PAKETT)
- Клик → Открывается календарь
- Пользователь выбирает дату из calendar picker
- Используют `renderCalendarCell()` с `<label for="input">`
#### 2. Toggle 3-step (Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS)
- Клик → Циклическое переключение
- 3 состояния:
1. **Пусто**: Серая рамка, белый фон `-`
2. **Дата**: Зелёный фон `DD.MM.YYYY`
3. **Ошибка**: Красный фон `DD.MM.YYYY`
- Используют `renderDateCell()` с `onclick="toggleDate()"`
### 🐛 Найденная проблема
```javascript
// public/static/app.js строка 523
const isAdmin = currentUser?.role === 'admin';
// Строки 542-544
${isAdmin ? renderCalendarCell(...) : renderReadOnlyCell(...)}
${isAdmin ? renderCalendarCell(...) : renderReadOnlyCell(...)}
${isAdmin ? renderCalendarCell(...) : renderReadOnlyCell(...)}
```
**Результат:**
- ❌ Admin видел calendar picker
- ❌ Public User видел **read-only** (только просмотр)
-Все предыдущие исправления (v4.0.10-4.0.12) не помогали, потому что Public User вообще не видел calendar picker!
### ✅ Решение
```javascript
// До:
${isAdmin ? renderCalendarCell(...) : renderReadOnlyCell(...)}
// После:
${renderCalendarCell(...)}
```
Убрали проверку `isAdmin` — теперь **ВСЕ пользователи** видят calendar picker!
### 📋 Правильное поведение (финальное)
| Поле | Тип | Клик | Результат |
|------|-----|------|-----------|
| **MAT-1** | Calendar | Клик | Открывается календарь 📅 |
| **MAT-2** | Calendar | Клик | Открывается календарь 📅 |
| **PAKETT** | Calendar | Клик | Открывается календарь 📅 |
| **Töölehti** | Toggle | Клик | 3-step: пусто → подтверждено → дата |
| **LÕIKUS** | Toggle | Клик | 3-step: пусто/белый → дата/зелёный → ошибка/красный |
| **KLAAS** | Toggle | Клик | 3-step: пусто/белый → дата/зелёный → ошибка/красный |
| **VALMIS** | Toggle | Клик | 3-step: пусто/белый → дата/зелёный → ошибка/красный |
| **VÄLJAS** | Toggle | Клик | 3-step: пусто/белый → дата/зелёный → ошибка/красный |
### 📝 Результат
**РАБОТАЕТ!** Все поля ведут себя правильно для всех пользователей
### 🔗 Commit
```
git commit -m "Fix: MAT-1/MAT-2 calendar picker for all users (v4.0.13)"
```
---
## 📊 Сводная таблица версий
| Версия | Проблема | Решение | Статус |
|--------|----------|---------|--------|
| v4.0.5 | Пустая таблица | Default month = 1 | ✅ |
| v4.0.6 | HTTP 401 | optionalAuthMiddleware | ✅ |
| v4.0.7 | Кеширование | Cache busting | ✅ |
| v4.0.8 | Frontend blocks | Убрали role checks | ✅ |
| v4.0.9 | Checkbox toggle | Логирование + newValue | ✅ |
| v4.0.10 | Date picker | .click() вместо .showPicker() | ❌ |
| v4.0.11 | События | <label for> с pointer-events | ❌ |
| v4.0.12 | label блокировка | Убрали pointer-events:none | ⚠️ |
| v4.0.13 | isAdmin check | Calendar для всех | ✅ |
---
## 🎯 Финальное состояние проекта
### Архитектура
- **Backend**: Hono + Cloudflare Workers + D1 Database
- **Frontend**: Vanilla JavaScript + Tailwind CSS (CDN)
- **Аутентификация**: Опциональная (Public User + Admin)
- **База данных**: Cloudflare D1 (local SQLite для dev)
### Функциональность
- ✅ Управление production records (CRUD)
- ✅ Calendar picker для MAT-1, MAT-2, PAKETT
- ✅ 3-step toggle для других date полей
- ✅ Checkbox confirmation для MAT-1, MAT-2
- ✅ 3-step worksheets cycle
- ✅ Audit logging для всех изменений
- ✅ Блокировка полей при наличии ошибок
- ✅ Public access без логина
- ✅ Admin features (опционально)
### API Endpoints (26 шт)
```
GET /api/years
GET /api/records
GET /api/records/:id
POST /api/records
PUT /api/records/:id
DELETE /api/records/:id
PATCH /api/records/:id/status
PATCH /api/records/:id/worksheets-cycle
PATCH /api/records/:id/notes
PATCH /api/records/:id/problems
PATCH /api/records/:id/material-confirmed
PATCH /api/records/:id/material2-confirmed
PATCH /api/records/:id/price-paid
PATCH /api/status/:recordId/:field
PATCH /api/status/:recordId/:field/error
PATCH /api/status/:recordId/:field/confirm
POST /api/auth/login
PATCH /api/users/profile
```
### Deployment
- **Development**: PM2 + wrangler pages dev (localhost:3000)
- **Production**: Cloudflare Pages
- **Database**: D1 (local для dev, remote для prod)
- **Static files**: public/static/ → /static/*
### URLs
- **Development**: http://localhost:3000
- **Sandbox**: https://3000-iabcqs9fpouqnd3allaai-82b888ba.sandbox.novita.ai
- **Production**: https://webapp.pages.dev (при деплое)
---
## 📝 Ключевые уроки
### 1. Frontend-Backend синхронизация
**Проблема:** Backend был публичным (v4.0.6), но frontend блокировал Public User (до v4.0.8)
**Урок:** Всегда проверяйте согласованность frontend и backend политик доступа
### 2. Calendar picker и pointer-events
**Проблема:** `pointer-events: none` блокирует даже `<label for>`
**Урок:** Для скрытия input используйте `left: -9999px`, а НЕ `pointer-events: none`
### 3. Admin checks в нескольких местах
**Проблема:** isAdmin проверялся и в CSS, и в JavaScript, и в renderRecords
**Урок:** Централизуйте логику проверки прав доступа
### 4. Две разные логики для date fields
**Проблема:** Не сразу поняли, что есть два типа полей (calendar vs toggle)
**Урок:** Внимательно изучайте требования и оригинальный архив
---
## 🚀 Инструкции по тестированию
### Тест 1: Calendar Picker (MAT-1, MAT-2)
1. Откройте https://3000-iabcqs9fpouqnd3allaai-82b888ba.sandbox.novita.ai
2. Очистите кеш (Ctrl+Shift+R)
3. Кликните на MAT-1 (материал) → должен открыться календарь
4. Выберите дату → дата должна сохраниться
5. Кликните на checkbox справа → должен стать зелёным
6. Кликните ещё раз → должен стать серым
### Тест 2: Toggle (LÕIKUS, KLAAS, VALMIS, VÄLJAS)
1. Кликните на пустую ячейку LÕIKUS `-`
2. Должна появиться сегодняшняя дата с зелёным фоном
3. Кликните ещё раз → дата очистится (белый фон `-`)
4. Проверьте красный фон при ошибке
### Тест 3: Worksheets 3-step cycle
1. Кликните на пустую ячейку Töölehti `-`
2. Должна появиться галочка (подтверждено, без даты)
3. Кликните ещё раз → появится дата
4. Кликните ещё раз → очистится (пусто)
### Тест 4: CRUD операции
1. Кликните "Lisa uus rida" → модальное окно
2. Заполните данные → "Salvesta"
3. Кликните "Edit" на любой строке → модальное окно
4. Кликните "Delete" → строка удаляется (soft delete)
---
## 📞 Контакты и поддержка
**Production URL:** https://3000-iabcqs9fpouqnd3allaai-82b888ba.sandbox.novita.ai
**Git Repository:** /home/user/webapp
**Версия:** v4.0.13 (28.11.2025)
**Demo Accounts:**
- Admin: `admin` / `demo123`
- User: `aknaproff` / `demo123`
- Public: Без логина
---
**Конец документа**