wip: student profile/report

This commit is contained in:
dev
2026-09-19 13:06:21 +03:00
parent 72eeb5cf9b
commit 1ef81f9d1c
10 changed files with 3269 additions and 480 deletions
+78
View File
@@ -0,0 +1,78 @@
# TODO: профиль ученика + ZIP-архив
## Задача
1. На `/students.html` у каждого ученика — кнопка **«Профиль»**, которая одним нажатием
отдаёт ZIP-архив со всеми данными ученика (фото, работы/файлы, записи журнала).
2. Сгенерированная страница `index.html` внутри архива должна быть оформлена
**полностью по дизайну `student-profile.html`** (тёмная/светлая тема, токены,
топбар, hero, статистика, образование, опыт, навыки, проекты, уроки, «о себе», футер).
3. Если в БД нет данных для заполнения блоков этой страницы — страница не должна
ломаться или показывать выдуманные данные: блоки рендерятся из того, что есть,
пустые разделы получают аккуратный empty-state, а **данные можно добавить позже**
(без правок кода).
## План работ
- [x] Разведка: `server.js` (экспорт `/api/export/student`, `createZipWriter`,
`renderStudentReport`), `public/students.html` + `public/js/students.js`,
`db/init.sql` + `db/migration.sql`, дизайн-макет `student-profile.html`.
- [x] `todo.md` (этот файл).
- [x] БД: у ученика появляются поля для будущего заполнения профиля —
`students.profile JSONB` и `students.photo_path` (`db/init.sql`, `db/migration.sql`).
- [x] Бэкап/восстановление: новые поля сохраняются и восстанавливаются
(`normalizeRestoreData`, `INSERT INTO students`).
- [x] API: `GET/PUT /api/students/:id/profile` (requireAuth + проверка филиала,
валидация структуры профиля, аудит `student.profile.update`).
- [x] Новый модуль `student-report.js`: ZIP-writer (перенесён из `server.js`) +
`renderStudentReport()` — страница профиля ученика по дизайну
`student-profile.html` (самодостаточный HTML+CSS+JS, Lucide через CDN с
fallback-заглушками, лайтбокс, ленты, тема, печать, анимации).
- [x] `server.js`: вызов рендера через модуль, убрать старый рендер из `server.js`,
расширить выборки экспорта (модули, статус ИИ, филиал, `students.profile`).
- [x] `public/students.html` + `public/js/students.js`: кнопка **«Профиль»**
(прямая выгрузка ZIP) и кнопка **«Данные»** (модальное окно заполнения данных
профиля: роль, статус, город, наставник, о себе, цитата, теги, контакты,
достижения, навыки, опыт, прогресс по модулям).
- [x] Архив: `index.html` + `photos/` + `files/` + `data.json` (метаданные профиля).
- [x] Проверка: `node --check`, генерация тестового ZIP и разбор `zipfile`
(структура + наличие разделов профиля в `index.html`).
## Решения и допущения
- «index.html» из задачи — это `index.html` внутри ZIP-архива (страница профиля
ученика). Именно он теперь оформлен по `student-profile.html`. Публичный
`public/index.html` (форма отправки записи) не переделывался: это отдельная
страница, и в задаче про неё данных нет.
- Кнопка называется «Профиль» (в задаче опечатка «Профаил»), подпись в подсказке:
«Скачать профиль ученика (ZIP: фото и работы)».
- Дизайн-макет использует Lucide-иконки через CDN; в архиве оставлен тот же подход,
потому что он полностью офлайн-деградирует (класс `no-icons`), как и в макете.
- Данные, которых нет в БД (опыт, навыки, контакты, достижения, прогресс по модулям,
фото), рендерятся из полей `students.profile`; пока их нет — показывается
empty-state с подсказкой, а заполнить их можно в админке («Данные» у ученика).
- В `renderStudentReport` добавлен раздел «Фотографии» (лента в стиле макета) —
без него фото из архива негде было бы посмотреть; ленты и лайтбокс — в стиле
`student-profile.html`.
## Как применить на существующей базе
```bash
docker compose exec -T db psql -U app -d whereldo \
-c "ALTER TABLE students ADD COLUMN IF NOT EXISTS photo_path VARCHAR(255);" \
-c "ALTER TABLE students ADD COLUMN IF NOT EXISTS profile JSONB;"
docker compose up -d --build app
```
(`db/migration.sql` уже содержит эти же ALTER-команды; на чистой базе `init.sql`
создаёт поля сразу.)
## Как проверить
```bash
# 1. Админка → «Ученики»: у каждого ученика кнопки «Профиль» и «Данные»
# 2. «Данные» → заполнить роль/био/навыки/контакты/достижения/опыт → Сохранить
# 3. «Профиль» → скачивается profile_<имя>_<дата>.zip:
# index.html (страница профиля по student-profile.html), photos/, files/, data.json
```