feat(journal): карточка отчёта по ученику в модалке
- новая модалка #reportCardModal с фото, именем, текстом записи и бейджами - кнопка «Карточка отчёта» (иконка contact) в плитке и в карточке журнала - закрытие по клику по оверлею и по Escape - imgModal поднят над карточкой, чтобы просмотр фото открывался поверх неё
This commit is contained in:
@@ -223,6 +223,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
|
|||||||
.modal-overlay.open{display:flex}
|
.modal-overlay.open{display:flex}
|
||||||
.modal-overlay > img{max-width:90%;max-height:90dvh;border-radius:var(--radius)}
|
.modal-overlay > img{max-width:90%;max-height:90dvh;border-radius:var(--radius)}
|
||||||
#videoModal{background:rgba(0,0,0,.85)}
|
#videoModal{background:rgba(0,0,0,.85)}
|
||||||
|
#imgModal{z-index:130}
|
||||||
.video-modal{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:16px;width:min(1000px,94vw);max-height:94dvh;display:flex;flex-direction:column;gap:12px}
|
.video-modal{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:16px;width:min(1000px,94vw);max-height:94dvh;display:flex;flex-direction:column;gap:12px}
|
||||||
.video-head{display:flex;align-items:center;gap:12px;font-size:.85rem;color:var(--text)}
|
.video-head{display:flex;align-items:center;gap:12px;font-size:.85rem;color:var(--text)}
|
||||||
.video-head > span:first-child{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
.video-head > span:first-child{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||||
@@ -704,4 +705,112 @@ body[data-page=settings] .page-head{align-items:flex-end}
|
|||||||
.lesson-dash-when{color:var(--muted);font-size:.74rem;margin-left:auto;white-space:nowrap}
|
.lesson-dash-when{color:var(--muted);font-size:.74rem;margin-left:auto;white-space:nowrap}
|
||||||
.lesson-dash-text{font-size:.8rem;color:var(--muted);overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.45}
|
.lesson-dash-text{font-size:.8rem;color:var(--muted);overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.45}
|
||||||
|
|
||||||
|
/* --- Карточка отчёта об ученике (модалка на journal.html) --- */
|
||||||
|
.report-overlay{background:rgba(0,0,0,.62);backdrop-filter:blur(10px) saturate(1.08);-webkit-backdrop-filter:blur(10px) saturate(1.08);padding:24px 16px;overflow:auto;overscroll-behavior:contain}
|
||||||
|
.student-card{
|
||||||
|
--sc-primary:#3478F6;
|
||||||
|
--sc-primary-dark:#173B78;
|
||||||
|
--sc-primary-soft:#E8F2FF;
|
||||||
|
--sc-text:#172033;
|
||||||
|
--sc-text-secondary:#68758A;
|
||||||
|
--sc-border:#E5EBF3;
|
||||||
|
--sc-yellow:#FFB71B;
|
||||||
|
--sc-radius-card:20px;
|
||||||
|
--sc-radius-photo:18px;
|
||||||
|
--sc-radius-pill:999px;
|
||||||
|
--sc-pad-x:22px;
|
||||||
|
--sc-media-inset:20px;
|
||||||
|
--sc-smiley-size:50px;
|
||||||
|
--sc-smiley-bottom:16px;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
max-width:440px;
|
||||||
|
background:var(--card);
|
||||||
|
color:var(--sc-text);
|
||||||
|
border-radius:var(--sc-radius-card);
|
||||||
|
box-shadow:0 10px 30px rgba(39,91,160,.10),0 2px 8px rgba(39,91,160,.06),0 24px 60px rgba(0,0,0,.35);
|
||||||
|
overflow:hidden;
|
||||||
|
flex-shrink:0;
|
||||||
|
-webkit-tap-highlight-color:transparent;
|
||||||
|
}
|
||||||
|
.student-card__media{
|
||||||
|
position:relative;
|
||||||
|
margin:var(--sc-media-inset) var(--sc-media-inset) 0;
|
||||||
|
aspect-ratio:1.55/1;
|
||||||
|
border-radius:var(--sc-radius-photo);
|
||||||
|
overflow:hidden;
|
||||||
|
background:var(--sc-primary-soft);
|
||||||
|
rotate:-3deg;
|
||||||
|
transform-origin:50% 50%;
|
||||||
|
}
|
||||||
|
.student-card__photo{display:block;width:100%;height:100%;object-fit:cover;filter:saturate(1.04) contrast(1.02) brightness(1.02);cursor:zoom-in}
|
||||||
|
.student-card__photo[hidden]{display:none}
|
||||||
|
.student-card__media::after{
|
||||||
|
content:"";
|
||||||
|
position:absolute;
|
||||||
|
inset:auto 0 0;
|
||||||
|
height:25%;
|
||||||
|
background:linear-gradient(to bottom,transparent,rgba(10,25,50,.08));
|
||||||
|
pointer-events:none;
|
||||||
|
}
|
||||||
|
.student-card__nophoto{
|
||||||
|
position:absolute;
|
||||||
|
inset:0;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
color:#A9BEDC;
|
||||||
|
}
|
||||||
|
.student-card__nophoto[hidden]{display:none}
|
||||||
|
.student-card__nophoto svg{width:44%;height:44%}
|
||||||
|
.student-card__content{
|
||||||
|
position:relative;
|
||||||
|
padding:24px var(--sc-pad-x) calc(var(--sc-smiley-bottom) + var(--sc-smiley-size) + 18px);
|
||||||
|
}
|
||||||
|
.student-card__sparks{position:absolute;top:15px;right:16px;width:104px;color:#E1ECFB;pointer-events:none}
|
||||||
|
.student-card__name{margin:0 0 8px;font-size:20px;font-weight:750;line-height:1.25;letter-spacing:-.1px;color:var(--sc-text);overflow-wrap:break-word}
|
||||||
|
.student-card__text{margin:0;font-size:15px;line-height:1.6;color:var(--sc-text-secondary);white-space:pre-wrap;overflow-wrap:break-word}
|
||||||
|
.student-card__text:empty{display:none}
|
||||||
|
.student-card__smiley{position:absolute;right:24px;bottom:var(--sc-smiley-bottom);width:var(--sc-smiley-size);height:var(--sc-smiley-size);pointer-events:none}
|
||||||
|
.student-card__footer{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
padding:14px var(--sc-pad-x);
|
||||||
|
border-top:1px solid var(--sc-border);
|
||||||
|
background:linear-gradient(to bottom,#FBFDFF 0%,#FFFFFF 100%);
|
||||||
|
}
|
||||||
|
.student-card__footer-badges{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;min-width:0}
|
||||||
|
.student-card__badge{
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:7px;
|
||||||
|
max-width:100%;
|
||||||
|
padding:8px 15px;
|
||||||
|
border-radius:var(--sc-radius-pill);
|
||||||
|
font-size:14px;
|
||||||
|
font-weight:700;
|
||||||
|
line-height:1;
|
||||||
|
letter-spacing:.1px;
|
||||||
|
color:#FFFFFF;
|
||||||
|
white-space:nowrap;
|
||||||
|
user-select:none;
|
||||||
|
}
|
||||||
|
.student-card__badge--time{background:rgba(23,59,120,.94)}
|
||||||
|
.student-card__badge--tool{background:var(--sc-primary);box-shadow:0 4px 12px rgba(52,120,246,.28)}
|
||||||
|
.student-card__badge--tool[hidden]{display:none}
|
||||||
|
.student-card__badge > span{overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.student-card__icon{display:block;flex:none}
|
||||||
|
.student-card__icon--badge{width:17px;height:17px}
|
||||||
|
.student-card__icon--ps{width:19px;height:19px}
|
||||||
|
.student-card :focus-visible{outline:2px solid var(--sc-primary);outline-offset:2px;border-radius:6px}
|
||||||
|
.journal-card .foot .edit svg,.card .edit svg{width:16px;height:16px;display:block}
|
||||||
|
@media(max-width:420px){
|
||||||
|
.report-overlay{padding:16px 12px}
|
||||||
|
.student-card{
|
||||||
|
--sc-pad-x:15px;
|
||||||
|
--sc-media-inset:14px;
|
||||||
|
--sc-smiley-size:40px;
|
||||||
|
}
|
||||||
|
.student-card__badge{font-size:12px;padding:6px 11px}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,6 +263,69 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay report-overlay" id="reportCardModal">
|
||||||
|
<article class="student-card" aria-labelledby="reportCardName">
|
||||||
|
<div class="student-card__media">
|
||||||
|
<img class="student-card__photo" id="reportCardPhoto" alt="" decoding="async">
|
||||||
|
<div class="student-card__nophoto" id="reportCardNoPhoto" hidden>
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
||||||
|
<rect x="3" y="4" width="18" height="16" rx="3"/>
|
||||||
|
<path d="M3 15.5l4.5-4.2 3.5 3.2 3.2-2.8L21 15.5"/>
|
||||||
|
<circle cx="9" cy="9.2" r="1.4"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="student-card__content">
|
||||||
|
<svg class="student-card__sparks" viewBox="0 0 120 40" aria-hidden="true" focusable="false">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round">
|
||||||
|
<path d="M6 30c14-6 30-6 44 0"/>
|
||||||
|
<path d="M74 16h16M82 8v16"/>
|
||||||
|
<path d="M104 6l3 6M104 26l3-6"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<h2 class="student-card__name" id="reportCardName"></h2>
|
||||||
|
<p class="student-card__text" id="reportCardText"></p>
|
||||||
|
<svg class="student-card__smiley" viewBox="0 0 72 72"
|
||||||
|
fill="none" stroke="#FFB71B" stroke-width="2.6"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
aria-hidden="true" focusable="false">
|
||||||
|
<path d="M38 5.6v5.4M60.6 15l-3.8 3.8M68.4 38H63M60.6 61l-3.8-3.8M38 70.4V65M15.4 61l3.8-3.8M7.6 38H13M15.4 15l3.8 3.8"/>
|
||||||
|
<path d="M57.6 30.8c1.9 4.4 2.7 9 2.2 13.5-.9 8-6 15-13.7 17.4-7.7 2.4-15.9.6-20.9-5-5-5.6-6.6-14-4-21.2 2.6-7.2 8.8-12.6 16.4-14.3 7.7-1.7 15.9 1.8 20 9.6Z"/>
|
||||||
|
<path d="M30.6 33.2h.02M45.4 32.4h.02" stroke-width="3.6"/>
|
||||||
|
<path d="M28.6 45c2.6 4 6.6 6.3 11 6.3 4.5 0 8.6-2.3 11.3-6.4"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<footer class="student-card__footer">
|
||||||
|
<div class="student-card__footer-badges">
|
||||||
|
<span class="student-card__badge student-card__badge--time">
|
||||||
|
<svg class="student-card__icon student-card__icon--badge" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="1.9"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
aria-hidden="true" focusable="false">
|
||||||
|
<path d="M16 19v-1.5a3.5 3.5 0 0 0-3.5-3.5h-5A3.5 3.5 0 0 0 4 17.5V19"/>
|
||||||
|
<circle cx="10" cy="8" r="3.4"/>
|
||||||
|
<path d="M20 19v-1.5a3.5 3.5 0 0 0-2.6-3.38"/>
|
||||||
|
<path d="M15.4 4.6a3.4 3.4 0 0 1 0 6.4"/>
|
||||||
|
</svg>
|
||||||
|
<span id="reportCardTime"></span>
|
||||||
|
</span>
|
||||||
|
<span class="student-card__badge student-card__badge--tool">
|
||||||
|
<svg class="student-card__icon student-card__icon--ps" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="1.9"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
aria-hidden="true" focusable="false">
|
||||||
|
<rect x="2.2" y="2.2" width="19.6" height="19.6" rx="5.8" fill="currentColor" fill-opacity=".16"/>
|
||||||
|
<text x="12" y="16.9" text-anchor="middle" font-size="11.6" font-weight="800"
|
||||||
|
letter-spacing="-.3" fill="currentColor" stroke="none"
|
||||||
|
font-family="inherit">Ps</text>
|
||||||
|
</svg>
|
||||||
|
<span id="reportCardModule"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
<script src="vendor/lucide.min.js"></script>
|
<script src="vendor/lucide.min.js"></script>
|
||||||
<script src="js/datetime.js"></script>
|
<script src="js/datetime.js"></script>
|
||||||
|
|||||||
@@ -48,6 +48,42 @@ function mainPhotoPath(e) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openReportCard(id) {
|
||||||
|
const e = currentEntries.find(x => x.id === id);
|
||||||
|
if (!e) return;
|
||||||
|
const mp = mainPhotoPath(e);
|
||||||
|
const photo = document.getElementById('reportCardPhoto');
|
||||||
|
const noPhoto = document.getElementById('reportCardNoPhoto');
|
||||||
|
if (mp) {
|
||||||
|
photo.src = thumbSrc(mp);
|
||||||
|
photo.dataset.img = `${API}${mp}`;
|
||||||
|
photo.alt = e.student_name || '';
|
||||||
|
photo.hidden = false;
|
||||||
|
noPhoto.hidden = true;
|
||||||
|
} else {
|
||||||
|
photo.src = '';
|
||||||
|
delete photo.dataset.img;
|
||||||
|
photo.alt = '';
|
||||||
|
photo.hidden = true;
|
||||||
|
noPhoto.hidden = false;
|
||||||
|
}
|
||||||
|
document.getElementById('reportCardName').textContent = e.student_name || '';
|
||||||
|
document.getElementById('reportCardText').textContent = e.description || '';
|
||||||
|
const groupBadge = document.getElementById('reportCardTime');
|
||||||
|
const group = e.group_name || '';
|
||||||
|
groupBadge.textContent = group;
|
||||||
|
groupBadge.parentElement.hidden = !group;
|
||||||
|
const toolBadge = document.getElementById('reportCardModule').parentElement;
|
||||||
|
const tool = e.module_name || '';
|
||||||
|
document.getElementById('reportCardModule').textContent = tool;
|
||||||
|
toolBadge.hidden = !tool;
|
||||||
|
document.getElementById('reportCardModal').classList.add('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeReportCard() {
|
||||||
|
document.getElementById('reportCardModal').classList.remove('open');
|
||||||
|
}
|
||||||
|
|
||||||
function filesHTML(files) {
|
function filesHTML(files) {
|
||||||
if (!files || !files.length) return '';
|
if (!files || !files.length) return '';
|
||||||
return `<div class="entry-files">${files.map(f => {
|
return `<div class="entry-files">${files.map(f => {
|
||||||
@@ -126,6 +162,7 @@ function gridEntryHTML(e) {
|
|||||||
${filesHTML(e.files)}
|
${filesHTML(e.files)}
|
||||||
<div class="time">${fmtFull(e.created_at)}</div>
|
<div class="time">${fmtFull(e.created_at)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="edit" data-report="${e.id}" title="Карточка отчёта"><i data-lucide="contact"></i></button>
|
||||||
<button class="edit" data-edit="${e.id}" title="Редактировать">✎</button>
|
<button class="edit" data-edit="${e.id}" title="Редактировать">✎</button>
|
||||||
${mp ? `<button class="edit" data-enhance="${e.id}" title="Улучшить фото">✦</button>` : ''}
|
${mp ? `<button class="edit" data-enhance="${e.id}" title="Улучшить фото">✦</button>` : ''}
|
||||||
<button class="del" data-del="${e.id}" title="Удалить">×</button>
|
<button class="del" data-del="${e.id}" title="Удалить">×</button>
|
||||||
@@ -150,6 +187,7 @@ function cardEntryHTML(e) {
|
|||||||
<div class="foot">
|
<div class="foot">
|
||||||
<span>${fmtFull(e.created_at)}</span>
|
<span>${fmtFull(e.created_at)}</span>
|
||||||
<span>
|
<span>
|
||||||
|
<button class="edit" data-report="${e.id}" title="Карточка отчёта"><i data-lucide="contact"></i></button>
|
||||||
<button class="edit" data-edit="${e.id}" title="Редактировать">✎</button>
|
<button class="edit" data-edit="${e.id}" title="Редактировать">✎</button>
|
||||||
${mp ? `<button class="edit" data-enhance="${e.id}" title="Улучшить фото">✦</button>` : ''}
|
${mp ? `<button class="edit" data-enhance="${e.id}" title="Улучшить фото">✦</button>` : ''}
|
||||||
<button class="del" data-del="${e.id}" title="Удалить">×</button>
|
<button class="del" data-del="${e.id}" title="Удалить">×</button>
|
||||||
@@ -1514,10 +1552,17 @@ function copyLink() { copyText(createdLinkUrl); }
|
|||||||
document.getElementById('imgModal').addEventListener('click', e => e.currentTarget.classList.remove('open'));
|
document.getElementById('imgModal').addEventListener('click', e => e.currentTarget.classList.remove('open'));
|
||||||
document.getElementById('editModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeEdit(); });
|
document.getElementById('editModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeEdit(); });
|
||||||
document.getElementById('linkModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeLinkCreate(); });
|
document.getElementById('linkModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeLinkCreate(); });
|
||||||
|
document.getElementById('reportCardModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeReportCard(); });
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
if (e.key !== 'Escape') return;
|
||||||
|
if (document.getElementById('reportCardModal').classList.contains('open')) closeReportCard();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('grid').addEventListener('click', e => {
|
document.getElementById('grid').addEventListener('click', e => {
|
||||||
const det = e.target.closest('[data-detach]');
|
const det = e.target.closest('[data-detach]');
|
||||||
if (det) { detachFile(parseInt(det.dataset.detach, 10)); return; }
|
if (det) { detachFile(parseInt(det.dataset.detach, 10)); return; }
|
||||||
|
const rp = e.target.closest('[data-report]');
|
||||||
|
if (rp) { openReportCard(parseInt(rp.dataset.report, 10)); return; }
|
||||||
const ed = e.target.closest('[data-edit]');
|
const ed = e.target.closest('[data-edit]');
|
||||||
if (ed) { openEdit(parseInt(ed.dataset.edit, 10)); return; }
|
if (ed) { openEdit(parseInt(ed.dataset.edit, 10)); return; }
|
||||||
const en = e.target.closest('[data-enhance]');
|
const en = e.target.closest('[data-enhance]');
|
||||||
|
|||||||
Reference in New Issue
Block a user