feat(ui): modern theme, dark/light toggle, polished templates

This commit is contained in:
2025-09-03 11:06:14 +03:00
commit 71c87979c0
16 changed files with 753 additions and 0 deletions

129
app/routes.py Normal file
View File

@@ -0,0 +1,129 @@
from __future__ import annotations
import datetime as dt
from typing import Dict, Any
from flask import Blueprint, current_app, render_template, request, redirect, url_for, jsonify, flash
from .clients.zammad import ZammadClient, ZammadError
from .services import aggregations as agg
web_bp = Blueprint("web", __name__)
def _client() -> ZammadClient:
return ZammadClient(current_app.config["ZAMMAD_URL"], current_app.config["ZAMMAD_TOKEN"])
@web_bp.route("/")
def index():
return render_template(
"index.html",
default_date_from=current_app.config["DEFAULT_DATE_FROM"],
default_date_to=current_app.config["DEFAULT_DATE_TO"],
)
AVAILABLE_GROUPS: Dict[str, Dict[str, Any]] = {
"overview": {"title": "Общее количество тикетов"},
"by_agent": {"title": "Тикеты по агентам"},
"by_group": {"title": "Тикеты по группам"},
"by_state": {"title": "Тикеты по статусам"},
"open_closed": {"title": "Открытые vs Закрытые"},
"by_priority": {"title": "Тикеты по приоритетам"},
"by_day": {"title": "Динамика: тикеты по дням"},
}
@web_bp.get("/report")
def report():
date_from = request.args.get("date_from")
date_to = request.args.get("date_to")
group_by = request.args.get("group_by", "overview")
if not date_from or not date_to:
flash("Укажите период дат.", "warning")
return redirect(url_for("web:index"))
if group_by not in AVAILABLE_GROUPS:
group_by = "overview"
z = _client()
try:
tickets = z.search_tickets(date_from, date_to)
except ZammadError as e:
flash(f"Ошибка Zammad API: {e}", "danger")
return redirect(url_for("web:index"))
overview = agg.summarize_overview(tickets, z)
# Determine main chart
if group_by == "by_agent":
data = agg.by_agent(tickets, z)
elif group_by == "by_group":
data = agg.by_group(tickets, z)
elif group_by == "by_state":
data = agg.by_state(tickets, z)
elif group_by == "open_closed":
data = agg.by_open_closed(tickets, z)
elif group_by == "by_priority":
data = agg.by_priority(tickets, z)
elif group_by == "by_day":
data = agg.by_day_created(tickets)
else:
data = {"Всего": overview["total"]}
labels, values = agg.dict_to_series(data)
title = AVAILABLE_GROUPS[group_by]["title"]
# Supplemental tables
by_agent = sorted(agg.by_agent(tickets, z).items(), key=lambda x: x[1], reverse=True)
by_group = sorted(agg.by_group(tickets, z).items(), key=lambda x: x[1], reverse=True)
return render_template(
"report.html",
date_from=date_from,
date_to=date_to,
group_by=group_by,
title=title,
chart_labels=labels,
chart_values=values,
overview=overview,
by_agent=by_agent,
by_group=by_group,
)
@web_bp.get("/api/report.json")
def report_json():
date_from = request.args.get("date_from")
date_to = request.args.get("date_to")
group_by = request.args.get("group_by", "overview")
if not date_from or not date_to:
return jsonify({"error": "date_from and date_to are required"}), 400
z = _client()
tickets = z.search_tickets(date_from, date_to)
overview = agg.summarize_overview(tickets, z)
payload: Dict[str, Any] = {"overview": overview}
payload.update(
{
"by_agent": agg.by_agent(tickets, z),
"by_group": agg.by_group(tickets, z),
"by_state": agg.by_state(tickets, z),
"open_closed": agg.by_open_closed(tickets, z),
"by_priority": agg.by_priority(tickets, z),
"by_day": agg.by_day_created(tickets),
}
)
# Optional: main_group result
if group_by in payload:
labels, values = agg.dict_to_series(payload[group_by])
payload["chart"] = {"labels": labels, "values": values}
return jsonify(payload)