460 lines
14 KiB
HTML
460 lines
14 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Дашборд — Morozovka Portal{% endblock %}
|
||
{% block main_class %}container{% endblock %}
|
||
|
||
{% block head %}
|
||
<style>
|
||
.dashboard-hero {
|
||
background: linear-gradient(135deg, #161b22 0%, #1c2128 100%);
|
||
border: 1px solid #30363d;
|
||
border-radius: 12px;
|
||
padding: 24px 32px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.dashboard-hero h1 { margin: 0 0 6px; font-size: 24px; }
|
||
.dashboard-hero .greeting { color: #8b949e; font-size: 14px; }
|
||
|
||
.widgets {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.widget {
|
||
background: #161b22;
|
||
border: 1px solid #30363d;
|
||
border-radius: 10px;
|
||
padding: 0;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.widget-header {
|
||
padding: 14px 18px;
|
||
border-bottom: 1px solid #30363d;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: #1c2128;
|
||
}
|
||
.widget-header h3 {
|
||
margin: 0;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #e6edf3;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.widget-badge {
|
||
background: #30363d;
|
||
color: #e6edf3;
|
||
padding: 1px 8px;
|
||
border-radius: 10px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
}
|
||
.widget-badge.alert { background: #da3633; color: #fff; }
|
||
.widget-header a {
|
||
color: #58a6ff;
|
||
font-size: 12px;
|
||
text-decoration: none;
|
||
}
|
||
.widget-header a:hover { text-decoration: underline; }
|
||
.widget-body { padding: 8px 18px; flex: 1; }
|
||
.widget-empty {
|
||
padding: 24px 18px;
|
||
color: #8b949e;
|
||
font-size: 13px;
|
||
text-align: center;
|
||
}
|
||
|
||
/* Event item */
|
||
.dash-event {
|
||
display: flex;
|
||
gap: 12px;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #21262d;
|
||
}
|
||
.dash-event:last-child { border-bottom: none; }
|
||
.dash-event-time {
|
||
color: #58a6ff;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
min-width: 90px;
|
||
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||
}
|
||
.dash-event-body { flex: 1; min-width: 0; }
|
||
.dash-event-title {
|
||
color: #e6edf3;
|
||
font-weight: 500;
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.dash-event-meta { color: #8b949e; font-size: 11px; margin-top: 2px; }
|
||
|
||
/* Task item */
|
||
.dash-task {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: flex-start;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #21262d;
|
||
}
|
||
.dash-task:last-child { border-bottom: none; }
|
||
.dash-task-priority {
|
||
width: 4px;
|
||
height: 32px;
|
||
border-radius: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
.dash-task-priority.critical { background: #f85149; }
|
||
.dash-task-priority.high { background: #d29922; }
|
||
.dash-task-priority.medium { background: #58a6ff; }
|
||
.dash-task-priority.low { background: #8b949e; }
|
||
.dash-task-body { flex: 1; min-width: 0; }
|
||
.dash-task-title {
|
||
color: #e6edf3;
|
||
font-weight: 500;
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
text-decoration: none;
|
||
display: block;
|
||
}
|
||
.dash-task-title:hover { color: #58a6ff; text-decoration: none; }
|
||
.dash-task-meta { color: #8b949e; font-size: 11px; margin-top: 2px; }
|
||
|
||
/* Project item */
|
||
.dash-project {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #21262d;
|
||
text-decoration: none;
|
||
}
|
||
.dash-project:last-child { border-bottom: none; }
|
||
.dash-project:hover { text-decoration: none; }
|
||
.dash-project:hover .dash-project-title { color: #58a6ff; }
|
||
.dash-project-info { flex: 1; min-width: 0; }
|
||
.dash-project-title {
|
||
color: #e6edf3;
|
||
font-weight: 500;
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.dash-project-meta { color: #8b949e; font-size: 11px; margin-top: 2px; }
|
||
.dash-project-role {
|
||
font-size: 10px;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
flex-shrink: 0;
|
||
background: #30363d;
|
||
color: #c9d1d9;
|
||
}
|
||
|
||
/* Notification item */
|
||
.dash-notif {
|
||
display: block;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #21262d;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
}
|
||
.dash-notif:last-child { border-bottom: none; }
|
||
.dash-notif:hover { text-decoration: none; }
|
||
.dash-notif:hover .dash-notif-title { color: #58a6ff; }
|
||
.dash-notif-title { color: #e6edf3; font-size: 13px; font-weight: 500; }
|
||
.dash-notif-body { color: #8b949e; font-size: 12px; margin-top: 2px; }
|
||
.dash-notif-time { color: #6e7681; font-size: 11px; margin-top: 2px; }
|
||
|
||
/* Material item */
|
||
.dash-material {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: flex-start;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #21262d;
|
||
}
|
||
.dash-material:last-child { border-bottom: none; }
|
||
.dash-material-icon { font-size: 18px; flex-shrink: 0; }
|
||
.dash-material-body { flex: 1; min-width: 0; }
|
||
.dash-material-title { color: #e6edf3; font-size: 13px; font-weight: 500; }
|
||
.dash-material-title a { color: inherit; text-decoration: none; }
|
||
.dash-material-title a:hover { color: #58a6ff; text-decoration: none; }
|
||
.dash-material-meta { color: #8b949e; font-size: 11px; margin-top: 2px; }
|
||
|
||
/* Quick links */
|
||
.quick-links {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||
gap: 10px;
|
||
padding: 16px;
|
||
}
|
||
.quick-link {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
padding: 14px 8px;
|
||
background: #0d1117;
|
||
border: 1px solid #30363d;
|
||
border-radius: 8px;
|
||
color: #e6edf3;
|
||
text-decoration: none;
|
||
transition: all .15s;
|
||
font-size: 12px;
|
||
text-align: center;
|
||
}
|
||
.quick-link:hover {
|
||
border-color: #58a6ff;
|
||
background: #161b22;
|
||
text-decoration: none;
|
||
color: #58a6ff;
|
||
}
|
||
.quick-link-icon { font-size: 22px; }
|
||
|
||
/* Commit item */
|
||
.dash-commit-repo {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
color: #58a6ff;
|
||
padding: 4px 0;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.dash-commit-repo:not(:first-child) {
|
||
margin-top: 12px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid #21262d;
|
||
}
|
||
.dash-commit { padding: 4px 0; }
|
||
.dash-commit-sha {
|
||
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||
font-size: 11px;
|
||
color: #58a6ff;
|
||
background: rgba(88,166,255,0.1);
|
||
padding: 1px 6px;
|
||
border-radius: 3px;
|
||
margin-right: 4px;
|
||
}
|
||
.dash-commit-msg { color: #c9d1d9; font-size: 12px; }
|
||
.dash-commit-meta { color: #8b949e; font-size: 11px; margin-top: 2px; margin-left: 2px; }
|
||
|
||
@media (max-width: 768px) {
|
||
.dashboard-hero { padding: 16px 20px; }
|
||
.dashboard-hero h1 { font-size: 20px; }
|
||
.widgets { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="dashboard-hero">
|
||
<h1>Привет, {{ session.get('username') }}!</h1>
|
||
<div class="greeting">Твой персональный дашборд</div>
|
||
</div>
|
||
|
||
<div class="widgets">
|
||
|
||
<!-- 1. Ближайшие события -->
|
||
<div class="widget">
|
||
<div class="widget-header">
|
||
<h3>📅 Ближайшие события</h3>
|
||
<a href="/schedule">Все →</a>
|
||
</div>
|
||
{% if upcoming_events %}
|
||
<div class="widget-body">
|
||
{% for ev in upcoming_events %}
|
||
<div class="dash-event">
|
||
<div class="dash-event-time">{{ ev.start_time|msk_strftime('%d.%m %H:%M') }}</div>
|
||
<div class="dash-event-body">
|
||
<div class="dash-event-title">{{ ev.title }}</div>
|
||
{% if ev.location %}
|
||
<div class="dash-event-meta">📍 {{ ev.location }}</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="widget-empty">Ближайших событий нет</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- 2. Мои проекты -->
|
||
<div class="widget">
|
||
<div class="widget-header">
|
||
<h3>🗂 Мои проекты <span class="widget-badge">{{ my_projects|length }}</span></h3>
|
||
<a href="/projects">Все →</a>
|
||
</div>
|
||
{% if my_projects %}
|
||
<div class="widget-body">
|
||
{% for proj, member in my_projects %}
|
||
<a class="dash-project" href="/projects/{{ proj.id }}">
|
||
<div class="dash-project-info">
|
||
<div class="dash-project-title">{{ proj.title }}</div>
|
||
<div class="dash-project-meta">
|
||
{% if proj.status == 'idea' %}Идея
|
||
{% elif proj.status == 'in_progress' %}В работе
|
||
{% elif proj.status == 'completed' %}Завершён
|
||
{% else %}Заморожен{% endif %}
|
||
</div>
|
||
</div>
|
||
<span class="dash-project-role">{{ member.role }}</span>
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="widget-empty">Вы пока не в проектах</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- 3. Мои задачи -->
|
||
<div class="widget">
|
||
<div class="widget-header">
|
||
<h3>✅ Мои задачи <span class="widget-badge">{{ my_tasks|length }}</span></h3>
|
||
<a href="/projects">Все →</a>
|
||
</div>
|
||
{% if my_tasks %}
|
||
<div class="widget-body">
|
||
{% for task, proj in my_tasks %}
|
||
<div class="dash-task">
|
||
<div class="dash-task-priority {{ task.priority }}"></div>
|
||
<div class="dash-task-body">
|
||
<a class="dash-task-title" href="/projects/{{ proj.id }}/board">{{ task.title }}</a>
|
||
<div class="dash-task-meta">
|
||
{{ proj.title }}
|
||
{% if task.due_date %} · до {{ task.due_date|date_ru }}{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="widget-empty">Активных задач нет</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- 4. Непрочитанные уведомления -->
|
||
<div class="widget">
|
||
<div class="widget-header">
|
||
<h3>🔔 Уведомления {% if unread_count > 0 %}<span class="widget-badge alert">{{ unread_count }}</span>{% endif %}</h3>
|
||
<a href="/notifications">Все →</a>
|
||
</div>
|
||
{% if unread %}
|
||
<div class="widget-body">
|
||
{% for n in unread %}
|
||
<a class="dash-notif" href="{{ n.link_url or '/notifications' }}">
|
||
<div class="dash-notif-title">{{ n.title }}</div>
|
||
{% if n.body %}<div class="dash-notif-body">{{ n.body }}</div>{% endif %}
|
||
<div class="dash-notif-time">{{ n.created_at|msk_strftime('%d.%m.%Y %H:%M') }}</div>
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="widget-empty">Нет непрочитанных уведомлений</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- 5. Новые материалы МК -->
|
||
<div class="widget">
|
||
<div class="widget-header">
|
||
<h3>📚 Материалы МК <span class="widget-badge">{{ new_materials|length }}</span></h3>
|
||
<a href="/workshops">Все →</a>
|
||
</div>
|
||
{% if new_materials %}
|
||
<div class="widget-body">
|
||
{% for material, ws in new_materials %}
|
||
<div class="dash-material">
|
||
<div class="dash-material-icon">
|
||
{% if material.external_url %}🔗{% else %}📎{% endif %}
|
||
</div>
|
||
<div class="dash-material-body">
|
||
<div class="dash-material-title">
|
||
{% if material.external_url %}
|
||
<a href="{{ material.external_url }}" target="_blank">{{ material.title }}</a>
|
||
{% elif material.file_id %}
|
||
<a href="/files/download/{{ material.file_id }}">{{ material.title }}</a>
|
||
{% else %}
|
||
{{ material.title }}
|
||
{% endif %}
|
||
</div>
|
||
<div class="dash-material-meta">{{ ws.title }}</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="widget-empty">Пока нет новых материалов</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- 6. Gitea-коммиты (только если есть данные) -->
|
||
{% if gitea_widget %}
|
||
<div class="widget">
|
||
<div class="widget-header">
|
||
<h3>💻 Последние коммиты</h3>
|
||
<a href="/projects">Все →</a>
|
||
</div>
|
||
<div class="widget-body">
|
||
{% for repo in gitea_widget %}
|
||
<div class="dash-commit-repo">{{ repo.project_title }}</div>
|
||
{% for c in repo.commits %}
|
||
<div class="dash-commit">
|
||
<span class="dash-commit-sha">{{ c.sha_short }}</span>
|
||
<span class="dash-commit-msg">{{ c.message }}</span>
|
||
<div class="dash-commit-meta">{{ c.author }}{% if c.date %} · {{ c.date[:16].replace('T', ' ') }}{% endif %}</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- 7. Быстрые ссылки -->
|
||
<div class="widget">
|
||
<div class="widget-header">
|
||
<h3>⚡ Быстрые ссылки</h3>
|
||
</div>
|
||
<div class="quick-links">
|
||
<a href="/schedule" class="quick-link">
|
||
<span class="quick-link-icon">📅</span>
|
||
Расписание
|
||
</a>
|
||
<a href="/projects" class="quick-link">
|
||
<span class="quick-link-icon">🗂</span>
|
||
Проекты
|
||
</a>
|
||
<a href="/workshops" class="quick-link">
|
||
<span class="quick-link-icon">🎓</span>
|
||
Мастер-классы
|
||
</a>
|
||
<a href="/feed" class="quick-link">
|
||
<span class="quick-link-icon">📰</span>
|
||
Лента
|
||
</a>
|
||
<a href="/files" class="quick-link">
|
||
<span class="quick-link-icon">📁</span>
|
||
Файлы
|
||
</a>
|
||
<a href="/onboarding" class="quick-link">
|
||
<span class="quick-link-icon">🎯</span>
|
||
Тур
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
{% endblock %}
|