86 lines
4.2 KiB
Markdown
86 lines
4.2 KiB
Markdown
# Финальный план рефакторинга — store + render
|
|
|
|
Согласовано: DeepSeek + Opus. Все открытые вопросы закрыты.
|
|
|
|
## Архитектурное решение
|
|
|
|
**Патерн:** единый `state` объект + одна `render(state)` + actions мутируют state.
|
|
|
|
```
|
|
action → мутация state → render(state)
|
|
```
|
|
|
|
- Асинхронные операции (upload, classify, compare-SSE) — actions, пишут в state
|
|
- Синхронные трансформации (group, applyParseResult) — чистые функции
|
|
- DOM — проекция state, никто не читает из DOM
|
|
- `console.log(state)` показывает всё
|
|
|
|
## Структура state
|
|
|
|
```javascript
|
|
state = {
|
|
batchId, contractId,
|
|
files: [{ id, name, size, status: { kind, pct, text, count }, parse, classify, supp_id, detailOpen }],
|
|
groups: [{
|
|
contractNumber, counterparty, documents, unresolved,
|
|
compare: { status, sections: { [suppId]: { header, body, ops } }, totalTime, collapsed }
|
|
}],
|
|
compareAll: { status, sections, totalTime },
|
|
ui: { steps: { upload, classify, groups, compare } },
|
|
_activeCompare: { es, timer }
|
|
}
|
|
```
|
|
|
|
## Фазы
|
|
|
|
### Фаза 0 — Каркас
|
|
- Ввести `state`, `render(state)` (пока вызывает существующие рендеры)
|
|
- Соглашение: мутировал → `render()`
|
|
|
|
### Фаза 1 — Файлы
|
|
- `fileQueue` → `state.files`
|
|
- `renderTable` → `renderFiles(state)`
|
|
- `fileInput change` разбит:
|
|
- `onFilesSelected(files)` — оркестратор (10-15 строк)
|
|
- `reconcileSelection(newFiles)` — чистая мутация массива
|
|
- `addZipFiles(file)` — async-action
|
|
- `addRegularFile(file)` — async-action
|
|
- `applyParseResult(entry, parsed)` — чистая функция (убирает дубль zip/обычной ветки)
|
|
- `finalizeUpload()` — refreshSupps + stepDone + syncDB + кнопки
|
|
- `status` → структура `{ kind, pct, text, count }`, HTML через `statusToHTML(status)`
|
|
- Убрать `renderTable()` из обработчиков — только `render()` в конце action
|
|
|
|
### Фаза 2 — Группы
|
|
- `loadGroups` → `loadGroupsAction()` (fetch → state.groups) + `renderGroups(state)`
|
|
- `buildGroupCard` → `renderGroupCard(group)` — чистая HTML-функция
|
|
- **`markGroupDone` удалить** — готовность = `state.groups[gi].compare.status === 'done'`
|
|
- Убрать `window._groupsData`
|
|
- `renderGroups` пересобирает ВСЕ карточки целиком
|
|
|
|
### Фаза 3 — Сравнение/SSE
|
|
- Унификация через общие функции:
|
|
- `applyCompareEvent(target, event)` — чистая мутация SSE-события в target
|
|
- `startCompareSSE(url, target, { onDone })` — жизненный цикл SSE
|
|
- `renderCompareSections(sections)` — общий рендер таблиц ops
|
|
- `runCompareForGroup` → тонкая обёртка над `startCompareSSE`
|
|
- `llmBtn` → тонкая обёртка над `startCompareSSE`
|
|
- ~160 строк дублирования → ~60 строк общих + 2 вызова
|
|
|
|
### Фаза 4 — Чистка
|
|
- `contractId`, `batchId` → `state`
|
|
- Степпер → `renderStepper(state)`
|
|
- Промпты, чат, showText — вне scope (отдельная итерация)
|
|
|
|
## Верификация
|
|
|
|
1. После каждой правки: `node -c` на app.js + app_utils.js
|
|
2. После Фазы 2: node-тесты на чистые функции (`applyParseResult`, `statusToHTML`, `renderGroupCard`)
|
|
3. Ручной прогон upload→classify→group→compare после git push
|
|
4. Регресс: «Готово» не перезапускается, параллельные сравнения заблокированы, группы не затираются
|
|
|
|
## Релевантные файлы
|
|
|
|
- `contractor/deploy/app.js` — все фазы (~1040 строк)
|
|
- `contractor/deploy/app_utils.js` — removeFile/moveUp/moveDown (Фаза 1)
|
|
- `contractor/index.cfm` — DOM-скелет, версия v1.0.177, `?v=` для cache bust
|