anycoder-dcd56b82 / index.html
Multimedix's picture
Upload folder using huggingface_hub
dbaba6b verified
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Haushaltsbuch Pro - Ausgaben-Tracker</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #6366f1;
--primary-dark: #4f46e5;
--secondary: #22d3ee;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--dark: #1e293b;
--light: #f8fafc;
--gray: #64748b;
--border: #e2e8f0;
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 25px -5px rgb(0 0 0 / 0.1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: var(--dark);
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
header {
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 25px;
margin-bottom: 30px;
box-shadow: var(--shadow-lg);
animation: slideDown 0.5s ease;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 20px;
}
.logo {
display: flex;
align-items: center;
gap: 15px;
font-size: 28px;
font-weight: 700;
color: var(--primary);
}
.logo i {
font-size: 36px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header-stats {
display: flex;
gap: 30px;
flex-wrap: wrap;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: var(--dark);
}
.stat-label {
font-size: 12px;
color: var(--gray);
text-transform: uppercase;
letter-spacing: 1px;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 25px;
margin-bottom: 30px;
}
.card {
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 25px;
box-shadow: var(--shadow-lg);
animation: fadeInUp 0.5s ease;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 40px -10px rgb(0 0 0 / 0.2);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid var(--border);
}
.card-title {
font-size: 20px;
font-weight: 600;
color: var(--dark);
display: flex;
align-items: center;
gap: 10px;
}
.card-title i {
color: var(--primary);
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--dark);
font-size: 14px;
}
input, select, textarea {
width: 100%;
padding: 12px 15px;
border: 2px solid var(--border);
border-radius: 10px;
font-size: 15px;
transition: all 0.3s ease;
background: white;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px -5px rgba(99, 102, 241, 0.4);
}
.btn-success {
background: linear-gradient(135deg, var(--success), #059669);
color: white;
}
.btn-danger {
background: linear-gradient(135deg, var(--danger), #dc2626);
color: white;
}
.btn-secondary {
background: var(--light);
color: var(--dark);
border: 2px solid var(--border);
}
.btn-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.transaction-list {
max-height: 400px;
overflow-y: auto;
padding-right: 10px;
}
.transaction-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
margin-bottom: 10px;
background: var(--light);
border-radius: 10px;
transition: all 0.3s ease;
border-left: 4px solid transparent;
}
.transaction-item:hover {
transform: translateX(5px);
box-shadow: var(--shadow);
}
.transaction-item.income {
border-left-color: var(--success);
}
.transaction-item.expense {
border-left-color: var(--danger);
}
.transaction-info {
flex: 1;
}
.transaction-category {
font-size: 12px;
color: var(--gray);
margin-top: 4px;
}
.transaction-amount {
font-weight: 700;
font-size: 18px;
}
.amount-income {
color: var(--success);
}
.amount-expense {
color: var(--danger);
}
.chart-container {
position: relative;
height: 300px;
margin-top: 20px;
}
canvas {
max-width: 100%;
height: auto !important;
}
.category-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-right: 8px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
z-index: 1000;
animation: fadeIn 0.3s ease;
}
.modal.active {
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
border-radius: 20px;
padding: 30px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
animation: slideUp 0.3s ease;
}
.toast {
position: fixed;
bottom: 30px;
right: 30px;
padding: 15px 20px;
background: white;
border-radius: 10px;
box-shadow: var(--shadow-lg);
display: none;
align-items: center;
gap: 10px;
animation: slideInRight 0.3s ease;
z-index: 2000;
}
.toast.show {
display: flex;
}
.toast.success {
border-left: 4px solid var(--success);
}
.toast.error {
border-left: 4px solid var(--danger);
}
.empty-state {
text-align: center;
padding: 40px;
color: var(--gray);
}
.empty-state i {
font-size: 48px;
margin-bottom: 15px;
opacity: 0.5;
}
.filter-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.tab {
padding: 8px 16px;
background: var(--light);
border: 2px solid var(--border);
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
}
.tab.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.tab:hover:not(.active) {
background: var(--border);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
.grid-container {
grid-template-columns: 1fr;
}
.header-content {
flex-direction: column;
text-align: center;
}
.header-stats {
justify-content: center;
}
.btn-group {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
}
.summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.summary-card {
background: linear-gradient(135deg, var(--light), white);
padding: 20px;
border-radius: 15px;
text-align: center;
border: 2px solid var(--border);
transition: all 0.3s ease;
}
.summary-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow);
}
.summary-card-icon {
font-size: 24px;
margin-bottom: 10px;
}
.summary-card-value {
font-size: 24px;
font-weight: 700;
margin-bottom: 5px;
}
.summary-card-label {
font-size: 12px;
color: var(--gray);
text-transform: uppercase;
}
.built-with {
position: absolute;
top: 10px;
right: 20px;
font-size: 12px;
color: var(--gray);
}
.built-with a {
color: var(--primary);
text-decoration: none;
font-weight: 600;
}
.built-with a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="built-with">
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
</div>
<div class="header-content">
<div class="logo">
<i class="fas fa-wallet"></i>
<span>Haushaltsbuch Pro</span>
</div>
<div class="header-stats">
<div class="stat-item">
<div class="stat-value" id="totalBalance">0,00 €</div>
<div class="stat-label">Gesamtguthaben</div>
</div>
<div class="stat-item">
<div class="stat-value" id="monthIncome">0,00 €</div>
<div class="stat-label">Monatseinnahmen</div>
</div>
<div class="stat-item">
<div class="stat-value" id="monthExpense">0,00 €</div>
<div class="stat-label">Monatsausgaben</div>
</div>
</div>
</div>
</header>
<div class="summary-cards">
<div class="summary-card">
<div class="summary-card-icon" style="color: var(--success);">
<i class="fas fa-arrow-trend-up"></i>
</div>
<div class="summary-card-value" style="color: var(--success);" id="todayIncome">0,00 €</div>
<div class="summary-card-label">Heutige Einnahmen</div>
</div>
<div class="summary-card">
<div class="summary-card-icon" style="color: var(--danger);">
<i class="fas fa-arrow-trend-down"></i>
</div>
<div class="summary-card-value" style="color: var(--danger);" id="todayExpense">0,00 €</div>
<div class="summary-card-label">Heutige Ausgaben</div>
</div>
<div class="summary-card">
<div class="summary-card-icon" style="color: var(--primary);">
<i class="fas fa-chart-pie"></i>
</div>
<div class="summary-card-value" style="color: var(--primary);" id="categoryCount">0</div>
<div class="summary-card-label">Kategorien</div>
</div>
<div class="summary-card">
<div class="summary-card-icon" style="color: var(--warning);">
<i class="fas fa-receipt"></i>
</div>
<div class="summary-card-value" style="color: var(--warning);" id="transactionCount">0</div>
<div class="summary-card-label">Transaktionen</div>
</div>
</div>
<div class="grid-container">
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-plus-circle"></i>
Neue Buchung
</h2>
</div>
<form id="transactionForm">
<div class="form-group">
<label for="type">Typ</label>
<select id="type" required>
<option value="">Bitte wählen</option>
<option value="income">Einnahme</option>
<option value="expense">Ausgabe</option>
</select>
</div>
<div class="form-group">
<label for="amount">Betrag (€)</label>
<input type="number" id="amount" step="0.01" min="0" required placeholder="0,00">
</div>
<div class="form-group">
<label for="category">Kategorie</label>
<select id="category" required>
<option value="">Bitte wählen</option>
</select>
</div>
<div class="form-group">
<label for="date">Datum</label>
<input type="date" id="date" required>
</div>
<div class="form-group">
<label for="description">Beschreibung</label>
<textarea id="description" rows="2" placeholder="Optionale Beschreibung..."></textarea>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%;">
<i class="fas fa-save"></i>
Buchung speichern
</button>
</form>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-history"></i>
Letzte Buchungen
</h2>
<button class="btn btn-secondary" onclick="clearAllTransactions()">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="filter-tabs">
<div class="tab active" onclick="filterTransactions('all')">Alle</div>
<div class="tab" onclick="filterTransactions('income')">Einnahmen</div>
<div class="tab" onclick="filterTransactions('expense')">Ausgaben</div>
</div>
<div class="transaction-list" id="transactionList">
<div class="empty-state">
<i class="fas fa-inbox"></i>
<p>Keine Buchungen vorhanden</p>
</div>
</div>
</div>
</div>
<div class="grid-container">
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-chart-line"></i>
Ausgaben nach Kategorien
</h2>
</div>
<div class="chart-container">
<canvas id="categoryChart"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-calendar-alt"></i>
Monatlicher Verlauf
</h2>
</div>
<div class="chart-container">
<canvas id="monthlyChart"></canvas>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-cog"></i>
Datenverwaltung
</h2>
</div>
<div class="btn-group">
<button class="btn btn-success" onclick="exportToCSV()">
<i class="fas fa-download"></i>
CSV exportieren
</button>
<label class="btn btn-primary">
<i class="fas fa-upload"></i>
CSV importieren
<input type="file" accept=".csv" style="display: none;" onchange="importFromCSV(event)">
</label>
<button class="btn btn-danger" onclick="clearAllData()">
<i class="fas fa-trash-alt"></i>
Alle Daten löschen
</button>
</div>
</div>
</div>
<div class="toast" id="toast">
<i class="fas fa-check-circle"></i>
<span id="toastMessage">Erfolgreich gespeichert!</span>
</div>
<script>
// Daten initialisieren
let transactions = JSON.parse(localStorage.getItem('transactions')) || [];
let currentFilter = 'all';
const categories = {
income: ['Gehalt', 'Nebenjob', 'Investment', 'Geschenk', 'Sonstige Einnahmen'],
expense: ['Lebensmittel', 'Miete', 'Transport', 'Utilities', 'Unterhaltung', 'Gesundheit', 'Shopping', 'Bildung', 'Restaurant', 'Sonstige Ausgaben']
};
// Datum auf heute setzen
document.getElementById('date').valueAsDate = new Date();
// Typ-Änderung behandeln
document.getElementById('type').addEventListener('change', function() {
updateCategoryOptions(this.value);
});
function updateCategoryOptions(type) {
const categorySelect = document.getElementById('category');
categorySelect.innerHTML = '<option value="">Bitte wählen</option>';
if (type && categories[type]) {
categories[type].forEach(cat => {
const option = document.createElement('option');
option.value = cat;
option.textContent = cat;
categorySelect.appendChild(option);
});
}
}
// Formular absenden
document.getElementById('transactionForm').addEventListener('submit', function(e) {
e.preventDefault();
const transaction = {
id: Date.now(),
type: document.getElementById('type').value,
amount: parseFloat(document.getElementById('amount').value),
category: document.getElementById('category').value,
date: document.getElementById('date').value,
description: document.getElementById('description').value,
timestamp: new Date().toISOString()
};
transactions.push(transaction);
saveTransactions();
updateUI();
// Formular zurücksetzen
this.reset();
document.getElementById('date').valueAsDate = new Date();
showToast('Buchung erfolgreich gespeichert!', 'success');
});
function saveTransactions() {
localStorage.setItem('transactions', JSON.stringify(transactions));
}
function updateUI() {
updateStats();
updateTransactionList();
updateCharts();
}
function updateStats() {
const now = new Date();
const currentMonth = now.getMonth();
const currentYear = now.getFullYear();
const today = now.toISOString().split('T')[0];
let totalIncome = 0;
let totalExpense = 0;
let monthIncome = 0;
let monthExpense = 0;
let todayIncome = 0;
let todayExpense = 0;
let uniqueCategories = new Set();
transactions.forEach(t => {
const tDate = new Date(t.date);
const tMonth = tDate.getMonth();
const tYear = tDate.getFullYear();
uniqueCategories.add(t.category);
if (t.type === 'income') {
totalIncome += t.amount;
if (tMonth === currentMonth && tYear === currentYear) {
monthIncome += t.amount;
}
if (t.date === today) {
todayIncome += t.amount;
}
} else {
totalExpense += t.amount;
if (tMonth === currentMonth && tYear === currentYear) {
monthExpense += t.amount;
}
if (t.date === today) {
todayExpense += t.amount;
}
}
});
const balance = totalIncome - totalExpense;
document.getElementById('totalBalance').textContent = formatCurrency(balance);
document.getElementById('monthIncome').textContent = formatCurrency(monthIncome);
document.getElementById('monthExpense').textContent = formatCurrency(monthExpense);
document.getElementById('todayIncome').textContent = formatCurrency(todayIncome);
document.getElementById('todayExpense').textContent = formatCurrency(todayExpense);
document.getElementById('categoryCount').textContent = uniqueCategories.size;
document.getElementById('transactionCount').textContent = transactions.length;
}
function updateTransactionList() {
const list = document.getElementById('transactionList');
let filteredTransactions = transactions;
if (currentFilter !== 'all') {
filteredTransactions = transactions.filter(t => t.type === currentFilter);
}
// Sortieren nach Datum (neueste zuerst)
filteredTransactions.sort((a, b) => new Date(b.date) - new Date(a.date));
if (filteredTransactions.length === 0) {
list.innerHTML = `
<div class="empty-state">
<i class="fas fa-inbox"></i>
<p>Keine Buchungen vorhanden</p>
</div>
`;
return;
}
list.innerHTML = filteredTransactions.slice(0, 10).map(t => `
<div class="transaction-item ${t.type}">
<div class="transaction-info">
<div>${t.description || t.category}</div>
<div class="transaction-category">${t.category}${formatDate(t.date)}</div>
</div>
<div class="transaction-amount amount-${t.type}">
${t.type === 'income' ? '+' : '-'}${formatCurrency(t.amount)}
</div>
<button class="btn btn-danger" onclick="deleteTransaction(${t.id})" style="padding: 8px 12px;">
<i class="fas fa-times"></i>
</button>
</div>
`).join('');
}
function deleteTransaction(id) {
if (confirm('Möchten Sie diese Buchung wirklich löschen?')) {
transactions = transactions.filter(t => t.id !== id);
saveTransactions();
updateUI();
showToast('Buchung gelöscht', 'success');
}
}
function filterTransactions(type) {
currentFilter = type;
// Tabs aktualisieren
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.classList.add('active');
updateTransactionList();
}
function updateCharts() {
drawCategoryChart();
drawMonthlyChart();
}
function drawCategoryChart() {
const canvas = document.getElementById('categoryChart');
const ctx = canvas.getContext('2d');
// Kategorie-Ausgaben summieren
const categoryTotals = {};
transactions.filter(t => t.type === 'expense').forEach(t => {
categoryTotals[t.category] = (categoryTotals[t.category] || 0) + t.amount;
});
const labels = Object.keys(categoryTotals);
const data = Object.values(categoryTotals);
if (labels.length === 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = '16px Arial';
ctx.fillStyle = '#999';
ctx.textAlign = 'center';
ctx.fillText('Keine Daten verfügbar', canvas.width / 2, canvas.height / 2);
return;
}
// Farben generieren
const colors = labels.map((_, i) => `hsl(${(i * 360) / labels.length}, 70%, 60%)`);
// Canvas-Größe setzen
canvas.width = canvas.offsetWidth;
canvas.height = 300;
// Kuchendiagramm zeichnen
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 40;
let currentAngle = -Math.PI / 2;
const total = data.reduce((sum, val) => sum + val, 0);
data.forEach((value, i) => {
const sliceAngle = (value / total) * 2 * Math.PI;
// Segment zeichnen
ctx.beginPath();
ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + sliceAngle);
ctx.lineTo(centerX, centerY);
ctx.fillStyle = colors[i];
ctx.fill();
// Label zeichnen
const labelAngle = currentAngle + sliceAngle / 2;
const labelX = centerX + Math.cos(labelAngle) * (radius * 0.7);
const labelY = centerY + Math.sin(labelAngle) * (radius * 0.7);
ctx.fillStyle = 'white';
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.fillText(`${((value / total) * 100).toFixed(1)}%`, labelX, labelY);
currentAngle += sliceAngle;
});
// Legende zeichnen
let legendY = 20;
labels.forEach((label, i) => {
ctx.fillStyle = colors[i];
ctx.fillRect(10, legendY, 15, 15);
ctx.fillStyle = '#333';
ctx.font = '12px Arial';
ctx.textAlign = 'left';
ctx.fillText(`${label}: ${formatCurrency(data[i])}`, 30, legendY + 12);
legendY += 20;
});
}
function drawMonthlyChart() {
const canvas = document.getElementById('monthlyChart');
const ctx = canvas.getContext('2d');
// Letzte 6 Monate berechnen
const months = [];
const incomeData = [];
const expenseData = [];
for (let i = 5; i >= 0; i--) {
const date = new Date();
date.setMonth(date.getMonth() - i);
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
months.push(date.toLocaleDateString('de-DE', { month: 'short', year: 'numeric' }));
const monthIncome = transactions
.filter(t => t.type === 'income' && t.date.startsWith(monthKey))
.reduce((sum, t) => sum + t.amount, 0);
const monthExpense = transactions
.filter(t => t.type === 'expense' && t.date.startsWith(monthKey))
.reduce((sum, t) => sum + t.amount, 0);
incomeData.push(monthIncome);
expenseData.push(monthExpense);
}
// Canvas-Größe setzen
canvas.width = canvas.offsetWidth;
canvas.height = 300;
const padding = 40;
const chartWidth = canvas.width - padding * 2;
const chartHeight = canvas.height - padding * 2;
const barWidth = chartWidth / (months.length * 2 + months.length - 1);
const maxValue = Math.max(...incomeData, ...expenseData, 1);
// Achsen zeichnen
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, canvas.height - padding);
ctx.lineTo(canvas.width - padding, canvas.height - padding);
ctx.stroke();
// Balken zeichnen
months.forEach((month, i) => {
const x = padding + i * (barWidth * 2 + barWidth);
// Einnahmen-Balken
const incomeHeight = (incomeData[i] / maxValue) * chartHeight;
ctx.fillStyle = '#10b981';
ctx.fillRect(x, canvas.height - padding - incomeHeight, barWidth, incomeHeight);
// Ausgaben-Balken
const expenseHeight = (expenseData[i] / maxValue) * chartHeight;
ctx.fillStyle = '#ef4444';
ctx.fillRect(x + barWidth, canvas.height - padding - expenseHeight, barWidth, expenseHeight);
// Monats-Label
ctx.fillStyle = '#666';
ctx.font = '11px Arial';
ctx.textAlign = 'center';
ctx.fillText(month, x + barWidth, canvas.height - padding + 20);
});
// Legende
ctx.fillStyle = '#10b981';
ctx.fillRect(canvas.width - 100, 10, 15, 15);
ctx.fillStyle = '#333';
ctx.font = '12px Arial';
ctx.textAlign = 'left';
ctx.fillText('Einnahmen', canvas.width - 80, 22);
ctx.fillStyle = '#ef4444';
ctx.fillRect(canvas.width - 100, 30, 15, 15);
ctx.fillStyle = '#333';
ctx.fillText('Ausgaben', canvas.width - 80, 42);
}
function exportToCSV() {
if (transactions.length === 0) {
showToast('Keine Daten zum Exportieren', 'error');
return;
}
let csv = 'Typ,Betrag,Kategorie,Datum,Beschreibung\n';
transactions.forEach(t => {
csv += `${t.type === 'income' ? 'Einnahme' : 'Ausgabe'},${t.amount},${t.category},${t.date},"${t.description || ''}"\n`;
});
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `haushaltsbuch_${new Date().toISOString().split('T')[0]}.csv`;
link.click();
showToast('CSV erfolgreich exportiert', 'success');
}
function importFromCSV(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const lines = e.target.result.split('\n');
const imported = [];
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '') continue;
const parts = lines[i].split(',');
if (parts.length >= 4) {
const transaction = {
id: Date.now() + i,
type: parts[0].trim() === 'Einnahme' ? 'income' : 'expense',
amount: parseFloat(parts[1]),
category: parts[2].trim(),
date: parts[3].trim(),
description: parts[4] ? parts[4].replace(/"/g, '').trim() : '',
timestamp: new Date().toISOString()
};
imported.push(transaction);
}
}
if (imported.length > 0) {
transactions = [...transactions, ...imported];
saveTransactions();
updateUI();
showToast(`${imported.length} Buchungen importiert`, 'success');
} else {
showToast('Keine gültigen Daten gefunden', 'error');
}
} catch (error) {
showToast('Fehler beim Importieren der Datei', 'error');
}
};
reader.readAsText(file);
// File input zurücksetzen
event.target.value = '';
}
function clearAllTransactions() {
if (confirm('Möchten Sie alle Buchungen wirklich löschen?')) {
transactions = [];
saveTransactions();
updateUI();
showToast('Alle Buchungen gelöscht', 'success');
}
}
function clearAllData() {
if (confirm('Möchten Sie wirklich alle Daten löschen? Diese Aktion kann nicht rückgängig gemacht werden!')) {
localStorage.clear();
transactions = [];
updateUI();
showToast('Alle Daten gelöscht', 'success');
}
}
function formatCurrency(amount) {
return new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(amount);
}
function formatDate(dateString) {
return new Date(dateString).toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
}
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
toast.className = `toast ${type}`;
toastMessage.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 300