Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Creating project files and zipping them for download.
- from pathlib import Path, PurePosixPath
- import json, zipfile, os, textwrap
- OUT_DIR = Path('/mnt/data/medicycle_app')
- OUT_DIR.mkdir(parents=True, exist_ok=True)
- # index.html
- index_html = """<!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>MediCycle — Medicine Reminder</title>
- <!-- Bootstrap 5 -->
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
- <link href="css/style.css" rel="stylesheet">
- </head>
- <body class="app-shell">
- <nav class="navbar navbar-expand-lg navbar-light bg-light px-3">
- <div class="container-fluid">
- <span class="navbar-brand logo"><i class="fa-solid fa-pills me-2"></i>MediCycle</span>
- <div class="d-flex gap-2 align-items-center">
- <button id="toggleTheme" class="btn btn-outline-secondary btn-sm" title="Toggle theme"><i class="fa-solid fa-moon"></i></button>
- <button id="exportBtn" class="btn btn-outline-primary btn-sm">Export</button>
- <label class="btn btn-outline-success btn-sm mb-0">
- Import <input id="importFile" type="file" accept="application/json" hidden>
- </label>
- </div>
- </div>
- </nav>
- <main class="container py-4">
- <!-- Start: Welcome / Profile selection / Create profile -->
- <div id="screen-profile" class="card p-4 mx-auto" style="max-width:820px; display:none">
- <div id="no-data-screen">
- <h4>Welcome to MediCycle</h4>
- <p class="mb-3">No profiles found. Create a profile to start adding medicine routines.</p>
- <button id="createProfileBtn" class="btn btn-primary">Create profile</button>
- </div>
- <div id="select-profile-screen" style="display:none">
- <h5>Select profile</h5>
- <div id="profilesList" class="mb-3"></div>
- <button id="addProfileBtn" class="btn btn-outline-primary">Add new profile</button>
- </div>
- </div>
- <!-- App Dashboard -->
- <div id="screen-app" style="display:none">
- <div class="d-flex align-items-center justify-content-between mb-3">
- <div>
- <h4 id="profileTitle">Profile</h4>
- <div class="small-muted">Age: <span id="profileAge"></span> • ID: <span id="profileId"></span></div>
- </div>
- <div class="text-end">
- <small class="d-block">Made by © VKY Soft</small>
- <button id="switchProfileBtn" class="btn btn-link btn-sm">Switch Profile</button>
- <button id="deleteProfileBtn" class="btn btn-link btn-sm text-danger">Delete Profile</button>
- </div>
- </div>
- <div class="card mb-3 p-3">
- <div class="d-flex align-items-center justify-content-between">
- <h5 class="mb-0">Dashboard</h5>
- <div>
- <button id="addMedicineBtn" class="btn btn-success btn-sm"><i class="fa-solid fa-plus"></i> Add Medicine</button>
- </div>
- </div>
- </div>
- <div class="card p-2 mb-3">
- <ul class="nav nav-tabs" id="mainTabs">
- <li class="nav-item"><a class="nav-link active" data-tab="all">All</a></li>
- <li class="nav-item"><a class="nav-link" data-tab="today">Today</a></li>
- <li class="nav-item"><a class="nav-link" data-tab="history">History</a></li>
- </ul>
- <div id="tabContent" class="p-3">
- <div id="tab-all" class="tab-pane active">
- <div id="medListAll"></div>
- </div>
- <div id="tab-today" class="tab-pane" style="display:none">
- <div id="medListToday"></div>
- </div>
- <div id="tab-history" class="tab-pane" style="display:none">
- <div id="historyList"></div>
- </div>
- </div>
- </div>
- </div>
- <!-- Add/Edit Medicine Modal (simple in-page screen) -->
- <div id="screen-add" class="card p-3 mx-auto" style="max-width:720px; display:none">
- <h5 id="addTitle">Add Medicine</h5>
- <form id="medForm">
- <div class="mb-2">
- <label class="form-label">Medicine name</label>
- <input id="medName" class="form-control" required />
- </div>
- <div class="mb-2">
- <label class="form-label">Dosage (e.g. 2 tablets)</label>
- <input id="medDosage" class="form-control" required />
- </div>
- <div class="mb-2">
- <label class="form-label d-block">Food</label>
- <div class="form-check form-check-inline"><input class="form-check-input" type="radio" name="food" id="afterFood" value="after" checked><label class="form-check-label" for="afterFood">After Food</label></div>
- <div class="form-check form-check-inline"><input class="form-check-input" type="radio" name="food" id="beforeFood" value="before"><label class="form-check-label" for="beforeFood">Before Food</label></div>
- </div>
- <div class="mb-2 row">
- <div class="col-sm-6">
- <label class="form-label">Time</label>
- <input id="medTime" type="time" class="form-control" required>
- </div>
- <div class="col-sm-6">
- <label class="form-label">Cycle</label>
- <select id="medCycle" class="form-select">
- <option value="daily">Daily</option>
- <option value="monthly">Specific date of month</option>
- <option value="weekly">Specific day of week</option>
- </select>
- </div>
- </div>
- <div id="cycleDetails" class="mb-2"></div>
- <div class="d-flex gap-2">
- <button class="btn btn-primary" id="saveMedBtn">Save</button>
- <button class="btn btn-secondary" id="cancelMedBtn" type="button">Cancel</button>
- </div>
- </form>
- </div>
- </main>
- <footer class="border-top py-2 text-center small-muted">
- © VKY Soft — MediCycle • Local-only (no backend) • Works on mobile & PC
- </footer>
- <script src="js/app.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
- </body>
- </html>
- """
- # css/style.css
- css = """:root { --bg: #ffffff; --text: #111827; --card: #f8fafc; }
- .dark { --bg: #0b1220; --text: #e6eef8; --card: #0f1724; }
- body { background: var(--bg); color: var(--text); }
- .card { background: var(--card); }
- .app-shell { min-height: 100vh; display:flex; flex-direction:column; }
- .logo { font-weight:700; letter-spacing:0.6px; }
- .small-muted { font-size:0.85rem; color:rgba(0,0,0,0.55); }
- .time-pill { font-weight:600; }
- .cursor-pointer { cursor:pointer; }
- @media (max-width:576px){ .desktop-only{ display:none } }
- /* Dark-mode tweaks */
- :root.dark .small-muted { color: rgba(255,255,255,0.6); }
- """
- # js/app.js - improved from user's code with passkey check and delete profile
- js = r"""// ---------- Data model and localStorage helpers ----------
- const STORAGE_KEY = 'medicycle_data_v1';
- function defaultData(){
- return {
- profiles: [], // {id, name, age, passkey, medicines: [med objects]}
- history: [], // {profileId, medId, medName, timeTakenISO, dosage}
- currentProfileId: null
- };
- }
- function loadData(){
- try{
- const raw = localStorage.getItem(STORAGE_KEY);
- if(!raw) return defaultData();
- return JSON.parse(raw);
- }catch(e){ console.error('loadData', e); return defaultData(); }
- }
- function saveData(data){ localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); }
- // Unique id helper
- function uid(prefix='id'){ return prefix + '_' + Math.random().toString(36).slice(2,9); }
- // ---------- DOM refs ----------
- const screenProfile = document.getElementById('screen-profile');
- const screenApp = document.getElementById('screen-app');
- const screenAdd = document.getElementById('screen-add');
- const createProfileBtn = document.getElementById('createProfileBtn');
- const addProfileBtn = document.getElementById('addProfileBtn');
- const profilesList = document.getElementById('profilesList');
- const profileTitle = document.getElementById('profileTitle');
- const profileAgeEl = document.getElementById('profileAge');
- const profileIdEl = document.getElementById('profileId');
- const switchProfileBtn = document.getElementById('switchProfileBtn');
- const deleteProfileBtn = document.getElementById('deleteProfileBtn');
- const addMedicineBtn = document.getElementById('addMedicineBtn');
- const medListAll = document.getElementById('medListAll');
- const medListToday = document.getElementById('medListToday');
- const historyList = document.getElementById('historyList');
- const medForm = document.getElementById('medForm');
- const medName = document.getElementById('medName');
- const medDosage = document.getElementById('medDosage');
- const medTime = document.getElementById('medTime');
- const medCycle = document.getElementById('medCycle');
- const cycleDetails = document.getElementById('cycleDetails');
- const saveMedBtn = document.getElementById('saveMedBtn');
- const cancelMedBtn = document.getElementById('cancelMedBtn');
- const addTitle = document.getElementById('addTitle');
- const toggleTheme = document.getElementById('toggleTheme');
- const exportBtn = document.getElementById('exportBtn');
- const importFile = document.getElementById('importFile');
- // Tabs
- document.querySelectorAll('#mainTabs .nav-link').forEach(a => {
- a.addEventListener('click', ()=>{
- document.querySelectorAll('#mainTabs .nav-link').forEach(x=>x.classList.remove('active'));
- a.classList.add('active');
- document.querySelectorAll('.tab-pane').forEach(p=>p.style.display='none');
- document.getElementById('tab-'+a.dataset.tab).style.display='block';
- if(a.dataset.tab === 'history') renderHistory();
- if(a.dataset.tab === 'today') renderToday();
- if(a.dataset.tab === 'all') renderAll();
- });
- });
- // ---------- Profile creation / selection UI ----------
- function showCreateProfileForm(){
- // Use simple built-in prompts for quick UX
- const name = prompt('Name:'); if(!name) return;
- const age = prompt('Age:'); if(!age) return;
- const passkey = prompt('Passkey (used to protect profile):'); if(!passkey) return;
- const data = loadData();
- const id = uid('profile');
- data.profiles.push({ id, name, age, passkey, medicines: [] });
- data.currentProfileId = id;
- saveData(data);
- initApp();
- }
- function renderProfiles(){
- const data = loadData();
- profilesList.innerHTML = '';
- if(data.profiles.length===0){
- document.getElementById('no-data-screen').style.display='block';
- document.getElementById('select-profile-screen').style.display='none';
- } else {
- document.getElementById('no-data-screen').style.display='none';
- document.getElementById('select-profile-screen').style.display='block';
- data.profiles.forEach(p=>{
- const div = document.createElement('div');
- div.className = 'd-flex align-items-center justify-content-between mb-2 card p-2';
- div.innerHTML = `<div><strong>${escapeHtml(p.name)}</strong><div class='small-muted'>Age ${p.age}</div></div>
- <div>
- <button class='btn btn-sm btn-primary select-profile' data-id='${p.id}'>Open</button>
- <button class='btn btn-sm btn-outline-danger ms-1 delete-profile' data-id='${p.id}'>Delete</button>
- </div>`;
- profilesList.appendChild(div);
- });
- document.querySelectorAll('.select-profile').forEach(b=> b.addEventListener('click', async ()=>{
- const id = b.dataset.id; const data = loadData(); const profile = data.profiles.find(x=>x.id===id);
- if(!profile) return alert('Profile not found');
- // ask passkey
- const pass = prompt('Enter passkey for profile "'+profile.name+'":');
- if(pass === null) return;
- if(pass === profile.passkey){
- data.currentProfileId = id; saveData(data); initApp();
- } else {
- alert('Wrong passkey');
- }
- }));
- document.querySelectorAll('.delete-profile').forEach(b=> b.addEventListener('click', (e)=> {
- const id = b.dataset.id; const data = loadData(); const profile = data.profiles.find(x=>x.id===id);
- if(!profile) return;
- if(confirm('Delete profile "'+profile.name+'"? This will remove all medicines and history for this profile.')) {
- // remove profile and related history
- data.profiles = data.profiles.filter(p=> p.id !== id);
- data.history = data.history.filter(h=> h.profileId !== id);
- if(data.currentProfileId === id) data.currentProfileId = null;
- saveData(data); initApp();
- }
- }));
- }
- }
- // ---------- App init / rendering ----------
- let data = loadData();
- let currentProfile = null;
- let editingMedId = null;
- function initApp(){
- data = loadData();
- if(!data || !data.profiles || data.profiles.length===0){
- // show create profile screen
- screenProfile.style.display='block';
- screenApp.style.display='none';
- screenAdd.style.display='none';
- renderProfiles();
- return;
- }
- // if a currentProfileId exists, require passkey before opening automatically
- if(data.currentProfileId){
- const profile = data.profiles.find(p=> p.id === data.currentProfileId);
- if(profile){
- const pass = prompt('Enter passkey to open profile "'+profile.name+'": (cancel to choose another profile)');
- if(pass === profile.passkey){
- currentProfile = profile;
- openAppForProfile();
- return;
- } else {
- // wrong or cancelled -> clear currentProfileId and show selector
- data.currentProfileId = null; saveData(data);
- screenProfile.style.display='block'; screenApp.style.display='none'; screenAdd.style.display='none';
- renderProfiles();
- return;
- }
- } else {
- data.currentProfileId = null; saveData(data);
- initApp(); return;
- }
- } else {
- // no current profile set -> show selector
- screenProfile.style.display='block'; screenApp.style.display='none'; screenAdd.style.display='none';
- renderProfiles();
- return;
- }
- }
- function openAppForProfile(){
- // show dashboard
- screenProfile.style.display='none'; screenApp.style.display='block'; screenAdd.style.display='none';
- profileTitle.textContent = currentProfile.name;
- profileAgeEl.textContent = currentProfile.age;
- profileIdEl.textContent = currentProfile.id;
- renderAll(); renderToday();
- }
- // ---------- Render lists ----------
- function renderAll(){
- medListAll.innerHTML = '';
- if(!currentProfile || currentProfile.medicines.length===0){ medListAll.innerHTML = '<div class="text-muted">No medicines added yet</div>'; return; }
- currentProfile.medicines.forEach(m=>{
- const card = document.createElement('div'); card.className='card p-2 mb-2';
- card.innerHTML = `<div class="d-flex justify-content-between align-items-start">
- <div>
- <div class="h6 mb-0">${escapeHtml(m.name)}</div>
- <div class="small-muted">${escapeHtml(m.dosage)} • ${m.food==='after'? 'After Food':'Before Food'}</div>
- <div class="small-muted">Time: <span class='time-pill'>${m.time}</span> • Cycle: ${cycleLabel(m)}</div>
- </div>
- <div class="text-end">
- <button class='btn btn-sm btn-outline-primary edit-med' data-id='${m.id}'>Edit</button>
- <button class='btn btn-sm btn-outline-danger ms-1 del-med' data-id='${m.id}'>Delete</button>
- </div>
- </div>`;
- medListAll.appendChild(card);
- });
- document.querySelectorAll('.edit-med').forEach(b=> b.addEventListener('click', ()=> openEditMedicine(b.dataset.id)));
- document.querySelectorAll('.del-med').forEach(b=> b.addEventListener('click', ()=> deleteMed(b.dataset.id)));
- }
- function renderToday(){
- medListToday.innerHTML = '';
- if(!currentProfile) return;
- const today = new Date();
- const todays = currentProfile.medicines.filter(m=> isMedicineForDate(m, today));
- if(todays.length===0){ medListToday.innerHTML = '<div class="text-muted">No medicines scheduled for today</div>'; return; }
- todays.forEach(m=>{
- const card = document.createElement('div'); card.className='card p-2 mb-2 d-flex justify-content-between align-items-center';
- // check if already taken today
- const taken = wasTakenToday(currentProfile.id, m.id);
- card.innerHTML = `<div>
- <div class='h6 mb-0'>${escapeHtml(m.name)} ${taken? '<span class="badge bg-success ms-2">Taken</span>': ''}</div>
- <div class='small-muted'>${escapeHtml(m.dosage)} • ${m.time} • ${m.food==='after'?'After Food':'Before Food'}</div>
- </div>
- <div>
- <button class='btn btn-sm btn-success mark-taken' data-id='${m.id}' ${taken? 'disabled':''}>Mark taken</button>
- </div>`;
- medListToday.appendChild(card);
- });
- document.querySelectorAll('.mark-taken').forEach(b=> b.addEventListener('click', ()=> markTaken(b.dataset.id)));
- }
- function renderHistory(){
- historyList.innerHTML = '';
- const data = loadData();
- const list = data.history.filter(h=> h.profileId === currentProfile.id).sort((a,b)=> new Date(b.timeTakenISO)-new Date(a.timeTakenISO));
- if(list.length===0) { historyList.innerHTML = '<div class="text-muted">No history yet</div>'; return; }
- list.forEach(h=>{
- const el = document.createElement('div'); el.className='card p-2 mb-2';
- el.innerHTML = `<div class='d-flex justify-content-between'><div><strong>${escapeHtml(h.medName)}</strong><div class='small-muted'>${escapeHtml(h.dosage)} • ${new Date(h.timeTakenISO).toLocaleString()}</div></div>
- <div><button class='btn btn-sm btn-outline-secondary btn-copy' data-iso='${h.timeTakenISO}'>Copy time</button></div></div>`;
- historyList.appendChild(el);
- });
- document.querySelectorAll('.btn-copy').forEach(b=> b.addEventListener('click', ()=> navigator.clipboard.writeText(b.dataset.iso)));
- }
- // ---------- Helpers: cycle label, isMedicineForDate, taken checks ----------
- function cycleLabel(m){
- if(m.cycle==='daily') return 'Daily';
- if(m.cycle==='monthly') return 'Monthly on ' + (m.monthDay||'?');
- if(m.cycle==='weekly') return 'Weekly on ' + (m.weekDays? m.weekDays.join(', '):'?');
- return '';
- }
- function isMedicineForDate(m, date){
- if(!m) return false;
- if(m.cycle==='daily') return true;
- if(m.cycle==='monthly'){
- const d = Number(m.monthDay);
- return d === date.getDate();
- }
- if(m.cycle==='weekly'){
- const names = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
- return m.weekDays && m.weekDays.includes(names[date.getDay()]);
- }
- return false;
- }
- function wasTakenToday(profileId, medId){
- const d = loadData();
- const today = (new Date()).toDateString();
- return d.history.some(h=> h.profileId===profileId && h.medId===medId && new Date(h.timeTakenISO).toDateString()===today);
- }
- // ---------- Add/Edit/Delete medicine ----------
- function openAddMedicine(){
- editingMedId = null; addTitle.textContent = 'Add Medicine'; medForm.reset(); cycleDetails.innerHTML=''; medCycle.value='daily'; renderCycleDetails();
- screenProfile.style.display='none'; screenApp.style.display='none'; screenAdd.style.display='block';
- }
- function openEditMedicine(id){
- const med = currentProfile.medicines.find(x=> x.id===id); if(!med) return;
- editingMedId = id; addTitle.textContent = 'Edit Medicine';
- medName.value = med.name; medDosage.value = med.dosage; medTime.value = med.time; document.querySelectorAll('input[name="food"]').forEach(r=> r.checked = (r.value === med.food));
- medCycle.value = med.cycle; renderCycleDetails(med);
- screenProfile.style.display='none'; screenApp.style.display='none'; screenAdd.style.display='block';
- }
- function deleteMed(id){ if(!confirm('Delete this medicine?')) return; const data = loadData(); const profile = data.profiles.find(p=> p.id===data.currentProfileId); profile.medicines = profile.medicines.filter(m=> m.id!==id); saveData(data); initApp(); }
- medCycle.addEventListener('change', ()=> renderCycleDetails());
- function renderCycleDetails(med=null){
- const v = medCycle.value; if(v==='daily'){ cycleDetails.innerHTML = '<div class="form-text">Will repeat every day</div>'; return; }
- if(v==='monthly'){
- const val = med? (med.monthDay||'') : '';
- cycleDetails.innerHTML = `<label class='form-label'>Date of month (1-31)</label><input id='monthDay' type='number' min='1' max='31' class='form-control' value='${val}' required>`; return;
- }
- if(v==='weekly'){
- const names = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
- const selected = med && med.weekDays ? med.weekDays : [];
- cycleDetails.innerHTML = '<label class="form-label d-block">Select weekdays</label>' + names.map(n=> `<div class='form-check form-check-inline'>
- <input class='form-check-input week-day' type='checkbox' value='${n}' id='wd_${n}' ${selected.includes(n)? 'checked':''}>
- <label class='form-check-label' for='wd_${n}'>${n.slice(0,3)}</label>
- </div>`).join('');
- return;
- }
- }
- medForm.addEventListener('submit', (e)=>{ e.preventDefault(); saveMedicine(); });
- cancelMedBtn.addEventListener('click', ()=> { screenAdd.style.display='none'; screenApp.style.display='block'; });
- function saveMedicine(){
- const name = medName.value.trim(); const dosage = medDosage.value.trim(); const time = medTime.value; const food = document.querySelector('input[name="food"]:checked').value;
- const cycle = medCycle.value;
- const data = loadData(); const profile = data.profiles.find(p=> p.id === data.currentProfileId);
- let medObj = editingMedId ? profile.medicines.find(m=> m.id===editingMedId) : null;
- if(!medObj){ medObj = { id: uid('med'), name, dosage, time, food, cycle }; profile.medicines.push(medObj); }
- else { medObj.name = name; medObj.dosage = dosage; medObj.time = time; medObj.food = food; medObj.cycle = cycle; }
- if(cycle==='monthly'){ medObj.monthDay = Number(document.getElementById('monthDay').value); }
- if(cycle==='weekly'){ medObj.weekDays = Array.from(document.querySelectorAll('.week-day:checked')).map(x=>x.value); }
- saveData(data); screenAdd.style.display='none'; screenApp.style.display='block'; initApp();
- }
- // ---------- Mark taken ----------
- function markTaken(medId){
- const data = loadData(); const profile = data.profiles.find(p=> p.id===data.currentProfileId); const med = profile.medicines.find(m=> m.id===medId);
- data.history.push({ profileId: profile.id, medId: med.id, medName: med.name, dosage: med.dosage, timeTakenISO: new Date().toISOString() });
- saveData(data); renderToday();
- }
- // ---------- Export / Import ----------
- exportBtn.addEventListener('click', ()=>{
- const data = loadData(); const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a'); a.href = url; a.download = 'medicycle_data.json'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
- });
- importFile.addEventListener('change', async (e)=>{
- const f = e.target.files[0]; if(!f) return; try{
- const txt = await f.text(); const imported = JSON.parse(txt);
- if(!imported.profiles) throw new Error('Invalid file');
- if(confirm('This will replace current data. Proceed?')){ localStorage.setItem(STORAGE_KEY, JSON.stringify(imported)); alert('Imported. Reloading view.'); initApp(); }
- }catch(err){ alert('Failed to import: '+err.message); }
- importFile.value='';
- });
- // ---------- Notifications ----------
- let notifiedSet = new Set(); // in-memory, resets on reload
- function requestNotifPermission(){ if('Notification' in window) Notification.requestPermission().then(perm=> console.log('notification permission', perm)); }
- function checkReminders(){
- const now = new Date(); const hhmm = now.toTimeString().slice(0,5);
- const data = loadData();
- if(!data.currentProfileId) return;
- const profile = data.profiles.find(p=> p.id===data.currentProfileId); if(!profile) return;
- profile.medicines.forEach(m=>{
- if(m.time === hhmm && isMedicineForDate(m, now)){
- const key = profile.id + '|' + m.id + '|' + now.toDateString() + '|' + hhmm;
- if(notifiedSet.has(key)) return; notifiedSet.add(key);
- // show notification
- if('Notification' in window && Notification.permission === 'granted'){
- new Notification('MediCycle: ' + m.name, { body: `${m.dosage} • ${m.food==='after'? 'After Food':'Before Food'}` });
- } else {
- // fallback: console log
- console.log('Reminder:', m.name, m.dosage);
- }
- }
- });
- }
- // check every 20 seconds
- setInterval(checkReminders, 20000);
- // ---------- Theme toggle ----------
- function applyTheme(isDark){ if(isDark) document.documentElement.classList.add('dark'); else document.documentElement.classList.remove('dark'); localStorage.setItem('medicycle_theme', isDark? 'dark':'light'); }
- toggleTheme.addEventListener('click', ()=>{ const isDark = document.documentElement.classList.toggle('dark'); localStorage.setItem('medicycle_theme', isDark? 'dark':'light'); });
- // apply persisted theme
- (function(){ const t = localStorage.getItem('medicycle_theme'); if(t==='dark') applyTheme(true); else applyTheme(false); })();
- // ---------- Utility: escapeHtml ----------
- function escapeHtml(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
- // ---------- Buttons wiring ----------
- createProfileBtn.addEventListener('click', showCreateProfileForm);
- addProfileBtn.addEventListener('click', showCreateProfileForm);
- switchProfileBtn.addEventListener('click', ()=>{ data = loadData(); data.currentProfileId=null; saveData(data); initApp(); });
- addMedicineBtn.addEventListener('click', openAddMedicine);
- deleteProfileBtn.addEventListener('click', ()=> {
- if(!currentProfile) return;
- if(!confirm('Delete current profile "'+currentProfile.name+'"? This removes all medicines and history for this profile.')) return;
- const data = loadData();
- data.profiles = data.profiles.filter(p=> p.id !== currentProfile.id);
- data.history = data.history.filter(h=> h.profileId !== currentProfile.id);
- data.currentProfileId = null;
- saveData(data);
- currentProfile = null;
- initApp();
- });
- // Request notification permissions on first interaction (polite)
- document.addEventListener('click', ()=>{ if(window.Notification && Notification.permission === 'default') requestNotifPermission(); }, { once:true });
- // Initialize
- initApp();
- """
- # README
- readme = """MediCycle — Medicine Reminder (Local-only webapp)
- Files:
- - index.html
- - css/style.css
- - js/app.js
- Features implemented:
- - Multiple profiles with passkey protection (prompt on open).
- - Create/Edit/Delete medicine routines (daily / monthly / weekly).
- - "Today" tab shows only medicines scheduled for today; you can mark them as taken.
- - History tab shows taken records per profile.
- - Export / Import JSON (local file).
- - Dark / Light theme toggle (persisted).
- - Browser notifications (requests permission once).
- - All data stored in browser localStorage, no backend.
- How to use:
- 1. Open index.html in a browser (supports mobile & desktop).
- 2. Create a profile (name, age, passkey).
- 3. Add medicines and schedules.
- 4. Use Export to backup, Import to restore (replaces current data).
- """
- # write files
- (OUT_DIR / 'index.html').write_text(index_html, encoding='utf-8')
- css_dir = OUT_DIR / 'css'
- css_dir.mkdir(exist_ok=True)
- (css_dir / 'style.css').write_text(css, encoding='utf-8')
- js_dir = OUT_DIR / 'js'
- js_dir.mkdir(exist_ok=True)
- (js_dir / 'app.js').write_text(js, encoding='utf-8')
- (OUT_DIR / 'README.txt').write_text(readme, encoding='utf-8')
- # create zip
- zip_path = Path('/mnt/data/medicycle_app.zip')
- with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED) as z:
- for p in OUT_DIR.rglob('*'):
- z.write(p, arcname=str(p.relative_to(OUT_DIR.parent)))
- print("Created:", zip_path)
- print("Files included:")
- for p in OUT_DIR.rglob('*'):
- print("-", p.relative_to(OUT_DIR))
Advertisement
Add Comment
Please, Sign In to add comment