194 lines
6.7 KiB
JavaScript
194 lines
6.7 KiB
JavaScript
|
|
class AdminBilling extends HTMLElement {
|
||
|
|
constructor() {
|
||
|
|
super();
|
||
|
|
this.pricingConfig = [];
|
||
|
|
this.stats = null;
|
||
|
|
this.boundHandleClick = this.handleClick.bind(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
async connectedCallback() {
|
||
|
|
this.addEventListener('click', this.boundHandleClick);
|
||
|
|
await this.loadData();
|
||
|
|
this.render();
|
||
|
|
this.attachEventListeners();
|
||
|
|
}
|
||
|
|
|
||
|
|
disconnectedCallback() {
|
||
|
|
this.removeEventListener('click', this.boundHandleClick);
|
||
|
|
}
|
||
|
|
|
||
|
|
async loadData() {
|
||
|
|
try {
|
||
|
|
const [pricing, stats] = await Promise.all([
|
||
|
|
this.fetchPricing(),
|
||
|
|
this.fetchStats()
|
||
|
|
]);
|
||
|
|
|
||
|
|
this.pricingConfig = pricing;
|
||
|
|
this.stats = stats;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to load admin billing data:', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async fetchPricing() {
|
||
|
|
const response = await fetch('/api/admin/billing/pricing', {
|
||
|
|
headers: {'Authorization': `Bearer ${localStorage.getItem('token')}`}
|
||
|
|
});
|
||
|
|
return await response.json();
|
||
|
|
}
|
||
|
|
|
||
|
|
async fetchStats() {
|
||
|
|
const response = await fetch('/api/admin/billing/stats', {
|
||
|
|
headers: {'Authorization': `Bearer ${localStorage.getItem('token')}`}
|
||
|
|
});
|
||
|
|
return await response.json();
|
||
|
|
}
|
||
|
|
|
||
|
|
formatCurrency(amount) {
|
||
|
|
return new Intl.NumberFormat('en-US', {
|
||
|
|
style: 'currency',
|
||
|
|
currency: 'USD'
|
||
|
|
}).format(amount);
|
||
|
|
}
|
||
|
|
|
||
|
|
render() {
|
||
|
|
this.innerHTML = `
|
||
|
|
<div class="admin-billing">
|
||
|
|
<h2>Billing Administration</h2>
|
||
|
|
|
||
|
|
<div class="stats-cards">
|
||
|
|
<div class="stat-card">
|
||
|
|
<h3>Total Revenue</h3>
|
||
|
|
<div class="stat-value">${this.formatCurrency(this.stats?.total_revenue || 0)}</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card">
|
||
|
|
<h3>Total Invoices</h3>
|
||
|
|
<div class="stat-value">${this.stats?.total_invoices || 0}</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card">
|
||
|
|
<h3>Pending Invoices</h3>
|
||
|
|
<div class="stat-value">${this.stats?.pending_invoices || 0}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="pricing-config-section">
|
||
|
|
<h3>Pricing Configuration</h3>
|
||
|
|
<table class="pricing-table">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Configuration</th>
|
||
|
|
<th>Current Value</th>
|
||
|
|
<th>Unit</th>
|
||
|
|
<th>Actions</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
${this.pricingConfig.map(config => `
|
||
|
|
<tr data-config-id="${config.id}">
|
||
|
|
<td>${config.description || config.config_key}</td>
|
||
|
|
<td class="config-value">${config.config_value}</td>
|
||
|
|
<td>${config.unit || '-'}</td>
|
||
|
|
<td>
|
||
|
|
<button class="btn-edit" data-config-id="${config.id}">Edit</button>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
`).join('')}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="invoice-generation-section">
|
||
|
|
<h3>Generate Invoices</h3>
|
||
|
|
<div class="invoice-gen-form">
|
||
|
|
<label>
|
||
|
|
Year:
|
||
|
|
<input type="number" id="invoiceYear" value="${new Date().getFullYear()}" min="2020">
|
||
|
|
</label>
|
||
|
|
<label>
|
||
|
|
Month:
|
||
|
|
<input type="number" id="invoiceMonth" value="${new Date().getMonth() + 1}" min="1" max="12">
|
||
|
|
</label>
|
||
|
|
<button class="btn-primary" id="generateInvoices">Generate All Invoices</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
|
||
|
|
handleClick(e) {
|
||
|
|
const target = e.target;
|
||
|
|
|
||
|
|
if (target.classList.contains('btn-edit')) {
|
||
|
|
const configId = target.dataset.configId;
|
||
|
|
this.editPricing(configId);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (target.id === 'generateInvoices') {
|
||
|
|
this.generateInvoices();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
attachEventListeners() {
|
||
|
|
}
|
||
|
|
|
||
|
|
async editPricing(configId) {
|
||
|
|
const config = this.pricingConfig.find(c => c.id === parseInt(configId));
|
||
|
|
if (!config) return;
|
||
|
|
|
||
|
|
const newValue = prompt(`Enter new value for ${config.config_key}:`, config.config_value);
|
||
|
|
if (newValue === null) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch(`/api/admin/billing/pricing/${configId}`, {
|
||
|
|
method: 'PUT',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
config_key: config.config_key,
|
||
|
|
config_value: parseFloat(newValue)
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
if (response.ok) {
|
||
|
|
alert('Pricing updated successfully');
|
||
|
|
await this.loadData();
|
||
|
|
this.render();
|
||
|
|
this.attachEventListeners();
|
||
|
|
} else {
|
||
|
|
alert('Failed to update pricing');
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error updating pricing:', error);
|
||
|
|
alert('Error updating pricing');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async generateInvoices() {
|
||
|
|
const year = parseInt(this.querySelector('#invoiceYear').value);
|
||
|
|
const month = parseInt(this.querySelector('#invoiceMonth').value);
|
||
|
|
|
||
|
|
if (!confirm(`Generate invoices for ${month}/${year}?`)) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch(`/api/admin/billing/generate-invoices/${year}/${month}`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {'Authorization': `Bearer ${localStorage.getItem('token')}`}
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = await response.json();
|
||
|
|
alert(`Generated ${result.generated} invoices, skipped ${result.skipped} users`);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error generating invoices:', error);
|
||
|
|
alert('Failed to generate invoices');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
customElements.define('admin-billing', AdminBilling);
|
||
|
|
|
||
|
|
export default AdminBilling;
|