Skip to content

Commit

Permalink
Update different types cash charts
Browse files Browse the repository at this point in the history
  • Loading branch information
raymond0208 committed Jan 6, 2025
1 parent 734d743 commit 2574b47
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 30 deletions.
91 changes: 84 additions & 7 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,14 +575,34 @@ def currency_formatter(x, p):
@login_required
def monthly_balances():
try:
app.logger.info("Starting to generate monthly balance chart")
chart_image = generate_monthly_balance_chart()
app.logger.info("Chart generated successfully")
return jsonify({'chart_image': chart_image})
app.logger.info("Starting to generate monthly balance data")
transactions = Transaction.query.filter_by(user_id=current_user.id).order_by(Transaction.date).all()
initial_balance_record = InitialBalance.query.filter_by(user_id=current_user.id).first()
initial_balance = initial_balance_record.balance if initial_balance_record else 0.0

monthly_data = {}
current_balance = initial_balance

for transaction in transactions:
month = transaction.date.strftime('%Y-%m') if isinstance(transaction.date, datetime) else datetime.strptime(transaction.date, '%Y-%m-%d').strftime('%Y-%m')

if month not in monthly_data:
monthly_data[month] = current_balance

current_balance += transaction.amount
monthly_data[month] = current_balance

app.logger.info(f"Monthly balance data generated: {monthly_data}")
return jsonify({
'success': True,
'data': {
'labels': list(monthly_data.keys()),
'balances': list(monthly_data.values())
}
})
except Exception as e:
app.logger.error(f"Error generating monthly balance chart: {str(e)}")
app.logger.exception("Detailed traceback:")
return jsonify({'error': 'Failed to generate chart: ' + str(e)}), 500
app.logger.error(f"Error generating monthly balance data: {str(e)}")
return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/settings')
@login_required
Expand Down Expand Up @@ -698,5 +718,62 @@ def create_transaction():

return redirect(url_for('cash_activities'))

@app.route('/monthly-income-expense', methods=['GET'])
@login_required
def monthly_income_expense():
try:
transactions = Transaction.query.filter_by(user_id=current_user.id).order_by(Transaction.date).all()
monthly_data = {}

for transaction in transactions:
month = transaction.date.strftime('%Y-%m') if isinstance(transaction.date, datetime) else datetime.strptime(transaction.date, '%Y-%m-%d').strftime('%Y-%m')

if month not in monthly_data:
monthly_data[month] = {'income': 0, 'expense': 0}

if transaction.amount > 0:
monthly_data[month]['income'] += transaction.amount
else:
monthly_data[month]['expense'] += abs(transaction.amount)

