0% found this document useful (0 votes)
5 views42 pages

Javascript

The document outlines a JavaScript application that integrates with Supabase for user authentication and expense tracking. It includes functionalities for user registration, login, expense management, and budget settings, along with a color palette for charts and navigation setup. The application maintains a state for the current user, expenses, categories, and settings, and handles user data loading and profile management through Supabase's database operations.

Uploaded by

joyette.silao
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views42 pages

Javascript

The document outlines a JavaScript application that integrates with Supabase for user authentication and expense tracking. It includes functionalities for user registration, login, expense management, and budget settings, along with a color palette for charts and navigation setup. The application maintains a state for the current user, expenses, categories, and settings, and handles user data loading and profile management through Supabase's database operations.

Uploaded by

joyette.silao
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

const SUPABASE_URL = "[Link]

co";

const SUPABASE_KEY = "sb_publishable_p5mwf4IaC-7v2uxdWbNjhA_9pNcfeEu";

const supabaseClient = [Link](SUPABASE_URL, SUPABASE_KEY);

// ─── Chart colour palette (mapped to category color names) ─────────────────

const CHART_PALETTE = {

pink: "rgba(244, 114, 182, 0.85)",

blue: "rgba(96, 165, 250, 0.85)",

lavender: "rgba(167, 139, 250, 0.85)",

yellow: "rgba(251, 191, 36, 0.85)",

mint: "rgba(52, 211, 153, 0.85)",

sky: "rgba(56, 189, 248, 0.85)",

gray: "rgba(156, 163, 175, 0.85)"

};

const MONTH_NAMES = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

// ─── App state ──────────────────────────────────────────────────────────────

