first commit
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"whereismymoney/internal/models"
|
||||
)
|
||||
|
||||
templ RecurringTransactions(userName string, rules []models.RecurrenceRule) {
|
||||
@Layout("Wiederkehrende Transaktionen - WhereIsMyMoney") {
|
||||
@Navigation(userName)
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div class="px-4 py-6 sm:px-0">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Wiederkehrende Transaktionen</h1>
|
||||
<p class="mt-2 text-gray-600">Verwalten Sie Ihre regelmäßigen Ein- und Ausgaben</p>
|
||||
</div>
|
||||
|
||||
<!-- Regeln Liste -->
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h3 class="text-lg font-medium text-gray-900">Ihre wiederkehrenden Transaktionen</h3>
|
||||
</div>
|
||||
|
||||
if len(rules) == 0 {
|
||||
<div class="px-6 py-8 text-center">
|
||||
<p class="text-gray-500">Noch keine wiederkehrenden Transaktionen vorhanden.</p>
|
||||
</div>
|
||||
} else {
|
||||
<div class="divide-y divide-gray-200">
|
||||
for _, rule := range rules {
|
||||
<div class="px-6 py-4" data-rule-id={ fmt.Sprintf("%d", rule.ID) }>
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center space-x-3">
|
||||
<h4 class="text-lg font-medium text-gray-900">{ rule.Description }</h4>
|
||||
if rule.IsActive {
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
Aktiv
|
||||
</span>
|
||||
} else {
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
||||
Inaktiv
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex items-center space-x-6 text-sm text-gray-500">
|
||||
<div class="flex items-center">
|
||||
<span class="font-medium">Betrag:</span>
|
||||
if rule.Type == "income" {
|
||||
<span class="ml-1 text-green-600 font-semibold">+€{ fmt.Sprintf("%.2f", rule.Amount) }</span>
|
||||
} else {
|
||||
<span class="ml-1 text-red-600 font-semibold">-€{ fmt.Sprintf("%.2f", rule.Amount) }</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<span class="font-medium">Intervall:</span>
|
||||
<span class="ml-1">
|
||||
switch rule.Interval {
|
||||
case "daily":
|
||||
if rule.IntervalCount == 1 {
|
||||
Täglich
|
||||
} else {
|
||||
Alle { fmt.Sprintf("%d", rule.IntervalCount) } Tage
|
||||
}
|
||||
case "weekly":
|
||||
if rule.IntervalCount == 1 {
|
||||
Wöchentlich
|
||||
} else {
|
||||
Alle { fmt.Sprintf("%d", rule.IntervalCount) } Wochen
|
||||
}
|
||||
case "monthly":
|
||||
if rule.IntervalCount == 1 {
|
||||
Monatlich
|
||||
} else {
|
||||
Alle { fmt.Sprintf("%d", rule.IntervalCount) } Monate
|
||||
}
|
||||
case "yearly":
|
||||
if rule.IntervalCount == 1 {
|
||||
Jährlich
|
||||
} else {
|
||||
Alle { fmt.Sprintf("%d", rule.IntervalCount) } Jahre
|
||||
}
|
||||
default:
|
||||
{ rule.Interval }
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
if rule.Category != nil {
|
||||
<div class="flex items-center">
|
||||
<span class="font-medium">Kategorie:</span>
|
||||
<span class="ml-1">{ rule.Category.Name }</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex items-center">
|
||||
<span class="font-medium">Start:</span>
|
||||
<span class="ml-1">{ rule.StartDate.Format("02.01.2006") }</span>
|
||||
</div>
|
||||
|
||||
if rule.EndDate != nil {
|
||||
<div class="flex items-center">
|
||||
<span class="font-medium">Ende:</span>
|
||||
<span class="ml-1">{ rule.EndDate.Format("02.01.2006") }</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
<button
|
||||
class="edit-rule-btn inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
data-rule-id={ fmt.Sprintf("%d", rule.ID) }
|
||||
data-description={ rule.Description }
|
||||
data-amount={ fmt.Sprintf("%.2f", rule.Amount) }
|
||||
data-type={ rule.Type }
|
||||
data-interval={ rule.Interval }
|
||||
data-start-date={ rule.StartDate.Format("2006-01-02") }
|
||||
if rule.EndDate != nil {
|
||||
data-end-date={ rule.EndDate.Format("2006-01-02") }
|
||||
}
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="toggle-rule-btn inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
data-rule-id={ fmt.Sprintf("%d", rule.ID) }
|
||||
data-active={ fmt.Sprintf("%t", rule.IsActive) }
|
||||
>
|
||||
if rule.IsActive {
|
||||
Deaktivieren
|
||||
} else {
|
||||
Aktivieren
|
||||
}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="delete-rule-btn inline-flex items-center px-3 py-1 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
data-rule-id={ fmt.Sprintf("%d", rule.ID) }
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal (wird per JavaScript eingeblendet) -->
|
||||
<div id="editModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Wiederkehrende Transaktion bearbeiten</h3>
|
||||
|
||||
<form id="editForm">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Beschreibung</label>
|
||||
<input type="text" id="editDescription" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Betrag (€)</label>
|
||||
<input type="number" step="0.01" id="editAmount" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Typ</label>
|
||||
<select id="editType" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<option value="income">Einnahme</option>
|
||||
<option value="expense">Ausgabe</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Intervall</label>
|
||||
<select id="editInterval" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<option value="daily">Täglich</option>
|
||||
<option value="weekly">Wöchentlich</option>
|
||||
<option value="monthly">Monatlich</option>
|
||||
<option value="yearly">Jährlich</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Startdatum</label>
|
||||
<input type="date" id="editStartDate" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Enddatum (optional)</label>
|
||||
<input type="date" id="editEndDate" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<p class="mt-1 text-sm text-gray-500">Leer lassen für unbegrenzte Laufzeit</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 mt-6">
|
||||
<button type="button" id="cancelEdit" class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-300 rounded-md hover:bg-gray-400">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript für Interaktionen -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Edit Modal Funktionalität
|
||||
const modal = document.getElementById('editModal');
|
||||
const editForm = document.getElementById('editForm');
|
||||
const cancelBtn = document.getElementById('cancelEdit');
|
||||
let currentRuleId = null;
|
||||
|
||||
// Edit Button Event Listener
|
||||
document.querySelectorAll('.edit-rule-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
currentRuleId = this.dataset.ruleId;
|
||||
|
||||
// Formular mit aktuellen Werten füllen
|
||||
document.getElementById('editDescription').value = this.dataset.description;
|
||||
document.getElementById('editAmount').value = this.dataset.amount;
|
||||
document.getElementById('editType').value = this.dataset.type;
|
||||
document.getElementById('editInterval').value = this.dataset.interval;
|
||||
document.getElementById('editStartDate').value = this.dataset.startDate;
|
||||
document.getElementById('editEndDate').value = this.dataset.endDate || '';
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// Cancel Button
|
||||
cancelBtn.addEventListener('click', function() {
|
||||
modal.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Form Submit
|
||||
editForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = {
|
||||
description: document.getElementById('editDescription').value,
|
||||
amount: parseFloat(document.getElementById('editAmount').value),
|
||||
type: document.getElementById('editType').value,
|
||||
interval: document.getElementById('editInterval').value,
|
||||
start_date: document.getElementById('editStartDate').value,
|
||||
end_date: document.getElementById('editEndDate').value
|
||||
};
|
||||
|
||||
fetch(`/api/recurring-rules/${currentRuleId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Fehler beim Speichern: ' + (data.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Fehler: ' + error.message);
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle Button Event Listener
|
||||
document.querySelectorAll('.toggle-rule-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const ruleId = this.dataset.ruleId;
|
||||
|
||||
fetch(`/transactions/recurring/${ruleId}/toggle`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Delete Button Event Listener
|
||||
document.querySelectorAll('.delete-rule-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
if (confirm('Sind Sie sicher, dass Sie diese wiederkehrende Transaktion löschen möchten?')) {
|
||||
const ruleId = this.dataset.ruleId;
|
||||
|
||||
fetch(`/api/recurring-rules/${ruleId}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user