/* =========================================================
Дашборд кафедры — логика
Данные хранятся в localStorage (без сервера/БД)
========================================================= */
const STORE_KEY = 'clinical_dashboard_v1';
// ---- Базовые ( неудаляемые ) поля ----
const BASE_FIELDS = [
{ key:'fullName', label:'ФИО', type:'text', required:true, locked:true },
{ key:'birthDate', label:'Дата рождения', type:'date', locked:true },
{ key:'education', label:'Образование', type:'textarea',locked:true },
{ key:'academicDegree', label:'Научная степень', type:'select', options:['Нет','Кандидат наук','Доктор наук'], locked:true },
{ key:'category', label:'Категория', type:'select', options:['Нет','Вторая','Первая','Высшая'], locked:true },
{ key:'specialistCertificate', label:'Сертификат специалиста', type:'text', locked:true },
{ key:'associations', label:'Членство в ассоциациях', type:'text', locked:true },
{ key:'clinicalBase', label:'Клиническая база', type:'text', locked:true },
];
// ---- Состояние ----
let state = {
teachers: [],
customFields: [],
editingId: null,
};
let filters = { search:'', degree:'', category:'', base:'' };
/* ---------- Инициализация ---------- */
function init(){
load();
if(state.teachers.length===0 && state.customFields.length===0){
seedDemo();
}
bindEvents();
buildFilterOptions();
renderAll();
}
function load(){
try{
const raw = localStorage.getItem(STORE_KEY);
if(raw){
const data = JSON.parse(raw);
state.teachers = data.teachers || [];
state.customFields = data.customFields || [];
}
}catch(e){ console.warn('load failed', e); }
}
function save(){
localStorage.setItem(STORE_KEY, JSON.stringify({
teachers: state.teachers,
customFields: state.customFields,
}));
}
function allFields(){ return BASE_FIELDS.concat(state.customFields); }
function uid(){ return 't_' + Math.random().toString(36).slice(2,10) + Date.now().toString(36).slice(-4); }
/* ---------- Демо-данные при первом запуске ---------- */
function seedDemo(){
state.teachers = [
{ id:uid(), fullName:'Ахметова Гульнара Сериковна', birthDate:'1975-03-12',
education:'КазНМУ им. С.Асфендиярова, лечебное дело, 1998; интернатура по кардиологии',
academicDegree:'Доктор наук', category:'Высшая',
specialistCertificate:'№АА1122334, 2022, Кардиология',
associations:'Ассоциация кардиологов РК, ESC',
clinicalBase:'Городской кардиологический центр' },
{ id:uid(), fullName:'Оспанов Тимур Бакытович', birthDate:'1982-11-05',
education:'КГМУ, педиатрия, 2005; резидентура по неонатологии',
academicDegree:'Кандидат наук', category:'Первая',
specialistCertificate:'№АА5566778, 2023, Неонатология',
associations:'Ассоциация неонатологов РК',
clinicalBase:'Перинатальный центр №1' },
{ id:uid(), fullName:'Жумабаева Айгерим Нурлановна', birthDate:'1989-07-21',
education:'КазНМУ, общая медицина, 2012; резидентура по терапии',
academicDegree:'Нет', category:'Вторая',
specialistCertificate:'№АА9988776, 2024, Терапия',
associations:'',
clinicalBase:'ГКП Поликлиника №12' },
];
save();
}
/* ---------- События ---------- */
function bindEvents(){
// Табы
document.querySelectorAll('.tab').forEach(t=>{
t.addEventListener('click', ()=>{
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));
document.querySelectorAll('.tabpanel').forEach(x=>x.classList.remove('active'));
t.classList.add('active');
document.getElementById('tab-'+t.dataset.tab).classList.add('active');
if(t.dataset.tab==='reports') renderReports();
});
});
// Кнопки
document.getElementById('btnAdd').addEventListener('click', addRow);
document.getElementById('btnFields').addEventListener('click', openFields);
document.getElementById('btnExport').addEventListener('click', exportCSV);
document.getElementById('btnImport').addEventListener('click', ()=>document.getElementById('importFile').click());
document.getElementById('importFile').addEventListener('change', importJSON);
document.getElementById('btnAddField').addEventListener('click', addCustomField);
document.getElementById('newFieldType').addEventListener('change', e=>{
document.getElementById('newFieldOptions').hidden = e.target.value!=='select';
});
document.getElementById('btnReportCsv').addEventListener('click', exportReportCSV);
document.getElementById('reportField').addEventListener('change', renderReports);
// Фильтры
document.getElementById('search').addEventListener('input', e=>{ filters.search=e.target.value.toLowerCase(); renderTable(); });
document.getElementById('filterDegree').addEventListener('change', e=>{ filters.degree=e.target.value; renderTable(); });
document.getElementById('filterCategory').addEventListener('change', e=>{ filters.category=e.target.value; renderTable(); });
document.getElementById('filterBase').addEventListener('change', e=>{ filters.base=e.target.value; renderTable(); });
// Инлайн-редактирование таблицы (делегирование событий)
document.getElementById('tableBody').addEventListener('input', onCellInput);
document.getElementById('tableBody').addEventListener('change', onCellInput);
// Закрытие модалок по клику на фон
document.querySelectorAll('.modal-overlay').forEach(ov=>{
ov.addEventListener('click', e=>{ if(e.target===ov) ov.hidden=true; });
});
document.addEventListener('keydown', e=>{ if(e.key==='Escape'){ closeFields(); } });
}
/* ---------- Выпуски фильтров ---------- */
function buildFilterOptions(){
const degreeSel = document.getElementById('filterDegree');
const catSel = document.getElementById('filterCategory');
const baseSel = document.getElementById('filterBase');
degreeSel.innerHTML = ''+uniqueValues('academicDegree').map(v=>``).join('');
catSel.innerHTML = ''+uniqueValues('category').map(v=>``).join('');
baseSel.innerHTML = ''+uniqueValues('clinicalBase').map(v=>``).join('');
}
function uniqueValues(key){
const set = new Set();
state.teachers.forEach(t=>{ if(t[key]) set.add(t[key]); });
return [...set].sort();
}
/* ---------- Рендер всего ---------- */
function renderAll(){ renderStats(); renderDatalists(); renderTable(); buildFilterOptions(); buildReportFieldOptions(); }
/* ---------- Карточки статистики ---------- */
function renderStats(){
const total = state.teachers.length;
const doctors = state.teachers.filter(t=>t.academicDegree==='Доктор наук').length;
const candidates = state.teachers.filter(t=>t.academicDegree==='Кандидат наук').length;
const withDegree = doctors+candidates;
const withCert = state.teachers.filter(t=>t.specialistCertificate && t.specialistCertificate.trim()).length;
const inAssoc = state.teachers.filter(t=>t.associations && t.associations.trim()).length;
document.getElementById('statsGrid').innerHTML = `
${statCard('Всего преподавателей', total, 'всего на кафедре','accent')}
${statCard('С научной степенью', withDegree, `${candidates} кандидат · ${doctors} доктор`,'green')}
${statCard('Докторов наук', doctors, 'высшая квалификация','amber')}
${statCard('С сертификатом', withCert, 'специалиста','accent')}
${statCard('Члены ассоциаций', inAssoc, 'проф. членство','green')}
`;
}
function statCard(label,value,sub,cls){
return `
${label}
${value}
${sub||''}
`;
}
/* ---------- Таблица (инлайн-редактирование) ---------- */
function renderTable(){
const head = document.getElementById('tableHead');
const body = document.getElementById('tableBody');
const empty = document.getElementById('emptyState');
const fields = allFields();
// Заголовок: все поля + колонка действий
head.innerHTML = fields.map(f=>`${esc(f.label)}${f.required?' *':''} | `).join('') + ' | ';
const rows = filtered();
if(rows.length===0){
document.getElementById('teacherTable').style.display = state.teachers.length===0 ? 'none':'table';
if(state.teachers.length!==0){
empty.hidden=false; empty.querySelector('p').textContent='Ничего не найдено по фильтрам.';
empty.querySelector('button').style.display='none';
} else {
empty.hidden=false; empty.querySelector('p').textContent='Пока нет ни одного преподавателя.';
empty.querySelector('button').style.display='';
}
document.getElementById('countPill').textContent='';
body.innerHTML='';
return;
}
document.getElementById('teacherTable').style.display='table';
empty.hidden=true;
body.innerHTML = rows.map(t=>`
${fields.map(f=>`| ${cellInput(f,t)} | `).join('')}
|
`).join('');
document.getElementById('countPill').textContent = `Показано: ${rows.length} из ${state.teachers.length}`;
}
// Поле ввода прямо в ячейке таблицы
function cellInput(f, t){
const val = t[f.key] ?? '';
const name = f.key;
if(f.type==='textarea')
return ``;
if(f.type==='date')
return ``;
if(f.type==='number')
return ``;
if(f.type==='checkbox')
return ``;
if(f.type==='select')
return ``;
return ``;
}
// Один