let state = {

currentUser: null,

expenses: [],

categories: [],

settings: {

currency: "PHP",

budgets: { weekly: 0, monthly: 0, yearly: 0 }

};
// Chart instance store (destroy before re-render)

const charts = { today: null, month: null, year: null };

// ─── Boot ───────────────────────────────────────────────────────────────────

[Link]("DOMContentLoaded", async () => {

setupAuthTabs();

setupAuthForms();

setupNavigation();

setupExpenseForm();

setupSettings();

setupLogout();

setupChartTabs();

setupAllocTabs();

setupUserCategories();

setTodayDate();

const { data } = await [Link]();

if ([Link]?.user) {

await handleAuthenticatedUser([Link]);

} else {

showAuth();

[Link](async (_event, session) => {

if (session?.user) {

await handleAuthenticatedUser([Link]);

} else {

[Link] = null;

showAuth();
}

});

});

async function handleAuthenticatedUser(user) {

[Link] = {

id: [Link],

name: user.user_metadata?.name || user.user_metadata?.full_name || [Link]?.split("@")[0] ||


"User",

email: [Link]

};

await ensureProfileAndDefaults();

await loadUserData();

showApp();

// ─── AUTH ────────────────────────────────────────────────────────────────────

function setupAuthTabs() {

[Link]("loginTabBtn").addEventListener("click", () => switchAuthTab("login"));

[Link]("registerTabBtn").addEventListener("click", () => switchAuthTab("register"));

function switchAuthTab(tab) {

const loginForm = [Link]("loginForm");

const registerForm = [Link]("registerForm");

const loginTabBtn = [Link]("loginTabBtn");

const registerTabBtn= [Link]("registerTabBtn");

const authMessage = [Link]("authMessage");

[Link] = "";
if (tab === "login") {

[Link]("hidden");

[Link]("hidden");

[Link]("active");

[Link]("active");

} else {

[Link]("hidden");

[Link]("hidden");

[Link]("active");

[Link]("active");

function setupAuthForms() {

[Link]("loginForm").addEventListener("submit", handleLogin);

[Link]("registerForm").addEventListener("submit", handleRegister);

async function handleRegister(event) {

[Link]();

const name = [Link]("registerName").[Link]();

const email = [Link]("registerEmail").[Link]().toLowerCase();

const password = [Link]("registerPassword").value;

const confirmPassword = [Link]("registerConfirmPassword").value;

const authMessage = [Link]("authMessage");

[Link] = "#dc2626";
if (!name || !email || !password || !confirmPassword) { [Link] = "Please complete
all fields."; return; }

if ([Link] < 6) { [Link] = "Password must be at least 6 characters.";


return; }

if (password !== confirmPassword) { [Link] = "Passwords do not match."; return; }

const { error } = await [Link]({ email, password, options: { data: { name } } });

if (error) { [Link] = [Link]; return; }

[Link] = "#16a34a";

[Link] = "Account created! You can now sign in.";

[Link]("registerForm").reset();

switchAuthTab("login");

async function handleLogin(event) {

[Link]();

const email = [Link]("loginEmail").[Link]().toLowerCase();

const password = [Link]("loginPassword").value;

const authMessage = [Link]("authMessage");

[Link] = "#dc2626";

const { error } = await [Link]({ email, password });

if (error) [Link] = [Link];

function setupLogout() {

[Link]("logoutBtn").addEventListener("click", async () => {

await [Link]();

});
}

// ─── VISIBILITY ─────────────────────────────────────────────────────────────

function showAuth() {

[Link]("authScreen").[Link]("hidden");

[Link]("appShell").[Link]("hidden");

function showApp() {

[Link]("authScreen").[Link]("hidden");

[Link]("appShell").[Link]("hidden");

[Link]("welcomeText").textContent = `Welcome, ${[Link]}!`;

renderAll();

// ─── DATABASE ────────────────────────────────────────────────────────────────

async function ensureProfileAndDefaults() {

// Upsert profile

await [Link]("profiles").upsert({

id: [Link],

name: [Link],

email: [Link]

});

// Ensure user_settings row exists

const settingsRes = await supabaseClient

.from("user_settings")

.select("*")

.eq("user_id", [Link])
.maybeSingle();

if (![Link]) {

await [Link]("user_settings").insert({

user_id: [Link],

currency: "PHP",

weekly_budget: 0,

monthly_budget: 0,

yearly_budget: 0

});

// NOTE: Categories are now global — no per-user category seeding needed.

async function loadUserData() {

// Fetch global categories (user_id IS NULL) + user's own categories

// RLS policy handles this: select is allowed for (user_id is null OR user_id = [Link]())

const categoriesResponse = await supabaseClient

.from("categories")

.select("*")

.order("created_at", { ascending: true });

const expensesResponse = await supabaseClient

.from("expenses")

.select("*")

.eq("user_id", [Link])

.order("expense_date", { ascending: false });

const settingsResponse = await supabaseClient


.from("user_settings")

.select("*")

.eq("user_id", [Link])

.maybeSingle();

if ([Link]) { [Link]([Link]);
alert([Link]); }

if ([Link]) { [Link]([Link]);
alert([Link]); }

if ([Link]) { [Link]([Link]);
alert([Link]); }

[Link] = [Link] || [];

[Link] = ([Link] || []).map((expense) => {

const category = [Link]((cat) => [Link] === expense.category_id);

return {

id: [Link],

amount: Number([Link]),

description: [Link],

date: expense.expense_date,

categoryId: expense.category_id,

categoryName: category?.name || "Unknown",

categoryIcon: category?.icon || "📦",

categoryColor: category?.color || "gray"

};

});

const settings = [Link] || null;

[Link] = {
currency: settings?.currency || "PHP",

budgets: {

weekly: Number(settings?.weekly_budget || 0),

monthly: Number(settings?.monthly_budget || 0),

yearly: Number(settings?.yearly_budget || 0)

};

// ─── NAVIGATION ──────────────────────────────────────────────────────────────

function setupNavigation() {

[Link](".nav-item").forEach((item) => {

[Link]("click", () => showPage([Link]));

});

function showPage(pageId) {

[Link](".page").forEach((page) => {

[Link]("active", [Link] === pageId);

});

[Link](".nav-item").forEach((item) => {

[Link]("active", [Link] === pageId);

});

// ─── EXPENSE FORM ────────────────────────────────────────────────────────────

function setupExpenseForm() {

[Link]("expenseForm").addEventListener("submit", handleAddExpense);

[Link]("cancelExpenseBtn").addEventListener("click", () => setTodayDate());


}

async function handleAddExpense(event) {

[Link]();

const amount = parseFloat([Link]("amount").value);

const categoryId = [Link]("categorySelect").value;

const description = [Link]("description").[Link]();

const expenseDate = [Link]("expenseDate").value;

if (!amount || amount <= 0) { alert("Please enter a valid amount."); return; }

if (!categoryId) { alert("Please select a category."); return; }

if (!description) { alert("Please enter a description."); return; }

const { error } = await [Link]("expenses").insert({

user_id: [Link],

user_email: [Link],

amount,

description,

expense_date: expenseDate,

category_id: categoryId

});

if (error) { [Link]("Add expense error:", error); alert([Link]); return; }

[Link]();

setTodayDate();

await loadUserData();

renderAll();

showPage("all-expenses");
}

async function deleteExpense(id) {

if (![Link]("Delete this expense?")) return;

const { error } = await supabaseClient

.from("expenses").delete()

.eq("id", id)

.eq("user_id", [Link]);

if (error) { [Link](error); alert([Link]); return; }

await loadUserData();

renderAll();

// ─── SETTINGS ────────────────────────────────────────────────────────────────

function setupSettings() {

const budgetForm = [Link]("budgetForm");

if (budgetForm) [Link]("submit", handleSaveBudgetSettings);

setupBudgetCascade();

// ── Budget cascade: yearly → monthly+weekly | monthly → weekly | weekly → nothing

function setupBudgetCascade() {

const yearlyEl = [Link]("yearlyBudgetInput");

const monthlyEl = [Link]("monthlyBudgetInput");

const weeklyEl = [Link]("weeklyBudgetInput");

if (!yearlyEl || !monthlyEl || !weeklyEl) return;

// YEARLY typed → cascade down to monthly & weekly


[Link]("input", () => {

const yearly = parseFloat([Link]) || 0;

if (yearly > 0) {

const monthly = +(yearly / 12).toFixed(2);

const weekly = +(yearly / 52).toFixed(2);

[Link] = monthly;

[Link] = weekly;

setAutoField("monthly", true, `Auto: ₱${[Link]()} ÷ 12 months`);

setAutoField("weekly", true, `Auto: ₱${[Link]()} ÷ 52 weeks`);

setHint("yearlyHint", `→ Monthly: ${formatCurrency(monthly)} · Weekly: $


{formatCurrency(weekly)}`);

} else {

// Yearly cleared — reset cascaded fields

[Link] = 0;

[Link] = 0;

setAutoField("monthly", false, "");

setAutoField("weekly", false, "");

setHint("yearlyHint", "");

});

// MONTHLY typed → cascade down to weekly only (yearly stays untouched)

[Link]("input", () => {

// Only cascade if yearly is not driving things

const yearly = parseFloat([Link]) || 0;

if (yearly > 0) return; // yearly is master, skip


const monthly = parseFloat([Link]) || 0;

if (monthly > 0) {

const weekly = +(monthly / 4.3333).toFixed(2);

[Link] = weekly;

setAutoField("weekly", true, `Auto: ₱${[Link]()} ÷ 4.33 weeks`);

setHint("monthlyHint", `→ Weekly: ${formatCurrency(weekly)}`);

} else {

[Link] = 0;

setAutoField("weekly", false, "");

setHint("monthlyHint", "");

});

// WEEKLY typed → no cascade; clear its own auto state

[Link]("input", () => {

const yearly = parseFloat([Link]) || 0;

const monthly = parseFloat([Link]) || 0;

// If neither yearly nor monthly is driving weekly, remove auto badge

if (!yearly && !monthly) {

setAutoField("weekly", false, "");

setHint("weeklyHint", "Monthly & Yearly remain unchanged.");

});

// Helper: toggle auto-badge + input styling

function setAutoField(period, isAuto, hintText) {


const badge = [Link](`${period}AutoBadge`);

const field = [Link](`${period}BudgetField`);

const input = [Link](`${period}BudgetInput`);

const hint = [Link](`${period}Hint`);

if (badge) [Link]("hidden", !isAuto);

if (field) [Link]("is-auto", isAuto);

if (input) [Link]("input-auto", isAuto);

if (hint) [Link] = hintText || "";

function setHint(id, text) {

const el = [Link](id);

if (el) [Link] = text;

async function handleSaveBudgetSettings(event) {

[Link]();

const currency = [Link]("currencyInput").[Link]().toUpperCase();

const weekly = parseFloat([Link]("weeklyBudgetInput").value || "0");

const monthly = parseFloat([Link]("monthlyBudgetInput").value || "0");

const yearly = parseFloat([Link]("yearlyBudgetInput").value || "0");

if (!currency || [Link] !== 3) {

showBudgetMsg("Currency must be exactly 3 letters, e.g. PHP.", "error");

return;

if (weekly < 0 || monthly < 0 || yearly < 0) {

showBudgetMsg("Budgets cannot be negative.", "error");


return;

const { error } = await [Link]("user_settings").upsert({

user_id: [Link],

currency,

weekly_budget: weekly,

monthly_budget: monthly,

yearly_budget: yearly

});

if (error) {

[Link]("Save settings error:", error);

showBudgetMsg([Link], "error");

return;

await loadUserData();

renderAll();

showBudgetMsg("✅ Budget settings saved successfully!", "success");

// Inline save message (replaces browser alert)

function showBudgetMsg(text, type) {

const el = [Link]("budgetSaveMsg");

if (!el) return;

[Link] = text;

[Link] = `budget-save-msg ${type}`;

[Link]("hidden");
setTimeout(() => [Link]("hidden"), 4000);

// ─── CHART TABS ──────────────────────────────────────────────────────────────

function setupChartTabs() {

[Link](".chart-tab-btn").forEach((btn) => {

[Link]("click", () => {

const view = [Link];

[Link](".chart-tab-btn").forEach((b) => [Link]("active"));

[Link](".chart-view").forEach((v) => [Link]("active"));

[Link]("active");

const panelId = `chartView${[Link](0).toUpperCase() + [Link](1)}`;

[Link](panelId)?.[Link]("active");

});

});

// ─── RENDER ALL ──────────────────────────────────────────────────────────────

function renderAll() {

renderCategorySelect();

renderSettingsCategories();

renderUserCategories();

renderDashboard();

renderExpensesList();

fillSettingsForm();

renderBudgetOverview();

renderCategoryBudgetAllocation();

renderCharts();

}
// ─── CATEGORY SELECT ─────────────────────────────────────────────────────────

function renderCategorySelect() {

const select = [Link]("categorySelect");

[Link] = `<option value="">Select category</option>`;

[Link]((category) => {

const option = [Link]("option");

[Link] = [Link];

[Link] = `${[Link]} ${[Link]}`;

[Link](option);

});

// ─── SETTINGS CATEGORIES — admin global (read-only) ─────────────────────────

function renderSettingsCategories() {

const container = [Link]("settingsCategoryGrid");

[Link] = "";

// Only global/admin categories (user_id is null)

const adminCats = [Link]((c) => !c.user_id);

if (![Link]) {

[Link] = `<div class="empty-state"><p>No default categories found.</p></div>`;

return;

[Link]((category) => {

// locked field is now dynamic — admin can toggle it in Table Editor

const lockBadge = [Link]


? `<span class="lock-badge">🔒 Locked</span>`

: `<span class="unlock-badge">🔓 Unlocked</span>`;

const item = [Link]("div");

[Link] = `settings-category-item${![Link] ? " default-cat-unlocked" : ""}`;

[Link] = `

<div class="settings-left">

<div class="icon-circle ${[Link] || "gray"}">${[Link]}</div>

<div class="user-cat-info">

<span>${escapeHtml([Link])}</span>

<small class="cat-admin-note">Admin default · visible to all users</small>

</div>

</div>

${lockBadge}

`;

[Link](item);

});

// ─── USER CUSTOM CATEGORIES ──────────────────────────────────────────────────

function setupUserCategories() {

const form = [Link]("addUserCategoryForm");

if (form) [Link]("submit", handleAddUserCategory);

function renderUserCategories() {

const container = [Link]("userCategoryGrid");

const countBadge = [Link]("userCatCount");

if (!container) return;
// Only the current user's own categories

const userCats = [Link](

(c) => c.user_id && c.user_id === [Link]

);

if (countBadge) [Link] = `${[Link]} custom`;

[Link] = "";

if (![Link]) {

[Link] = `<div class="empty-state"><p>No custom categories yet. Add one


above!</p></div>`;

return;

[Link]((category) => {

const isUsed = [Link]((e) => [Link] === [Link]);

const adminLocked = [Link] === true; // admin toggled this in Table Editor

const cantDelete = isUsed || adminLocked;

// Decide status badge/note

let statusNote = "";

if (adminLocked) {

statusNote = `<span class="cat-admin-lock-badge">🔒 Admin Locked</span>`;

} else if (isUsed) {

statusNote = `<small class="cat-in-use-note">In use — cannot delete</small>`;

}
// Delete button disabled reason

let disabledAttr = "";

let disabledStyle = "";

let disabledTitle = "";

if (adminLocked) {

disabledAttr = "disabled";

disabledStyle = "opacity:0.35;cursor:not-allowed;";

disabledTitle = `title="Locked by admin — contact your admin to unlock"`;

} else if (isUsed) {

disabledAttr = "disabled";

disabledStyle = "opacity:0.35;cursor:not-allowed;";

disabledTitle = `title="This category is linked to an expense"`;

const item = [Link]("div");

[Link] = `settings-category-item${adminLocked ? " cat-item-locked" : ""}`;

[Link] = `

<div class="settings-left">

<div class="icon-circle ${[Link] || "gray"}">${[Link]}</div>

<div class="user-cat-info">

<span>${escapeHtml([Link])}</span>

${statusNote}

</div>

</div>

<button

class="delete-btn user-cat-delete-btn"

type="button"

${disabledAttr}

${disabledTitle}
style="${disabledStyle}"

>🗑 Delete</button>

`;

if (!cantDelete) {

[Link](".user-cat-delete-btn")

.addEventListener("click", () => handleDeleteUserCategory([Link], [Link]));

[Link](item);

});

async function handleAddUserCategory(event) {

[Link]();

const name = [Link]("newCategoryName").[Link]();

const icon = [Link]("newCategoryIcon").[Link]() || "🏷";

const color = [Link]("newCategoryColor").value || "gray";

if (!name) {

showUserCatMsg("Please enter a category name.", "error");

return;

// Check for duplicate name among all categories visible to this user

const duplicate = [Link](

(c) => [Link]() === [Link]()

);
if (duplicate) {

showUserCatMsg(`A category named "${name}" already exists.`, "error");

return;

const { error } = await [Link]("categories").insert({

user_id: [Link],

user_email: [Link], // stored so admin can see who owns it

name,

icon,

color,

locked: false

});

if (error) {

[Link]("Add user category error:", error);

// Friendly duplicate error

const msg = [Link]?.toLowerCase() || "";

if ([Link]("duplicate") || [Link]("unique")) {

showUserCatMsg(`A category named "${name}" already exists.`, "error");

} else {

showUserCatMsg([Link], "error");

return;

[Link]("addUserCategoryForm").reset();

await loadUserData();

renderAll();
showUserCatMsg(`✅ Category "${name}" added successfully!`, "success");

async function handleDeleteUserCategory(id, name) {

if (![Link](`Delete the category "${name}"? This cannot be undone.`)) return;

const { error } = await supabaseClient

.from("categories")

.delete()

.eq("id", id)

.eq("user_id", [Link]); // safety: only own rows

if (error) {

[Link]("Delete user category error:", error);

showUserCatMsg([Link], "error");

return;

await loadUserData();

renderAll();

showUserCatMsg(`🗑 Category "${name}" deleted.`, "success");

function showUserCatMsg(text, type) {

const el = [Link]("userCatMsg");

if (!el) return;

[Link] = text;

[Link] = `user-cat-msg ${type}`;

[Link]("hidden");
setTimeout(() => [Link]("hidden"), 4000);

// ─── DASHBOARD ───────────────────────────────────────────────────────────────

function renderDashboard() {

const total = sumExpenses([Link]);

const monthTotal = sumExpenses(getCurrentMonthExpenses());

[Link]("dashboardTotal").textContent = formatCurrency(total);

[Link]("dashboardMonthTotal").textContent = formatCurrency(monthTotal);

[Link]("dashboardCount").textContent = [Link];

const container = [Link]("recentExpensesContainer");

[Link] = "";

if (![Link]) {

[Link] = `<div class="empty-state"><p>No expenses yet</p></div>`;

return;

[Link](0, 5).forEach((expense) => {

const item = [Link]("div");

[Link] = "simple-list-item";

[Link] = `

<div class="simple-list-left">

<div class="icon-circle ${[Link] || "gray"}">${[Link]}</div>

<div>

<strong>${escapeHtml([Link])}</strong>

<p>${escapeHtml([Link])} • ${formatDisplayDate([Link])}</p>
</div>

</div>

<strong>${formatCurrency([Link])}</strong>

`;

[Link](item);

});

// ─── EXPENSES LIST ───────────────────────────────────────────────────────────

function renderExpensesList() {

const expensesList = [Link]("expensesList");

[Link] = "";

[Link]("allExpenseCount").textContent =

`${[Link]} transaction${[Link] === 1 ? "" : "s"}`;

[Link]("allExpenseTotal").textContent =
formatCurrency(sumExpenses([Link]));

if (![Link]) {

[Link] = `<div class="empty-state"><p>No expenses yet</p></div>`;

return;

[Link]((expense) => {

const item = [Link]("div");

[Link] = "expense-item";

[Link] = `

<div class="expense-left">

<div class="icon-circle ${[Link] || "gray"}">${[Link]}</div>


<div class="expense-details">

<h4>${escapeHtml([Link])}</h4>

<p>${escapeHtml([Link])} • ${formatDisplayDate([Link])}</p>

<small class="user-badge">${escapeHtml([Link])}</small>

</div>

</div>

<div class="expense-right">

<div class="expense-amount">${formatCurrency([Link])}</div>

<button class="delete-btn" type="button">Delete</button>

</div>

`;

[Link](".delete-btn").addEventListener("click", () => deleteExpense([Link]));

[Link](item);

});

// ─── SETTINGS FORM ───────────────────────────────────────────────────────────

function fillSettingsForm() {

const c = [Link]("currencyInput");

const w = [Link]("weeklyBudgetInput");

const m = [Link]("monthlyBudgetInput");

const y = [Link]("yearlyBudgetInput");

if (!c) return;

const currency = [Link] || "PHP";

const weekly = Number([Link] || 0);

const monthly = Number([Link] || 0);

const yearly = Number([Link] || 0);


[Link] = currency;

[Link] = weekly;

[Link] = monthly;

[Link] = yearly;

// Restore cascade visual state based on loaded values

const yearlyDrivesAll = yearly > 0 && [Link](monthly - yearly / 12) < 0.05 && [Link](weekly -
yearly / 52) < 0.05;

const monthlyDrivesWeek = !yearlyDrivesAll && monthly > 0 && [Link](weekly - monthly / 4.3333)
< 0.05;

if (yearlyDrivesAll) {

setAutoField("monthly", true, `Auto: ${formatCurrency(yearly)} ÷ 12 months`);

setAutoField("weekly", true, `Auto: ${formatCurrency(yearly)} ÷ 52 weeks`);

setHint("yearlyHint", `→ Monthly: ${formatCurrency(monthly)} · Weekly: $


{formatCurrency(weekly)}`);

} else if (monthlyDrivesWeek) {

setAutoField("monthly", false, "");

setAutoField("weekly", true, `Auto: ${formatCurrency(monthly)} ÷ 4.33 weeks`);

setHint("monthlyHint", `→ Weekly: ${formatCurrency(weekly)}`);

} else {

setAutoField("monthly", false, "");

setAutoField("weekly", false, "");

setHint("yearlyHint", "");

setHint("monthlyHint", "");

setHint("weeklyHint", "");

// ─── BUDGET OVERVIEW ─────────────────────────────────────────────────────────


function renderBudgetOverview() {

const weekSpent = getCurrentWeekTotal();

const monthSpent = sumExpenses(getCurrentMonthExpenses());

const yearSpent = getCurrentYearTotal();

const wb = Number([Link] || 0);

const mb = Number([Link] || 0);

const yb = Number([Link] || 0);

const set = (id, val) => { const el = [Link](id); if (el) [Link] = val; };

set("weeklyBudgetStatus", `${formatCurrency(weekSpent)} / ${formatCurrency(wb)}`);

set("monthlyBudgetStatus", `${formatCurrency(monthSpent)} / ${formatCurrency(mb)}`);

set("yearlyBudgetStatus", `${formatCurrency(yearSpent)} / ${formatCurrency(yb)}`);

set("weeklyBudgetHint", buildBudgetHint(weekSpent, wb));

set("monthlyBudgetHint", buildBudgetHint(monthSpent, mb));

set("yearlyBudgetHint", buildBudgetHint(yearSpent, yb));

//
════════════════════════════════════════════════════════════
═══════════════

// CHARTS

//
════════════════════════════════════════════════════════════
═══════════════

function renderCharts() {

renderTodayChart();

renderMonthChart();
renderYearChart();

/* ── Shared: destroy old instance ── */

function destroyChart(key) {

if (charts[key]) { charts[key].destroy(); charts[key] = null; }

/* ── Shared: show/hide empty state ── */

function setChartEmpty(canvasId, emptyId, isEmpty) {

const canvas = [Link](canvasId);

const empty = [Link](emptyId);

if (!canvas || !empty) return;

[Link] = isEmpty ? "none" : "block";

[Link]("hidden", !isEmpty);

/* ── TODAY: doughnut chart by category ── */

function renderTodayChart() {

destroyChart("today");

const today = new Date().toISOString().split("T")[0];

const todayExpenses = [Link]((e) => [Link] === today);

if (![Link]) {

setChartEmpty("todayChart", "todayEmpty", true);

[Link]("todayLegend").innerHTML = "";

return;

}
setChartEmpty("todayChart", "todayEmpty", false);

// Aggregate by category

const catMap = {};

[Link]((e) => {

if (!catMap[[Link]]) {

catMap[[Link]] = { total: 0, color: [Link], icon: [Link] };

catMap[[Link]].total += [Link];

});

const labels = [Link](catMap);

const data = [Link]((l) => catMap[l].total);

const bgColors = [Link]((l) => CHART_PALETTE[catMap[l].color] || CHART_PALETTE.gray);

const totalToday = [Link]((a, b) => a + b, 0);

const ctx = [Link]("todayChart");

[Link] = new Chart(ctx, {

type: "doughnut",

data: {

labels,

datasets: [{

data,

backgroundColor: bgColors,

borderWidth: 3,

borderColor: "#ffffff",

hoverOffset: 8

}]

},
options: {

responsive: true,

maintainAspectRatio: true,

cutout: "65%",

plugins: {

legend: { display: false },

tooltip: {

callbacks: {

label: (ctx) => ` ${formatCurrency([Link])} (${(([Link] / totalToday) * 100).toFixed(1)}%)`

});

// Custom legend

const legend = [Link]("todayLegend");

[Link] = [Link]((label, i) => `

<div class="legend-item">

<span class="legend-dot" style="background:${bgColors[i]}"></span>

<span class="legend-label">${escapeHtml(label)}</span>

<span class="legend-value">${formatCurrency(data[i])}</span>

</div>

`).join("");

/* ── MONTH: bar chart — daily totals for current month ── */

function renderMonthChart() {

destroyChart("month");
const now = new Date();

const year = [Link]();

const month = [Link]();

const daysInMonth = new Date(year, month + 1, 0).getDate();

const dailyTotals = Array(daysInMonth).fill(0);

[Link]((e) => {

const d = new Date(`${[Link]}T00:00:00`);

if ([Link]() === year && [Link]() === month) {

dailyTotals[[Link]() - 1] += [Link];

});

const hasData = [Link]((v) => v > 0);

if (!hasData) { setChartEmpty("monthChart", "monthEmpty", true); return; }

setChartEmpty("monthChart", "monthEmpty", false);

const labels = [Link]({ length: daysInMonth }, (_, i) => String(i + 1));

const ctx = [Link]("monthChart");

[Link] = new Chart(ctx, {

type: "bar",

data: {

labels,

datasets: [{

label: "Daily Spending",

data: dailyTotals,

backgroundColor: "rgba(37, 99, 235, 0.75)",


borderRadius: 6,

borderSkipped: false

}]

},

options: {

responsive: true,

maintainAspectRatio: true,

plugins: {

legend: { display: false },

tooltip: {

callbacks: {

title: (items) => `Day ${items[0].label}`,

label: (ctx) => ` ${formatCurrency([Link].y)}`

},

scales: {

x: {

grid: { display: false },

ticks: { font: { size: 11 } }

},

y: {

grid: { color: "rgba(0,0,0,0.05)" },

ticks: {

callback: (v) => formatCurrency(v),

font: { size: 11 }

}
}

});

/* ── YEAR: bar chart — monthly totals for current year ── */

function renderYearChart() {

destroyChart("year");

const year = new Date().getFullYear();

const monthlyTotals = Array(12).fill(0);

[Link]((e) => {

const d = new Date(`${[Link]}T00:00:00`);

if ([Link]() === year) monthlyTotals[[Link]()] += [Link];

});

const hasData = [Link]((v) => v > 0);

if (!hasData) { setChartEmpty("yearChart", "yearEmpty", true); return; }

setChartEmpty("yearChart", "yearEmpty", false);

const ctx = [Link]("yearChart");

[Link] = new Chart(ctx, {

type: "bar",

data: {

labels: MONTH_NAMES,

datasets: [{

label: "Monthly Spending",

data: monthlyTotals,

backgroundColor: [Link]((v) =>


v === [Link](...monthlyTotals)

? "rgba(37, 99, 235, 0.85)"

: "rgba(37, 99, 235, 0.4)"

),

borderRadius: 8,

borderSkipped: false

}]

},

options: {

responsive: true,

maintainAspectRatio: true,

plugins: {

legend: { display: false },

tooltip: {

callbacks: {

label: (ctx) => ` ${formatCurrency([Link].y)}`

},

scales: {

x: { grid: { display: false }, ticks: { font: { size: 12 } } },

y: {

grid: { color: "rgba(0,0,0,0.05)" },

ticks: { callback: (v) => formatCurrency(v), font: { size: 11 } }

});

}
// ─── CATEGORY BUDGET ALLOCATION (NEW) ────────────────────────────────────────

function setupAllocTabs() {

[Link](".alloc-tab").forEach((btn) => {

[Link]("click", () => {

[Link](".alloc-tab").forEach((b) => [Link]("active"));

[Link]("active");

renderCategoryBudgetAllocation();

});

});

function renderCategoryBudgetAllocation() {

const container = [Link]("categoryAllocationList");

if (!container) return;

const numCats = [Link];

if (!numCats) {

[Link] = `<p class="alloc-empty">No categories found.</p>`;

return;

// Determine active period tab

const activeTab = [Link](".[Link]");

const period = activeTab?.[Link] || "monthly";

const budgetMap = {

weekly: [Link],
monthly: [Link],

yearly: [Link]

};

const budget = Number(budgetMap[period] || 0);

if (!budget || budget <= 0) {

[Link] = `<p class="alloc-empty">Set a ${period} budget in Settings → Budget Settings


to see per-category allocation.</p>`;

return;

const perCategory = budget / numCats;

const pct = (100 / numCats).toFixed(1);

// Also compute how much each category has actually been spent this period

const spentMap = {};

[Link]((cat) => { spentMap[[Link]] = 0; });

const now = new Date();

[Link]((e) => {

const d = new Date(`${[Link]}T00:00:00`);

let inPeriod = false;

if (period === "weekly") {

const diff = [Link]() === 0 ? 6 : [Link]() - 1;

const startOfWeek = new Date(now);

[Link](0, 0, 0, 0);
[Link]([Link]() - diff);

const endOfWeek = new Date(startOfWeek);

[Link]([Link]() + 7);

inPeriod = d >= startOfWeek && d < endOfWeek;

} else if (period === "monthly") {

inPeriod = [Link]() === [Link]() && [Link]() === [Link]();

} else if (period === "yearly") {

inPeriod = [Link]() === [Link]();

if (inPeriod && [Link]([Link])) {

spentMap[[Link]] += [Link];

});

const periodLabel = { weekly: "this week", monthly: "this month", yearly: "this year" }[period];

[Link] = [Link]((cat) => {

const spent = spentMap[[Link]] || 0;

const remaining = perCategory - spent;

const usedPct = [Link]((spent / perCategory) * 100, 100);

const isOver = spent > perCategory;

const barColor = isOver

? "#ef4444"

: usedPct > 80

? "#f59e0b"

: "var(--primary)";
const hintText = isOver

? `Over by ${formatCurrency([Link](remaining))}`

: `${formatCurrency(remaining)} left`;

return `

<div class="alloc-item">

<div class="alloc-left">

<div class="icon-circle ${[Link] || "gray"}">${[Link]}</div>

<div class="alloc-meta">

<span class="alloc-name">${escapeHtml([Link])}</span>

<span class="alloc-hint ${isOver ? "over" : ""}">${hintText} ${periodLabel}</span>

</div>

</div>

<div class="alloc-right">

<div class="alloc-bar-track">

<div class="alloc-bar-fill" style="width:${[Link](1)}%; background:${barColor};"></div>

</div>

<div class="alloc-numbers">

<span class="alloc-spent">${formatCurrency(spent)}</span>

<span class="alloc-sep">/</span>

<span class="alloc-cap">${formatCurrency(perCategory)}</span>

<span class="alloc-pct-badge">${pct}%</span>

</div>

</div>

</div>

`;

}).join("");

}
// ─── HELPERS ─────────────────────────────────────────────────────────────────

function setTodayDate() {

[Link]("expenseDate").value = new Date().toISOString().split("T")[0];

function sumExpenses(expenses) {

return [Link]((sum, e) => sum + Number([Link] || 0), 0);

function getCurrentMonthExpenses() {

const now = new Date();

return [Link]((e) => {

const d = new Date(`${[Link]}T00:00:00`);

return [Link]() === [Link]() && [Link]() === [Link]();

});

function getCurrentWeekTotal() {

const now = new Date();

const diff = [Link]() === 0 ? 6 : [Link]() - 1;

const startOfWeek = new Date(now);

[Link](0, 0, 0, 0);

[Link]([Link]() - diff);

const endOfWeek = new Date(startOfWeek);

[Link]([Link]() + 7);

return [Link]((sum, e) => {

const d = new Date(`${[Link]}T00:00:00`);

return (d >= startOfWeek && d < endOfWeek) ? sum + Number([Link] || 0) : sum;


}, 0);

function getCurrentYearTotal() {

const year = new Date().getFullYear();

return [Link]((sum, e) => {

return new Date(`${[Link]}T00:00:00`).getFullYear() === year

? sum + Number([Link] || 0) : sum;

}, 0);

function buildBudgetHint(spent, budget) {

if (!budget || budget <= 0) return "No budget set";

const rem = budget - spent;

return rem >= 0

? `Remaining: ${formatCurrency(rem)}`

: `Over budget by ${formatCurrency([Link](rem))}`;

function formatCurrency(amount) {

const currency = [Link]?.currency || "PHP";

try {

return new [Link]("en-PH", { style: "currency", currency }).format(amount || 0);

} catch {

return `${currency} ${Number(amount || 0).toFixed(2)}`;

function formatDisplayDate(dateString) {
return new Date(`${dateString}T00:00:00`).toLocaleDateString("en-PH", {

year: "numeric", month: "short", day: "numeric"

});

function escapeHtml(text) {

const div = [Link]("div");

[Link] = text;

return [Link];

You might also like