You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

319 lines
12 KiB

{% extends "base.html" %}
{% block title %}Payment Plans - Plutus{% endblock %}
{% block head %}
<style>
/* Background styling */
body {
background-color: #3a3a3a !important;
background-image: url("{{ url_for('static', filename='images/plutus3.JPG') }}") !important;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
position: relative;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(58, 58, 58, 0.85);
z-index: -1;
}
.container-fluid, .container {
position: relative;
z-index: 1;
}
/* Ensure navbar and footer are above background */
.navbar, .footer, main {
position: relative;
z-index: 1;
}
/* Page title and breadcrumb styling */
h1, h2, h3, h4, h5, h6 {
color: #faf8f0 !important;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
}
.breadcrumb-item a {
color: #d4af37 !important;
}
.breadcrumb-item.active {
color: #faf8f0 !important;
}
.card {
background-color: rgba(250, 248, 240, 0.98) !important;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.table-responsive {
background-color: rgba(250, 248, 240, 0.98);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.alert {
background-color: rgba(250, 248, 240, 0.98);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
</style>
{% endblock %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('main.index') }}">Dashboard</a></li>
<li class="breadcrumb-item active" aria-current="page">Payment Plans</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-start mb-4">
<div>
<h1 class="h2 mb-1">Payment Plans</h1>
<p class="text-muted">Recurring payment management</p>
</div>
<a class="btn btn-primary" href="{{ url_for('main.payment_plans_create') }}">
<i class="fas fa-plus"></i> New Payment Plan
</a>
</div>
<!-- Summary Statistics -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card shadow text-center">
<div class="card-body">
<p class="h4 text-success mb-1">{{ summary.active_plans }}</p>
<p class="text-muted mb-0">Active Plans</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card shadow text-center">
<div class="card-body">
<p class="h4 text-warning mb-1">{{ summary.inactive_plans }}</p>
<p class="text-muted mb-0">Inactive Plans</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card shadow text-center">
<div class="card-body">
<p class="h4 text-info mb-1">{{ summary.total_plans }}</p>
<p class="text-muted mb-0">Total Plans</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card shadow text-center">
<div class="card-body">
<p class="h4 text-primary mb-1">{{ summary.total_recurring_amount | currency }}</p>
<p class="text-muted mb-0">Monthly Recurring</p>
</div>
</div>
</div>
</div>
<!-- Payment Plans Table -->
<div class="card shadow">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h4 mb-0">Payment Plans</h2>
<div class="input-group" style="max-width: 350px;">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input class="form-control" type="text" id="searchInput" placeholder="Search Customer ID, Amount...">
</div>
</div>
<!-- Filter Controls -->
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label form-label-sm">Filter by Status:</label>
<select class="form-select form-select-sm" id="statusFilter">
<option value="">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label form-label-sm">Filter by Frequency:</label>
<select class="form-select form-select-sm" id="frequencyFilter">
<option value="">All</option>
<option value="Weekly">Weekly</option>
<option value="Fortnightly">Fortnightly</option>
</select>
</div>
</div>
{% if plans %}
<div class="table-responsive">
<table class="table table-striped table-hover" id="plansTable">
<thead>
<tr>
<th>Plan ID</th>
<th>Customer</th>
<th>Splynx ID</th>
<th>Amount</th>
<th>Frequency</th>
<th>Start Date</th>
<th>Status</th>
<th>Created</th>
<th>Created By</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for plan in plans %}
<tr data-status="{{ 'active' if plan.Enabled else 'inactive' }}"
data-frequency="{{ plan.Frequency }}"
data-splynx-id="{{ plan.Splynx_ID }}"
data-amount="{{ plan.Amount }}"
data-customer-name="">
<td>
<a href="{{ url_for('main.payment_plans_detail', plan_id=plan.id) }}" class="fw-semibold">
#{{ plan.id }}
</a>
</td>
<td>
<span class="customer-name" data-splynx-id="{{ plan.Splynx_ID }}">
<i class="fas fa-spinner fa-spin"></i>
Loading...
</span>
</td>
<td>
<a href="https://billing.interphone.com.au/admin/customers/view?id={{ plan.Splynx_ID }}"
target="_blank" class="badge bg-info">{{ plan.Splynx_ID }}</a>
</td>
<td>
<strong>{{ plan.Amount | currency }}</strong>
</td>
<td>
<span class="badge {% if plan.Frequency == 'Weekly' %}bg-warning{% elif plan.Frequency == 'Fortnightly' %}bg-info{% else %}bg-secondary{% endif %}">
{{ plan.Frequency }}
</span>
</td>
<td>{{ plan.Start_Date.strftime('%Y-%m-%d') if plan.Start_Date else '-' }}</td>
<td>
{% if plan.Enabled %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-danger">Inactive</span>
{% endif %}
</td>
<td>{{ plan.Created.strftime('%Y-%m-%d %H:%M') if plan.Created else '-' }}</td>
<td>{{ plan.created_by or 'Unknown' }}</td>
<td>
<div class="btn-group btn-group-sm" role="group">
<a class="btn btn-info btn-sm"
href="{{ url_for('main.payment_plans_detail', plan_id=plan.id) }}">
<i class="fas fa-eye"></i>
</a>
<a class="btn btn-warning btn-sm"
href="{{ url_for('main.payment_plans_edit', plan_id=plan.id) }}">
<i class="fas fa-edit"></i>
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-calendar-alt fa-3x text-muted mb-3"></i>
<p class="h5 text-muted">No Payment Plans Found</p>
<p class="text-muted">Get started by creating your first payment plan.</p>
<a class="btn btn-primary" href="{{ url_for('main.payment_plans_create') }}">
<i class="fas fa-plus"></i> Create Payment Plan
</a>
</div>
{% endif %}
</div>
</div>
<script>
// Load customer names asynchronously
document.addEventListener('DOMContentLoaded', function() {
const customerElements = document.querySelectorAll('.customer-name');
customerElements.forEach(function(element) {
const splynxId = element.dataset.splynxId;
fetch(`/api/splynx/${splynxId}`)
.then(response => response.json())
.then(data => {
if (data && data.name) {
element.innerHTML = data.name;
// Update the row data attribute for search
const row = element.closest('tr');
row.dataset.customerName = data.name.toLowerCase();
} else {
element.innerHTML = '<span class="text-danger">Unknown Customer</span>';
}
})
.catch(error => {
console.error('Error fetching customer:', error);
element.innerHTML = '<span class="text-danger">Error Loading</span>';
});
});
});
// Search and filter functionality
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('searchInput');
const statusFilter = document.getElementById('statusFilter');
const frequencyFilter = document.getElementById('frequencyFilter');
const table = document.getElementById('plansTable');
if (!table) return; // No table to filter
function filterTable() {
const searchTerm = searchInput.value.toLowerCase();
const statusValue = statusFilter.value;
const frequencyValue = frequencyFilter.value;
const rows = table.querySelectorAll('tbody tr');
rows.forEach(function(row) {
const splynxId = row.dataset.splynxId;
const amount = row.dataset.amount;
const customerName = row.dataset.customerName || '';
const status = row.dataset.status;
const frequency = row.dataset.frequency;
// Search filter
const searchMatch = !searchTerm ||
splynxId.includes(searchTerm) ||
amount.includes(searchTerm) ||
customerName.includes(searchTerm);
// Status filter
const statusMatch = !statusValue || status === statusValue;
// Frequency filter
const frequencyMatch = !frequencyValue || frequency === frequencyValue;
// Show/hide row
if (searchMatch && statusMatch && frequencyMatch) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
// Add event listeners
searchInput.addEventListener('input', filterTable);
statusFilter.addEventListener('change', filterTable);
frequencyFilter.addEventListener('change', filterTable);
});
</script>
{% endblock %}