mirror of
https://github.com/Solo-Web-Works/BillTrak.git
synced 2026-01-29 09:50:34 +00:00
✨feature: Basic features complete
This commit is contained in:
105
includes/api.php
105
includes/api.php
@@ -1,10 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
require_once './bill.php';
|
require_once './bill.php';
|
||||||
require_once './db.php';
|
require_once './db.php';
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
class InvalidActionException extends Exception {}
|
class InvalidActionException extends Exception {}
|
||||||
|
class MissingRequiredException extends Exception {}
|
||||||
|
class UpdateException extends Exception {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$action = $_GET['action'] ?? '';
|
$action = $_GET['action'] ?? '';
|
||||||
@@ -12,24 +18,111 @@ try {
|
|||||||
switch($action) {
|
switch($action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
$data = [
|
$data = [
|
||||||
'date' => $_POST['date'],
|
'date' => (new DateTime($_POST['date']))->format('Y-m-d'), // Normalize to YYYY-MM-DD
|
||||||
'billName' => $_POST['billName'],
|
'billName' => $_POST['billName'],
|
||||||
'amount' => (float)$_POST['amount'],
|
'amount' => (float)$_POST['amount'],
|
||||||
'paymentId' => $_POST['paymentId'],
|
'paymentId' => $_POST['paymentId'],
|
||||||
'year' => (int)explode('-', $_POST['date'])[0]
|
'year' => (int)explode('-', $_POST['date'])[0],
|
||||||
|
'comment' => $_POST['comment']
|
||||||
];
|
];
|
||||||
|
|
||||||
echo json_encode(['success' => Bill::add($data)]);
|
$result = Bill::add($data);
|
||||||
|
echo json_encode(['success' => $result]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit':
|
||||||
|
file_put_contents('debug.log', print_r($_POST, true), FILE_APPEND);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = [
|
||||||
|
'id' => $_POST['id'],
|
||||||
|
'date' => (new DateTime($_POST['date']))->format('Y-m-d'),
|
||||||
|
'billName' => $_POST['billName'],
|
||||||
|
'amount' => (float)$_POST['amount'],
|
||||||
|
'paymentId' => $_POST['paymentId'],
|
||||||
|
'comment' => $_POST['comment'] ?? ''
|
||||||
|
];
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (empty($data['id']) || empty($data['date']) || empty($data['billName']) || $data['amount'] <= 0) {
|
||||||
|
throw new MissingRequiredException('Missing required fields.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare and execute the update statement
|
||||||
|
$stmt = DB::connect()->prepare("
|
||||||
|
UPDATE bills
|
||||||
|
SET billDate = ?, billName = ?, amount = ?, paymentId = ?, comment = ?
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$result = $stmt->execute([
|
||||||
|
$data['date'],
|
||||||
|
$data['billName'],
|
||||||
|
$data['amount'],
|
||||||
|
$data['paymentId'],
|
||||||
|
$data['comment'],
|
||||||
|
$data['id']
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
throw new UpdateException('Failed to update the database.');
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'getPayees':
|
||||||
|
$stmt = DB::connect()->query("SELECT DISTINCT billName FROM bills ORDER BY billName ASC");
|
||||||
|
echo json_encode($stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'addPayee':
|
||||||
|
$stmt = DB::connect()->prepare("INSERT INTO bills (billName) VALUES (?)");
|
||||||
|
$result = $stmt->execute([$_POST['billName']]);
|
||||||
|
echo json_encode(['success' => $result]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'getAll':
|
case 'getAll':
|
||||||
echo json_encode(Bill::getAll()->fetchAll(PDO::FETCH_ASSOC));
|
echo json_encode(Bill::getAll()->fetchAll(PDO::FETCH_ASSOC));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'getTotals':
|
case 'getById':
|
||||||
|
$id = $_GET['id'];
|
||||||
|
$stmt = DB::connect()->prepare("SELECT * FROM bills WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
echo json_encode($stmt->fetch(PDO::FETCH_ASSOC));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'getTotals':
|
||||||
echo json_encode(Bill::getYearlyTotals()->fetchAll(PDO::FETCH_ASSOC));
|
echo json_encode(Bill::getYearlyTotals()->fetchAll(PDO::FETCH_ASSOC));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'getByYear':
|
||||||
|
$year = $_GET['year'] ?? date('Y');
|
||||||
|
$sort = $_GET['sort'] ?? 'date_asc'; // Default sorting
|
||||||
|
|
||||||
|
$sortOptions = [
|
||||||
|
'date_asc' => 'billDate ASC',
|
||||||
|
'date_desc' => 'billDate DESC',
|
||||||
|
'payee' => 'billName ASC, billDate ASC',
|
||||||
|
];
|
||||||
|
|
||||||
|
$orderBy = $sortOptions[$sort] ?? $sortOptions['date_asc'];
|
||||||
|
|
||||||
|
$stmt = DB::connect()->prepare("SELECT * FROM bills WHERE year = ? ORDER BY $orderBy");
|
||||||
|
$stmt->execute([$year]);
|
||||||
|
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'getYears':
|
||||||
|
$stmt = DB::connect()->query("SELECT DISTINCT year FROM bills ORDER BY year DESC");
|
||||||
|
echo json_encode($stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new InvalidActionException('Invalid action');
|
throw new InvalidActionException('Invalid action');
|
||||||
}
|
}
|
||||||
|
|||||||
137
index.php
137
index.php
@@ -9,61 +9,128 @@
|
|||||||
<link href="css/style.css" rel="stylesheet">
|
<link href="css/style.css" rel="stylesheet">
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script src="app.js" defer></script>
|
<script src="js/app.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-gray-100">
|
<body class="bg-gray-100">
|
||||||
<div class="container mx-auto p-4">
|
<div class="container mx-auto p-4">
|
||||||
<h1 class="text-2xl font-bold mb-4">Bill Payments</h1>
|
<h1 class="text-4xl font-bold mb-4">Bill Tracker</h1>
|
||||||
|
|
||||||
<!-- Add Bill Form -->
|
<!-- Add Bill Form -->
|
||||||
<form id="billForm" class="bg-white p-4 mb-6 rounded shadow">
|
<form id="billForm" class="bg-white p-4 mb-6 rounded shadow">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<h2 class="text-2xl font-bold mb-4">Bill Payments</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||||
<input type="date" name="date" required class="p-2 border rounded">
|
<input type="date" name="date" required class="p-2 border rounded">
|
||||||
<select name="billName" required class="p-2 border rounded">
|
|
||||||
<option value="MTS">MTS</option>
|
<select required id="billName" name="billName" class="border rounded p-2">
|
||||||
<option value="Hydro">Hydro</option>
|
<option value="" disabled selected>Select Payee</option>
|
||||||
<option value="Shaw">Shaw</option>
|
|
||||||
<option value="Telus">Telus</option>
|
|
||||||
<option value="Water">Water</option>
|
|
||||||
<option value="PC Mastercard">PC Mastercard</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<input type="number" step="0.01" name="amount" placeholder="Amount" required class="p-2 border rounded">
|
<input type="number" step="0.01" name="amount" placeholder="Amount" required class="p-2 border rounded">
|
||||||
<input type="text" name="paymentId" placeholder="Payment ID" class="p-2 border rounded">
|
<input type="text" name="paymentId" placeholder="Payment ID" class="p-2 border rounded">
|
||||||
|
|
||||||
|
<input type="text" name="comment" class="p-2 border rounded" placeholder="Add a comment (optional)"></input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="mt-4 bg-blue-500 text-white px-4 py-2 rounded">Add Bill</button>
|
<button type="submit" class="mt-4 bg-blue-500 text-white px-4 py-2 rounded">Add Bill</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Chart -->
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 bg-white p-4 mb-6 rounded shadow">
|
||||||
<div class="bg-white p-4 mb-6 rounded shadow">
|
<!-- Add Payee Form -->
|
||||||
<canvas id="chart"></canvas>
|
<form id="addPayeeForm" class="border p-4 rounded shadow bg-gray-100">
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Add New Payee</h2>
|
||||||
|
|
||||||
|
<input type="text" id="newPayeeName" name="billName" class="bg-white border rounded p-2 w-5/6" placeholder="New Payee Name">
|
||||||
|
|
||||||
|
<button type="submit" class="mt-4 bg-blue-500 text-white px-4 py-2 rounded">Add Payee</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Chart -->
|
||||||
|
<div class="border p-4 rounded shadow bg-gray-100">
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Chart</h2>
|
||||||
|
<canvas id="chart"></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bills Table -->
|
<!-- Bill List -->
|
||||||
<div class="bg-white rounded shadow overflow-hidden">
|
<div class="bg-white p-4 shodow rounded">
|
||||||
<table class="w-full">
|
<div class="flex justify-between">
|
||||||
<thead class="bg-gray-50">
|
<h2 class="text-2xl font-bold mb-4">Payments</h2>
|
||||||
<tr>
|
|
||||||
<th class="px-4 py-2">Date</th>
|
|
||||||
<th class="px-4 py-2">Bill</th>
|
|
||||||
<th class="px-4 py-2">Amount</th>
|
|
||||||
<th class="px-4 py-2">Payment ID</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody id="billList">
|
<div class="ml-auto flex items-center gap-4 mb-4 w-fit">
|
||||||
<?php foreach (Bill::getAll() as $bill): ?>
|
<div class="">
|
||||||
<tr class="border-t">
|
<label for="sortSelect" class="block mb-2 font-bold">Sort By:</label>
|
||||||
<td class="px-4 py-2"><?php echo date('Y-m-d', strtotime($bill['billDate'])) ?></td>
|
<select id="sortSelect" class="border rounded px-3 py-2 bg-gray-50">
|
||||||
<td class="px-4 py-2"><?php echo htmlspecialchars($bill['billName']) ?></td>
|
<option value="date_desc">Date (Descending)</option>
|
||||||
<td class="px-4 py-2">$<?php echo number_format($bill['amount'], 2) ?></td>
|
<option value="date_asc">Date (Ascending)</option>
|
||||||
<td class="px-4 py-2"><?php echo $bill['paymentId'] ?></td>
|
<option value="payee">Payee (Sorted by Date)</option>
|
||||||
</tr>
|
</select>
|
||||||
<?php endforeach; ?>
|
</div>
|
||||||
</tbody>
|
|
||||||
</table>
|
<div class="">
|
||||||
|
<label for="yearSelect" class="block mb-2 font-bold">Select Year:</label>
|
||||||
|
<select id="yearSelect" class="border rounded px-3 py-2 bg-gray-50">
|
||||||
|
<option value="2025">2025</option>
|
||||||
|
<option value="2024">2024</option>
|
||||||
|
<option value="2023">2023</option>
|
||||||
|
<option value="2022">2022</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="billList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div id="editModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
|
||||||
|
<div class="bg-white p-6 rounded-lg shadow-lg w-96">
|
||||||
|
<h2 class="text-xl font-bold mb-4">Edit Payment</h2>
|
||||||
|
<form id="editForm" class="space-y-4">
|
||||||
|
<!-- Hidden input for the payment ID -->
|
||||||
|
<input type="hidden" id="editFormId" name="id">
|
||||||
|
|
||||||
|
<!-- Date -->
|
||||||
|
<div>
|
||||||
|
<label for="editFormDate" class="block text-sm font-medium">Date</label>
|
||||||
|
<input type="date" id="editFormDate" name="date" class="border rounded px-3 py-2 w-full">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bill Name -->
|
||||||
|
<div>
|
||||||
|
<label for="editFormBillName" class="block text-sm font-medium">Payee</label>
|
||||||
|
<select id="editFormBillName" name="billName" class="border rounded px-3 py-2 w-full">
|
||||||
|
<option value="" disabled>Select Payee</option>
|
||||||
|
<!-- Options will be populated dynamically -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Amount -->
|
||||||
|
<div>
|
||||||
|
<label for="editFormAmount" class="block text-sm font-medium">Amount</label>
|
||||||
|
<input type="number" id="editFormAmount" name="amount" step="0.01" class="border rounded px-3 py-2 w-full">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Payment ID -->
|
||||||
|
<div>
|
||||||
|
<label for="editFormPaymentId" class="block text-sm font-medium">Payment ID</label>
|
||||||
|
<input type="text" id="editFormPaymentId" name="paymentId" class="border rounded px-3 py-2 w-full">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comment -->
|
||||||
|
<div>
|
||||||
|
<label for="editFormComment" class="block text-sm font-medium">Comment</label>
|
||||||
|
<textarea id="editFormComment" name="comment" class="border rounded px-3 py-2 w-full" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<div class="flex justify-end space-x-2">
|
||||||
|
<button type="button" id="editFormCancel" class="bg-red-300 px-4 py-2 rounded hover:bg-red-400">Cancel</button>
|
||||||
|
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
214
js/app.js
214
js/app.js
@@ -1,6 +1,148 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const form = document.getElementById('billForm');
|
const yearSelect = document.getElementById('yearSelect');
|
||||||
|
const sortSelect = document.getElementById('sortSelect');
|
||||||
const billList = document.getElementById('billList');
|
const billList = document.getElementById('billList');
|
||||||
|
const form = document.getElementById('billForm');
|
||||||
|
|
||||||
|
// Populate the year dropdown
|
||||||
|
async function loadYears() {
|
||||||
|
const response = await fetch('/includes/api.php?action=getYears');
|
||||||
|
const years = await response.json();
|
||||||
|
|
||||||
|
yearSelect.innerHTML = ''; // Clear existing options
|
||||||
|
years.forEach(year => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = year;
|
||||||
|
option.textContent = year;
|
||||||
|
yearSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set default year to the most recent one
|
||||||
|
if (years.length > 0) {
|
||||||
|
yearSelect.value = years[0];
|
||||||
|
loadBills(years[0], sortSelect.value); // Load bills for the most recent year with the default sort order
|
||||||
|
} else {
|
||||||
|
billList.innerHTML = '<p class="text-gray-500">No data available.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch and display payees
|
||||||
|
async function loadPayees() {
|
||||||
|
const response = await fetch('/includes/api.php?action=getPayees');
|
||||||
|
const payees = await response.json();
|
||||||
|
|
||||||
|
const payeeSelect = document.getElementById('editFormBillName');
|
||||||
|
payeeSelect.innerHTML = ''; // Clear existing options
|
||||||
|
|
||||||
|
// Add default "Select Payee" option
|
||||||
|
const defaultOption = document.createElement('option');
|
||||||
|
defaultOption.value = '';
|
||||||
|
defaultOption.textContent = 'Select Payee';
|
||||||
|
defaultOption.disabled = true;
|
||||||
|
payeeSelect.appendChild(defaultOption);
|
||||||
|
|
||||||
|
// Populate payees
|
||||||
|
payees.forEach(payee => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = payee;
|
||||||
|
option.textContent = payee;
|
||||||
|
payeeSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch and display bills
|
||||||
|
async function loadBills(year, sort) {
|
||||||
|
const response = await fetch(`/includes/api.php?action=getByYear&year=${year}&sort=${sort}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
billList.innerHTML = ''; // Clear current bills
|
||||||
|
if (data.length === 0) {
|
||||||
|
billList.innerHTML = '<p class="text-gray-500">No bills found for this year.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.forEach(bill => {
|
||||||
|
const billItem = document.createElement('div');
|
||||||
|
billItem.className = 'mb-2 p-4 border rounded bg-gray-100 shadow-sm relative';
|
||||||
|
|
||||||
|
// Parse the date string as a local date
|
||||||
|
const dateParts = bill.billDate.split('-'); // Split "YYYY-MM-DD"
|
||||||
|
const localDate = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); // Month is 0-indexed
|
||||||
|
|
||||||
|
const formattedDate = new Intl.DateTimeFormat('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
}).format(localDate);
|
||||||
|
|
||||||
|
billItem.innerHTML = `
|
||||||
|
<p><strong>Date:</strong> ${formattedDate}</p>
|
||||||
|
<p><strong>Bill Name:</strong> ${bill.billName}</p>
|
||||||
|
<p><strong>Amount:</strong> $${bill.amount.toFixed(2)}</p>
|
||||||
|
<p><strong>Payment ID:</strong> ${bill.paymentId || 'N/A'}</p>
|
||||||
|
<p><strong>Comment:</strong> ${bill.comment || 'N/A'}</p>
|
||||||
|
<button class="block absolute top-2 right-2 border text-gray-500 hover:text-gray-700 px-2 py-0 rounded" data-id="${bill.id}">Edit</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
billList.appendChild(billItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit a bill
|
||||||
|
async function editBill(id) {
|
||||||
|
// Find the specific bill data by ID
|
||||||
|
const response = await fetch(`/includes/api.php?action=getById&id=${id}`);
|
||||||
|
const bill = await response.json();
|
||||||
|
|
||||||
|
if (!bill) {
|
||||||
|
alert("Error: Could not retrieve the bill data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate modal fields with the bill data
|
||||||
|
document.getElementById('editFormId').value = bill.id;
|
||||||
|
document.getElementById('editFormDate').value = bill.billDate;
|
||||||
|
document.getElementById('editFormAmount').value = bill.amount;
|
||||||
|
document.getElementById('editFormPaymentId').value = bill.paymentId;
|
||||||
|
document.getElementById('editFormComment').value = bill.comment || '';
|
||||||
|
|
||||||
|
// Populate the payee dropdown and select the correct option
|
||||||
|
await loadPayees(); // Ensure the payee dropdown is populated
|
||||||
|
document.getElementById('editFormBillName').value = bill.billName;
|
||||||
|
|
||||||
|
// Open the modal
|
||||||
|
document.getElementById('editModal').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener for year selection
|
||||||
|
yearSelect.addEventListener('change', (e) => {
|
||||||
|
const selectedYear = e.target.value;
|
||||||
|
loadBills(selectedYear, sortSelect.value); // Use the selected sort order
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener for sort selection
|
||||||
|
sortSelect.addEventListener('change', (e) => {
|
||||||
|
const selectedSort = e.target.value;
|
||||||
|
loadBills(yearSelect.value, selectedSort); // Use the selected year
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add new payee
|
||||||
|
document.getElementById('addPayeeForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
const response = await fetch('/includes/api.php?action=addPayee', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
loadPayees(); // Reload payee dropdown
|
||||||
|
e.target.reset(); // Clear the form
|
||||||
|
} else {
|
||||||
|
alert('Failed to add payee. Please try again.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Add new bill
|
// Add new bill
|
||||||
form.addEventListener('submit', async (e) => {
|
form.addEventListener('submit', async (e) => {
|
||||||
@@ -14,45 +156,49 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
form.reset();
|
form.reset();
|
||||||
loadBills();
|
loadYears(); // Reload years in case a new year was added
|
||||||
updateChart();
|
} else {
|
||||||
|
alert('Failed to add bill. Please try again.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load bills via AJAX
|
// Event listener for edit buttons within bill items
|
||||||
async function loadBills() {
|
billList.addEventListener('click', (e) => {
|
||||||
const response = await fetch('/includes/api.php?action=getAll');
|
if (e.target.tagName === 'BUTTON' && e.target.textContent === 'Edit') {
|
||||||
const bills = await response.json();
|
const billId = e.target.getAttribute('data-id');
|
||||||
|
editBill(billId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
billList.innerHTML = bills.map(bill => `
|
// Save edited bill
|
||||||
<tr class="border-t">
|
document.getElementById('editForm').addEventListener('submit', async (e) => {
|
||||||
<td class="px-4 py-2">${new Date(bill.billDate).toISOString().split('T')[0]}</td>
|
e.preventDefault();
|
||||||
<td class="px-4 py-2">${bill.billName}</td>
|
|
||||||
<td class="px-4 py-2">$${parseFloat(bill.amount).toFixed(2)}</td>
|
|
||||||
<td class="px-4 py-2">${bill.paymentId}</td>
|
|
||||||
</tr>
|
|
||||||
`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize chart
|
const formData = new FormData(e.target);
|
||||||
let chart;
|
|
||||||
async function updateChart() {
|
|
||||||
const response = await fetch('/includes/api.php?action=getTotals');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (chart) chart.destroy();
|
const response = await fetch('/includes/api.php?action=edit', {
|
||||||
|
method: 'POST',
|
||||||
chart = new Chart(document.getElementById('chart'), {
|
body: formData
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: [...new Set(data.map(item => item.year))],
|
|
||||||
datasets: Object.groupBy(data, ({ billName }) => billName).map(([name, values]) => ({
|
|
||||||
label: name,
|
|
||||||
data: values.map(v => v.total)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
updateChart();
|
if (response.ok) {
|
||||||
|
loadBills(yearSelect.value, sortSelect.value); // Reload bills after editing
|
||||||
|
document.getElementById('editModal').classList.add('hidden'); // Close the modal
|
||||||
|
} else {
|
||||||
|
alert('Failed to edit bill. Please try again.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener for close button in the modal
|
||||||
|
document.getElementById('editFormCancel').addEventListener('click', () => {
|
||||||
|
// Close the modal
|
||||||
|
document.getElementById('editModal').classList.add('hidden');
|
||||||
|
|
||||||
|
// Clear modal fields
|
||||||
|
document.getElementById('editForm').reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
// On page load
|
||||||
|
loadYears();
|
||||||
|
loadPayees();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user