Add comprehensive recurring payments system with scheduling

- Add RecurringPayment model with flexible scheduling options
- Implement node-cron based scheduler for payment processing
- Create API endpoints for CRUD operations on recurring payments
- Add recurring payments management UI with create/edit forms
- Integrate scheduler initialization in hooks.server.ts
- Enhance payments/add form with progressive enhancement
- Add recurring payments button to main dashboard
- Improve server-side rendering for better performance
This commit is contained in:
2025-09-12 12:41:18 +02:00
parent 078ed0c642
commit eedfd3ecec
28 changed files with 4412 additions and 94 deletions
+57 -16
View File
@@ -7,15 +7,23 @@
export let data;
let payments = [];
let loading = true;
// Use server-side data with progressive enhancement
let payments = data.payments || [];
let loading = false; // Start as false since we have server data
let error = null;
let currentPage = 0;
let limit = 20;
let hasMore = true;
let currentPage = Math.floor(data.currentOffset / data.limit);
let limit = data.limit || 20;
let hasMore = data.hasMore || false;
// Progressive enhancement: only load if JavaScript is available
onMount(async () => {
await loadPayments();
// Mark that JavaScript is loaded for CSS
document.body.classList.add('js-loaded');
// Only refresh if we don't have server data
if (payments.length === 0) {
await loadPayments();
}
});
async function loadPayments(page = 0) {
@@ -136,7 +144,7 @@
{:else}
<div class="payments-grid">
{#each payments as payment}
<div class="payment-card" class:settlement-card={isSettlementPayment(payment)}>
<a href="/cospend/payments/view/{payment._id}" class="payment-card" class:settlement-card={isSettlementPayment(payment)}>
<div class="payment-header">
{#if isSettlementPayment(payment)}
<div class="settlement-flow">
@@ -234,17 +242,34 @@
</div>
{/if}
</div>
</div>
</a>
{/each}
</div>
{#if hasMore}
<div class="load-more">
<button class="btn btn-secondary" on:click={loadMore} disabled={loading}>
{loading ? 'Loading...' : 'Load More'}
<!-- Pagination that works without JavaScript -->
<div class="pagination">
{#if data.currentOffset > 0}
<a href="?offset={Math.max(0, data.currentOffset - data.limit)}&limit={data.limit}"
class="btn btn-secondary">
← Previous
</a>
{/if}
{#if hasMore}
<a href="?offset={data.currentOffset + data.limit}&limit={data.limit}"
class="btn btn-secondary">
Next →
</a>
{/if}
<!-- Progressive enhancement: JavaScript load more button -->
{#if hasMore}
<button class="btn btn-secondary js-only" on:click={loadMore} disabled={loading}
style="display: none;">
{loading ? 'Loading...' : 'Load More (JS)'}
</button>
</div>
{/if}
{/if}
</div>
{/if}
</main>
@@ -350,15 +375,20 @@
}
.payment-card {
display: block;
background: white;
border-radius: 0.75rem;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.2s;
text-decoration: none;
color: inherit;
}
.payment-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
text-decoration: none;
color: inherit;
}
.settlement-card {
@@ -593,11 +623,22 @@
background-color: #c62828;
}
.load-more {
text-align: center;
.pagination {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 2rem;
}
/* Progressive enhancement: show JS features only when JS is loaded */
.js-only {
display: none;
}
:global(body.js-loaded) .js-only {
display: inline-block;
}
@media (max-width: 600px) {
.payments-list {
padding: 1rem;