- Move all JavaScript code from index.html to app.js. - Update index.html to include a script tag for app.js.
394 lines
22 KiB
HTML
394 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>SimpleLedger - Your Personal Bookkeeper</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
|
|
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
|
|
|
|
<style>
|
|
body { font-family: 'Inter', sans-serif; }
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
.spinner {
|
|
border: 4px solid rgba(0, 0, 0, .1);
|
|
border-left-color: #2563eb;
|
|
border-radius: 50%;
|
|
width: 40px;
|
|
height: 40px;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
/* Modal base */
|
|
.modal {
|
|
transition: opacity 0.25s ease;
|
|
}
|
|
.modal-content {
|
|
transition: transform 0.25s ease;
|
|
}
|
|
/* Sortable table header */
|
|
.sortable-header {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.sortable-header:hover {
|
|
background-color: #f9fafb;
|
|
}
|
|
.sortable-header.sort-asc::after {
|
|
content: ' ▲';
|
|
font-size: 0.8em;
|
|
}
|
|
.sortable-header.sort-desc::after {
|
|
content: ' ▼';
|
|
font-size: 0.8em;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-100">
|
|
|
|
<div id="authView" class="flex items-center justify-center min-h-screen">
|
|
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-sm">
|
|
<h2 id="authTitle" class="text-2xl font-bold mb-6 text-center">Login</h2>
|
|
<div id="authError" class="text-red-500 text-sm mb-4 hidden"></div>
|
|
|
|
<form id="loginForm">
|
|
<div class="mb-4">
|
|
<label for="loginUsername" class="block text-sm font-medium text-gray-700">Username</label>
|
|
<input type="text" id="loginUsername" name="username" required class="mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<div class="mb-6">
|
|
<label for="loginPassword" class="block text-sm font-medium text-gray-700">Password</label>
|
|
<input type="password" id="loginPassword" name="password" required class="mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-blue-700">
|
|
Login
|
|
</button>
|
|
</form>
|
|
|
|
<form id="registerForm" class="hidden">
|
|
<div class="mb-4">
|
|
<label for="regUsername" class="block text-sm font-medium text-gray-700">Username</label>
|
|
<input type="text" id="regUsername" name="username" required class="mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<div class="mb-6">
|
|
<label for="regPassword" class="block text-sm font-medium text-gray-700">Password</label>
|
|
<input type="password" id="regPassword" name="password" required class="mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<button type="submit" class="w-full bg-green-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-green-700">
|
|
Register
|
|
</button>
|
|
</form>
|
|
|
|
<p class="text-center text-sm text-gray-600 mt-6">
|
|
<a href="#" id="toggleAuth" class="text-blue-600 hover:underline">Need an account? Register</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="loadingModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div class="bg-white p-6 rounded-lg shadow-xl flex items-center">
|
|
<div class="spinner mr-4"></div>
|
|
<span id="loadingModalText" class="text-lg">Loading...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="editTxModal" class="modal hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-40">
|
|
<div class="modal-content bg-white p-6 rounded-lg shadow-xl w-full max-w-3xl">
|
|
<input type="hidden" id="splitTxId">
|
|
<input type="hidden" id="splitTxTotal">
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
<div>
|
|
<h3 class="text-xl font-semibold" id="splitModalTitle">Split Transaction</h3>
|
|
<p class="text-sm text-gray-600" id="splitModalDescription"></p>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-lg font-bold" id="splitModalTotal"></div>
|
|
<div class="text-sm font-medium" id="splitModalRemaining"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="splitsContainer" class="max-h-96 overflow-y-auto space-y-3 p-2 bg-gray-50 rounded">
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center mt-6">
|
|
<div>
|
|
<button type="button" id="addSplitBtn" class="bg-green-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-green-700">
|
|
Add Split
|
|
</button>
|
|
<button type="button" id="createRuleFromSplit" class="bg-indigo-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-indigo-700">
|
|
Create Rule
|
|
</button>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<button type="button" id="closeTxModalBtn" class="bg-gray-300 text-gray-800 py-2 px-4 rounded-lg font-semibold hover:bg-gray-400">
|
|
Cancel
|
|
</button>
|
|
<button type="button" id="saveSplitsBtn" class="bg-blue-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-blue-700 disabled:opacity-50">
|
|
Save Changes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="appContainer" class="hidden relative min-h-screen md:flex">
|
|
<!-- Mobile menu button -->
|
|
<div class="md:hidden flex justify-between items-center bg-blue-800 text-white p-4">
|
|
<h1 class="text-2xl font-bold">SimpleLedger</h1>
|
|
<button id="mobileMenuBtn">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<nav id="sidebar" class="hidden md:flex w-64 bg-blue-800 text-white flex-col p-4 absolute inset-y-0 left-0 z-30 md:relative md:translate-x-0 transform -translate-x-full transition-transform duration-200 ease-in-out">
|
|
<h1 class="text-2xl font-bold mb-6">SimpleLedger</h1>
|
|
<a href="#" data-view="dashboard" class="block p-3 rounded-lg text-blue-100 hover:bg-blue-600">Dashboard</a>
|
|
<a href="#" data-view="transactions" class="block p-3 rounded-lg text-blue-100 hover:bg-blue-600">Transactions</a>
|
|
<a href="#" data-view="upload" class="block p-3 rounded-lg text-blue-100 hover:bg-blue-600">Upload</a>
|
|
<a href="#" data-view="categories" class="block p-3 rounded-lg text-blue-100 hover:bg-blue-600">Categories</a>
|
|
<a href="#" data-view="vendors" class="block p-3 rounded-lg text-blue-100 hover:bg-blue-600">Vendors</a>
|
|
<a href="#" data-view="rules" class="block p-3 rounded-lg text-blue-100 hover:bg-blue-600">Rules</a>
|
|
<a href="#" data-view="reports" class="block p-3 rounded-lg text-blue-100 hover:bg-blue-600">Reports</a>
|
|
<div class="mt-auto">
|
|
<a href="#" id="logoutButton" class="p-3 rounded-lg text-blue-100 hover:bg-blue-600 block">Logout</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<main class="flex-1 p-4 md:p-8 overflow-y-auto">
|
|
|
|
<section id="dashboard">
|
|
<h2 class="text-3xl font-bold mb-6">Dashboard</h2>
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-5 gap-6 mb-6">
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-sm font-medium text-gray-500">Gross Income</h3>
|
|
<p id="statGrossIncome" class="text-2xl font-semibold text-green-600">$0.00</p>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-sm font-medium text-gray-500">Returns & Discounts</h3>
|
|
<p id="statReturns" class="text-2xl font-semibold text-yellow-600">$0.00</p>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-sm font-medium text-gray-500">Net Income</h3>
|
|
<p id="statNetIncome" class="text-2xl font-semibold text-green-700">$0.00</p>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-sm font-medium text-gray-500">Total Expenses</h3>
|
|
<p id="statTotalExpenses" class="text-2xl font-semibold text-red-600">$0.00</p>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-sm font-medium text-gray-500">Net Profit / (Loss)</h3>
|
|
<p id="statNetProfit" class="text-2xl font-semibold">$0.00</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white p-6 rounded-lg shadow-md h-96">
|
|
<h3 class="text-xl font-semibold mb-4">Top 10 Expenses</h3>
|
|
<div class="relative h-full w-full max-h-[300px]">
|
|
<canvas id="expensePieChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="transactions" class="hidden">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-3xl font-bold">Transactions</h2>
|
|
<div class="flex items-end space-x-4">
|
|
<div>
|
|
<label for="dateFilterSelect" class="text-sm font-medium text-gray-700">Date Range</label>
|
|
<select id="dateFilterSelect" class="p-2 border rounded-md shadow-sm">
|
|
<option value="all">All Time</option>
|
|
<option value="lastMonth" selected>Last Month</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
</div>
|
|
<div id="customDateRangePicker" class="hidden items-end space-x-4">
|
|
<div>
|
|
<label for="startDate" class="text-sm font-medium text-gray-700">Start Date</label>
|
|
<input type="date" id="startDate" class="p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<div>
|
|
<label for="endDate" class="text-sm font-medium text-gray-700">End Date</label>
|
|
<input type="date" id="endDate" class="p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
</div>
|
|
<button id="filterByDateBtn" class="p-2 bg-blue-600 text-white rounded-md shadow-sm">Apply</button>
|
|
<div class="flex-grow"></div>
|
|
<div>
|
|
<label for="transactionSearch" class="text-sm font-medium text-gray-700">Search</label>
|
|
<input type="search" id="transactionSearch" placeholder="Descriptions..." class="p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<div>
|
|
<label for="transactionFilter" class="text-sm font-medium text-gray-700">Status</label>
|
|
<select id="transactionFilter" class="p-2 border rounded-md shadow-sm">
|
|
<option value="all">All</option>
|
|
<option value="uncategorized">Uncategorized</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white p-4 rounded-lg shadow-md overflow-x-auto">
|
|
<table class="w-full">
|
|
<thead class="border-b">
|
|
<tr>
|
|
<th class="p-3 text-left sortable-header" data-sort="date">Date</th>
|
|
<th class="p-3 text-left sortable-header" data-sort="description">Description</th>
|
|
<th class="p-3 text-left sortable-header" data-sort="category">Category</th>
|
|
<th class="p-3 text-left sortable-header" data-sort="vendorName">Vendor</th>
|
|
<th class="p-3 text-right sortable-header" data-sort="amount">Amount</th>
|
|
<th class="p-3 text-center">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="transactionsTableBody">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="upload" class="hidden">
|
|
<h2 class="text-3xl font-bold mb-6">Upload Files</h2>
|
|
<form id="uploadForm" class="bg-white p-6 rounded-lg shadow-md max-w-lg">
|
|
<div class="mb-4">
|
|
<label for="bankFile" class="block text-sm font-medium text-gray-700 mb-2">1. Bank Transactions CSV</label>
|
|
<p class="text-xs text-gray-500 mb-2">(e.g., your `transactions.csv` file. Must have 'Date', 'Description', 'Debit', 'Credit' columns)</p>
|
|
<input type="file" id="bankFile" accept=".csv" class="w-full p-2 border rounded-md">
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<label for="salesFile" class="block text-sm font-medium text-gray-700 mb-2">2. Sales Summary CSV</label>
|
|
<p class="text-xs text-gray-500 mb-2">(e.g., your `sales-summary.csv` file. Used to find 'Net sales' and 'Square payment processing fees')</p>
|
|
<input type="file" id="salesFile" accept=".csv" class="w-full p-2 border rounded-md">
|
|
</div>
|
|
|
|
<button type="submit" class="w-full bg-blue-600 text-white py-3 px-4 rounded-lg font-semibold hover:bg-blue-700">
|
|
Process Files
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<section id="categories" class="hidden">
|
|
<h2 class="text-3xl font-bold mb-6">Manage Categories</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-xl font-semibold mb-4">Add New Category</h3>
|
|
<form id="categoryForm">
|
|
<div class="mb-4">
|
|
<label for="categoryName" class="block text-sm font-medium text-gray-700">Name</label>
|
|
<input type="text" id="categoryName" name="categoryName" required class="mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="categoryType" class="block text-sm font-medium text-gray-700">Type</label>
|
|
<select id="categoryType" name="categoryType" class="mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
<option value="income">Income</option>
|
|
<option value="expense">Expense</option>
|
|
<option value="liability">Liability</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-blue-700">
|
|
Add Category
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-xl font-semibold mb-4">Existing Categories</h3>
|
|
<div id="categoryList" class="space-y-2 max-h-96 overflow-y-auto">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="vendors" class="hidden">
|
|
<h2 class="text-3xl font-bold mb-6">Manage Vendors</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-xl font-semibold mb-4">Add New Vendor</h3>
|
|
<form id="vendorForm">
|
|
<div class="mb-4">
|
|
<label for="vendorName" class="block text-sm font-medium text-gray-700">Name</label>
|
|
<input type="text" id="vendorName" name="vendorName" required class="mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-blue-700">
|
|
Add Vendor
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-xl font-semibold mb-4">Existing Vendors</h3>
|
|
<div id="vendorList" class="space-y-2 max-h-96 overflow-y-auto">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="rules" class="hidden">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-3xl font-bold">Categorization Rules</h2>
|
|
<button id="applyAllRulesBtn" class="bg-green-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-green-700">
|
|
Apply Rules to Uncategorized
|
|
</button>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-xl font-semibold mb-4">Add New Rule</h3>
|
|
<form id="ruleForm">
|
|
<div class="mb-4">
|
|
<label for="ruleKeyword" class="block text-sm font-medium text-gray-700">If 'Description' contains:</label>
|
|
<input type="text" id="ruleKeyword" name="ruleKeyword" placeholder="e.g., ADP PAYROLL" required class="mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label for="ruleCategory" class="block text-sm font-medium text-gray-700">Set Category to:</label>
|
|
<select id="ruleCategory" name="ruleCategory" class="category-select mt-1 w-full p-2 border rounded-md shadow-sm">
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-blue-700">
|
|
Add Rule
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-xl font-semibold mb-4">Existing Rules</h3>
|
|
<div id="rulesList" class="space-y-2 max-h-96 overflow-y-auto">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="reports" class="hidden">
|
|
<h2 class="text-3xl font-bold mb-6">Reports</h2>
|
|
<div class="bg-white p-6 rounded-lg shadow-md">
|
|
<h3 class="text-xl font-semibold mb-4">Profit & Loss Statement</h3>
|
|
<div class="flex items-center space-x-4 mb-4">
|
|
<div>
|
|
<label for="reportStartDate" class="block text-sm font-medium text-gray-700">Start Date</label>
|
|
<input type="date" id="reportStartDate" class="mt-1 p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<div>
|
|
<label for="reportEndDate" class="block text-sm font-medium text-gray-700">End Date</label>
|
|
<input type="date" id="reportEndDate" class="mt-1 p-2 border rounded-md shadow-sm">
|
|
</div>
|
|
<button id="generateReportBtn" class="self-end bg-blue-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-blue-700">
|
|
Generate
|
|
</button>
|
|
<button id="exportReportBtn" class="hidden self-end bg-green-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-green-700">
|
|
Export to CSV
|
|
</button>
|
|
</div>
|
|
|
|
<table class="w-full max-w-2xl mt-6">
|
|
<tbody id="plReportBody">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
</div>
|
|
|
|
<script src="app.js"></script>
|
|
|
|
</body>
|
|
</html> |