Group page: B3 fidelity (cell-main/cell-sub, defense code in cell), class-based eye/focus, utilities; drop import button
This commit is contained in:
@@ -102,21 +102,23 @@ a:not(.btn):hover { color: var(--c-text-link-hover); }
|
|||||||
.breadcrumb .nav-sep { user-select: none; }
|
.breadcrumb .nav-sep { user-select: none; }
|
||||||
.check-list { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.25rem; max-height: 12rem; overflow-y: auto; }
|
.check-list { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.25rem; max-height: 12rem; overflow-y: auto; }
|
||||||
|
|
||||||
/* Buttons — по спецификации Б3: настоящая рамка вместо box-shadow,
|
/* Buttons — по спецификации Б3: основная кнопка залита акцентом,
|
||||||
компактный кегль 13px, бордо только на основном действии */
|
вторичная и опасная — контурные. Компактный кегль 13px. */
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
|
display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
|
||||||
border: 1px solid transparent; border-radius: var(--radius);
|
border: 1px solid var(--c-accent); border-radius: var(--radius);
|
||||||
|
background: var(--c-accent); color: #ffffff;
|
||||||
padding: 0.5rem 1rem; font-size: 0.8125rem; font-weight: 500;
|
padding: 0.5rem 1rem; font-size: 0.8125rem; font-weight: 500;
|
||||||
cursor: pointer; text-decoration: none;
|
cursor: pointer; text-decoration: none;
|
||||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||||
}
|
}
|
||||||
.btn:active { opacity: 0.85; }
|
.btn:active { opacity: 0.85; }
|
||||||
.btn:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
|
.btn:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
|
||||||
|
.btn:hover { background: var(--c-accent-hover); border-color: var(--c-accent-hover); }
|
||||||
.btn-primary { background: var(--c-accent); border-color: var(--c-accent); color: #ffffff; }
|
.btn-primary { background: var(--c-accent); border-color: var(--c-accent); color: #ffffff; }
|
||||||
.btn-primary:hover { background: var(--c-accent-hover); border-color: var(--c-accent-hover); }
|
.btn-primary:hover { background: var(--c-accent-hover); border-color: var(--c-accent-hover); }
|
||||||
.btn-secondary { background: var(--c-surface); border-color: var(--c-border-strong); color: var(--c-text); }
|
.btn-secondary { background: var(--c-surface); border-color: var(--c-border-strong); color: var(--c-text); }
|
||||||
.btn-secondary:hover { background: var(--c-bg); }
|
.btn-secondary:hover { background: var(--c-bg); border-color: var(--c-border-strong); color: var(--c-text); }
|
||||||
.btn-danger { background: var(--c-surface); border-color: #dcb9be; color: var(--c-accent); }
|
.btn-danger { background: var(--c-surface); border-color: #dcb9be; color: var(--c-accent); }
|
||||||
.btn-danger:hover { background: var(--c-accent-soft); border-color: var(--c-accent); }
|
.btn-danger:hover { background: var(--c-accent-soft); border-color: var(--c-accent); }
|
||||||
|
|
||||||
@@ -153,40 +155,53 @@ a:not(.btn):hover { color: var(--c-text-link-hover); }
|
|||||||
.meta b { color: var(--c-text); font-weight: 600; }
|
.meta b { color: var(--c-text); font-weight: 600; }
|
||||||
.actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
.actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||||
.toolbar { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
|
.toolbar { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
|
||||||
.toolbar .table-search { width: 260px; }
|
|
||||||
.stat-value { margin: 0.5rem 0 0; font-size: 1.875rem; font-weight: 600; }
|
.stat-value { margin: 0.5rem 0 0; font-size: 1.875rem; font-weight: 600; }
|
||||||
.stat-title { font-size: 0.875rem; color: var(--c-text-muted); margin: 0; }
|
.stat-title { font-size: 0.875rem; color: var(--c-text-muted); margin: 0; }
|
||||||
.text-muted { color: var(--c-text-muted); }
|
/* ---- Utilities: плоские классы поверх компонентов (в духе Bootstrap) ---- */
|
||||||
.opacity-30 { opacity: 0.3; }
|
.flex { display: flex; }
|
||||||
.text-center { text-align: center; }
|
.flex-wrap { flex-wrap: wrap; }
|
||||||
.text-sm { font-size: 0.875rem; }
|
.items-center { align-items: center; }
|
||||||
.font-mono { font-family: monospace; }
|
.items-start { align-items: flex-start; }
|
||||||
|
.justify-between { justify-content: space-between; }
|
||||||
/* Spacing */
|
.justify-center { justify-content: center; }
|
||||||
|
.m-0 { margin: 0; }
|
||||||
.mt-1 { margin-top: 0.25rem; }
|
.mt-1 { margin-top: 0.25rem; }
|
||||||
.mt-2 { margin-top: 0.5rem; }
|
.mt-2 { margin-top: 0.5rem; }
|
||||||
.mt-4 { margin-top: 1rem; }
|
.mt-4 { margin-top: 1rem; }
|
||||||
.mt-6 { margin-top: 1.5rem; }
|
.mt-6 { margin-top: 1.5rem; }
|
||||||
.mt-8 { margin-top: 2rem; }
|
.mt-8 { margin-top: 2rem; }
|
||||||
|
.mb-0 { margin-bottom: 0; }
|
||||||
|
.mb-1 { margin-bottom: 0.25rem; }
|
||||||
.mb-2 { margin-bottom: 0.5rem; }
|
.mb-2 { margin-bottom: 0.5rem; }
|
||||||
.mb-4 { margin-bottom: 1rem; }
|
.mb-4 { margin-bottom: 1rem; }
|
||||||
.mb-6 { margin-bottom: 1.5rem; }
|
.mb-6 { margin-bottom: 1.5rem; }
|
||||||
|
.mb-8 { margin-bottom: 2rem; }
|
||||||
|
.ml-1 { margin-left: 0.25rem; }
|
||||||
.ml-2 { margin-left: 0.5rem; }
|
.ml-2 { margin-left: 0.5rem; }
|
||||||
|
.ml-auto { margin-left: auto; }
|
||||||
.p-0 { padding: 0; }
|
.p-0 { padding: 0; }
|
||||||
.p-4 { padding: 1rem; }
|
.p-4 { padding: 1rem; }
|
||||||
.p-6 { padding: 1.5rem; }
|
.p-6 { padding: 1.5rem; }
|
||||||
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
|
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
|
||||||
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
|
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
|
||||||
|
.gap-1 { gap: 0.25rem; }
|
||||||
.gap-2 { gap: 0.5rem; }
|
.gap-2 { gap: 0.5rem; }
|
||||||
|
.gap-3 { gap: 0.75rem; }
|
||||||
.gap-4 { gap: 1rem; }
|
.gap-4 { gap: 1rem; }
|
||||||
|
|
||||||
/* Flex */
|
|
||||||
.flex { display: flex; }
|
|
||||||
.flex-wrap { flex-wrap: wrap; }
|
|
||||||
.items-center { align-items: center; }
|
|
||||||
.justify-between { justify-content: space-between; }
|
|
||||||
.w-full { width: 100%; }
|
.w-full { width: 100%; }
|
||||||
|
.w-260 { width: 260px; }
|
||||||
.overflow-x-auto { overflow-x: auto; }
|
.overflow-x-auto { overflow-x: auto; }
|
||||||
|
.text-sm { font-size: 0.875rem; }
|
||||||
|
.text-muted { color: var(--c-text-muted); }
|
||||||
|
.text-strong { font-weight: 600; color: var(--c-text); }
|
||||||
|
.text-center { text-align: center; }
|
||||||
|
.text-right { text-align: right; }
|
||||||
|
.text-break { overflow-wrap: anywhere; }
|
||||||
|
.font-mono { font-family: monospace; }
|
||||||
|
.font-medium { font-weight: 500; }
|
||||||
|
.font-semibold { font-weight: 600; }
|
||||||
|
.opacity-30 { opacity: 0.3; }
|
||||||
|
.offscreen { position: fixed; top: -1000px; left: 0; }
|
||||||
|
|
||||||
/* Badges — «этикетки»: строгая рамка, разрядка капсом; только индикаторы статуса */
|
/* Badges — «этикетки»: строгая рамка, разрядка капсом; только индикаторы статуса */
|
||||||
.badge {
|
.badge {
|
||||||
@@ -212,12 +227,9 @@ tr:last-child td { border-bottom: none; }
|
|||||||
.alert-success { background: #dcfce7; color: #166534; }
|
.alert-success { background: #dcfce7; color: #166534; }
|
||||||
.alert-warning { background: #fef3c7; color: #92400e; }
|
.alert-warning { background: #fef3c7; color: #92400e; }
|
||||||
|
|
||||||
.ml-2 { margin-left: 0.5rem; }
|
|
||||||
.list-pad { padding-left: 1.25rem; }
|
.list-pad { padding-left: 1.25rem; }
|
||||||
.import-card { border-color: rgba(16,185,129,0.4); }
|
.import-card { border-color: rgba(16,185,129,0.4); }
|
||||||
.mini-alert { padding: 0.5rem 0.75rem; border-radius: var(--radius-xs); margin-bottom: 0.5rem; }
|
.mini-alert { padding: 0.5rem 0.75rem; border-radius: var(--radius-xs); margin-bottom: 0.5rem; }
|
||||||
.font-semibold { font-weight: 600; }
|
|
||||||
|
|
||||||
/* Theme toggle */
|
/* Theme toggle */
|
||||||
.theme-btn { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
|
.theme-btn { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
|
||||||
|
|
||||||
@@ -386,8 +398,8 @@ code.code { padding: 0.125rem 0.5rem; border-radius: var(--radius-xs); font-fami
|
|||||||
.ac-del:disabled { opacity: 0.3; cursor: not-allowed; }
|
.ac-del:disabled { opacity: 0.3; cursor: not-allowed; }
|
||||||
.ac-empty { padding: 0.4rem 0.6rem; font-size: 0.875rem; color: var(--c-text-muted); }
|
.ac-empty { padding: 0.4rem 0.6rem; font-size: 0.875rem; color: var(--c-text-muted); }
|
||||||
/* Ячейка матрицы в стиле Б3: вариант и оценка — текстом, подстрока — сдача и защита с точкой */
|
/* Ячейка матрицы в стиле Б3: вариант и оценка — текстом, подстрока — сдача и защита с точкой */
|
||||||
.cell-stack { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; }
|
.cell-stack { display: flex; flex-direction: column; align-items: flex-start; gap: 0.375rem; }
|
||||||
.cell-top { display: flex; align-items: center; justify-content: center; gap: 0.4rem; }
|
.cell-main { display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
|
||||||
/* Публикация в ячейке — «глаз»-точка: ○ скрыто, ◉ опубликовано (нативный checkbox под капотом) */
|
/* Публикация в ячейке — «глаз»-точка: ○ скрыто, ◉ опубликовано (нативный checkbox под капотом) */
|
||||||
.pub-toggle {
|
.pub-toggle {
|
||||||
appearance: none; -webkit-appearance: none;
|
appearance: none; -webkit-appearance: none;
|
||||||
@@ -403,10 +415,10 @@ code.code { padding: 0.125rem 0.5rem; border-radius: var(--radius-xs); font-fami
|
|||||||
.pub-toggle:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
|
.pub-toggle:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
|
||||||
.variant-btn {
|
.variant-btn {
|
||||||
display: inline-block; padding: 0; border: none; background: none;
|
display: inline-block; padding: 0; border: none; background: none;
|
||||||
font-size: 0.8125rem; font-weight: 600; white-space: nowrap; cursor: pointer;
|
font-size: 0.84375rem; font-weight: 600; white-space: nowrap; cursor: pointer;
|
||||||
color: var(--c-accent); text-decoration: none;
|
color: var(--c-text); text-decoration: none;
|
||||||
}
|
}
|
||||||
.variant-btn:hover { text-decoration: underline; }
|
.variant-btn:hover { color: var(--c-accent); text-decoration: underline; }
|
||||||
.variant-btn.manual { font-weight: 700; }
|
.variant-btn.manual { font-weight: 700; }
|
||||||
.variant-btn .cell-none { color: var(--c-text-muted); font-weight: 400; }
|
.variant-btn .cell-none { color: var(--c-text-muted); font-weight: 400; }
|
||||||
.variant-btn.manual .cell-none { font-weight: 600; }
|
.variant-btn.manual .cell-none { font-weight: 600; }
|
||||||
@@ -417,7 +429,7 @@ code.code { padding: 0.125rem 0.5rem; border-radius: var(--radius-xs); font-fami
|
|||||||
}
|
}
|
||||||
.score-link:hover { color: var(--c-accent); text-decoration: underline; }
|
.score-link:hover { color: var(--c-accent); text-decoration: underline; }
|
||||||
.score-link.none { color: var(--c-text-muted); font-weight: 400; }
|
.score-link.none { color: var(--c-text-muted); font-weight: 400; }
|
||||||
.cell-sub { display: flex; align-items: center; justify-content: center; gap: 0.75rem; font-size: 0.75rem; }
|
.cell-sub { display: flex; align-items: center; gap: 0.875rem; flex-wrap: wrap; font-size: 0.75rem; }
|
||||||
.sub-link { color: var(--c-text-muted); text-decoration: none; font-variant-numeric: tabular-nums; }
|
.sub-link { color: var(--c-text-muted); text-decoration: none; font-variant-numeric: tabular-nums; }
|
||||||
.sub-link:hover { color: var(--c-text); text-decoration: underline; }
|
.sub-link:hover { color: var(--c-text); text-decoration: underline; }
|
||||||
.sub-dim { color: var(--c-text-muted); }
|
.sub-dim { color: var(--c-text-muted); }
|
||||||
@@ -448,9 +460,10 @@ code.code { padding: 0.125rem 0.5rem; border-radius: var(--radius-xs); font-fami
|
|||||||
background: var(--c-surface); color: var(--c-text); text-decoration: none; font-size: 0.85rem;
|
background: var(--c-surface); color: var(--c-text); text-decoration: none; font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.mini-btn:hover { background: var(--c-bg); }
|
.mini-btn:hover { background: var(--c-bg); }
|
||||||
.mini-btn.ok { color: var(--c-tone-pos); }
|
.js-pub-eye .eye-closed { display: none; }
|
||||||
.mini-btn.warn { color: #b45309; }
|
.js-pub-eye.is-empty .eye-open { display: none; }
|
||||||
.mini-btn.partial { color: var(--c-accent); }
|
.js-pub-eye.is-empty .eye-closed { display: inline-block; }
|
||||||
|
.js-pub-count.is-hidden { display: none; }
|
||||||
|
|
||||||
/* Увеличенный диалог (оценка + комментарии) */
|
/* Увеличенный диалог (оценка + комментарии) */
|
||||||
dialog.modal.modal-lg { width: min(72rem, calc(100vw - 2rem)); }
|
dialog.modal.modal-lg { width: min(72rem, calc(100vw - 2rem)); }
|
||||||
@@ -489,12 +502,6 @@ dialog.modal.modal-lg .modal-body { max-height: 88vh; min-height: 62vh; }
|
|||||||
.section-body { padding-bottom: 0.4rem; }
|
.section-body { padding-bottom: 0.4rem; }
|
||||||
|
|
||||||
/* ---- Матрица группы и карточки защиты ---- */
|
/* ---- Матрица группы и карточки защиты ---- */
|
||||||
.m-0 { margin: 0; }
|
|
||||||
.mb-8 { margin-bottom: 2rem; }
|
|
||||||
.mb-0 { margin-bottom: 0; }
|
|
||||||
.text-strong { font-weight: 600; color: var(--c-text); }
|
|
||||||
.text-right { text-align: right; }
|
|
||||||
.font-medium { font-weight: 500; }
|
|
||||||
.scroll-x { overflow-x: auto; }
|
.scroll-x { overflow-x: auto; }
|
||||||
.matrix { border-collapse: separate; border-spacing: 0; }
|
.matrix { border-collapse: separate; border-spacing: 0; }
|
||||||
.matrix th {
|
.matrix th {
|
||||||
@@ -511,7 +518,7 @@ dialog.modal.modal-lg .modal-body { max-height: 88vh; min-height: 62vh; }
|
|||||||
.matrix td.student-cell { min-width: 12rem; }
|
.matrix td.student-cell { min-width: 12rem; }
|
||||||
.student-name { font-weight: 600; font-size: 0.84375rem; }
|
.student-name { font-weight: 600; font-size: 0.84375rem; }
|
||||||
.student-cell .font-mono { margin-top: 0.125rem; }
|
.student-cell .font-mono { margin-top: 0.125rem; }
|
||||||
.lab-col-head { display: flex; flex-direction: column; align-items: center; gap: 0.375rem; }
|
.lab-col-head { display: flex; flex-direction: column; align-items: flex-start; gap: 0.375rem; }
|
||||||
.lab-col-title {
|
.lab-col-title {
|
||||||
max-width: 9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
max-width: 9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||||
font-size: 0.7rem; font-weight: 400; color: var(--c-text-muted); letter-spacing: 0;
|
font-size: 0.7rem; font-weight: 400; color: var(--c-text-muted); letter-spacing: 0;
|
||||||
@@ -576,7 +583,8 @@ dialog.modal.modal-lg .modal-body { max-height: 88vh; min-height: 62vh; }
|
|||||||
.actions-cell .inline-form { display: inline-block; }
|
.actions-cell .inline-form { display: inline-block; }
|
||||||
/* Действия столбиком: ровные одинаковые кнопки друг под другом */
|
/* Действия столбиком: ровные одинаковые кнопки друг под другом */
|
||||||
.actions-col { display: inline-flex; flex-direction: column; gap: 0.35rem; align-items: stretch; min-width: 6.5rem; }
|
.actions-col { display: inline-flex; flex-direction: column; gap: 0.35rem; align-items: stretch; min-width: 6.5rem; }
|
||||||
.actions-col .btn { text-align: center; }
|
.actions-col .inline-form { display: block; width: 100%; }
|
||||||
|
.actions-col .btn { display: block; width: 100%; text-align: center; }
|
||||||
/* Монохромные SVG-иконки в ячейках матрицы (защита, оценка) */
|
/* Монохромные SVG-иконки в ячейках матрицы (защита, оценка) */
|
||||||
.cell-ico {
|
.cell-ico {
|
||||||
width: 0.95rem; height: 0.95rem; display: block;
|
width: 0.95rem; height: 0.95rem; display: block;
|
||||||
|
|||||||
@@ -306,16 +306,13 @@ function updateEyeCounts() {
|
|||||||
var total = parseInt(eye.getAttribute('data-total') || '0', 10);
|
var total = parseInt(eye.getAttribute('data-total') || '0', 10);
|
||||||
var count = parseInt(eye.getAttribute('data-count') || '0', 10);
|
var count = parseInt(eye.getAttribute('data-count') || '0', 10);
|
||||||
var all = eye.getAttribute('data-all') === 'true';
|
var all = eye.getAttribute('data-all') === 'true';
|
||||||
eye.classList.remove('ok', 'partial', 'warn');
|
eye.classList.toggle('is-empty', count === 0);
|
||||||
eye.classList.add(all ? 'ok' : (count > 0 ? 'partial' : 'warn'));
|
eye.classList.toggle('is-all', all);
|
||||||
var on = eye.querySelector('.eye-open');
|
eye.classList.toggle('is-partial', count > 0 && !all);
|
||||||
var off = eye.querySelector('.eye-closed');
|
|
||||||
if (on) on.style.display = count > 0 ? '' : 'none';
|
|
||||||
if (off) off.style.display = count > 0 ? 'none' : '';
|
|
||||||
var c = eye.querySelector('.js-pub-count');
|
var c = eye.querySelector('.js-pub-count');
|
||||||
if (c) {
|
if (c) {
|
||||||
c.textContent = count;
|
c.textContent = count;
|
||||||
c.style.display = (count > 0 && !all) ? '' : 'none';
|
c.classList.toggle('is-hidden', count === 0 || all);
|
||||||
}
|
}
|
||||||
eye.title = all
|
eye.title = all
|
||||||
? 'Оценки опубликованы всей группе — нажмите, чтобы скрыть у всех'
|
? 'Оценки опубликованы всей группе — нажмите, чтобы скрыть у всех'
|
||||||
@@ -363,7 +360,7 @@ function applyDefenseToCell(s) {
|
|||||||
label.textContent = started ? ''
|
label.textContent = started ? ''
|
||||||
: (s.status == null ? '—'
|
: (s.status == null ? '—'
|
||||||
: (s.status === 'ready' ? 'готова'
|
: (s.status === 'ready' ? 'готова'
|
||||||
: (s.code ? 'код' : 'ожидает')));
|
: (s.code ? 'код ' + s.code : 'ожидает')));
|
||||||
}
|
}
|
||||||
if (s.status === 'ready') {
|
if (s.status === 'ready') {
|
||||||
btn.title = 'Защита завершена: ' + (s.readyReason === 'timeout' ? 'время вышло' : 'студент нажал «Готов»');
|
btn.title = 'Защита завершена: ' + (s.readyReason === 'timeout' ? 'время вышло' : 'студент нажал «Готов»');
|
||||||
@@ -451,7 +448,7 @@ function defenseReasonText(reason) {
|
|||||||
function defenseStatusText(status, code, seconds, reason) {
|
function defenseStatusText(status, code, seconds, reason) {
|
||||||
if (status === 'waiting') {
|
if (status === 'waiting') {
|
||||||
return code
|
return code
|
||||||
? 'Задание назначено, код выдан — ждём входа студента.'
|
? 'Задание назначено. Код входа: ' + code + ' — ждём входа студента.'
|
||||||
: 'Задание назначено, код ещё не выдан.';
|
: 'Задание назначено, код ещё не выдан.';
|
||||||
}
|
}
|
||||||
if (status === 'started') return 'Студент выполняет работу, осталось ' + fmtSeconds(seconds) + '.';
|
if (status === 'started') return 'Студент выполняет работу, осталось ' + fmtSeconds(seconds) + '.';
|
||||||
@@ -598,7 +595,7 @@ function focusLab() {
|
|||||||
if (!s) return;
|
if (!s) return;
|
||||||
var labId = s.value;
|
var labId = s.value;
|
||||||
document.querySelectorAll('th.lab-col[data-lab], td.cell-lab[data-lab], .filter-row th[data-lab]').forEach(function (el) {
|
document.querySelectorAll('th.lab-col[data-lab], td.cell-lab[data-lab], .filter-row th[data-lab]').forEach(function (el) {
|
||||||
el.style.display = (!labId || el.getAttribute('data-lab') === labId) ? '' : 'none';
|
el.toggleAttribute('hidden', !!labId && el.getAttribute('data-lab') !== labId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
var area = document.createElement('textarea');
|
var area = document.createElement('textarea');
|
||||||
area.value = text;
|
area.value = text;
|
||||||
area.setAttribute('readonly', '');
|
area.setAttribute('readonly', '');
|
||||||
area.style.position = 'fixed';
|
area.className = 'offscreen';
|
||||||
area.style.top = '-1000px';
|
|
||||||
document.body.appendChild(area);
|
document.body.appendChild(area);
|
||||||
area.select();
|
area.select();
|
||||||
try { document.execCommand('copy'); } catch (e) { /* игнорируем */ }
|
try { document.execCommand('copy'); } catch (e) { /* игнорируем */ }
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-secondary" type="button" onclick="openModal('modal-defense-import')">Импорт заданий</button>
|
|
||||||
<a class="btn btn-secondary" th:href="@{/admin/groups/{id}/submissions.zip(id=${group.id})}"
|
<a class="btn btn-secondary" th:href="@{/admin/groups/{id}/submissions.zip(id=${group.id})}"
|
||||||
title="ZIP со всеми последними решениями группы по всем лабораторным">Скачать работы</a>
|
title="ZIP со всеми последними решениями группы по всем лабораторным">Скачать работы</a>
|
||||||
<a class="btn btn-secondary" th:href="@{/admin/groups/{id}/credentials(id=${group.id}, format='xlsx')}">Скачать пароли</a>
|
<a class="btn btn-secondary" th:href="@{/admin/groups/{id}/credentials(id=${group.id}, format='xlsx')}">Скачать пароли</a>
|
||||||
@@ -25,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<input class="input table-search" data-target="group-table"
|
<input class="input table-search w-260" data-target="group-table"
|
||||||
placeholder="Поиск по студенту…" aria-label="Поиск по студенту">
|
placeholder="Поиск по студенту…" aria-label="Поиск по студенту">
|
||||||
<select class="input" id="lab-focus" onchange="focusLab()">
|
<select class="input" id="lab-focus" onchange="focusLab()">
|
||||||
<option value="">все лабораторные</option>
|
<option value="">все лабораторные</option>
|
||||||
@@ -61,11 +60,11 @@
|
|||||||
th:value="${all} ? 'false' : 'true'">
|
th:value="${all} ? 'false' : 'true'">
|
||||||
<button class="mini-btn js-pub-eye" type="submit"
|
<button class="mini-btn js-pub-eye" type="submit"
|
||||||
th:attr="data-lab=${lab.id},data-count=${pc},data-total=${pub != null ? pub.total : 0},data-all=${all}"
|
th:attr="data-lab=${lab.id},data-count=${pc},data-total=${pub != null ? pub.total : 0},data-all=${all}"
|
||||||
th:classappend="${all} ? 'ok' : (${pc > 0} ? 'partial' : 'warn')"
|
th:classappend="${all} ? 'is-all' : (${pc > 0} ? 'is-partial' : 'is-empty')"
|
||||||
th:title="${pub == null} ? '' : (${all} ? 'Оценки опубликованы всей группе — нажмите, чтобы скрыть у всех' : (${pc > 0} ? 'Опубликовано ' + ${pc} + ' из ' + ${pub.total} + ' — нажмите, чтобы опубликовать всем' : 'Показать оценки и комментарии всей группе'))">
|
th:title="${pub == null} ? '' : (${all} ? 'Оценки опубликованы всей группе — нажмите, чтобы скрыть у всех' : (${pc > 0} ? 'Опубликовано ' + ${pc} + ' из ' + ${pub.total} + ' — нажмите, чтобы опубликовать всем' : 'Показать оценки и комментарии всей группе'))">
|
||||||
<svg class="eye-open" th:style="${pc > 0} ? '' : 'display:none'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
<svg class="eye-open" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||||
<svg class="eye-closed" th:style="${pc > 0} ? 'display:none' : ''" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/><line x1="3" y1="3" x2="21" y2="21"/></svg>
|
<svg class="eye-closed" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/><line x1="3" y1="3" x2="21" y2="21"/></svg>
|
||||||
<span class="mini-count js-pub-count" th:style="(${all} or ${pc == 0}) ? 'display:none' : ''" th:text="${pc}"></span>
|
<span class="mini-count js-pub-count" th:classappend="(${all} or ${pc == 0}) ? 'is-hidden' : ''" th:text="${pc}"></span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<a class="mini-btn"
|
<a class="mini-btn"
|
||||||
@@ -87,7 +86,7 @@
|
|||||||
<td th:each="cell, iterStat : ${row.cells}" class="text-center cell-lab"
|
<td th:each="cell, iterStat : ${row.cells}" class="text-center cell-lab"
|
||||||
th:attr="data-lab=${cell.labId},data-sort=${cell.variantNumber},data-filter-label=${cell.variantNumber != null ? 'вариант ' + cell.variantNumber : 'без варианта'}">
|
th:attr="data-lab=${cell.labId},data-sort=${cell.variantNumber},data-filter-label=${cell.variantNumber != null ? 'вариант ' + cell.variantNumber : 'без варианта'}">
|
||||||
<div class="cell-stack">
|
<div class="cell-stack">
|
||||||
<div class="cell-top">
|
<div class="cell-main">
|
||||||
<button class="variant-btn" type="button"
|
<button class="variant-btn" type="button"
|
||||||
th:classappend="${cell.variantManual} ? 'manual' : ''"
|
th:classappend="${cell.variantManual} ? 'manual' : ''"
|
||||||
th:attr="data-student=${row.studentId},data-student-name=${row.fullName},data-lab=${cell.labId},data-lab-name='ЛР ' + ${cell.labNumber},data-variant=${cell.variantId}"
|
th:attr="data-student=${row.studentId},data-student-name=${row.fullName},data-lab=${cell.labId},data-lab-name='ЛР ' + ${cell.labNumber},data-variant=${cell.variantId}"
|
||||||
@@ -120,7 +119,7 @@
|
|||||||
<span class="cell-dim">защита:</span>
|
<span class="cell-dim">защита:</span>
|
||||||
<span class="js-def-label"
|
<span class="js-def-label"
|
||||||
th:text="${cell.defenseStatus == null ? '—' : (cell.defenseStatus == 'waiting'
|
th:text="${cell.defenseStatus == null ? '—' : (cell.defenseStatus == 'waiting'
|
||||||
? (cell.defenseCode != null ? 'код' : 'ожидает')
|
? (cell.defenseCode != null ? 'код ' + cell.defenseCode : 'ожидает')
|
||||||
: (cell.defenseStatus == 'ready' ? 'готова' : ''))}">—</span>
|
: (cell.defenseStatus == 'ready' ? 'готова' : ''))}">—</span>
|
||||||
<span th:if="${cell.defenseStatus == 'started'}" class="defense-cell-timer js-def-timer" th:data-seconds="${cell.defenseSecondsLeft}">идёт</span>
|
<span th:if="${cell.defenseStatus == 'started'}" class="defense-cell-timer js-def-timer" th:data-seconds="${cell.defenseSecondsLeft}">идёт</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -263,33 +262,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Диалог импорта CSV -->
|
|
||||||
<dialog id="modal-defense-import" class="modal">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 class="modal-title">Импорт заданий</h2>
|
|
||||||
<button class="modal-close" type="button" aria-label="Закрыть" onclick="closeModal('modal-defense-import')">✕</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form class="form-stack" th:action="@{/admin/defenses/import}" method="post" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
|
||||||
<input type="hidden" name="groupId" th:value="${group.id}">
|
|
||||||
<div><label class="label" for="cimport-lab">Лабораторная</label>
|
|
||||||
<select class="input" id="cimport-lab" name="labId">
|
|
||||||
<option value="">— определить из файла (3-й столбец) —</option>
|
|
||||||
<option th:each="lab : ${labs}" th:value="${lab.id}" th:text="|ЛР ${lab.number}. ${lab.title}|">—</option>
|
|
||||||
</select></div>
|
|
||||||
<div><label class="label" for="cimport-file">Файл CSV</label>
|
|
||||||
<input class="input" id="cimport-file" type="file" name="file" accept=".csv,.txt" required></div>
|
|
||||||
<p class="hint">
|
|
||||||
Разделитель <code class="code">;</code>, <code class="code">,</code> или таб; заголовок не обязателен; кавычки поддерживаются.<br>
|
|
||||||
<b>2 столбца</b> (нужна выбранная лаба): <code class="code">студенческий_номер;задание</code>.<br>
|
|
||||||
<b>3 столбца</b>: <code class="code">студенческий_номер;номер_ЛР;задание</code> — лабораторная берётся из файла.<br>
|
|
||||||
Переносы внутри задания — как <code class="code">\n</code>.
|
|
||||||
</p>
|
|
||||||
<button class="btn btn-primary" type="submit">Импортировать</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<!-- Диалог добавления студента -->
|
<!-- Диалог добавления студента -->
|
||||||
<dialog id="modal-add-student" class="modal">
|
<dialog id="modal-add-student" class="modal">
|
||||||
|
|||||||
Reference in New Issue
Block a user