Write a code offline la
Write a code for offline app for app which calculate all daily expenses it will write and also make to do list
What platform should this offline app be built for?
localStorage (persists offline, no server needed)mkdir -p /home/daytona/workspace/daily-app && ls /home/daytona/workspace/daily-app
~/daily-app/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Daily Planner - Expenses & To-Do</title>
<style>
/* ===== RESET & BASE ===== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f172a;
--surface: #1e293b;
--surface2: #273449;
--border: #334155;
--accent: #6366f1;
--accent-hover: #4f46e5;
--green: #22c55e;
--red: #ef4444;
--yellow: #f59e0b;
--text: #f1f5f9;
--text2: #94a3b8;
--radius: 12px;
--shadow: 0 4px 24px rgba(0,0,0,0.4);
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
padding: 20px 16px 60px;
}
h1.app-title {
text-align: center;
font-size: 1.6rem;
font-weight: 700;
margin-bottom: 6px;
background: linear-gradient(135deg, var(--accent), #a78bfa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.app-subtitle {
text-align: center;
color: var(--text2);
font-size: 0.85rem;
margin-bottom: 28px;
}
/* ===== TABS ===== */
.tabs {
display: flex;
gap: 8px;
justify-content: center;
margin-bottom: 24px;
}
.tab-btn {
padding: 9px 28px;
border-radius: 40px;
border: 2px solid var(--border);
background: var(--surface);
color: var(--text2);
font-size: 0.92rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.tab-btn.active {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
.tab-btn:hover:not(.active) { border-color: var(--accent); color: var(--text); }
/* ===== PANELS ===== */
.panel { display: none; max-width: 680px; margin: 0 auto; }
.panel.active { display: block; }
/* ===== CARD ===== */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px;
margin-bottom: 18px;
box-shadow: var(--shadow);
}
.card h2 {
font-size: 1rem;
font-weight: 600;
color: var(--text2);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 14px;
}
/* ===== INPUTS ===== */
.input-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
input[type="text"], input[type="number"], input[type="date"], select {
flex: 1 1 120px;
padding: 10px 14px;
border-radius: 8px;
border: 1.5px solid var(--border);
background: var(--surface2);
color: var(--text);
font-size: 0.93rem;
outline: none;
transition: border 0.2s;
}
input:focus, select:focus { border-color: var(--accent); }
input::placeholder { color: var(--text2); }
.btn {
padding: 10px 20px;
border-radius: 8px;
border: none;
font-size: 0.92rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, transform 0.1s;
}
.btn:active { transform: scale(0.97); }
.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover { background: var(--accent-hover); }
.btn-danger { background: transparent; color: var(--red); border: 1.5px solid var(--red); padding: 5px 10px; font-size: 0.8rem; border-radius: 6px; }
.btn-danger:hover { background: var(--red); color: #fff; }
.btn-sm-green { background: transparent; color: var(--green); border: 1.5px solid var(--green); padding: 5px 10px; font-size: 0.8rem; border-radius: 6px; }
.btn-sm-green:hover { background: var(--green); color: #fff; }
/* ===== DATE FILTER ===== */
.date-filter {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 4px;
}
.date-filter label { color: var(--text2); font-size: 0.85rem; }
/* ===== SUMMARY STRIP ===== */
.summary-strip {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 18px;
}
.summary-box {
flex: 1 1 140px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px;
text-align: center;
}
.summary-box .label { font-size: 0.75rem; color: var(--text2); text-transform: uppercase; letter-spacing: 0.05em; }
.summary-box .value { font-size: 1.5rem; font-weight: 700; margin-top: 4px; }
.value.total { color: var(--accent); }
.value.today { color: var(--yellow); }
.value.count { color: var(--green); }
/* ===== EXPENSE LIST ===== */
.expense-list { list-style: none; }
.expense-list li {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 0;
border-bottom: 1px solid var(--border);
font-size: 0.9rem;
}
.expense-list li:last-child { border-bottom: none; }
.exp-icon {
width: 34px; height: 34px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 1.1rem;
flex-shrink: 0;
}
.exp-info { flex: 1; }
.exp-name { font-weight: 600; }
.exp-meta { font-size: 0.78rem; color: var(--text2); margin-top: 2px; }
.exp-amount { font-weight: 700; font-size: 1rem; white-space: nowrap; }
.exp-amount.neg { color: var(--red); }
.exp-amount.pos { color: var(--green); }
.empty-msg {
text-align: center;
color: var(--text2);
padding: 28px 0;
font-size: 0.9rem;
}
/* ===== CATEGORY BADGE ===== */
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 20px;
font-size: 0.72rem;
font-weight: 600;
}
/* ===== TODO LIST ===== */
.todo-list { list-style: none; }
.todo-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid var(--border);
}
.todo-item:last-child { border-bottom: none; }
.todo-check {
width: 20px; height: 20px;
border-radius: 50%;
border: 2px solid var(--border);
flex-shrink: 0;
cursor: pointer;
margin-top: 2px;
display: flex; align-items: center; justify-content: center;
transition: all 0.2s;
}
.todo-check.done { background: var(--green); border-color: var(--green); }
.todo-check.done::after { content: 'โ'; color: #fff; font-size: 0.7rem; font-weight: 700; }
.todo-text-wrap { flex: 1; }
.todo-text { font-size: 0.93rem; transition: all 0.2s; }
.todo-text.done { text-decoration: line-through; color: var(--text2); }
.todo-priority { font-size: 0.72rem; margin-top: 3px; }
.todo-date-label { font-size: 0.72rem; color: var(--text2); margin-top: 2px; }
.todo-actions { display: flex; gap: 6px; }
/* Priority colors */
.p-high { color: var(--red); }
.p-medium { color: var(--yellow); }
.p-low { color: var(--green); }
/* ===== FILTER ROW ===== */
.filter-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 4px;
}
.filter-btn {
padding: 5px 14px;
border-radius: 20px;
border: 1.5px solid var(--border);
background: transparent;
color: var(--text2);
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.filter-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
/* ===== PROGRESS BAR ===== */
.progress-bar-wrap { margin-top: 8px; }
.progress-label { display: flex; justify-content: space-between; font-size: 0.78rem; color: var(--text2); margin-bottom: 4px; }
.progress-bar { height: 6px; background: var(--border); border-radius: 4px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--accent); border-radius: 4px; transition: width 0.4s; }
/* ===== CHART AREA ===== */
.chart-wrap { margin-top: 4px; }
.cat-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.cat-label { width: 80px; font-size: 0.8rem; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cat-bar-wrap { flex: 1; height: 10px; background: var(--border); border-radius: 5px; overflow: hidden; }
.cat-bar { height: 100%; border-radius: 5px; transition: width 0.4s; }
.cat-val { font-size: 0.8rem; font-weight: 600; width: 60px; text-align: right; }
/* ===== CLEAR ALL BUTTON ===== */
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
.section-header h2 { margin-bottom: 0; }
/* ===== TOAST ===== */
#toast {
position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%) translateY(80px);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 40px;
padding: 10px 24px;
font-size: 0.88rem;
color: var(--text);
box-shadow: var(--shadow);
transition: transform 0.3s ease;
z-index: 999;
white-space: nowrap;
}
#toast.show { transform: translateX(-50%) translateY(0); }
/* ===== SCROLLABLE LIST ===== */
.scroll-list { max-height: 340px; overflow-y: auto; padding-right: 4px; }
.scroll-list::-webkit-scrollbar { width: 4px; }
.scroll-list::-webkit-scrollbar-track { background: transparent; }
.scroll-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
</style>
</head>
<body>
<h1 class="app-title">๐ Daily Planner</h1>
<p class="app-subtitle">Offline - All data saved in your browser</p>
<!-- TABS -->
<div class="tabs">
<button class="tab-btn active" onclick="switchTab('expenses')">๐ธ Expenses</button>
<button class="tab-btn" onclick="switchTab('todo')">โ
To-Do List</button>
</div>
<!-- ==================== EXPENSES PANEL ==================== -->
<div class="panel active" id="panel-expenses">
<!-- Summary -->
<div class="summary-strip">
<div class="summary-box">
<div class="label">Today</div>
<div class="value today" id="sum-today">โน0</div>
</div>
<div class="summary-box">
<div class="label">This Month</div>
<div class="value total" id="sum-month">โน0</div>
</div>
<div class="summary-box">
<div class="label">All Time</div>
<div class="value count" id="sum-all">โน0</div>
</div>
</div>
<!-- Add Expense -->
<div class="card">
<h2>Add Expense</h2>
<div class="input-row" style="margin-bottom:10px">
<input type="text" id="exp-name" placeholder="Description (e.g. Lunch)" />
<input type="number" id="exp-amount" placeholder="Amount (โน)" min="0" step="0.01" />
</div>
<div class="input-row">
<select id="exp-category">
<option value="food">๐ Food</option>
<option value="transport">๐ Transport</option>
<option value="shopping">๐ Shopping</option>
<option value="health">๐ Health</option>
<option value="bills">๐ก Bills</option>
<option value="entertainment">๐ฌ Entertainment</option>
<option value="education">๐ Education</option>
<option value="other">๐ฆ Other</option>
</select>
<input type="date" id="exp-date" />
<button class="btn btn-primary" onclick="addExpense()">+ Add</button>
</div>
</div>
<!-- Category Chart -->
<div class="card" id="chart-card">
<h2>Spending by Category</h2>
<div class="chart-wrap" id="cat-chart"></div>
</div>
<!-- Expense List -->
<div class="card">
<div class="section-header">
<h2>Expense Log</h2>
<button class="btn btn-danger" onclick="clearAllExpenses()">Clear All</button>
</div>
<div class="date-filter">
<label>Filter:</label>
<input type="date" id="filter-from" onchange="renderExpenses()" />
<span style="color:var(--text2)">to</span>
<input type="date" id="filter-to" onchange="renderExpenses()" />
<button class="btn" style="padding:6px 12px;font-size:0.8rem;background:var(--surface2);color:var(--text2)" onclick="clearFilter()">Reset</button>
</div>
<div class="scroll-list">
<ul class="expense-list" id="expense-list"></ul>
</div>
</div>
</div>
<!-- ==================== TODO PANEL ==================== -->
<div class="panel" id="panel-todo">
<!-- Add Todo -->
<div class="card">
<h2>Add Task</h2>
<div class="input-row" style="margin-bottom:10px">
<input type="text" id="todo-text" placeholder="Task description..." />
</div>
<div class="input-row">
<select id="todo-priority">
<option value="high">๐ด High Priority</option>
<option value="medium" selected>๐ก Medium Priority</option>
<option value="low">๐ข Low Priority</option>
</select>
<input type="date" id="todo-due" title="Due date (optional)" />
<button class="btn btn-primary" onclick="addTodo()">+ Add Task</button>
</div>
</div>
<!-- Progress -->
<div class="card">
<h2>Progress</h2>
<div id="todo-progress-text" style="font-size:0.9rem;color:var(--text2)">0 of 0 tasks completed</div>
<div class="progress-bar-wrap">
<div class="progress-bar"><div class="progress-fill" id="todo-progress-fill" style="width:0%"></div></div>
</div>
</div>
<!-- Todo List -->
<div class="card">
<div class="section-header">
<h2>Tasks</h2>
<button class="btn btn-danger" onclick="clearDoneTodos()">Clear Done</button>
</div>
<div class="filter-row">
<button class="filter-btn active" onclick="setTodoFilter('all', this)">All</button>
<button class="filter-btn" onclick="setTodoFilter('active', this)">Active</button>
<button class="filter-btn" onclick="setTodoFilter('done', this)">Done</button>
<button class="filter-btn" onclick="setTodoFilter('high', this)">๐ด High</button>
</div>
<div class="scroll-list" style="margin-top:8px">
<ul class="todo-list" id="todo-list"></ul>
</div>
</div>
</div>
<!-- Toast -->
<div id="toast"></div>
<script>
// ============================================================
// UTILITIES
// ============================================================
const $ = id => document.getElementById(id);
function toast(msg, color = '#6366f1') {
const t = $('toast');
t.textContent = msg;
t.style.borderColor = color;
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2600);
}
function fmtMoney(n) {
return 'โน' + Number(n).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function todayStr() {
return new Date().toISOString().split('T')[0];
}
function formatDate(str) {
if (!str) return '';
const d = new Date(str + 'T00:00:00');
return d.toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' });
}
// ============================================================
// TABS
// ============================================================
function switchTab(tab) {
document.querySelectorAll('.tab-btn').forEach((b, i) => b.classList.toggle('active', i === (tab === 'expenses' ? 0 : 1)));
$('panel-expenses').classList.toggle('active', tab === 'expenses');
$('panel-todo').classList.toggle('active', tab === 'todo');
}
// ============================================================
// CATEGORY CONFIG
// ============================================================
const CATS = {
food: { icon: '๐', color: '#f59e0b', bg: '#78350f22' },
transport: { icon: '๐', color: '#3b82f6', bg: '#1e3a8a22' },
shopping: { icon: '๐', color: '#a855f7', bg: '#4c1d9522' },
health: { icon: '๐', color: '#22c55e', bg: '#14532d22' },
bills: { icon: '๐ก', color: '#ef4444', bg: '#7f1d1d22' },
entertainment: { icon: '๐ฌ', color: '#ec4899', bg: '#83185322' },
education: { icon: '๐', color: '#06b6d4', bg: '#16424422' },
other: { icon: '๐ฆ', color: '#94a3b8', bg: '#33415522' },
};
// ============================================================
// EXPENSES
// ============================================================
let expenses = JSON.parse(localStorage.getItem('dp_expenses') || '[]');
function saveExpenses() { localStorage.setItem('dp_expenses', JSON.stringify(expenses)); }
function addExpense() {
const name = $('exp-name').value.trim();
const amount = parseFloat($('exp-amount').value);
const cat = $('exp-category').value;
const date = $('exp-date').value || todayStr();
if (!name) return toast('Enter a description', '#ef4444');
if (!amount || amount <= 0) return toast('Enter a valid amount', '#ef4444');
expenses.unshift({ id: Date.now(), name, amount, cat, date });
saveExpenses();
$('exp-name').value = '';
$('exp-amount').value = '';
renderExpenses();
toast('Expense added!', '#22c55e');
}
function deleteExpense(id) {
expenses = expenses.filter(e => e.id !== id);
saveExpenses();
renderExpenses();
toast('Deleted', '#ef4444');
}
function clearAllExpenses() {
if (!confirm('Delete ALL expenses?')) return;
expenses = [];
saveExpenses();
renderExpenses();
toast('All expenses cleared');
}
function clearFilter() {
$('filter-from').value = '';
$('filter-to').value = '';
renderExpenses();
}
function renderExpenses() {
const from = $('filter-from').value;
const to = $('filter-to').value;
const filtered = expenses.filter(e => {
if (from && e.date < from) return false;
if (to && e.date > to) return false;
return true;
});
// Summary
const today = todayStr();
const thisMonth = today.slice(0, 7);
const todaySum = expenses.filter(e => e.date === today).reduce((s, e) => s + e.amount, 0);
const monthSum = expenses.filter(e => e.date.startsWith(thisMonth)).reduce((s, e) => s + e.amount, 0);
const allSum = expenses.reduce((s, e) => s + e.amount, 0);
$('sum-today').textContent = fmtMoney(todaySum);
$('sum-month').textContent = fmtMoney(monthSum);
$('sum-all').textContent = fmtMoney(allSum);
// List
const ul = $('expense-list');
if (filtered.length === 0) {
ul.innerHTML = '<li><div class="empty-msg">No expenses found.</div></li>';
} else {
ul.innerHTML = filtered.map(e => {
const c = CATS[e.cat] || CATS.other;
return `
<li>
<div class="exp-icon" style="background:${c.bg};">${c.icon}</div>
<div class="exp-info">
<div class="exp-name">${escHtml(e.name)}</div>
<div class="exp-meta">
<span class="badge" style="background:${c.bg};color:${c.color}">${e.cat}</span>
${formatDate(e.date)}
</div>
</div>
<div class="exp-amount neg">${fmtMoney(e.amount)}</div>
<button class="btn btn-danger" onclick="deleteExpense(${e.id})">โ</button>
</li>`;
}).join('');
}
// Category chart
renderCatChart(expenses);
}
function renderCatChart(data) {
const totals = {};
data.forEach(e => { totals[e.cat] = (totals[e.cat] || 0) + e.amount; });
const grand = Object.values(totals).reduce((s, v) => s + v, 0);
const sorted = Object.entries(totals).sort((a, b) => b[1] - a[1]);
if (sorted.length === 0) {
$('cat-chart').innerHTML = '<div class="empty-msg">No data yet.</div>';
return;
}
$('cat-chart').innerHTML = sorted.map(([cat, val]) => {
const c = CATS[cat] || CATS.other;
const pct = grand > 0 ? (val / grand * 100).toFixed(1) : 0;
return `
<div class="cat-row">
<div class="cat-label">${c.icon} ${cat}</div>
<div class="cat-bar-wrap">
<div class="cat-bar" style="width:${pct}%;background:${c.color}"></div>
</div>
<div class="cat-val" style="color:${c.color}">${fmtMoney(val)}</div>
</div>`;
}).join('');
}
// ============================================================
// TO-DO LIST
// ============================================================
let todos = JSON.parse(localStorage.getItem('dp_todos') || '[]');
let todoFilter = 'all';
function saveTodos() { localStorage.setItem('dp_todos', JSON.stringify(todos)); }
function addTodo() {
const text = $('todo-text').value.trim();
const priority = $('todo-priority').value;
const due = $('todo-due').value;
if (!text) return toast('Enter a task description', '#ef4444');
todos.unshift({ id: Date.now(), text, priority, due, done: false, createdAt: todayStr() });
saveTodos();
$('todo-text').value = '';
$('todo-due').value = '';
renderTodos();
toast('Task added!', '#22c55e');
}
function toggleTodo(id) {
const t = todos.find(t => t.id === id);
if (t) { t.done = !t.done; saveTodos(); renderTodos(); }
}
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
saveTodos();
renderTodos();
toast('Task removed', '#ef4444');
}
function clearDoneTodos() {
const count = todos.filter(t => t.done).length;
if (count === 0) return toast('No completed tasks to clear');
todos = todos.filter(t => !t.done);
saveTodos();
renderTodos();
toast(`${count} task(s) cleared`);
}
function setTodoFilter(f, btn) {
todoFilter = f;
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderTodos();
}
function renderTodos() {
// Progress
const total = todos.length;
const done = todos.filter(t => t.done).length;
$('todo-progress-text').textContent = `${done} of ${total} tasks completed`;
const pct = total > 0 ? (done / total * 100) : 0;
$('todo-progress-fill').style.width = pct + '%';
// Filter
const filtered = todos.filter(t => {
if (todoFilter === 'active') return !t.done;
if (todoFilter === 'done') return t.done;
if (todoFilter === 'high') return t.priority === 'high' && !t.done;
return true;
});
const PRIORITY_LABELS = { high: '๐ด High', medium: '๐ก Medium', low: '๐ข Low' };
const PRIORITY_CLASS = { high: 'p-high', medium: 'p-medium', low: 'p-low' };
const ul = $('todo-list');
if (filtered.length === 0) {
ul.innerHTML = '<li><div class="empty-msg">No tasks here.</div></li>';
return;
}
// Sort: undone high first, then medium, low; done at bottom
const order = { high: 0, medium: 1, low: 2 };
const sorted = [...filtered].sort((a, b) => {
if (a.done !== b.done) return a.done ? 1 : -1;
return order[a.priority] - order[b.priority];
});
ul.innerHTML = sorted.map(t => {
const dueLabel = t.due ? `<div class="todo-date-label">๐
Due: ${formatDate(t.due)}</div>` : '';
const overdue = t.due && t.due < todayStr() && !t.done ? '<span style="color:var(--red);font-size:0.72rem"> โ Overdue</span>' : '';
return `
<li class="todo-item">
<div class="todo-check ${t.done ? 'done' : ''}" onclick="toggleTodo(${t.id})"></div>
<div class="todo-text-wrap">
<div class="todo-text ${t.done ? 'done' : ''}">${escHtml(t.text)}${overdue}</div>
<div class="todo-priority ${PRIORITY_CLASS[t.priority]}">${PRIORITY_LABELS[t.priority]}</div>
${dueLabel}
</div>
<div class="todo-actions">
<button class="btn btn-danger" onclick="deleteTodo(${t.id})">โ</button>
</div>
</li>`;
}).join('');
}
// ============================================================
// UTILS
// ============================================================
function escHtml(s) {
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
// Allow Enter key to submit
$('exp-name').addEventListener('keydown', e => { if (e.key === 'Enter') addExpense(); });
$('exp-amount').addEventListener('keydown', e => { if (e.key === 'Enter') addExpense(); });
$('todo-text').addEventListener('keydown', e => { if (e.key === 'Enter') addTodo(); });
// ============================================================
// INIT
// ============================================================
$('exp-date').value = todayStr();
$('todo-due').value = '';
renderExpenses();
renderTodos();
</script>
</body>
</html>
ls -lh /home/daytona/workspace/daily-app/index.html
index
HTML File ยท HTML
| Feature | Details |
|---|---|
| Add expenses | Name, amount, category, date |
| 8 categories | Food, Transport, Shopping, Health, Bills, Entertainment, Education, Other |
| Summary cards | Today's spend, This Month, All Time |
| Category chart | Visual bar chart showing spending breakdown |
| Date filter | Filter expense log by date range |
| Delete / Clear All | Remove individual or all expenses |
| Feature | Details |
|---|---|
| Add tasks | Description, priority level, due date |
| 3 priorities | High (๐ด), Medium (๐ก), Low (๐ข) |
| Overdue warning | Tasks past due date get a red โ tag |
| Progress bar | Shows how many tasks completed |
| Filter tabs | All / Active / Done / High Priority |
| Toggle done | Click the circle to mark complete |
| Clear Done | Remove all completed tasks at once |
Enter to quickly add expenses or tasks