med-reminder/script.js

329 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ========== ДАННЫЕ О ЛЕКАРСТВАХ ==========
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));
});