mirror of
https://github.com/EDeev/tablo.git
synced 2026-10-07 20:49:31 +03:00
453 lines
24 KiB
HTML
453 lines
24 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}{{ schedule.name }}{% endblock %}
|
||
|
||
{% block nav_extra %}{% endblock %}
|
||
|
||
{% block content %}
|
||
{% from "macros/metrics.html" import render_metric %}
|
||
|
||
{% set TYPE_SHORT = {
|
||
'Лекция': 'Лек',
|
||
'Практика': 'Пр',
|
||
'Лаб. работа': 'Лаб',
|
||
'Лабораторная': 'Лаб',
|
||
'Семинар': 'Сем',
|
||
'Не указано': ''
|
||
} %}
|
||
|
||
{# ── Шапка ── #}
|
||
<div class="t-ph">
|
||
<div class="t-ph-name-row">
|
||
<h1 class="t-ph-title schedule-name-display">{{ schedule.name }}</h1>
|
||
{% if can_edit and not is_shared_view %}
|
||
<button type="button" class="t-btn-icon edit-ctrl rename-schedule-btn" title="Переименовать расписание">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
<div class="t-ph-right">
|
||
{% if can_edit and not is_shared_view %}
|
||
<a class="t-btn t-btn-outline" href="{{ url_for('subjects.subjects_page', schedule_id=schedule.id) }}">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||
Предметы
|
||
</a>
|
||
{% endif %}
|
||
{% if can_edit and not is_shared_view %}
|
||
<button type="button" class="t-btn t-btn-outline" id="share-schedule-btn" data-schedule-id="{{ schedule.id }}">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
|
||
Поделиться
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
{# ══════════════════════════════ РАСПИСАНИЕ ══════════════════════════════ #}
|
||
<div class="t-grid" id="schedule-grid">
|
||
{% for i in range(7) %}
|
||
{% set day_slots = week_view[i] %}
|
||
{% if day_slots %}
|
||
<div class="t-day-card {{ 'today' if i == today_idx else '' }}">
|
||
<div class="t-day-card-head {{ 'today' if i == today_idx else '' }}">
|
||
<span class="t-day-name">{{ days_display[i] }}</span>
|
||
{% if i == today_idx %}<span class="t-badge-today">сегодня</span>{% endif %}
|
||
<span class="t-day-date">{{ week_dates[i].strftime('%d.%m') }}</span>
|
||
</div>
|
||
|
||
{% for slot in day_slots %}
|
||
{% set sid = 'se-' ~ i ~ '-' ~ loop.index0 %}
|
||
|
||
{% if slot.get('_group') %}
|
||
{# Несколько предметов в одно время — показываем все сразу (фильтруем скрытые) #}
|
||
{% set visible_slots = [] %}
|
||
{% for s in slot.slots %}
|
||
{% set _cfg = configs.get(s.subject) %}
|
||
{% if not (_cfg and _cfg.hidden) %}{% set _ = visible_slots.append(s) %}{% endif %}
|
||
{% endfor %}
|
||
{% if visible_slots %}
|
||
<div class="t-slot-group">
|
||
<div class="t-slot-group-time">
|
||
<span class="t-slot-time">{{ slot.time or '—' }}</span>
|
||
{% if visible_slots | length > 1 %}<span class="t-slot-group-badge">{{ visible_slots | length }} предмета</span>{% endif %}
|
||
</div>
|
||
<div class="t-slot-group-items">
|
||
{% for s in visible_slots %}
|
||
<div class="t-slot-group-item">
|
||
<div class="min-w-0 flex-1">
|
||
<span class="t-slot-name">{{ s.subject }}</span>
|
||
{% if s.classroom %}
|
||
<div class="t-slot-room">
|
||
<svg width="9" height="9" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||
{{ s.classroom }}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% set t_short = TYPE_SHORT.get(s.type, s.type[:3] if s.type and s.type|length > 4 else s.type) %}
|
||
{% if t_short %}
|
||
<span class="t-type t-type-{{ s.color or 'gray' }}">{{ t_short }}</span>
|
||
{% endif %}
|
||
<div class="edit-ctrl t-slot-edit-controls">
|
||
<button type="button" class="t-btn-icon danger slot-delete-btn" title="Удалить"
|
||
data-schedule="{{ schedule.id }}" data-subject="{{ s.subject }}"
|
||
data-day="{{ s.day }}" data-time="{{ s.time }}"
|
||
data-type="{{ s.type_name }}" data-daterange="{{ s.date_range }}">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4h6v2"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}{# visible_slots #}
|
||
|
||
{% else %}
|
||
{# Обычный слот — пропустить если предмет скрыт #}
|
||
{% set _slot_cfg = configs.get(slot.subject) %}
|
||
{% if not (_slot_cfg and _slot_cfg.hidden) %}
|
||
{% set merged_type = '/' in (slot.type or '') %}
|
||
<div class="t-slot" id="slot-{{ sid }}" data-slot-id="{{ sid }}">
|
||
<div class="t-slot-time">{{ slot.time or '—' }}</div>
|
||
<div class="t-slot-body">
|
||
<div class="t-slot-name">{{ slot.subject }}</div>
|
||
{% if slot.classroom %}
|
||
<div class="t-slot-room">
|
||
<svg width="9" height="9" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||
{{ slot.classroom }}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="t-slot-right">
|
||
{% set t_short = TYPE_SHORT.get(slot.type, slot.type[:3] if slot.type and slot.type|length > 4 else slot.type) %}
|
||
{% if t_short and slot.type != 'Не указано' %}
|
||
<span class="t-type t-type-{{ slot.color or 'gray' }}">{{ t_short }}</span>
|
||
{% endif %}
|
||
{% if not merged_type %}
|
||
<div class="edit-ctrl t-slot-edit-controls">
|
||
<button type="button" class="t-btn-icon slot-edit-time-btn" title="Изменить время"
|
||
data-schedule="{{ schedule.id }}" data-subject="{{ slot.subject }}"
|
||
data-day="{{ slot.day }}" data-time="{{ slot.time }}"
|
||
data-type="{{ slot.type_name }}" data-daterange="{{ slot.date_range }}">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||
</button>
|
||
<button type="button" class="t-btn-icon danger slot-delete-btn" title="Удалить слот"
|
||
data-schedule="{{ schedule.id }}" data-subject="{{ slot.subject }}"
|
||
data-day="{{ slot.day }}" data-time="{{ slot.time }}"
|
||
data-type="{{ slot.type_name }}" data-daterange="{{ slot.date_range }}">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4h6v2"/></svg>
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
<span class="t-chevron">▾</span>
|
||
</div>
|
||
</div>
|
||
<div class="t-slot-detail" id="detail-{{ sid }}">
|
||
{% if slot.teacher %}
|
||
<div class="t-slot-teacher">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||
{{ slot.teacher }}
|
||
</div>
|
||
{% endif %}
|
||
{% if slot.date_range and slot.date_range != 'Весь период' %}
|
||
<div class="t-slot-teacher">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
||
{{ slot.date_range }}
|
||
</div>
|
||
{% endif %}
|
||
{% set cfg = configs.get(slot.subject) %}
|
||
{% if cfg and cfg.completion_type %}
|
||
<div class="t-slot-teacher">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c3 3 9 3 12 0v-5"/></svg>
|
||
{{ cfg.completion_type }}
|
||
{% if cfg.completed %}<span class="t-slot-completed-badge">Завершён</span>{% endif %}
|
||
</div>
|
||
{% elif cfg and cfg.completed %}
|
||
<div class="t-slot-teacher">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>
|
||
<span class="t-slot-completed-badge">Завершён</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if cfg and cfg.sdo_url %}
|
||
<div class="t-slot-teacher">
|
||
<a href="{{ cfg.sdo_url }}" target="_blank" rel="noopener noreferrer" class="t-slot-sdo-btn">
|
||
<svg width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||
Открыть в СДО
|
||
</a>
|
||
</div>
|
||
{% endif %}
|
||
{% if cfg and cfg.description %}
|
||
<div class="t-slot-desc">{{ cfg.description }}</div>
|
||
{% endif %}
|
||
{% set metrics = cfg.metrics | list if cfg else [] %}
|
||
{% if metrics %}
|
||
<div class="t-slot-metrics">
|
||
{% for m in metrics %}
|
||
<a href="#metric-{{ m.id }}" class="t-slot-metric-link" data-metric-id="{{ m.id }}">{{ m.label }}</a>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="text-muted fs-small t-desc-italic">Трекеры не добавлены</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}{# not hidden #}
|
||
{% endif %}{# _group / regular slot #}
|
||
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</div>
|
||
|
||
{# ══════════════════════════════ ДЭШБОРД ══════════════════════════════ #}
|
||
{% if all_metrics %}
|
||
{# Вычисляем сколько трекеров заполнены #}
|
||
{% set completed_count = namespace(n=0) %}
|
||
{% set filled_ids = [] %}
|
||
{% set total_count = all_metrics | length %}
|
||
{% for entry in all_metrics %}
|
||
{% set m = entry.metric %}
|
||
{% set prog = m.progress or {} %}
|
||
{% set cfg_m = m.config or {} %}
|
||
{% set is_full = false %}
|
||
{% if m.type == 'checkpoints' %}
|
||
{% set is_full = prog.get('checked', []) | length >= cfg_m.get('total', 1) | int %}
|
||
{% elif m.type == 'progress_bar' %}
|
||
{% set is_full = prog.get('current', 0) | float >= cfg_m.get('max', 1) | float %}
|
||
{% elif m.type == 'counter' %}
|
||
{% set is_full = cfg_m.get('max') and prog.get('current', 0) | int >= cfg_m.get('max') | int %}
|
||
{% elif m.type == 'checklist' %}
|
||
{% set is_full = cfg_m.get('items', []) | length > 0 and prog.get('checked', []) | length >= cfg_m.get('items', []) | length %}
|
||
{% elif m.type == 'deadlines' %}
|
||
{% set is_full = cfg_m.get('items', []) | length > 0 and prog.get('completed', []) | length >= cfg_m.get('items', []) | length %}
|
||
{% elif m.type == 'stages' %}
|
||
{% set is_full = cfg_m.get('stages', []) | length > 0 and prog.get('current_stage', 0) | int >= cfg_m.get('stages', []) | length %}
|
||
{% elif m.type == 'effort_hours' %}
|
||
{% set is_full = cfg_m.get('goal_hours') and prog.get('hours', 0) | float >= cfg_m.get('goal_hours') | float %}
|
||
{% endif %}
|
||
{% if is_full %}{% set completed_count.n = completed_count.n + 1 %}{% endif %}
|
||
{% endfor %}
|
||
|
||
<div class="t-dash-title-row" id="dashboard-section">
|
||
<span class="t-section-title t-section-title-inline">Трекеры</span>
|
||
<div class="t-dash-title-actions">
|
||
{% if completed_count.n > 0 %}
|
||
<button type="button" class="t-btn t-btn-ghost t-btn-xs" id="toggle-filled-btn"
|
||
data-showing="false" title="Показать/скрыть заполненные трекеры">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
|
||
Показать заполненные ({{ completed_count.n }})
|
||
</button>
|
||
{% endif %}
|
||
{% if can_edit and not is_shared_view %}
|
||
<button type="button" class="t-grid-edit-btn" id="dash-edit-btn"
|
||
data-grid="dashboard-grid" title="Редактировать расположение">
|
||
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
|
||
Расположение
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="t-metrics-grid" id="dashboard-grid"
|
||
data-context="dashboard" data-schedule="{{ schedule.id }}">
|
||
{% for entry in all_metrics %}
|
||
{% set m = entry.metric %}
|
||
{% set prog = m.progress or {} %}
|
||
{% set cfg_m = m.config or {} %}
|
||
{% set is_full = false %}
|
||
{% if m.type == 'checkpoints' %}
|
||
{% set is_full = prog.get('checked', []) | length >= cfg_m.get('total', 1) | int and cfg_m.get('total', 0) | int > 0 %}
|
||
{% elif m.type == 'progress_bar' %}
|
||
{% set is_full = prog.get('current', 0) | float >= cfg_m.get('max', 1) | float and cfg_m.get('max', 0) | float > 0 %}
|
||
{% elif m.type == 'counter' %}
|
||
{% set is_full = cfg_m.get('max') and prog.get('current', 0) | int >= cfg_m.get('max') | int %}
|
||
{% elif m.type == 'checklist' %}
|
||
{% set is_full = cfg_m.get('items', []) | length > 0 and prog.get('checked', []) | length >= cfg_m.get('items', []) | length %}
|
||
{% elif m.type == 'deadlines' %}
|
||
{% set is_full = cfg_m.get('items', []) | length > 0 and prog.get('completed', []) | length >= cfg_m.get('items', []) | length %}
|
||
{% elif m.type == 'stages' %}
|
||
{% set is_full = cfg_m.get('stages', []) | length > 0 and prog.get('current_stage', 0) | int >= cfg_m.get('stages', []) | length %}
|
||
{% elif m.type == 'effort_hours' %}
|
||
{% set is_full = cfg_m.get('goal_hours') and prog.get('hours', 0) | float >= cfg_m.get('goal_hours') | float %}
|
||
{% endif %}
|
||
{% if is_full %}{% set _ = filled_ids.append(m.id) %}{% endif %}
|
||
{{ render_metric(m, can_edit) }}
|
||
{% endfor %}
|
||
</div>
|
||
{% if filled_ids %}
|
||
<script>
|
||
[{{ filled_ids | join(', ') }}].forEach(id => {
|
||
const el = document.getElementById('metric-' + id);
|
||
if (el) el.classList.add('metric-filled-hidden');
|
||
});
|
||
</script>
|
||
{% endif %}
|
||
{% endif %}
|
||
|
||
|
||
|
||
{# ══ Модальное окно шаринга ══ #}
|
||
{% if can_edit and not is_shared_view %}
|
||
<div class="t-modal-backdrop" id="shareModalBackdrop" role="dialog" aria-modal="true" aria-label="Поделиться расписанием">
|
||
<div class="t-modal">
|
||
<div class="t-modal-header">
|
||
<span class="t-modal-title">Поделиться расписанием</span>
|
||
<button type="button" class="t-btn-icon t-modal-close-btn" onclick="closeModal('shareModalBackdrop')" title="Закрыть">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="t-modal-body">
|
||
<div class="t-field">
|
||
<label class="t-label" for="link-view">Ссылка для просмотра</label>
|
||
<div class="t-input-group">
|
||
<input type="text" class="t-input" id="link-view" readonly aria-label="Ссылка для просмотра">
|
||
<button type="button" class="t-btn t-btn-outline t-share-copy-btn copy-btn" data-target="link-view" title="Скопировать ссылку">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="t-field">
|
||
<label class="t-label" for="link-template">Ссылка-шаблон</label>
|
||
<div class="t-input-group">
|
||
<input type="text" class="t-input" id="link-template" readonly aria-label="Ссылка-шаблон">
|
||
<button type="button" class="t-btn t-btn-outline t-share-copy-btn copy-btn" data-target="link-template" title="Скопировать ссылку">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<hr class="t-divider">
|
||
<div class="t-field">
|
||
<label class="t-label" for="editor-login">Добавить редактора</label>
|
||
<div class="t-input-group">
|
||
<input type="text" class="t-input" id="editor-login" placeholder="Логин пользователя" aria-label="Логин редактора">
|
||
<button type="button" class="t-btn t-btn-primary" id="add-editor-btn" title="Добавить редактора" class="t-btn-group-end">Добавить</button>
|
||
</div>
|
||
</div>
|
||
<div id="editor-link-wrap" hidden class="t-field">
|
||
<label class="t-label" for="link-edit">Ссылка для редактора</label>
|
||
<div class="t-input-group">
|
||
<input type="text" class="t-input" id="link-edit" readonly aria-label="Ссылка для редактора">
|
||
<button type="button" class="t-btn t-btn-outline t-share-copy-btn copy-btn" data-target="link-edit" title="Скопировать ссылку">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# ══ Модальное окно переименования ══ #}
|
||
<div class="t-modal-backdrop" id="renameModalBackdrop" role="dialog" aria-modal="true" aria-labelledby="rename-modal-title">
|
||
<div class="t-modal t-modal-sm">
|
||
<div class="t-modal-header">
|
||
<span class="t-modal-title" id="rename-modal-title">Переименовать</span>
|
||
<button type="button" class="t-btn-icon t-modal-close-btn" onclick="closeModal('renameModalBackdrop')" title="Закрыть">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="t-modal-body">
|
||
<label class="t-label" for="rename-input">Новое название</label>
|
||
<input type="text" class="t-input" id="rename-input" aria-label="Новое название">
|
||
</div>
|
||
<div class="t-modal-footer">
|
||
<button type="button" class="t-btn t-btn-outline" onclick="closeModal('renameModalBackdrop')">Отмена</button>
|
||
<button type="button" class="t-btn t-btn-primary" id="rename-confirm-btn">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# ══ Модальное окно изменения времени ══ #}
|
||
<div class="t-modal-backdrop" id="slotTimeModalBackdrop" role="dialog" aria-modal="true" aria-label="Изменить время слота">
|
||
<div class="t-modal t-modal-sm">
|
||
<div class="t-modal-header">
|
||
<span class="t-modal-title">Изменить время слота</span>
|
||
<button type="button" class="t-btn-icon t-modal-close-btn" onclick="closeModal('slotTimeModalBackdrop')" title="Закрыть">
|
||
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="t-modal-body">
|
||
<label class="t-label" for="slot-time-input">Время (формат 09:00-10:30)</label>
|
||
<input type="text" class="t-input" id="slot-time-input" placeholder="09:00-10:30" aria-label="Время слота">
|
||
</div>
|
||
<div class="t-modal-footer">
|
||
<button type="button" class="t-btn t-btn-outline" onclick="closeModal('slotTimeModalBackdrop')">Отмена</button>
|
||
<button type="button" class="t-btn t-btn-primary" id="slot-time-save-btn">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script src="{{ url_for('static', filename='js/schedule.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/subjects.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/metrics.js') }}"></script>
|
||
<script>
|
||
const SCHEDULE_ID = {{ schedule.id }};
|
||
const CAN_EDIT = {{ 'true' if can_edit else 'false' }};
|
||
|
||
/* ── Заполненные трекеры: показать/скрыть ── */
|
||
(function() {
|
||
const btn = document.getElementById('toggle-filled-btn');
|
||
if (!btn) return;
|
||
const filled = document.querySelectorAll('.t-metric.metric-filled-hidden');
|
||
const count = filled.length;
|
||
let showing = false;
|
||
btn.addEventListener('click', () => {
|
||
showing = !showing;
|
||
filled.forEach(el => el.classList.toggle('metric-filled-hidden', !showing));
|
||
btn.innerHTML = showing
|
||
? `<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg> Скрыть заполненные (${count})`
|
||
: `<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg> Показать заполненные (${count})`;
|
||
});
|
||
})();
|
||
|
||
/* ── Слоты: разворот ── */
|
||
document.querySelectorAll('.t-slot').forEach(slot => {
|
||
slot.addEventListener('click', e => {
|
||
if (e.target.closest('button')) return;
|
||
const sid = slot.dataset.slotId;
|
||
const detail = document.getElementById('detail-' + sid);
|
||
if (!detail) return;
|
||
const isOpen = slot.classList.contains('open');
|
||
slot.classList.toggle('open', !isOpen);
|
||
detail.style.display = isOpen ? 'none' : 'block';
|
||
});
|
||
});
|
||
|
||
/* ── Модальные окна: закрытие по клику на backdrop ── */
|
||
document.querySelectorAll('.t-modal-backdrop').forEach(bd => {
|
||
bd.addEventListener('click', e => { if (e.target === bd) bd.classList.remove('open'); });
|
||
});
|
||
|
||
/* ── Кнопки копирования ── */
|
||
document.querySelectorAll('.copy-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
navigator.clipboard.writeText(document.getElementById(btn.dataset.target).value);
|
||
const orig = btn.innerHTML;
|
||
btn.innerHTML = '<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
|
||
setTimeout(() => btn.innerHTML = orig, 1500);
|
||
});
|
||
});
|
||
</script>
|
||
|
||
{% if can_edit and not is_shared_view %}
|
||
<script>
|
||
document.getElementById('share-schedule-btn')?.addEventListener('click', async () => {
|
||
for (const type of ['view', 'template']) {
|
||
const res = await apiFetch(`/schedules/${SCHEDULE_ID}/share`, 'POST', { type });
|
||
if (res.ok)
|
||
document.getElementById(`link-${type}`).value = `${location.origin}/shared/${res.token}`;
|
||
}
|
||
openModal('shareModalBackdrop');
|
||
});
|
||
|
||
document.getElementById('add-editor-btn')?.addEventListener('click', async () => {
|
||
const login = document.getElementById('editor-login').value.trim();
|
||
if (!login) return;
|
||
const res = await apiFetch(`/schedules/${SCHEDULE_ID}/share/editors`, 'POST', { login });
|
||
if (res.ok) {
|
||
document.getElementById('link-edit').value = `${location.origin}/shared/${res.token}`;
|
||
document.getElementById('editor-link-wrap').hidden = false;
|
||
} else alert(res.error || 'Ошибка');
|
||
});
|
||
</script>
|
||
{% endif %}
|
||
{% endblock %}
|