diff --git a/public/admin.css b/public/admin.css
index ea8791f..21ac7d6 100644
--- a/public/admin.css
+++ b/public/admin.css
@@ -161,6 +161,13 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
.item .name-input:focus{border-color:var(--accent)}
.item .group-select{background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:4px 8px;font-size:.8rem;outline:none;max-width:200px}
.item .group-select:focus{border-color:var(--accent)}
+.student-main{display:flex;align-items:center;gap:10px;flex:1;min-width:0}
+.student-main .name-input{flex:1;min-width:0}
+.student-avatar{width:40px;height:40px;flex:0 0 auto;border-radius:50%;overflow:hidden;border:1px solid var(--border);background:var(--bg);padding:0;cursor:zoom-in;display:flex;align-items:center;justify-content:center;transition:border-color .15s,opacity .15s}
+.student-avatar img{width:100%;height:100%;object-fit:cover;display:block}
+.student-avatar:hover{border-color:var(--accent);opacity:.9}
+.student-avatar.empty{cursor:pointer;border-style:dashed}
+.student-avatar.empty span{font-size:.75rem;font-weight:700;color:var(--muted);letter-spacing:.02em;user-select:none}
.item.expired{opacity:.6;border-color:rgba(239,68,68,.35);background:rgba(239,68,68,.03)}
.item.expired:hover{opacity:.85}
.badge-expired{display:inline-block;font-size:.68rem;font-weight:700;line-height:1;padding:3px 8px;border-radius:999px;background:rgba(239,68,68,.12);color:#ef4444;vertical-align:middle;margin-left:6px}
diff --git a/public/js/students.js b/public/js/students.js
index 22b6e94..3c7dacb 100644
--- a/public/js/students.js
+++ b/public/js/students.js
@@ -30,6 +30,53 @@ async function loadStudents() {
renderStudents();
}
+function studentInitials(name) {
+ const parts = String(name || '').trim().split(/\s+/).filter(Boolean);
+ if (!parts.length) return 'У';
+ const a = parts[0].charAt(0);
+ const b = parts.length > 1 ? parts[1].charAt(0) : (parts[0].length > 1 ? parts[0].charAt(1) : '');
+ return (a + b).toUpperCase();
+}
+
+function photoFullSrc(p) {
+ if (!p) return '';
+ return p.startsWith('/') ? `${API}${p}` : p;
+}
+
+function setStudentAvatar(el, path, name) {
+ if (!el) return;
+ if (path) {
+ el.classList.remove('empty');
+ el.dataset.studentPhoto = path;
+ el.title = 'Увеличить фото';
+ el.innerHTML = ``;
+ const img = el.querySelector('img');
+ if (img) img.addEventListener('error', () => setStudentAvatar(el, '', name), { once: true });
+ } else {
+ el.classList.add('empty');
+ delete el.dataset.studentPhoto;
+ el.title = 'Фото не загружено — нажмите, чтобы загрузить';
+ el.innerHTML = `${esc(studentInitials(name))}`;
+ }
+}
+
+async function syncStudentAvatar(id, photoPath) {
+ const el = document.getElementById(`savatar-${id}`);
+ if (!el) return;
+ const nameInput = document.getElementById(`sname-${id}`);
+ const name = nameInput ? nameInput.value : '';
+ if (photoPath !== undefined) {
+ setStudentAvatar(el, photoPath || '', name);
+ return;
+ }
+ try {
+ const res = await fetch(`${API}/api/students/${id}/photos`, { headers: hdr() });
+ if (!res.ok) return;
+ const data = await res.json();
+ setStudentAvatar(el, data.photo_path || '', name);
+ } catch (err) {}
+}
+
function renderStudents() {
const q = document.getElementById('studentSearch').value.trim().toLowerCase();
const gid = document.getElementById('studentGroupFilter').value;
@@ -43,7 +90,10 @@ function renderStudents() {
else {
list.innerHTML = slice.map(s => `