Загрузка данных
{% extends "base.html" %}
{% block title %}{{ intern.full_name }}{% endblock %}
{% block content %}
<!-- Шапка профиля стажёра -->
<div class="d-flex justify-content-between align-items-start mb-3 flex-wrap gap-2">
<div>
<a href="{{ url_for('interns.list_interns') }}" class="btn btn-sm btn-outline-secondary mb-2">
<i class="bi bi-arrow-left"></i> К списку
</a>
<h4 class="mb-1">
{{ intern.full_name }}
{% if intern.internship_type == 'online' %}
<span class="badge bg-info fs-6 align-middle ms-2">Дистанционно</span>
{% else %}
<span class="badge bg-secondary fs-6 align-middle ms-2">Очно</span>
{% endif %}
</h4>
<small class="text-muted">
{{ intern.direction or 'Направление не указано' }} · Наставник: {{ intern.mentor.full_name if intern.mentor else '—' }}
</small>
</div>
<div class="d-flex gap-2">
<a class="btn btn-success" href="{{ url_for('reports.export_excel', date_from=date_from, date_to=date_to) }}">
<i class="bi bi-file-earmark-excel"></i> Excel
</a>
{% if current_user.is_center %}
<form method="post" action="{{ url_for('interns.archive_intern', intern_id=intern.id) }}" onsubmit="return confirm('Отправить стажёра в архив?');">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-archive"></i> В архив
</button>
</form>
{% endif %}
</div>
</div>
<!-- Карточки со статистикой за 30 дней -->
<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="card stat-card h-100"><div class="card-body">
<div class="text-muted small">Посещаемость (30 дн.)</div>
<div class="fs-3 fw-bold text-primary">{{ stats_period.rate }}%</div>
</div></div>
</div>
<div class="col-6 col-md-3">
<div class="card stat-card h-100"><div class="card-body">
<div class="text-muted small">Присутствовал</div>
<div class="fs-4 fw-bold text-success">{{ stats_period.present }}</div>
</div></div>
</div>
<div class="col-6 col-md-3">
<div class="card stat-card h-100"><div class="card-body">
<div class="text-muted small">Опоздал / Отсутствовал</div>
<div class="fs-4 fw-bold">
<span class="text-warning">{{ stats_period.late }}</span> /
<span class="text-danger">{{ stats_period.absent }}</span>
</div>
</div></div>
</div>
<div class="col-6 col-md-3">
<div class="card stat-card h-100"><div class="card-body">
<div class="text-muted small">Всего отметок (за всё время)</div>
<div class="fs-4 fw-bold">{{ stats_all.total }}</div>
</div></div>
</div>
</div>
<div class="row mb-4">
<!-- График посещаемости -->
<div class="col-md-5">
<div class="card stat-card h-100">
<div class="card-body">
<h6 class="card-title mb-3">Распределение статусов (всё время)</h6>
<div style="height: 220px; position: relative;">
<canvas id="attendanceChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Календарь -->
<div class="col-md-7">
<div class="card stat-card h-100">
<div class="card-body">
<!-- Навигация по календарю (стрелки) -->
<div class="d-flex justify-content-between align-items-center mb-3">
<a class="btn btn-outline-primary btn-sm" href="?year={{ prev_year }}&month={{ prev_month }}">
<i class="bi bi-chevron-left"></i> {{ MONTH_NAMES[prev_month] }}
</a>
<h5 class="mb-0">{{ MONTH_NAMES[month] }} {{ year }}</h5>
<a class="btn btn-outline-primary btn-sm" href="?year={{ next_year }}&month={{ next_month }}">
{{ MONTH_NAMES[next_month] }} <i class="bi bi-chevron-right"></i>
</a>
</div>
<!-- Пресеты по месяцам -->
<div class="mb-3 d-flex flex-wrap gap-1 justify-content-center">
{% for m in range(1, 13) %}
<a href="?year={{ year }}&month={{ m }}"
class="btn btn-sm {{ 'btn-primary' if m == month else 'btn-outline-secondary' }} px-2 py-1"
style="font-size: 0.75rem;">
{{ MONTH_NAMES[m][:3] }}
</a>
{% endfor %}
</div>
<!-- Сетка календаря -->
<div class="table-responsive">
<table class="table table-bordered calendar-table mb-0">
<thead>
<tr class="text-center table-light">
<th>Пн</th><th>Вт</th><th>Ср</th><th>Чт</th><th>Пт</th>
<th class="text-muted">Сб</th><th class="text-muted">Вс</th>
</tr>
</thead>
<tbody>
{% for week in weeks %}
<tr>
{% for day in week %}
{% set is_current = (day.month == month) %}
{% set record = att_map.get(day.day) if is_current else None %}
<td class="{{ '' if is_current else 'empty' }}
{% if is_current %}calendar-cell{% endif %}
{% if is_current and day == today %}today{% endif %}"
{% if is_current %}
data-date="{{ day.isoformat() }}"
data-intern="{{ intern.id }}"
data-status="{{ record.status if record else '' }}"
{% endif %}>
{% if is_current %}
<div class="d-flex justify-content-between align-items-start">
<span class="day-number">{{ day.day }}</span>
{% if record %}
<span class="badge bg-{{ STATUS_COLORS[record.status] }} status-badge" title="{{ STATUS_LABELS[record.status] }}">
{{ record.status[0]|upper }}
</span>
{% endif %}
</div>
{% if record and record.note %}
<div class="note text-truncate" title="{{ record.note }}">{{ record.note }}</div>
{% endif %}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Легенда -->
<div class="mt-3 d-flex flex-wrap gap-2 small text-muted">
{% for key, label in STATUS_LABELS.items() %}
<div><span class="badge bg-{{ STATUS_COLORS[key] }}"> </span> {{ label }}</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<!-- Модалка отметки -->
<div class="modal fade" id="markModal" tabindex="-1">
<div class="modal-dialog">
<form method="post" action="{{ url_for('attendance.mark') }}" class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Отметка посещаемости</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="intern_id" value="{{ intern.id }}">
<input type="hidden" name="date" id="markDate">
<p class="mb-3">Дата: <strong id="markDateLabel"></strong></p>
<div class="mb-3">
<label class="form-label">Статус</label>
<select name="status" id="markStatus" class="form-select" required>
<option value="present">Присутствовал</option>
<option value="absent">Отсутствовал</option>
<option value="late">Опоздал</option>
<option value="excused">Уважительная причина</option>
</select>
</div>
<div class="mb-2">
<label class="form-label">Заметка (необязательно)</label>
<input type="text" name="note" class="form-control" maxlength="255">
</div>
</div>
<div class="modal-footer justify-content-between">
<button type="submit" class="btn btn-outline-danger"
formaction="{{ url_for('attendance.clear') }}"
onclick="return confirm('Удалить отметку за этот день?')">
<i class="bi bi-trash"></i> Удалить
</button>
<div>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
<!-- Подключение Chart.js для графика -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// --- 1. Логика модального окна для календаря ---
const markModalEl = document.getElementById('markModal');
const markModal = new bootstrap.Modal(markModalEl);
const dateInput = document.getElementById('markDate');
const dateLabel = document.getElementById('markDateLabel');
const statusSelect = document.getElementById('markStatus');
document.querySelectorAll('.calendar-cell').forEach(cell => {
cell.addEventListener('click', () => {
const d = cell.dataset.date;
dateInput.value = d;
const [y, m, day] = d.split('-');
dateLabel.textContent = `${day}.${m}.${y}`;
const s = cell.dataset.status;
statusSelect.value = s || 'present';
markModal.show();
});
});
// --- 2. Логика графика Chart.js ---
const stats = {
present: {{ stats_all.present|default(0) }},
late: {{ stats_all.late|default(0) }},
absent: {{ stats_all.absent|default(0) }},
excused: {{ stats_all.excused|default(0) }}
};
const ctx = document.getElementById('attendanceChart').getContext('2d');
new Chart(ctx, {
type: 'doughnut', // Кольцевая диаграмма
data: {
labels: ['Присутствовал', 'Опоздал', 'Отсутствовал', 'Уважительная'],
datasets: [{
data: [stats.present, stats.late, stats.absent, stats.excused],
backgroundColor: ['#198754', '#ffc107', '#dc3545', '#0dcaf0'], // Цвета Bootstrap
borderWidth: 1,
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: {
boxWidth: 12,
font: { size: 11 }
}
}
},
cutout: '65%' // Делает диаграмму более тонкой и аккуратной
}
});
</script>
{% endblock %}