1
0
Fork 0
mirror of https://github.com/EDeev/tablo.git synced 2026-10-07 20:49:31 +03:00
tablo/app/templates/schedule/view.html
2026-05-19 13:49:09 +03:00

453 lines
24 KiB
HTML
Raw Permalink 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.

{% 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 %}