Overview
SpendWisely George implements a daily budget system that tracks spending for each calendar day (IST timezone) and displays real-time budget usage with visual indicators. The budget resets automatically at midnight.
How It Works
Set daily budget
User configures budget amount (e.g., ₹350) in Settings
Track today's spending
System filters transactions by today’s date (IST) and type=DEBIT
Calculate usage
Budget usage % = (today’s spend / daily budget) × 100
Update UI
Progress bar and percentage update in real-time
Setting Budget
UI Component
< div class = "pt-4 border-t border-slate-100" >
< label class = "block text-xs font-bold text-slate-400 uppercase mb-2" >
Daily Budget
</ label >
< input type = "number" id = "budgetInput"
class = "w-full p-4 bg-slate-100 rounded-xl font-bold text-slate-900 outline-none focus:ring-2 ring-blue-500"
onchange = " pushSettings ()" >
</ div >
Saving to Backend
async function pushSettings ( updateKey = false ) {
const budget = parseFloat ( document . getElementById ( 'budgetInput' ). value );
const privacy = document . getElementById ( 'privacyToggle' ). checked ;
const key = document . getElementById ( 'geminiKeyInput' ). value ;
const payload = {
action: "save_settings" ,
budget: budget ,
privacy: privacy
};
if ( updateKey && key ) payload . geminiKey = key ;
// Update local state immediately
appData . budget = budget ;
appData . privacy = privacy ;
renderUI ();
// Persist to Google Apps Script
await fetch ( SCRIPT_URL , {
method: 'POST' ,
body: JSON . stringify ( payload )
});
if ( updateKey ) {
alert ( "Key Saved!" );
document . getElementById ( 'geminiKeyInput' ). value = "" ;
}
}
Google Apps Script Storage
const SCRIPT_PROP = PropertiesService . getScriptProperties ();
if ( body . action === "save_settings" ) {
SCRIPT_PROP . setProperty ( "DAILY_BUDGET" , body . budget . toString ());
SCRIPT_PROP . setProperty ( "PRIVACY_MODE" , body . privacy . toString ());
if ( body . geminiKey ) SCRIPT_PROP . setProperty ( "GEMINI_KEY" , body . geminiKey );
return ContentService . createTextOutput (
JSON . stringify ({ status: "Saved" })
). setMimeType ( ContentService . MimeType . JSON );
}
Loading Budget
function doGet () {
const sheet = getTargetSheet ();
const data = sheet . getDataRange (). getValues ();
const balance = calculateBalance ( sheet );
const budget = SCRIPT_PROP . getProperty ( "DAILY_BUDGET" ) || 350 ; // Default ₹350
const privacyRaw = SCRIPT_PROP . getProperty ( "PRIVACY_MODE" );
const privacy = privacyRaw === null ? true : ( privacyRaw === "true" );
const expenses = [];
// ... fetch expenses ...
return ContentService . createTextOutput ( JSON . stringify ({
balance: balance ,
expenses: expenses ,
budget: budget ,
privacy: privacy
})). setMimeType ( ContentService . MimeType . JSON );
}
Budget Tracking
Calculating Today’s Spending
function renderUI () {
// Get today's date in IST timezone
const todayIST = new Date (). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
});
// Filter transactions for today, DEBIT only
const todaySpend = appData . expenses . filter ( t =>
new Date ( t . date ). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
}) === todayIST && t . type === 'DEBIT'
). reduce (( sum , t ) => sum + parseFloat ( t . amount ), 0 );
// Calculate percentage (cap at 100%)
const budgetPct = Math . min (( todaySpend / appData . budget ) * 100 , 100 );
// Update progress bar
document . getElementById ( 'budgetBar' ). style . width = ` ${ budgetPct } %` ;
document . getElementById ( 'budgetPercent' ). innerText = ` ${ Math . round ( budgetPct ) } %` ;
}
UI Components
The budget indicator is displayed in the main balance card:
< div class = "card p-6 bg-slate-900 text-white border-none relative overflow-hidden shadow-2xl" >
<!-- Balance display -->
< h1 id = "uiBalance" class = "text-5xl font-black tracking-tighter mb-4 relative z-10" > ... </ h1 >
<!-- Budget Progress Bar -->
< div class = "relative pt-1 z-10" >
< div class = "flex mb-2 items-center justify-between" >
< div >
< span class = "text-[10px] font-semibold inline-block py-1 px-2 uppercase rounded-full text-blue-200 bg-blue-900"
id = "budgetLabel" >
Budget Usage
</ span >
</ div >
< div class = "text-right" >
< span class = "text-[10px] font-semibold inline-block text-blue-200"
id = "budgetPercent" >
0%
</ span >
</ div >
</ div >
< div class = "overflow-hidden h-2 mb-4 text-xs flex rounded bg-slate-700" >
< div id = "budgetBar" style = "width:0%"
class = "bg-blue-500 transition-all duration-1000" >
</ div >
</ div >
</ div >
</ div >
Visual States
The progress bar changes appearance based on usage:
Under Budget (< 80%)
Near Limit (80-100%)
Over Budget (≥ 100%)
/* Blue bar indicates healthy spending */
.bg-blue-500 { background-color : #3b82f6 ; }
Budget usage: 45%
Status: On track // Could add warning color
if ( budgetPct >= 80 && budgetPct < 100 ) {
document . getElementById ( 'budgetBar' ). classList . add ( 'bg-orange-500' );
}
Budget usage: 92%
Status: Warning if ( budgetPct >= 100 ) {
document . getElementById ( 'budgetBar' ). classList . add ( 'bg-red-500' );
}
Budget usage: 100% (capped)
Status: Over budget
The current implementation caps the visual progress at 100%, even if actual spending exceeds the budget. The percentage display will show the true value.
Budget Alerts
Current Implementation
The system provides visual-only alerts through the progress bar. There are no popup notifications or sound alerts in the current version.
Future Enhancement Example
// Add to renderUI() function
if ( budgetPct >= 90 && budgetPct < 100 ) {
// Show warning notification
document . getElementById ( 'budgetLabel' ). innerText = "⚠️ Budget Warning" ;
document . getElementById ( 'budgetLabel' ). classList . add ( 'bg-orange-900' , 'text-orange-200' );
} else if ( budgetPct >= 100 ) {
// Show over-budget alert
document . getElementById ( 'budgetLabel' ). innerText = "🚨 Over Budget" ;
document . getElementById ( 'budgetLabel' ). classList . add ( 'bg-red-900' , 'text-red-200' );
} else {
// Normal state
document . getElementById ( 'budgetLabel' ). innerText = "Budget Usage" ;
document . getElementById ( 'budgetLabel' ). classList . add ( 'bg-blue-900' , 'text-blue-200' );
}
Budget Reset Logic
Budget automatically resets at midnight IST because the calculation filters by todayIST:
const todayIST = new Date (). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
});
const todaySpend = appData . expenses . filter ( t =>
new Date ( t . date ). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
}) === todayIST && t . type === 'DEBIT'
). reduce (( sum , t ) => sum + parseFloat ( t . amount ), 0 );
When the date changes:
todayIST becomes the new date (e.g., “3/6/2026”)
Filter matches only transactions from the new date
Yesterday’s transactions are excluded
todaySpend resets to 0
Progress bar returns to 0%
Budget vs Balance
Balance Cumulative total of all CREDIT - DEBIT transactions across all timeDisplayed in: Top balance card
Budget Daily spending limit that resets at midnight ISTDisplayed in: Progress bar under balance
Relationship
// Balance: All-time total
appData . balance = Σ ( CREDIT ) - Σ ( DEBIT ) // All transactions
// Budget: Today's spending vs daily limit
todaySpend = Σ ( today 's DEBIT) // Today onl y
budgetPct = ( todaySpend / appData . budget ) × 100
Integration with Expense Tracking
Budget updates in real-time when expenses are added:
async function sendToAI ( mode ) {
// ... AI processing ...
if ( data . status === "Success" ) {
icon . innerText = mode === 'add' ? "✅" : "🧠" ;
tip . innerText = data . ai_response ;
if ( mode === 'add' ) {
appData . balance = parseFloat ( data . balance );
if ( data . parsed ) appData . expenses . push ( data . parsed );
// renderUI() recalculates budget progress
renderUI ();
}
}
}
Manual Fallback Updates Budget Too
async function handleManualFallback ( text ) {
// ... parse expense ...
appData . balance = ( type === 'CREDIT' )
? ( appData . balance + amount )
: ( appData . balance - amount );
appData . expenses . push ( manualTx );
// Budget bar updates here
renderUI ();
}
Budget Analytics
While not displayed in UI, the current implementation provides data for analytics:
Example: Budget Adherence Rate
// Calculate how often user stays under budget
function calculateAdherence ( expenses , budget ) {
const dailySpends = {};
expenses . forEach ( tx => {
if ( tx . type === 'DEBIT' ) {
const date = new Date ( tx . date ). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
});
dailySpends [ date ] = ( dailySpends [ date ] || 0 ) + tx . amount ;
}
});
const days = Object . keys ( dailySpends );
const underBudgetDays = days . filter ( d => dailySpends [ d ] <= budget ). length ;
return ( underBudgetDays / days . length ) * 100 ;
}
// Usage
const adherence = calculateAdherence ( appData . expenses , appData . budget );
console . log ( `Budget adherence: ${ adherence . toFixed ( 1 ) } %` );
Settings Integration
Loading Current Budget
function renderSettings () {
document . getElementById ( 'budgetInput' ). value = appData . budget ;
document . getElementById ( 'privacyToggle' ). checked = appData . privacy ;
}
// Called during init()
renderSettings ();
Initialization Flow
async function init () {
setStatus ( "SYNCING..." , "text-orange-500" , "bg-orange-500" );
disableInput ( true );
try {
const res = await fetch ( SCRIPT_URL );
const data = await res . json ();
appData . balance = parseFloat ( data . balance ) || 0 ;
appData . expenses = data . expenses || [];
appData . budget = parseFloat ( data . budget ) || 350 ; // Load budget
if ( data . privacy !== undefined ) appData . privacy = data . privacy ;
renderUI (); // Updates budget bar
renderSettings (); // Updates budget input field
setStatus ( "ONLINE" , "text-green-500" , "bg-green-500" );
disableInput ( false );
} catch ( e ) {
console . error ( "INIT ERROR:" , e );
setStatus ( "OFFLINE" , "text-red-500" , "bg-red-500" );
}
}
Best Practices
Don’t set too low : ₹100/day might be unrealistic if you eat outCalculate from history : Review last month’s average daily spendExample calculation :Last month spend: ₹12,000
Days in month: 30
Average daily: ₹400
Realistic budget: ₹350-450
Exclude irregular expenses
Budget is for daily recurring expenses like:
Food and coffee
Transport
Small purchases
Don’t include :
Rent (monthly)
Large purchases (electronics)
One-time expenses
Check budget adherence every Sunday: // How many days did you stay under budget this week?
const thisWeek = expenses . filter ( t =>
isThisWeek ( t . date ) && t . type === 'DEBIT'
);
Increase budget during:
Festival seasons (Diwali, Christmas)
Travel periods
Special events
Decrease during:
Work-from-home phases
Saving goals
Troubleshooting
Progress bar not updating
Check:
Transactions have correct date format (ISO 8601)
Transactions are marked as type=DEBIT
Browser timezone is set correctly
Debug: console . log ( 'Today IST:' , new Date (). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
}));
console . log ( 'Today \' s expenses:' , appData . expenses . filter ( t =>
new Date ( t . date ). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
}) === todayIST && t . type === 'DEBIT'
));
Budget shows 0% despite expenses
Possible causes:
Budget value is 0 or undefined
No DEBIT transactions today
Date filtering issue
Verify: console . log ( 'Budget:' , appData . budget );
console . log ( 'Today spend:' , todaySpend );
console . log ( 'Budget %:' , budgetPct );
This happens if:
Browser timezone is not IST
System clock is incorrect
Fix:
Ensure your system is set to IST (UTC+5:30) or the code uses hardcoded IST conversion.
Check:
Settings panel onchange event is firing
Google Apps Script is receiving the save_settings action
Script Properties are writable (check Apps Script permissions)
Test: // In browser console
await pushSettings ();
// Then reload and check if budget persists
Future Enhancements
Weekly/Monthly Budgets Support for longer budget periods beyond daily
Category Budgets Separate budgets for Food, Transport, etc.
Smart Budgets AI suggests budget based on spending history
Push Notifications Browser notifications when nearing budget limit
Example: Category-wise Budget
// Future implementation concept
const categoryBudgets = {
'Food' : 200 ,
'Transport' : 100 ,
'Tech' : 50
};
function calculateCategoryBudget ( category ) {
const todayIST = new Date (). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
});
const categorySpend = appData . expenses . filter ( t =>
new Date ( t . date ). toLocaleDateString ( 'en-US' , {
timeZone: 'Asia/Kolkata'
}) === todayIST &&
t . type === 'DEBIT' &&
t . category === category
). reduce (( sum , t ) => sum + parseFloat ( t . amount ), 0 );
return ( categorySpend / categoryBudgets [ category ]) * 100 ;
}