return jsonify({
'success': True,
'data': {
'labels': list(monthly_data.keys()),
'income': [data['income'] for data in monthly_data.values()],
'expense': [data['expense'] for data in monthly_data.values()]
}
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/cashout-categories', methods=['GET'])
@login_required
def cashout_categories():
try:
# Fix the filter syntax for negative amounts
transactions = Transaction.query.filter(
Transaction.user_id == current_user.id,
Transaction.amount < 0 # Correct syntax for filtering negative amounts
).all()

categories_data = {}
for transaction in transactions:
category = transaction.type
if category not in categories_data:
categories_data[category] = 0
categories_data[category] += abs(transaction.amount)

return jsonify({
'success': True,
'data': {
'labels': list(categories_data.keys()),
'amounts': list(categories_data.values())
}
})
except Exception as e:
app.logger.error(f"Error fetching cashout categories: {str(e)}")
return jsonify({'success': False, 'error': str(e)}), 500

if __name__ == '__main__':
app.run(debug=True)
221 changes: 203 additions & 18 deletions static/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,13 +447,7 @@ function handleAuthForms() {

function handleMonthlyBalanceChart() {
console.log("Setting up monthly balance chart handler");

// Check if we're on the cash overview page by looking for the chart container
const chartContainer = document.querySelector('.monthly-chart-section');
if (chartContainer) {
console.log("Found chart container, fetching monthly balances");
fetchMonthlyBalances();
}
fetchMonthlyBalances();
}

function fetchMonthlyBalances() {
Expand All @@ -465,18 +459,49 @@ function fetchMonthlyBalances() {
}
return response.json();
})
.then(data => {
console.log("Received monthly balance data:", data);
if (data.error) {
throw new Error(data.error);
.then(response => {
if (!response.success) {
throw new Error(response.error || 'Failed to fetch data');
}
const chartImg = document.getElementById('monthly-balance-chart');
if (chartImg) {
chartImg.src = data.chart_image;
console.log("Updated chart image source");
} else {
console.error("Chart image element not found");

const data = response.data;
const existingChart = Chart.getChart('monthly-balance-chart');
if (existingChart) {
existingChart.destroy();
}

const ctx = document.getElementById('monthly-balance-chart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: data.labels,
datasets: [{
label: 'Monthly Balance',
data: data.balances,
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: false,
ticks: {
callback: value => '$' + value.toLocaleString()
}
}
},
plugins: {
title: {
display: false
}
}
}
});
})
.catch(error => {
console.error('Error fetching monthly balances:', error);
Expand Down Expand Up @@ -606,6 +631,159 @@ function updateForecastChart(forecasts) {
});
}

function handleMonthlyIncomeExpense() {
fetch('/monthly-income-expense')
.then(response => response.json())
.then(response => {
if (!response.success) {
throw new Error(response.error || 'Failed to fetch data');
}

const data = response.data;

// Destroy existing charts if they exist
const existingIncomeChart = Chart.getChart('monthly-income-chart');
if (existingIncomeChart) {
existingIncomeChart.destroy();
}

const existingExpenseChart = Chart.getChart('monthly-expense-chart');
if (existingExpenseChart) {
existingExpenseChart.destroy();
}

// Create Income Chart
const incomeCtx = document.getElementById('monthly-income-chart').getContext('2d');
new Chart(incomeCtx, {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
label: 'Monthly Income',
data: data.income,
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: value => '$' + value.toLocaleString()
}
}
}
}
});

// Create Expense Chart
const expenseCtx = document.getElementById('monthly-expense-chart').getContext('2d');
new Chart(expenseCtx, {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
label: 'Monthly Expenses',
data: data.expense,
backgroundColor: 'rgba(255, 99, 132, 0.6)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: value => '$' + value.toLocaleString()
}
}
}
}
});
})
.catch(error => {
console.error('Error fetching monthly income/expense data:', error);
});
}

function handleCashoutCategories() {
fetch('/cashout-categories')
.then(response => response.json())
.then(response => {
if (!response.success) {
throw new Error(response.error || 'Failed to fetch data');
}

const data = response.data;
const existingChart = Chart.getChart('cashout-categories-chart');
if (existingChart) {
existingChart.destroy();
}

const ctx = document.getElementById('cashout-categories-chart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
label: 'Cash-out Amount',
data: data.amounts,
backgroundColor: [
'rgba(255, 99, 132, 0.6)',
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)',
'rgba(75, 192, 192, 0.6)',
'rgba(153, 102, 255, 0.6)',
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: value => '$' + value.toLocaleString()
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
const value = context.raw;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return `$${value.toLocaleString()} (${percentage}%)`;
}
}
}
}
}
});
})
.catch(error => {
console.error('Error fetching cashout categories:', error);
});
}

// Main execution
document.addEventListener('DOMContentLoaded', function() {
console.log("DOM fully loaded");
Expand Down Expand Up @@ -646,5 +824,12 @@ document.addEventListener('DOMContentLoaded', function() {
handleFileUpload();
handleEditTransaction();
handleAuthForms();
handleMonthlyBalanceChart();

// Initialize all charts if we're on the cash overview page
const monthlyChartsSection = document.querySelector('.monthly-charts-section');
if (monthlyChartsSection) {
handleMonthlyBalanceChart();
handleMonthlyIncomeExpense();
handleCashoutCategories();
}
});
Loading

0 comments on commit 2574b47

Please sign in to comment.