med-cms 1.0.0-beta.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +13 -0
- package/README.md +117 -0
- package/database/config/db-init.js +67 -53
- package/database/models/appointments.js +35 -20
- package/database/models/clinics.js +25 -13
- package/database/models/diseases.js +21 -13
- package/database/models/districts.js +55 -0
- package/database/models/news.js +25 -0
- package/database/models/user-addresses.js +21 -14
- package/database/models/users.js +69 -26
- package/database/services/admin/create-clinic.js +18 -6
- package/database/services/admin/db-analytics.js +42 -16
- package/database/services/admin/index.js +6 -6
- package/database/services/admin/manage-staff.js +47 -17
- package/database/services/doctor/conduct-exam.js +26 -20
- package/database/services/doctor/get-schedule.js +26 -18
- package/database/services/doctor/index.js +6 -6
- package/database/services/doctor/patient-access.js +34 -25
- package/database/services/patient/book-appointment.js +37 -17
- package/database/services/patient/calc-district.js +70 -21
- package/database/services/patient/get-profile.js +49 -21
- package/database/services/patient/index.js +5 -5
- package/env_init.js +55 -0
- package/index.d.ts +38 -24
- package/index.js +48 -25
- package/migrate-diseases.js +192 -120
- package/package.json +3 -3
- package/public/css/admin.css +80 -80
- package/public/css/auth.css +52 -44
- package/public/css/doctor.css +131 -125
- package/public/css/home.css +155 -0
- package/public/html/dashboard-admin.html +423 -101
- package/public/html/dashboard-doctor.html +155 -66
- package/public/html/dashboard-patient.html +199 -94
- package/public/html/home/about.html +0 -0
- package/public/html/home/contact.html +0 -0
- package/public/html/home/info.html +0 -0
- package/public/html/home.html +159 -0
- package/public/html/login.html +73 -43
- package/public/js/admin.js +329 -165
- package/public/js/auth.js +45 -41
- package/public/js/doctor.js +117 -103
- package/public/js/home.js +113 -0
- package/public/js/patient.js +130 -108
- package/server/midleware.js +33 -13
- package/server/permissions.js +105 -43
- package/server/server.js +517 -176
- package/README +0 -161
- package/test.js +0 -27
package/public/js/doctor.js
CHANGED
|
@@ -1,137 +1,151 @@
|
|
|
1
|
-
document.addEventListener(
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2
|
+
loadSchedule();
|
|
3
|
+
loadDiseasesCatalog();
|
|
4
|
+
loadDoctorProfile();
|
|
5
5
|
});
|
|
6
6
|
|
|
7
7
|
async function loadDiseasesCatalog() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch("/med/api/doctor/diseases-list");
|
|
10
|
+
const result = await res.json();
|
|
11
|
+
|
|
12
|
+
const select = document.getElementById("examDisease");
|
|
13
|
+
|
|
14
|
+
select.innerHTML =
|
|
15
|
+
'<option value="">-- Выберите диагноз из справочника МКБ --</option>';
|
|
16
|
+
|
|
17
|
+
if (result.success && result.data.length > 0) {
|
|
18
|
+
result.data.forEach((disease) => {
|
|
19
|
+
const opt = document.createElement("option");
|
|
20
|
+
opt.value = disease.id;
|
|
21
|
+
opt.textContent = `[${disease.code}] ${disease.name}`;
|
|
22
|
+
select.appendChild(opt);
|
|
23
|
+
});
|
|
24
|
+
console.log(
|
|
25
|
+
`🌱 [АРМ Врача] Успешно загружено ${result.data.length} диагнозов из SQLite таблицы.`,
|
|
26
|
+
);
|
|
27
|
+
} else {
|
|
28
|
+
select.innerHTML =
|
|
29
|
+
'<option value="">Справочник болезней пуст (Запустите миграцию)</option>';
|
|
27
30
|
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error("❌ Ошибка при получении справочника болезней:", err);
|
|
33
|
+
}
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
async function loadSchedule() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch("/med/api/doctor/schedule");
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
|
|
41
|
+
const list = document.getElementById("scheduleList");
|
|
42
|
+
list.innerHTML = "";
|
|
43
|
+
|
|
44
|
+
if (data.success && data.schedule && data.schedule.length > 0) {
|
|
45
|
+
data.schedule.forEach((ap) => {
|
|
46
|
+
const li = document.createElement("li");
|
|
47
|
+
li.innerHTML = `<b>${ap.time}</b> - ${ap.Patient.fullName}
|
|
42
48
|
<button onclick="startExamination('${ap.id}', '${ap.Patient.fullName}')" class="btn-action">Принять</button>`;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
console.error('Ошибка загрузки расписания:', err);
|
|
49
|
+
list.appendChild(li);
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
list.innerHTML = "<li>Ближайших записей на сегодня нет</li>";
|
|
50
53
|
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error("Ошибка загрузки расписания:", err);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
document.getElementById(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const selectedDiseaseId = document.getElementById('examDisease').value;
|
|
59
|
+
document.getElementById("examForm").addEventListener("submit", async (e) => {
|
|
60
|
+
e.preventDefault();
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
appointmentId: document.getElementById('activeAppointmentId').value,
|
|
60
|
-
complaints: document.getElementById('examComplaints').value,
|
|
61
|
-
objectively: document.getElementById('examObjective').value,
|
|
62
|
-
doctorNotes: document.getElementById('examNotes').value,
|
|
63
|
-
diseaseId: selectedDiseaseId ? parseInt(selectedDiseaseId, 10) : null
|
|
64
|
-
};
|
|
62
|
+
const selectedDiseaseId = document.getElementById("examDisease").value;
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
64
|
+
const payload = {
|
|
65
|
+
appointmentId: document.getElementById("activeAppointmentId").value,
|
|
66
|
+
complaints: document.getElementById("examComplaints").value,
|
|
67
|
+
objectively: document.getElementById("examObjective").value,
|
|
68
|
+
doctorNotes: document.getElementById("examNotes").value,
|
|
69
|
+
diseaseId: selectedDiseaseId ? parseInt(selectedDiseaseId, 10) : null,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetch("/med/api/doctor/complete-examination", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify(payload),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const result = await res.json();
|
|
80
|
+
if (result.success) {
|
|
81
|
+
alert(
|
|
82
|
+
"Прием успешно завершен! Карточка обновлена, статистика внесена в SQLite.",
|
|
83
|
+
);
|
|
84
|
+
document.getElementById("examBlock").classList.add("hidden");
|
|
85
|
+
document.getElementById("examForm").reset();
|
|
86
|
+
await loadSchedule();
|
|
87
|
+
} else {
|
|
88
|
+
alert("Ошибка завершения приема: " + result.error);
|
|
83
89
|
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
alert("Ошибка сети при отправке данных осмотра");
|
|
92
|
+
}
|
|
84
93
|
});
|
|
85
94
|
|
|
86
95
|
function startExamination(appointmentId, patientName) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
document.getElementById("examBlock").classList.remove("hidden");
|
|
97
|
+
document.getElementById("activeAppointmentId").value = appointmentId;
|
|
98
|
+
document.getElementById("currentPatientName").textContent = patientName;
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
async function logout() {
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
await fetch("/med/api/auth/logout", { method: "POST" });
|
|
103
|
+
window.location.href = "/med/html/login.html";
|
|
95
104
|
}
|
|
96
105
|
async function loadDoctorProfile() {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
console.error('Ошибка загрузки профиля сотрудника:', err);
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch("/med/api/doctor/me");
|
|
108
|
+
const result = await res.json();
|
|
109
|
+
if (result.success && result.doctor) {
|
|
110
|
+
document.getElementById("doctorName").textContent =
|
|
111
|
+
result.doctor.fullName;
|
|
112
|
+
document.getElementById("doctorPosition").textContent =
|
|
113
|
+
result.doctor.position || "Врач";
|
|
106
114
|
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error("Ошибка загрузки профиля сотрудника:", err);
|
|
117
|
+
}
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
function togglePasswordModal() {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
const modal = document.getElementById("passwordModal");
|
|
122
|
+
modal.classList.toggle("hidden");
|
|
123
|
+
document.getElementById("changePasswordForm").reset();
|
|
113
124
|
}
|
|
114
125
|
|
|
115
|
-
document
|
|
126
|
+
document
|
|
127
|
+
.getElementById("changePasswordForm")
|
|
128
|
+
.addEventListener("submit", async (e) => {
|
|
116
129
|
e.preventDefault();
|
|
117
130
|
|
|
118
|
-
const oldPassword = document.getElementById(
|
|
119
|
-
const newPassword = document.getElementById(
|
|
131
|
+
const oldPassword = document.getElementById("oldPassword").value;
|
|
132
|
+
const newPassword = document.getElementById("newPassword").value;
|
|
120
133
|
|
|
121
134
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
const response = await fetch("/med/api/auth/change-password", {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Content-Type": "application/json" },
|
|
138
|
+
body: JSON.stringify({ oldPassword, newPassword }),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const result = await response.json();
|
|
142
|
+
if (result.success) {
|
|
143
|
+
alert("Ваш пароль успешно изменен в базе данных SQLite!");
|
|
144
|
+
togglePasswordModal();
|
|
145
|
+
} else {
|
|
146
|
+
alert("Ошибка смены пароля: " + result.message);
|
|
147
|
+
}
|
|
134
148
|
} catch (err) {
|
|
135
|
-
|
|
149
|
+
alert("Ошибка сети при попытке изменить пароль");
|
|
136
150
|
}
|
|
137
|
-
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Запуск подгрузки живой ленты новостей при открытии сайта
|
|
2
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
3
|
+
loadLiveNews();
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
// Статические вкладки информационного портала
|
|
7
|
+
const staticSections = {
|
|
8
|
+
about: {
|
|
9
|
+
title: "О нашей клинике",
|
|
10
|
+
text: "Мы — современный цифровой медицинский центр РБ, работающий по стандартам автоматизированного распределения пациентов. Наша цель — сделать медицинскую помощь доступной и быстрой.",
|
|
11
|
+
},
|
|
12
|
+
info: {
|
|
13
|
+
title: "Информационный источник",
|
|
14
|
+
text: "Полезные статьи о профилактике сезонных ОРВИ, контроле артериального давления при гипертонии и правилах подготовки к плановым диагностическим осмотрам в кабинетах поликлиники.",
|
|
15
|
+
},
|
|
16
|
+
contacts: {
|
|
17
|
+
title: "Контактная информация",
|
|
18
|
+
text: "Телефон регистратуры: +375 (17) 000-00-00. Режим работы окон справок: Пн-Пт с 8:00 до 20:00. Адрес: г. Минск, центральный лечебный корпус.",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Динамическое скачивание новостей из SQLite без авторизации
|
|
23
|
+
async function loadLiveNews() {
|
|
24
|
+
const box = document.getElementById("news-box");
|
|
25
|
+
box.innerHTML =
|
|
26
|
+
'<p style="color: #64748b;">Загрузка свежих новостей клиники...</p>';
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch("/med/api/public/news-list");
|
|
30
|
+
const result = await res.json();
|
|
31
|
+
|
|
32
|
+
if (result.success && result.data.length > 0) {
|
|
33
|
+
let html = "";
|
|
34
|
+
result.data.forEach((post) => {
|
|
35
|
+
const date = new Date(post.createdAt).toLocaleDateString("ru-RU");
|
|
36
|
+
html += `
|
|
37
|
+
<div style="margin-bottom: 25px; border-bottom: 1px solid #e2e8f0; padding-bottom: 15px;">
|
|
38
|
+
<span style="font-size:12px; color:#64748b;">📅 Опубликовано: ${date}</span>
|
|
39
|
+
<h2 style="margin: 5px 0 10px 0; color:#1e3a8a;">${post.title}</h2>
|
|
40
|
+
<p style="line-height: 1.6; color:#475569; margin:0;">${post.content}</p>
|
|
41
|
+
</div>
|
|
42
|
+
`;
|
|
43
|
+
});
|
|
44
|
+
box.innerHTML = html;
|
|
45
|
+
} else {
|
|
46
|
+
box.innerHTML =
|
|
47
|
+
'<h2>Лента новостей</h2><p style="color:#64748b;">На данный момент новостей и объявлений нет.</p>';
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error("Ошибка загрузки новостей:", err);
|
|
51
|
+
box.innerHTML =
|
|
52
|
+
'<p style="color:red;">Не удалось загрузить ленту новостей сервера.</p>';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Переключатель вкладок навигации по чертежу
|
|
57
|
+
function switchSection(sectionKey, element) {
|
|
58
|
+
document
|
|
59
|
+
.querySelectorAll(".nav-btn")
|
|
60
|
+
.forEach((btn) => btn.classList.remove("active"));
|
|
61
|
+
element.classList.add("active");
|
|
62
|
+
const elements = {
|
|
63
|
+
news: document.getElementById("news-box"),
|
|
64
|
+
about: document.getElementById("about-box"),
|
|
65
|
+
info: document.getElementById("info-box"),
|
|
66
|
+
contact: document.getElementById("contacts-box"), // изменил ключ на contact для соответствия строке
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// 1. Вызываем функцию для новостей, если выбрана эта секция
|
|
70
|
+
if (sectionKey === "news" && typeof loadLiveNews === "function") {
|
|
71
|
+
loadLiveNews();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. Скрываем все элементы и показываем только нужный
|
|
75
|
+
Object.keys(elements).forEach((key) => {
|
|
76
|
+
if (elements[key]) {
|
|
77
|
+
// проверка, что элемент нашелся в DOM
|
|
78
|
+
elements[key].style.display = key === sectionKey ? "block" : "none";
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Онлайн-проверка лечебного участка по полному адресу кнопкой "ОК"
|
|
83
|
+
async function checkDistrictOnline() {
|
|
84
|
+
const city = document.getElementById("checkCityInput").value;
|
|
85
|
+
const street = document.getElementById("checkStreetInput").value;
|
|
86
|
+
const house = document.getElementById("checkHouseInput").value;
|
|
87
|
+
const resultBox = document.getElementById("checkResultBox");
|
|
88
|
+
|
|
89
|
+
if (!city || !street || !house) {
|
|
90
|
+
resultBox.innerHTML =
|
|
91
|
+
'<span style="color:red;">Заполните все три поля: Город, Улица, Дом!</span>';
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resultBox.innerHTML = "Проверка адреса в SQLite...";
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Формируем безопасный GET-запрос со всеми параметрами адреса
|
|
99
|
+
const url = `/med/api/public/check-district?clinicId=1&city=${encodeURIComponent(city)}&street=${encodeURIComponent(street)}&house=${encodeURIComponent(house)}`;
|
|
100
|
+
const res = await fetch(url);
|
|
101
|
+
const result = await res.json();
|
|
102
|
+
|
|
103
|
+
if (result.success) {
|
|
104
|
+
resultBox.innerHTML = `🩺 ${result.data.assignedDistrict} <br> 🚪 ${result.data.assignedCabinet}`;
|
|
105
|
+
} else {
|
|
106
|
+
resultBox.innerHTML = `<span style="color:red;">Ошибка: ${result.error}</span>`;
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(err);
|
|
110
|
+
resultBox.innerHTML =
|
|
111
|
+
'<span style="color:red;">Ошибка соединения с базой данных</span>';
|
|
112
|
+
}
|
|
113
|
+
}
|