diff --git a/api/alarms.php b/api/alarms.php index 386c89f..70d902e 100644 --- a/api/alarms.php +++ b/api/alarms.php @@ -3,99 +3,112 @@ require_once __DIR__ . '/../db/config.php'; header('Content-Type: application/json'); -$response = ['success' => false, 'message' => 'Invalid request']; +$response = ['success' => false, 'error' => 'Invalid request']; $pdo = db(); -if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) { - if ($_GET['action'] === 'check') { +$action = $_GET['action'] ?? $_POST['action'] ?? ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'create') { + $alarm_time = $_POST['alarm_time'] ?? null; + + if ($alarm_time) { try { $pdo->beginTransaction(); - // Find active alarms that are due and lock the rows - $stmt = $pdo->prepare("SELECT * FROM alarms WHERE alarm_time <= CURTIME() AND is_active = 1 FOR UPDATE"); - $stmt->execute(); - $alarms = $stmt->fetchAll(PDO::FETCH_ASSOC); + // 1. Create a new note + $noteStmt = $pdo->prepare("INSERT INTO notes (content) VALUES (?)"); + $noteStmt->execute(['']); + $noteId = $pdo->lastInsertId(); - if ($alarms) { - // Deactivate the found alarms to prevent them from ringing again - $alarmIds = array_map(function($alarm) { - return $alarm['id']; - }, $alarms); - - if (!empty($alarmIds)) { - $placeholders = implode(',', array_fill(0, count($alarmIds), '?')); - $updateStmt = $pdo->prepare("UPDATE alarms SET is_active = 0 WHERE id IN ($placeholders)"); - $updateStmt->execute($alarmIds); - } - - $response = ['success' => true, 'alarms' => $alarms]; - } else { - $response = ['success' => true, 'alarms' => []]; - } + // 2. Create the alarm and link it to the new note + $alarmStmt = $pdo->prepare("INSERT INTO alarms (alarm_time, note_id, is_active) VALUES (?, ?, 1)"); + $alarmStmt->execute([$alarm_time, $noteId]); + $alarmId = $pdo->lastInsertId(); $pdo->commit(); + + $response = [ + 'success' => true, + 'id' => $alarmId, + 'note_id' => $noteId, + 'alarm_time' => $alarm_time + ]; + } catch (PDOException $e) { $pdo->rollBack(); - $response['message'] = 'Database error: ' . $e->getMessage(); + $response['error'] = 'Database error: ' . $e->getMessage(); } + } else { + $response['error'] = 'Alarm time is required.'; } -} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') { - // Simple routing based on a POST field - $action = $_POST['action'] ?? ''; +} - if ($action === 'create') { - $alarm_time = $_POST['alarm_time'] ?? null; - $label = $_POST['label'] ?? ''; - - if ($alarm_time) { - try { - $stmt = $pdo->prepare("INSERT INTO alarms (alarm_time, label) VALUES (?, ?)"); - $stmt->execute([$alarm_time, $label]); - $response = ['success' => true, 'message' => 'Alarm created successfully.', 'id' => $pdo->lastInsertId()]; - } catch (PDOException $e) { - $response['message'] = 'Database error: ' . $e->getMessage(); - } - } else { - $response['message'] = 'Alarm time is required.'; - } - } elseif ($action === 'delete') { - $alarm_id = $_POST['alarm_id'] ?? null; - - if ($alarm_id) { - try { - $stmt = $pdo->prepare("DELETE FROM alarms WHERE id = ?"); - $stmt->execute([$alarm_id]); - if ($stmt->rowCount()) { - $response = ['success' => true, 'message' => 'Alarm deleted successfully.']; - } else { - $response['message'] = 'Alarm not found.'; - } - } catch (PDOException $e) { - $response['message'] = 'Database error: ' . $e->getMessage(); - } - } else { - $response['message'] = 'Alarm ID is required.'; - } - } elseif ($action === 'toggle') { - $alarm_id = $_POST['alarm_id'] ?? null; - $is_active = $_POST['is_active'] ?? null; - - if ($alarm_id && $is_active !== null) { - try { - $stmt = $pdo->prepare("UPDATE alarms SET is_active = ? WHERE id = ?"); - $stmt->execute([$is_active, $alarm_id]); - if ($stmt->rowCount()) { - $response = ['success' => true, 'message' => 'Alarm status updated.']; - } else { - $response['message'] = 'Alarm not found or status unchanged.'; - } - } catch (PDOException $e) { - $response['message'] = 'Database error: ' . $e->getMessage(); - } - } else { - $response['message'] = 'Alarm ID and active status are required.'; - } +elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'get') { + try { + $stmt = $pdo->query("SELECT id, alarm_time, note_id, is_active FROM alarms ORDER BY alarm_time"); + $alarms = $stmt->fetchAll(PDO::FETCH_ASSOC); + $response = ['success' => true, 'alarms' => $alarms]; + } catch (PDOException $e) { + $response['error'] = 'Database error: ' . $e->getMessage(); } } -echo json_encode($response); +elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'delete') { + $id = $_GET['id'] ?? null; + if ($id) { + try { + $stmt = $pdo->prepare("DELETE FROM alarms WHERE id = ?"); + $stmt->execute([$id]); + $response = ['success' => true]; + } catch (PDOException $e) { + $response['error'] = 'Database error: ' . $e->getMessage(); + } + } else { + $response['error'] = 'ID is required.'; + } +} + +elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'toggle') { + $id = $_POST['id'] ?? null; + $is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : null; + + if ($id && $is_active !== null) { + try { + $stmt = $pdo->prepare("UPDATE alarms SET is_active = ? WHERE id = ?"); + $stmt->execute([$is_active, $id]); + $response = ['success' => true]; + } catch (PDOException $e) { + $response['error'] = 'Database error: ' . $e->getMessage(); + } + } else { + $response['error'] = 'ID and active status are required.'; + } +} + +elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'check') { + try { + $pdo->beginTransaction(); + + $stmt = $pdo->prepare("SELECT * FROM alarms WHERE alarm_time <= CURTIME() AND is_active = 1 FOR UPDATE"); + $stmt->execute(); + $alarms = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if ($alarms) { + $alarmIds = array_map(fn($a) => $a['id'], $alarms); + $placeholders = implode(',', array_fill(0, count($alarmIds), '?')); + $updateStmt = $pdo->prepare("UPDATE alarms SET is_active = 0 WHERE id IN ($placeholders)"); + $updateStmt->execute($alarmIds); + + $response = ['success' => true, 'alarms' => $alarms]; + } else { + $response = ['success' => true, 'alarms' => []]; + } + + $pdo->commit(); + } catch (PDOException $e) { + $pdo->rollBack(); + $response['error'] = 'Database error: ' . $e->getMessage(); + } +} + +echo json_encode($response); \ No newline at end of file diff --git a/api/fetch_bell_icon.php b/api/fetch_bell_icon.php new file mode 100644 index 0000000..c4ac55f --- /dev/null +++ b/api/fetch_bell_icon.php @@ -0,0 +1,19 @@ + true, 'local' => 'assets/images/bell.png']); + exit; +} +$photo = $data['photos'][0]; +$src = $photo['src']['tiny'] ?? ($photo['src']['small'] ?? $photo['src']['original']); +$target = __DIR__ . '/../assets/images/bell.png'; +download_to($src, $target); +echo json_encode(['success' => true, 'local' => 'assets/images/bell.png']); diff --git a/assets/images/bell.png b/assets/images/bell.png new file mode 100644 index 0000000..f332071 Binary files /dev/null and b/assets/images/bell.png differ diff --git a/assets/js/main.js b/assets/js/main.js index ac580b2..2b7ce8a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,229 +1,187 @@ + document.addEventListener('DOMContentLoaded', function () { - // --- ELEMENTS --- - const createAlarmForm = document.getElementById('createAlarmForm'); - const alarmList = document.getElementById('alarmList'); - const noAlarmsMessage = document.getElementById('noAlarmsMessage'); - const alarmModalEl = document.getElementById('alarmModal'); - const alarmModal = new bootstrap.Modal(alarmModalEl); - const dismissAlarmBtn = document.getElementById('dismissAlarmBtn'); - const alarmSound = document.getElementById('alarmSound'); - const alarmModalMessage = document.getElementById('alarmModalMessage'); + const alarmForm = document.getElementById('alarm-form'); + const alarmsList = document.getElementById('alarms-list'); + const alarmModal = new bootstrap.Modal(document.getElementById('alarm-modal')); + const alarmSound = document.getElementById('alarm-sound'); + const dismissAlarmBtn = document.getElementById('dismiss-alarm'); + const enableNotificationsBtn = document.getElementById('enable-notifications'); - // --- STATE --- - let isAlarmModalShown = false; + let notificationPermission = false; - // --- FUNCTIONS --- - - /** - * Handles the submission of the create alarm form. - */ - const handleCreateAlarm = async (e) => { - e.preventDefault(); - const timeInput = document.getElementById('alarmTime'); - const labelInput = document.getElementById('alarmLabel'); - - const formData = new FormData(); - formData.append('action', 'create'); - formData.append('alarm_time', timeInput.value); - formData.append('label', labelInput.value); - - try { - const response = await fetch('api/alarms.php', { - method: 'POST', - body: formData - }); - const result = await response.json(); - - if (result.success) { - addAlarmToList(result.id, timeInput.value, labelInput.value); - timeInput.value = ''; - labelInput.value = ''; - if (noAlarmsMessage) { - noAlarmsMessage.style.display = 'none'; + // Request notification permission + if (enableNotificationsBtn) { + enableNotificationsBtn.addEventListener('click', () => { + Notification.requestPermission().then(permission => { + if (permission === 'granted') { + notificationPermission = true; + alert('Notifications enabled!'); + enableNotificationsBtn.style.display = 'none'; + } else { + alert('Notification permission denied.'); } - } else { - alert('Error: ' + result.message); - } - } catch (error) { - console.error('Failed to create alarm:', error); - alert('An error occurred while creating the alarm.'); - } + }); + }); + } + + + // Function to fetch and display alarms + const fetchAlarms = () => { + fetch('/api/alarms.php?action=get') + .then(response => response.json()) + .then(data => { + alarmsList.innerHTML = ''; + if (data.success) { + data.alarms.forEach(alarm => { + addAlarmToList(alarm.id, alarm.alarm_time, alarm.note_id, alarm.is_active); + }); + } + }); }; - /** - * Handles the click on a delete alarm form. - */ - const handleDeleteAlarm = async (e) => { - if (!e.target.closest('.delete-alarm-form')) return; - e.preventDefault(); - - const form = e.target.closest('.delete-alarm-form'); - const alarmId = form.querySelector('input[name="alarm_id"]').value; - - if (!confirm('Are you sure you want to delete this alarm?')) return; + // Function to add a single alarm to the list + const addAlarmToList = (id, time, noteId, isActive) => { + const listItem = document.createElement('li'); + listItem.className = 'list-group-item d-flex justify-content-between align-items-center'; + listItem.dataset.id = id; - const formData = new FormData(form); + const timeText = document.createElement('span'); + timeText.textContent = new Date(`1970-01-01T${time}`).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - try { - const response = await fetch('api/alarms.php', { + const noteLink = document.createElement('a'); + noteLink.href = `/note.php?id=${noteId}`; + noteLink.textContent = `Note #${noteId}`; + noteLink.className = 'mx-3'; + + const controls = document.createElement('div'); + + const toggleSwitch = document.createElement('div'); + toggleSwitch.className = 'form-check form-switch'; + const toggleInput = document.createElement('input'); + toggleInput.className = 'form-check-input'; + toggleInput.type = 'checkbox'; + toggleInput.role = 'switch'; + toggleInput.checked = isActive; + toggleInput.addEventListener('change', () => handleToggleAlarm(id, toggleInput.checked)); + toggleSwitch.appendChild(toggleInput); + + const deleteBtn = document.createElement('button'); + deleteBtn.className = 'btn btn-danger btn-sm ms-3'; + deleteBtn.textContent = 'Delete'; + deleteBtn.addEventListener('click', () => handleDeleteAlarm(id)); + + controls.appendChild(toggleSwitch); + controls.appendChild(deleteBtn); + + listItem.appendChild(timeText); + listItem.appendChild(noteLink); + listItem.appendChild(controls); + alarmsList.appendChild(listItem); + }; + + // Handle form submission to create a new alarm + if (alarmForm) { + alarmForm.addEventListener('submit', function (e) { + e.preventDefault(); + const formData = new FormData(alarmForm); + fetch('/api/alarms.php?action=create', { method: 'POST', body: formData - }); - const result = await response.json(); + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + addAlarmToList(data.id, data.alarm_time, data.note_id, true); + alarmForm.reset(); + } else { + alert('Error: ' + data.error); + } + }); + }); + } - if (result.success) { - const listItem = form.closest('li'); - listItem.remove(); - if (!alarmList.querySelector('li')) { - if (noAlarmsMessage) { - noAlarmsMessage.style.display = 'block'; + // Handle deleting an alarm + const handleDeleteAlarm = (id) => { + if (!confirm('Are you sure you want to delete this alarm?')) return; + fetch(`/api/alarms.php?action=delete&id=${id}`, { method: 'GET' }) + .then(response => response.json()) + .then(data => { + if (data.success) { + document.querySelector(`li[data-id='${id}']`).remove(); + } else { + alert('Error: ' + data.error); + } + }); + }; + + // Handle toggling an alarm's active state + const handleToggleAlarm = (id, isActive) => { + const formData = new FormData(); + formData.append('id', id); + formData.append('is_active', isActive ? 1 : 0); + + fetch('/api/alarms.php?action=toggle', { + method: 'POST', + body: formData + }).then(response => response.json()) + .then(data => { + if (!data.success) { + alert('Error updating alarm status.'); + } + }); + }; + + + // Function to check for due alarms + const checkAlarms = () => { + fetch('/api/alarms.php?action=check') + .then(response => response.json()) + .then(data => { + if (data.success && data.alarms.length > 0) { + const alarm = data.alarms[0]; // Assuming one alarm for now + showNotification(alarm); + // Also update the toggle on the main page + const alarmToggle = document.querySelector(`li[data-id='${alarm.id}'] input[type='checkbox']`); + if (alarmToggle) { + alarmToggle.checked = false; } } - } else { - alert('Error: ' + result.message); - } - } catch (error) { - console.error('Failed to delete alarm:', error); - alert('An error occurred while deleting the alarm.'); - } - }; - - /** - * Adds a new alarm item to the DOM. - */ - const addAlarmToList = (id, time, label, isActive = true) => { - const date = new Date(`1970-01-01T${time}`); - const formattedTime = date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); - - const li = document.createElement('li'); - li.className = 'list-group-item d-flex justify-content-between align-items-center'; - li.dataset.id = id; - li.innerHTML = ` -