morozovka-portal/app/modules/onboarding_routes.py

108 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Онбординг: пошаговый тур по разделам для новых пользователей."""
from datetime import datetime, timezone
from flask import render_template, request, redirect, url_for, session, jsonify
from sqlmodel import Session, select
from ..db import engine
from ..models import UserOnboarding
from ..helpers import login_required
STEPS = [
{"key": "visited_schedule", "num": 1, "icon": "📅", "title": "Расписание",
"url": "/schedule",
"desc": "Календарь занятий, статус Никиты Сергеевича, ассистент. Спроси «Где сейчас?» — ответит."},
{"key": "visited_projects", "num": 2, "icon": "🗂", "title": "Проекты",
"url": "/projects",
"desc": "Каталог проектов команды: карточки, участники, канбан-доски, лента новостей."},
{"key": "visited_workshops", "num": 3, "icon": "🎓", "title": "Мастер-классы",
"url": "/workshops",
"desc": "Пайка, embedded, Linux, творчество. Регистрация на занятия, материалы, посещаемость."},
{"key": "visited_files", "num": 4, "icon": "📁", "title": "Файлы",
"url": "/files",
"desc": "Единое хранилище документов. Загружай PDF, картинки, схемы — коллеги скачают."},
{"key": "visited_feed", "num": 5, "icon": "📰", "title": "Лента",
"url": "/feed",
"desc": "Все новости из проектов и мастер-классов в одном потоке. Поиск и фильтры."},
{"key": "visited_profile", "num": 6, "icon": "👤", "title": "Профиль",
"url": "/profile",
"desc": "Подписки на уведомления, активные сессии, безопасность."},
]
_TOTAL = len(STEPS)
def ensure_onboarding(db_session, user_id):
"""Создать запись прогресса, если её нет. Вернуть объект."""
if not user_id:
return None
ob = db_session.exec(select(UserOnboarding).where(UserOnboarding.user_id == user_id)).first()
if not ob:
ob = UserOnboarding(user_id=user_id)
db_session.add(ob)
db_session.commit()
db_session.refresh(ob)
return ob
def _progress(ob):
completed = sum(1 for s in STEPS if getattr(ob, s["key"]))
return completed, _TOTAL
def register(app):
@app.route("/onboarding")
@login_required
def onboarding_page():
user_id = session.get("user_id")
with Session(engine) as db_session:
ob = ensure_onboarding(db_session, user_id)
completed, total = _progress(ob)
return render_template("onboarding.html", steps=STEPS,
ob=ob, completed=completed, total=total,
just_done=request.args.get("done") == "1")
@app.route("/onboarding/mark", methods=["POST"])
@login_required
def onboarding_mark():
data = request.get_json(silent=True) or {}
key = data.get("key")
value = bool(data.get("value", True))
if key not in {s["key"] for s in STEPS}:
return jsonify({"ok": False, "error": "bad_key"}), 400
user_id = session.get("user_id")
with Session(engine) as db_session:
ob = ensure_onboarding(db_session, user_id)
setattr(ob, key, value)
all_done = all(getattr(ob, s["key"]) for s in STEPS)
if all_done and ob.completed_at is None:
ob.completed_at = datetime.now(timezone.utc)
elif not all_done:
ob.completed_at = None
db_session.add(ob)
db_session.commit()
completed, total = _progress(ob)
is_complete = ob.completed_at is not None
return jsonify({"ok": True, "completed": completed, "total": total,
"is_complete": is_complete})
@app.route("/onboarding/skip", methods=["POST"])
@login_required
def onboarding_skip():
"""Пропустить тур (не отмечать ничего)."""
return redirect(url_for("index", skip_tour="1"))
@app.route("/onboarding/complete", methods=["POST"])
@login_required
def onboarding_complete():
"""Отметить всё сразу — для тех, кто уже разобрался."""
user_id = session.get("user_id")
with Session(engine) as db_session:
ob = ensure_onboarding(db_session, user_id)
for s in STEPS:
setattr(ob, s["key"], True)
ob.completed_at = datetime.now(timezone.utc)
db_session.add(ob)
db_session.commit()
return redirect(url_for("onboarding_page", done="1"))