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/admin.js
CHANGED
|
@@ -1,199 +1,363 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Инициализация дашборда: строго один слушатель, который запускает всё по очереди
|
|
2
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
3
|
+
console.log("🚀 [Admin] Запуск инициализации панелей...");
|
|
4
|
+
await loadDashboardStats();
|
|
5
|
+
await loadClinicsDropdown();
|
|
6
|
+
await loadClinicsForDistrictsSelect();
|
|
7
|
+
await loadStaffForDistrictsSelect();
|
|
8
|
+
await renderLiveDistrictsTable();
|
|
3
9
|
});
|
|
10
|
+
|
|
11
|
+
// Базовая статистика верхних карточек
|
|
4
12
|
async function loadDashboardStats() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
document.getElementById('statClinics').textContent = result.stats.summary.totalClinics;
|
|
14
|
-
document.getElementById('statPatients').textContent = result.stats.summary.totalPatients;
|
|
15
|
-
document.getElementById('statStaff').textContent = result.stats.summary.totalStaff;
|
|
16
|
-
await loadClinicsDropdown();
|
|
17
|
-
} catch (err) {
|
|
18
|
-
console.error('Ошибка загрузки статистики:', err);
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch("/med/api/admin/stats");
|
|
15
|
+
const result = await response.json();
|
|
16
|
+
if (!result.success) {
|
|
17
|
+
alert("Ошибка доступа к админ-панели: " + result.message);
|
|
18
|
+
window.location.href = "/med/med-login";
|
|
19
|
+
return;
|
|
19
20
|
}
|
|
21
|
+
document.getElementById("statClinics").textContent =
|
|
22
|
+
result.stats.summary.totalClinics;
|
|
23
|
+
document.getElementById("statPatients").textContent =
|
|
24
|
+
result.stats.summary.totalPatients;
|
|
25
|
+
document.getElementById("statStaff").textContent =
|
|
26
|
+
result.stats.summary.totalStaff;
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error("Ошибка загрузки статистики:", err);
|
|
29
|
+
}
|
|
20
30
|
}
|
|
31
|
+
|
|
32
|
+
// Загрузка клиник в форму №2 (Создание сотрудника)
|
|
21
33
|
async function loadClinicsDropdown() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
select.innerHTML = '<option value="">-- Выберите больницу --</option>';
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch("/med/api/admin/list-clinics");
|
|
36
|
+
const result = await response.json();
|
|
37
|
+
const select = document.getElementById("staffClinic");
|
|
38
|
+
if (!select) return;
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
console.error('Ошибка наполнения списка клиник:', err);
|
|
40
|
+
select.innerHTML = '<option value="">-- Выберите больницу --</option>';
|
|
41
|
+
if (result.success && result.data.length > 0) {
|
|
42
|
+
result.data.forEach((clinic) => {
|
|
43
|
+
const opt = document.createElement("option");
|
|
44
|
+
opt.value = clinic.id;
|
|
45
|
+
opt.textContent = `[ID: ${clinic.id}] ${clinic.name}`;
|
|
46
|
+
select.appendChild(opt);
|
|
47
|
+
});
|
|
38
48
|
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error("Ошибка наполнения списка клиник:", err);
|
|
51
|
+
}
|
|
39
52
|
}
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
// Загрузка клиник в форму №5 (Конфигуратор участков)
|
|
55
|
+
async function loadClinicsForDistrictsSelect() {
|
|
56
|
+
const select = document.getElementById("distClinicId");
|
|
57
|
+
if (!select) return;
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch("/med/api/admin/list-clinics");
|
|
60
|
+
const result = await response.json();
|
|
61
|
+
select.innerHTML =
|
|
62
|
+
'<option value="">-- Выберите медицинское учреждение --</option>';
|
|
63
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
64
|
+
result.data.forEach((clinic) => {
|
|
65
|
+
const opt = document.createElement("option");
|
|
66
|
+
opt.value = clinic.id;
|
|
67
|
+
opt.textContent = `[ID: ${clinic.id}] ${clinic.name}`;
|
|
68
|
+
select.appendChild(opt);
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
select.innerHTML =
|
|
72
|
+
'<option value="">Клиники не найдены. Создайте их в форме №1</option>';
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error("Ошибка заполнения селекта клиник:", err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
49
78
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
// Загрузка существующих сотрудников в селект формы №5 (Конфигуратор участков)
|
|
80
|
+
async function loadStaffForDistrictsSelect() {
|
|
81
|
+
const select = document.getElementById("distDoctor");
|
|
82
|
+
if (!select) return;
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch("/med/api/admin/list-staff");
|
|
85
|
+
const result = await response.json();
|
|
86
|
+
select.innerHTML =
|
|
87
|
+
'<option value="">-- Выберите сотрудника из базы данных --</option>';
|
|
88
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
89
|
+
result.data.forEach((staff) => {
|
|
90
|
+
const opt = document.createElement("option");
|
|
91
|
+
opt.value = staff.fullName;
|
|
92
|
+
const clinicName = staff.Clinic ? staff.Clinic.name : "Без привязки";
|
|
93
|
+
opt.textContent = `${staff.fullName} (${staff.position || "Врач"} — ${clinicName})`;
|
|
94
|
+
select.appendChild(opt);
|
|
95
|
+
});
|
|
96
|
+
console.log(
|
|
97
|
+
`✅ Селект сотрудников успешно обновлен: ${result.data.length} шт.`,
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
select.innerHTML =
|
|
101
|
+
'<option value="">Нет зарегистрированных сотрудников. Создайте их в форме №2</option>';
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error("Ошибка заполнения селекта сотрудников:", err);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
53
107
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
108
|
+
// Отрисовка интерактивной таблицы участков из SQLite
|
|
109
|
+
async function renderLiveDistrictsTable() {
|
|
110
|
+
const container = document.getElementById("liveDistrictsTableBox");
|
|
111
|
+
if (!container) return;
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch("/med/api/admin/list-districts");
|
|
114
|
+
const result = await res.json();
|
|
115
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
116
|
+
let html = `
|
|
117
|
+
<table style="width:100%; border-collapse:collapse; font-size:13px; margin-top:15px; text-align:left;">
|
|
57
118
|
<thead>
|
|
58
|
-
<tr>
|
|
59
|
-
<th
|
|
60
|
-
<th
|
|
61
|
-
<th
|
|
62
|
-
<th
|
|
119
|
+
<tr style="background:#f1f5f9; color:#1e3a8a; border-bottom:2px solid #cbd5e1;">
|
|
120
|
+
<th style="padding:10px;">Медицинское учреждение</th>
|
|
121
|
+
<th style="padding:10px;">Название участка</th>
|
|
122
|
+
<th style="padding:10px;">Кабинет</th>
|
|
123
|
+
<th style="padding:10px;">Адрес покрытия</th>
|
|
124
|
+
<th style="padding:10px;">Дома</th>
|
|
125
|
+
<th style="padding:10px;">Закрепленный сотрудник</th>
|
|
63
126
|
</tr>
|
|
64
127
|
</thead>
|
|
65
128
|
<tbody>
|
|
66
129
|
`;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<td>${
|
|
130
|
+
result.data.forEach((d) => {
|
|
131
|
+
const clinicName = d.Clinic
|
|
132
|
+
? d.Clinic.name
|
|
133
|
+
: '<span style="color:red;">Не привязано</span>';
|
|
134
|
+
html += `
|
|
135
|
+
<tr style="border-bottom:1px solid #cbd5e1;">
|
|
136
|
+
<td style="padding:10px;"><b>${clinicName}</b></td>
|
|
137
|
+
<td style="padding:10px;"><span class="uid-badge" style="background:#e0f2fe; color:#0369a1; padding:3px 6px; border-radius:4px; font-weight:bold;">${d.name}</span></td>
|
|
138
|
+
<td style="padding:10px;"><span class="uid-badge" style="background:#dcfce7; color:#15803d; padding:3px 6px; border-radius:4px; font-weight:bold;">${d.cabinet}</span></td>
|
|
139
|
+
<td style="padding:10px;">г. ${d.city}, ул. ${d.street}</td>
|
|
140
|
+
<td style="padding:10px;">д. ${d.houses}</td>
|
|
141
|
+
<td style="padding:10px; color:#475569;">👤 ${d.doctorName || "Не назначен"}</td>
|
|
74
142
|
</tr>
|
|
75
143
|
`;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} catch (err) {
|
|
83
|
-
content.innerHTML = '<p style="color:red;">Ошибка загрузки данных клиник</p>';
|
|
144
|
+
});
|
|
145
|
+
html += "</tbody></table>";
|
|
146
|
+
container.innerHTML = html;
|
|
147
|
+
} else {
|
|
148
|
+
container.innerHTML =
|
|
149
|
+
'<p style="color:#64748b; font-style:italic; margin:15px 0 0 0;">В системе пока нет настроенных участков покрытия клиник.</p>';
|
|
84
150
|
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.error("Ошибка рендеринга таблицы участков:", err);
|
|
153
|
+
container.innerHTML =
|
|
154
|
+
'<p style="color:red; margin:15px 0 0 0;">Ошибка автоматического обновления таблицы участков</p>';
|
|
155
|
+
}
|
|
85
156
|
}
|
|
86
157
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
158
|
+
// Форма 1: Создание клиники
|
|
159
|
+
document.getElementById("clinicForm").addEventListener("submit", async (e) => {
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
const payload = {
|
|
162
|
+
name: document.getElementById("clinicName").value,
|
|
163
|
+
type: document.getElementById("clinicType").value,
|
|
164
|
+
address: document.getElementById("clinicAddress").value,
|
|
165
|
+
};
|
|
166
|
+
try {
|
|
167
|
+
const response = await fetch("/med/api/admin/clinics", {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "application/json",
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify(payload),
|
|
173
|
+
});
|
|
174
|
+
const result = await response.json();
|
|
175
|
+
if (result.success) {
|
|
176
|
+
alert(`Учреждение успешно добавлено в SQLite под ID №${result.data.id}!`);
|
|
177
|
+
document.getElementById("clinicForm").reset();
|
|
91
178
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
179
|
+
// АВТОМАТИКА: Обновляем карточки и выпадающие списки клиник во всех формах
|
|
180
|
+
await loadDashboardStats();
|
|
181
|
+
await loadClinicsDropdown();
|
|
182
|
+
await loadClinicsForDistrictsSelect();
|
|
183
|
+
} else {
|
|
184
|
+
alert("Ошибка: " + result.error);
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
alert("Ошибка сети при создании клиники");
|
|
188
|
+
console.error(err);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
95
191
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
192
|
+
// Форма 2: Создание сотрудника
|
|
193
|
+
document.getElementById("staffForm").addEventListener("submit", async (e) => {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
const clinicSelect = document.getElementById("staffClinic");
|
|
196
|
+
const selectedClinicId = clinicSelect.value;
|
|
197
|
+
const selectedProfile = document.getElementById("staffMedicalProfile").value;
|
|
198
|
+
if (!selectedClinicId) {
|
|
199
|
+
alert("Пожалуйста, выберите больницу из списка!");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const payload = {
|
|
203
|
+
fullName: document.getElementById("staffName").value,
|
|
204
|
+
email: document.getElementById("staffEmail").value,
|
|
205
|
+
rawPassword: document.getElementById("staffPassword").value,
|
|
206
|
+
medicalProfile: selectedProfile,
|
|
207
|
+
clinicId: parseInt(selectedClinicId, 10),
|
|
208
|
+
};
|
|
209
|
+
try {
|
|
210
|
+
const response = await fetch("/med/api/admin/staff", {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: { "Content-Type": "application/json" },
|
|
213
|
+
body: JSON.stringify(payload),
|
|
214
|
+
});
|
|
215
|
+
const result = await response.json();
|
|
216
|
+
if (result.success) {
|
|
217
|
+
alert(`Сотрудник успешно создан!\nПрофиль: ${selectedProfile}`);
|
|
218
|
+
document.getElementById("staffForm").reset();
|
|
99
219
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<th>ID Сотрудника</th>
|
|
106
|
-
<th>ФИО</th>
|
|
107
|
-
<th>Email</th>
|
|
108
|
-
<th>Должность</th> <!-- Новая колонка -->
|
|
109
|
-
<th>Специализация</th>
|
|
110
|
-
<th>Место работы</th>
|
|
111
|
-
</tr>
|
|
112
|
-
</thead>
|
|
113
|
-
<tbody>
|
|
114
|
-
`;
|
|
115
|
-
result.data.forEach(s => {
|
|
116
|
-
const clinicName = s.Clinic ? s.Clinic.name : '<span style="color:red;">Не привязан</span>';
|
|
117
|
-
html += `
|
|
118
|
-
<tr>
|
|
119
|
-
<td><span class="uid-badge">${s.id}</span></td>
|
|
120
|
-
<td><b>${s.fullName}</b></td>
|
|
121
|
-
<td>${s.email}</td>
|
|
122
|
-
<td><span class="badge" style="background:#f1f5f9; color:#475569;">${s.position || 'Врач'}</span></td> <!-- Выводим должность -->
|
|
123
|
-
<td>${s.specialty || 'Нет'}</td>
|
|
124
|
-
<td>${clinicName}</td>
|
|
125
|
-
</tr>
|
|
126
|
-
`;
|
|
127
|
-
});
|
|
128
|
-
html += '</tbody></table>';
|
|
129
|
-
content.innerHTML = html;
|
|
130
|
-
} else {
|
|
131
|
-
content.innerHTML = '<p>В штате системы пока нет зарегистрированных сотрудников.</p>';
|
|
132
|
-
}
|
|
133
|
-
} catch (err) {
|
|
134
|
-
content.innerHTML = '<p style="color:red;">Ошибка загрузки данных персонала</p>';
|
|
220
|
+
// АВТОМАТИКА: Мгновенно обновляем статистику и селект сотрудников в форме участков!
|
|
221
|
+
await loadDashboardStats();
|
|
222
|
+
await loadStaffForDistrictsSelect();
|
|
223
|
+
} else {
|
|
224
|
+
alert("Ошибка creation: " + result.error);
|
|
135
225
|
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
226
|
+
} catch (err) {
|
|
227
|
+
alert("Ошибка сети при создании сотрудника");
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Форма 4: Новости
|
|
232
|
+
document.getElementById("newsForm").addEventListener("submit", async (e) => {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
const payload = {
|
|
235
|
+
title: document.getElementById("newsTitle").value,
|
|
236
|
+
content: document.getElementById("newsContent").value,
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
const response = await fetch("/med/api/admin/news", {
|
|
240
|
+
method: "POST",
|
|
241
|
+
headers: { "Content-Type": "application/json" },
|
|
242
|
+
body: JSON.stringify(payload),
|
|
243
|
+
});
|
|
244
|
+
const result = await response.json();
|
|
245
|
+
if (result.success) {
|
|
246
|
+
alert(
|
|
247
|
+
`Новость "${result.data.title}" успешно опубликована в ленту главной страницы!`,
|
|
248
|
+
);
|
|
249
|
+
document.getElementById("newsForm").reset();
|
|
250
|
+
await loadDashboardStats();
|
|
251
|
+
} else {
|
|
252
|
+
alert("Ошибка публикации новости: " + result.error);
|
|
160
253
|
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
alert("Ошибка сети при попытке опубликовать новость");
|
|
256
|
+
}
|
|
161
257
|
});
|
|
162
|
-
|
|
258
|
+
|
|
259
|
+
// Форма 5: Отправка участка
|
|
260
|
+
document
|
|
261
|
+
.getElementById("districtForm")
|
|
262
|
+
.addEventListener("submit", async (e) => {
|
|
163
263
|
e.preventDefault();
|
|
164
|
-
const clinicSelect = document.getElementById('staffClinic');
|
|
165
|
-
const selectedClinicId = clinicSelect.value;
|
|
166
|
-
const selectedProfile = document.getElementById('staffMedicalProfile').value;
|
|
167
|
-
if (!selectedClinicId) {
|
|
168
|
-
alert('Пожалуйста, выберите больницу из списка!');
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
264
|
const payload = {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
265
|
+
clinicId: document.getElementById("distClinicId").value,
|
|
266
|
+
name: document.getElementById("distName").value,
|
|
267
|
+
city: document.getElementById("distCity").value,
|
|
268
|
+
street: document.getElementById("distStreet").value,
|
|
269
|
+
houses: document.getElementById("distHouses").value,
|
|
270
|
+
cabinet: document.getElementById("distCabinet").value,
|
|
271
|
+
doctorName: document.getElementById("distDoctor").value,
|
|
177
272
|
};
|
|
178
273
|
try {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
274
|
+
const response = await fetch("/med/api/admin/districts", {
|
|
275
|
+
method: "POST",
|
|
276
|
+
headers: { "Content-Type": "application/json" },
|
|
277
|
+
body: JSON.stringify(payload),
|
|
278
|
+
});
|
|
279
|
+
const result = await response.json();
|
|
280
|
+
if (result.success) {
|
|
281
|
+
alert(`Привязка для ${result.data.name} успешно зафиксирована!`);
|
|
282
|
+
document.getElementById("districtForm").reset();
|
|
283
|
+
await renderLiveDistrictsTable();
|
|
284
|
+
} else {
|
|
285
|
+
alert("Ошибка привязки участка: " + result.error);
|
|
286
|
+
}
|
|
192
287
|
} catch (err) {
|
|
193
|
-
|
|
288
|
+
alert("Ошибка сети при отправке данных участка");
|
|
194
289
|
}
|
|
195
|
-
});
|
|
290
|
+
});
|
|
291
|
+
// Просмотр реестров в таблице
|
|
292
|
+
async function viewClinicsTable() {
|
|
293
|
+
const block = document.getElementById("tableViewerBlock");
|
|
294
|
+
const title = document.getElementById("tableViewerTitle");
|
|
295
|
+
const content = document.getElementById("tableViewerContent");
|
|
296
|
+
block.classList.remove("hidden");
|
|
297
|
+
title.textContent = " 🏥 Реестр медицинских учреждений системы";
|
|
298
|
+
content.innerHTML = "Загрузка таблицы...";
|
|
299
|
+
try {
|
|
300
|
+
const res = await fetch("/med/api/admin/list-clinics");
|
|
301
|
+
const result = await res.json();
|
|
302
|
+
if (result.success && result.data.length > 0) {
|
|
303
|
+
let html =
|
|
304
|
+
"<table><thead><tr><th>ID Клиники</th><th>Название</th><th>Тип</th><th>Адрес</th></tr></thead><tbody>";
|
|
305
|
+
result.data.forEach((c) => {
|
|
306
|
+
html += `<tr><td><span class="uid-badge">${c.id}</span></td><td><b>${c.name}</b></td><td>${c.type}</td><td>${c.address}</td></tr>`;
|
|
307
|
+
});
|
|
308
|
+
html += "";
|
|
309
|
+
content.innerHTML = html;
|
|
310
|
+
} else {
|
|
311
|
+
content.innerHTML = "В базе данных пока нет созданных учреждений.";
|
|
312
|
+
}
|
|
313
|
+
} catch (err) {
|
|
314
|
+
content.innerHTML = "Ошибка загрузки данных клиник";
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function viewStaffTable() {
|
|
318
|
+
const block = document.getElementById("tableViewerBlock");
|
|
319
|
+
const title = document.getElementById("tableViewerTitle");
|
|
320
|
+
const content = document.getElementById("tableViewerContent");
|
|
321
|
+
block.classList.remove("hidden");
|
|
322
|
+
title.textContent = "👨⚕️ Зарегистрированный медицинский персонал";
|
|
323
|
+
content.innerHTML = "Загрузка таблицы...";
|
|
324
|
+
try {
|
|
325
|
+
const res = await fetch("/med/api/admin/list-staff");
|
|
326
|
+
const result = await res.json();
|
|
327
|
+
if (result.success && result.data.length > 0) {
|
|
328
|
+
let html = `<table><thead><tr><th>ID Сотрудника</th><th>ФИО</th><th>Email</th><th>Должность</th><th>Специализация</th><th>Место работы</th></tr></thead><tbody>`;
|
|
329
|
+
result.data.forEach((s) => {
|
|
330
|
+
const clinicName = s.Clinic ? s.Clinic.name : "Не привязан";
|
|
331
|
+
html += `
|
|
332
|
+
<tr>
|
|
333
|
+
<td>
|
|
334
|
+
<span class="uid-badge">${s.id}</span>
|
|
335
|
+
</td>
|
|
336
|
+
<td>
|
|
337
|
+
<b>${s.fullName}</b>
|
|
338
|
+
</td>
|
|
339
|
+
<td>${s.email}</td>
|
|
340
|
+
<td>
|
|
341
|
+
<span class="badge" style="background:#f1f5f9; color:#475569;">
|
|
342
|
+
${s.position || "Врач"}
|
|
343
|
+
</span>
|
|
344
|
+
</td>
|
|
345
|
+
<td>${s.specialty || "Нет"}</td>
|
|
346
|
+
<td>${clinicName}</td>
|
|
347
|
+
</tr>
|
|
348
|
+
`;
|
|
349
|
+
});
|
|
350
|
+
html += "";
|
|
351
|
+
content.innerHTML = html;
|
|
352
|
+
} else {
|
|
353
|
+
content.innerHTML =
|
|
354
|
+
"В штате системы пока нет зарегистрированных сотрудников.";
|
|
355
|
+
}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
content.innerHTML = "Ошибка загрузки данных персонала";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
196
360
|
async function logout() {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
361
|
+
await fetch("/med/api/auth/logout", { method: "POST" });
|
|
362
|
+
window.location.href = "/med/med-login";
|
|
363
|
+
}
|
package/public/js/auth.js
CHANGED
|
@@ -1,55 +1,59 @@
|
|
|
1
1
|
function toggleAuth() {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
document.getElementById("loginBox").classList.toggle("hidden");
|
|
3
|
+
document.getElementById("registerBox").classList.toggle("hidden");
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
document.getElementById(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
document.getElementById("loginForm").addEventListener("submit", async (e) => {
|
|
7
|
+
e.preventDefault();
|
|
8
|
+
const email = document.getElementById("loginEmail").value;
|
|
9
|
+
const password = document.getElementById("loginPassword").value;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch("/med/api/auth/login", {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: { "Content-Type": "application/json" },
|
|
15
|
+
body: JSON.stringify({ email, password }),
|
|
16
|
+
});
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
alert('Не удалось связаться с сервером');
|
|
18
|
+
const result = await response.json();
|
|
19
|
+
|
|
20
|
+
if (result.success) {
|
|
21
|
+
if (result.user.role === "admin")
|
|
22
|
+
window.location.href = "/med/html/dashboard-admin.html";
|
|
23
|
+
else if (result.user.role === "doctor")
|
|
24
|
+
window.location.href = "/med/html/dashboard-doctor.html";
|
|
25
|
+
else window.location.href = "/med/html/dashboard-patient.html";
|
|
26
|
+
} else {
|
|
27
|
+
alert("Ошибка: " + result.message);
|
|
29
28
|
}
|
|
29
|
+
} catch (err) {
|
|
30
|
+
alert("Не удалось связаться с сервером");
|
|
31
|
+
}
|
|
30
32
|
});
|
|
31
33
|
|
|
32
|
-
document
|
|
34
|
+
document
|
|
35
|
+
.getElementById("registerForm")
|
|
36
|
+
.addEventListener("submit", async (e) => {
|
|
33
37
|
e.preventDefault();
|
|
34
|
-
const fullName = document.getElementById(
|
|
35
|
-
const email = document.getElementById(
|
|
36
|
-
const password = document.getElementById(
|
|
38
|
+
const fullName = document.getElementById("regName").value;
|
|
39
|
+
const email = document.getElementById("regEmail").value;
|
|
40
|
+
const password = document.getElementById("regPassword").value;
|
|
37
41
|
|
|
38
42
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const response = await fetch("/med/api/auth/register", {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "Content-Type": "application/json" },
|
|
46
|
+
body: JSON.stringify({ email, password, fullName }),
|
|
47
|
+
});
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const result = await response.json();
|
|
50
|
+
if (result.success) {
|
|
51
|
+
alert("Регистрация прошла успешно! Теперь вы можете войти.");
|
|
52
|
+
toggleAuth();
|
|
53
|
+
} else {
|
|
54
|
+
alert("Ошибка регистрации: " + result.message);
|
|
55
|
+
}
|
|
52
56
|
} catch (err) {
|
|
53
|
-
|
|
57
|
+
alert("Ошибка сети");
|
|
54
58
|
}
|
|
55
|
-
});
|
|
59
|
+
});
|