329 lines
11 KiB
JavaScript
329 lines
11 KiB
JavaScript
// ========== ДАННЫЕ О ЛЕКАРСТВАХ ==========
|
||
const MEDICATIONS = [
|
||
// УТРО (7:00)
|
||
{ name: "Эутирокс", dose: "1 таблетка", time: "07:00", period: "утро", instruction: "утром" },
|
||
{ name: "Йодомарин", dose: "1 таблетка", time: "07:00", period: "утро", instruction: "после завтрака" },
|
||
{ name: "Биовит", dose: "1 ст. ложка", time: "07:00", period: "утро", instruction: "после еды" },
|
||
|
||
// ПЕРЕД ОБЕДОМ (11:30)
|
||
{ name: "Одестон 200", dose: "1 таблетка", time: "11:30", period: "обед", instruction: "за 20 мин до еды" },
|
||
{ name: "Аминосорб", dose: "1 ст. ложка", time: "11:30", period: "обед", instruction: "за 30 мин до еды" },
|
||
|
||
// ОБЕД (12:00)
|
||
{ name: "Креон", dose: "3 таблетки", time: "12:00", period: "обед", instruction: "во время еды" },
|
||
|
||
// ПОСЛЕ ОБЕДА (14:00)
|
||
{ name: "Урсосан 250", dose: "2 таблетки", time: "14:00", period: "обед", instruction: "после обеда" },
|
||
{ name: "Биовит", dose: "1 ст. ложка", time: "14:00", period: "обед", instruction: "после еды" },
|
||
|
||
// ПЕРЕД УЖИНОМ (17:30)
|
||
{ name: "Одестон 200", dose: "1 таблетка", time: "17:30", period: "ужин", instruction: "за 20 мин до еды" },
|
||
{ name: "Аминосорб", dose: "1 ст. ложка", time: "17:30", period: "ужин", instruction: "за 30 мин до еды" },
|
||
|
||
// УЖИН (18:00)
|
||
{ name: "Креон", dose: "3 таблетки", time: "18:00", period: "ужин", instruction: "во время еды" },
|
||
|
||
// ПОСЛЕ УЖИНА (20:00)
|
||
{ name: "Урсосан 250", dose: "2 таблетки", time: "20:00", period: "ужин", instruction: "после ужина" },
|
||
{ name: "Биовит", dose: "1 ст. ложка", time: "20:00", period: "ужин", instruction: "после еды" }
|
||
];
|
||
|
||
// Уникальные времена
|
||
const TIMES = ["07:00", "11:30", "12:00", "14:00", "17:30", "18:00", "20:00"];
|
||
|
||
// ========== СОСТОЯНИЕ ==========
|
||
let takenMeds = JSON.parse(localStorage.getItem('meds_taken') || '{}');
|
||
let notificationPermission = 'default';
|
||
let audioCtx = null;
|
||
|
||
// ========== ИНИЦИАЛИЗАЦИЯ ==========
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
checkNotificationPermission();
|
||
renderSchedule();
|
||
updateClock();
|
||
setInterval(updateClock, 1000);
|
||
setInterval(checkReminders, 10000);
|
||
updateTakenCount();
|
||
});
|
||
|
||
// ========== ЧАСЫ ==========
|
||
function updateClock() {
|
||
const now = new Date();
|
||
document.getElementById('current-time').textContent = now.toLocaleTimeString('ru-RU', {
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
});
|
||
updateNextReminder();
|
||
updateStatus();
|
||
}
|
||
|
||
// ========== УВЕДОМЛЕНИЯ ==========
|
||
function checkNotificationPermission() {
|
||
if (!('Notification' in window)) {
|
||
document.getElementById('notif-status').textContent = 'Браузер не поддерживает уведомления';
|
||
document.getElementById('notif-btn').classList.add('hidden');
|
||
return;
|
||
}
|
||
|
||
notificationPermission = Notification.permission;
|
||
updateNotificationUI();
|
||
}
|
||
|
||
function updateNotificationUI() {
|
||
const status = document.getElementById('notif-status');
|
||
const btn = document.getElementById('notif-btn');
|
||
|
||
if (notificationPermission === 'granted') {
|
||
status.textContent = '✅ Уведомления включены';
|
||
btn.classList.add('hidden');
|
||
} else if (notificationPermission === 'denied') {
|
||
status.textContent = '❌ Уведомления заблокированы. Включите в настройках браузера';
|
||
btn.classList.add('hidden');
|
||
} else {
|
||
status.textContent = 'Уведомления не включены';
|
||
btn.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
function requestNotificationPermission() {
|
||
if (!('Notification' in window)) return;
|
||
|
||
Notification.requestPermission().then(permission => {
|
||
notificationPermission = permission;
|
||
updateNotificationUI();
|
||
if (permission === 'granted') {
|
||
sendNotification('Готово! 🔔', 'Уведомления о приёме лекарств включены');
|
||
}
|
||
});
|
||
}
|
||
|
||
function sendNotification(title, body) {
|
||
if (notificationPermission === 'granted') {
|
||
new Notification(title, {
|
||
body: body,
|
||
icon: '💊',
|
||
badge: '💊',
|
||
tag: 'med-reminder',
|
||
requireInteraction: true
|
||
});
|
||
}
|
||
playSound();
|
||
}
|
||
|
||
// ========== ЗВУК ==========
|
||
function playSound() {
|
||
try {
|
||
if (!audioCtx) {
|
||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||
}
|
||
|
||
const oscillator = audioCtx.createOscillator();
|
||
const gainNode = audioCtx.createGain();
|
||
|
||
oscillator.connect(gainNode);
|
||
gainNode.connect(audioCtx.destination);
|
||
|
||
oscillator.frequency.value = 800;
|
||
oscillator.type = 'sine';
|
||
|
||
gainNode.gain.setValueAtTime(0.3, audioCtx.currentTime);
|
||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5);
|
||
|
||
oscillator.start(audioCtx.currentTime);
|
||
oscillator.stop(audioCtx.currentTime + 0.5);
|
||
} catch (e) {
|
||
console.log('Audio not supported');
|
||
}
|
||
}
|
||
|
||
// ========== РАСПИСАНИЕ ==========
|
||
function renderSchedule() {
|
||
const container = document.getElementById('schedule');
|
||
const now = new Date();
|
||
const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
||
|
||
let html = '';
|
||
|
||
for (const time of TIMES) {
|
||
const medsAtTime = MEDICATIONS.filter(m => m.time === time);
|
||
const isPast = time < currentTime;
|
||
const isCurrent = isTimeNear(time, currentTime);
|
||
|
||
let blockClass = 'time-block';
|
||
if (isCurrent) blockClass += ' active';
|
||
else if (isPast) blockClass += ' past';
|
||
|
||
html += `<div class="${blockClass}">`;
|
||
html += `<div class="flex items-center gap-2 mb-2">`;
|
||
html += `<span class="text-lg font-bold">${time}</span>`;
|
||
if (isCurrent) html += `<span class="text-xs bg-green-500 text-white px-2 py-1 rounded-full">СЕЙЧАС</span>`;
|
||
html += `</div>`;
|
||
|
||
for (const med of medsAtTime) {
|
||
const key = `${time}_${med.name}`;
|
||
const isTaken = takenMeds[key];
|
||
|
||
html += `<div class="pill ${isTaken ? 'taken' : (isCurrent ? 'current' : '')}" onclick="toggleMed('${key}')">`;
|
||
html += `<span>${isTaken ? '✅' : '💊'}</span>`;
|
||
html += `<span><strong>${med.name}</strong> — ${med.dose}</span>`;
|
||
html += `<span class="text-xs text-gray-500">(${med.instruction})</span>`;
|
||
html += `</div>`;
|
||
}
|
||
|
||
html += `</div>`;
|
||
}
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function isTimeNear(targetTime, currentTime) {
|
||
const [tH, tM] = targetTime.split(':').map(Number);
|
||
const [cH, cM] = currentTime.split(':').map(Number);
|
||
|
||
const targetMin = tH * 60 + tM;
|
||
const currentMin = cH * 60 + cM;
|
||
|
||
return Math.abs(targetMin - currentMin) <= 15;
|
||
}
|
||
|
||
// ========== ОТМЕТА ПРИНЯТИЯ ==========
|
||
function toggleMed(key) {
|
||
takenMeds[key] = !takenMeds[key];
|
||
localStorage.setItem('meds_taken', JSON.stringify(takenMeds));
|
||
renderSchedule();
|
||
updateTakenCount();
|
||
|
||
if (takenMeds[key]) {
|
||
playSound();
|
||
}
|
||
}
|
||
|
||
function markAllCurrentAsTaken() {
|
||
const now = new Date();
|
||
const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
||
|
||
for (const time of TIMES) {
|
||
if (isTimeNear(time, currentTime) || time <= currentTime) {
|
||
const medsAtTime = MEDICATIONS.filter(m => m.time === time);
|
||
for (const med of medsAtTime) {
|
||
const key = `${time}_${med.name}`;
|
||
takenMeds[key] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
localStorage.setItem('meds_taken', JSON.stringify(takenMeds));
|
||
renderSchedule();
|
||
updateTakenCount();
|
||
playSound();
|
||
}
|
||
|
||
function resetDay() {
|
||
if (confirm('Сбросить все отметки за сегодня?')) {
|
||
takenMeds = {};
|
||
localStorage.setItem('meds_taken', JSON.stringify(takenMeds));
|
||
renderSchedule();
|
||
updateTakenCount();
|
||
}
|
||
}
|
||
|
||
// ========== СТАТИСТИКА ==========
|
||
function updateTakenCount() {
|
||
const total = MEDICATIONS.length;
|
||
const taken = Object.values(takenMeds).filter(v => v).length;
|
||
|
||
document.getElementById('taken-count').textContent = `${taken} / ${total}`;
|
||
|
||
const pct = total > 0 ? (taken / total * 100) : 0;
|
||
document.getElementById('progress-bar').style.width = pct + '%';
|
||
}
|
||
|
||
// ========== СЛЕДУЮЩЕЕ НАПОМИНАНИЕ ==========
|
||
function updateNextReminder() {
|
||
const now = new Date();
|
||
const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
||
|
||
let nextTime = null;
|
||
let nextMeds = [];
|
||
|
||
for (const time of TIMES) {
|
||
if (time > currentTime) {
|
||
nextTime = time;
|
||
nextMeds = MEDICATIONS.filter(m => m.time === time);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Если все прошли сегодня - берём первое завтра
|
||
if (!nextTime) {
|
||
nextTime = TIMES[0];
|
||
nextMeds = MEDICATIONS.filter(m => m.time === TIMES[0]);
|
||
}
|
||
|
||
const medNames = [...new Set(nextMeds.map(m => m.name))].join(', ');
|
||
|
||
document.getElementById('next-med-name').textContent = medNames;
|
||
document.getElementById('next-med-time').textContent = `В ${nextTime} (${nextMeds[0].instruction})`;
|
||
}
|
||
|
||
function updateStatus() {
|
||
const now = new Date();
|
||
const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
||
|
||
let nextTime = null;
|
||
for (const time of TIMES) {
|
||
if (time > currentTime) {
|
||
nextTime = time;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!nextTime) nextTime = TIMES[0] + ' (завтра)';
|
||
|
||
document.getElementById('status-text').textContent = `Следующий приём: ${nextTime}`;
|
||
|
||
// Countdown
|
||
if (nextTime && !nextTime.includes('завтра')) {
|
||
const [nH, nM] = nextTime.split(':').map(Number);
|
||
const [cH, cM] = currentTime.split(':').map(Number);
|
||
const diff = (nH * 60 + nM) - (cH * 60 + cM);
|
||
|
||
if (diff > 0) {
|
||
const h = Math.floor(diff / 60);
|
||
const m = diff % 60;
|
||
document.getElementById('next-in').textContent = `через ${h > 0 ? h + ' ч ' : ''}${m} мин`;
|
||
} else {
|
||
document.getElementById('next-in').textContent = 'время приёма!';
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========== НАПОМИНАНИЯ ==========
|
||
function checkReminders() {
|
||
const now = new Date();
|
||
const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
||
|
||
for (const time of TIMES) {
|
||
if (isTimeNear(time, currentTime)) {
|
||
const key = `notified_${time}_${now.toDateString()}`;
|
||
if (!sessionStorage.getItem(key)) {
|
||
const meds = MEDICATIONS.filter(m => m.time === time);
|
||
const names = [...new Set(meds.map(m => m.name))].join(', ');
|
||
|
||
sendNotification(
|
||
'💊 Время принимать лекарства!',
|
||
`${names} — ${meds[0].instruction}`
|
||
);
|
||
|
||
sessionStorage.setItem(key, 'true');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Сохраняем при закрытии
|
||
window.addEventListener('beforeunload', () => {
|
||
localStorage.setItem('meds_taken', JSON.stringify(takenMeds));
|
||
});
|