mirror of
https://github.com/getnamingo/registry.git
synced 2025-05-13 08:07:00 +02:00
Added invoices page
This commit is contained in:
parent
ac61a4c3ad
commit
94053d17ca
8 changed files with 181 additions and 130 deletions
|
@ -18,9 +18,9 @@ class FinancialsController extends Controller
|
|||
return view($response,'admin/financials/overview.twig');
|
||||
}
|
||||
|
||||
public function pricing(Request $request, Response $response)
|
||||
public function invoices(Request $request, Response $response)
|
||||
{
|
||||
return view($response,'admin/financials/pricing.twig');
|
||||
return view($response,'admin/financials/invoices.twig');
|
||||
}
|
||||
|
||||
public function deposit(Request $request, Response $response)
|
||||
|
|
65
cp/resources/views/admin/financials/invoices.twig
Normal file
65
cp/resources/views/admin/financials/invoices.twig
Normal file
|
@ -0,0 +1,65 @@
|
|||
{% extends "layouts/app.twig" %}
|
||||
|
||||
{% block title %}{{ __('Invoices') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<!-- Page pre-title -->
|
||||
<div class="page-pretitle">
|
||||
{{ __('Overview') }}
|
||||
</div>
|
||||
<h2 class="page-title">
|
||||
{{ __('Invoices') }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body border-bottom py-3">
|
||||
<div class="d-flex">
|
||||
<div class="text-secondary">
|
||||
<button class="btn btn-info btn-icon" onclick="downloadCSV()"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 15a1 1 0 0 0 1 1h2a1 1 0 0 0 1 -1v-2a1 1 0 0 0 -1 -1h-2a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1"></path><path d="M17 8l2 8l2 -8"></path><path d="M7 10a2 2 0 1 0 -4 0v4a2 2 0 1 0 4 0"></path></svg></button>
|
||||
<button class="btn btn-info btn-icon" onclick="downloadJSON()"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M20 16v-8l3 8v-8"></path><path d="M15 8a2 2 0 0 1 2 2v4a2 2 0 1 1 -4 0v-4a2 2 0 0 1 2 -2z"></path><path d="M1 8h3v6.5a1.5 1.5 0 0 1 -3 0v-.5"></path><path d="M7 15a1 1 0 0 0 1 1h1a1 1 0 0 0 1 -1v-2a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-2a1 1 0 0 1 1 -1h1a1 1 0 0 1 1 1"></path></svg></button>
|
||||
<button class="btn btn-green btn-icon" onclick="downloadXLSX()"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M14 3v4a1 1 0 0 0 1 1h4"></path><path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"></path><path d="M4 15l4 6"></path><path d="M4 21l4 -6"></path><path d="M17 20.25c0 .414 .336 .75 .75 .75h1.25a1 1 0 0 0 1 -1v-1a1 1 0 0 0 -1 -1h-1a1 1 0 0 1 -1 -1v-1a1 1 0 0 1 1 -1h1.25a.75 .75 0 0 1 .75 .75"></path><path d="M11 15v6h3"></path></svg></button>
|
||||
<button class="btn btn-red btn-icon" onclick="downloadPDF()"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 8v8h2a2 2 0 0 0 2 -2v-4a2 2 0 0 0 -2 -2h-2z"></path><path d="M3 12h2a2 2 0 1 0 0 -4h-2v8"></path><path d="M17 12h3"></path><path d="M21 8h-4v8"></path></svg></button>
|
||||
</div>
|
||||
<div class="ms-auto text-secondary">
|
||||
Search:
|
||||
<div class="ms-2 d-inline-block">
|
||||
<input id="search-input" type="text" class="form-control" aria-label="Search invoices">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<div id="invoicesTable"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer footer-transparent d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
|
||||
<ul class="list-inline list-inline-dots mb-0">
|
||||
<li class="list-inline-item">
|
||||
Copyright © 2023
|
||||
<a href="https://namingo.org" target="_blank" class="link-secondary">Namingo</a>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,124 +0,0 @@
|
|||
{% extends "layouts/app.twig" %}
|
||||
|
||||
{% block title %}{{ __('Price Management') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<!-- Page pre-title -->
|
||||
<div class="page-pretitle">
|
||||
Overview
|
||||
</div>
|
||||
<h2 class="page-title">
|
||||
{{ __('Price Management') }}
|
||||
</h2>
|
||||
</div>
|
||||
<!-- Page title actions -->
|
||||
<div class="col-auto ms-auto d-print-none">
|
||||
<div class="btn-list">
|
||||
<span class="d-none d-sm-inline">
|
||||
<a href="#" class="btn">
|
||||
New view
|
||||
</a>
|
||||
</span>
|
||||
<a href="#" class="btn btn-primary d-none d-sm-inline-block" data-bs-toggle="modal" data-bs-target="#modal-report">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
|
||||
Create new report
|
||||
</a>
|
||||
<a href="#" class="btn btn-primary d-sm-none btn-icon" data-bs-toggle="modal" data-bs-target="#modal-report" aria-label="Create new report">
|
||||
<!-- Download SVG icon from http://tabler-icons.io/i/plus -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body border-bottom py-3">
|
||||
<!-- Price list (This will be dynamically populated using your backend) -->
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>TLD ID</th>
|
||||
<th>Command</th>
|
||||
<th>Initial</th>
|
||||
<th>1 Year</th>
|
||||
<th>2 Years</th>
|
||||
<th>3 Years</th>
|
||||
<th>4 Years</th>
|
||||
<th>5 Years</th>
|
||||
<th>6 Years</th>
|
||||
<th>7 Years</th>
|
||||
<th>8 Years</th>
|
||||
<th>9 Years</th>
|
||||
<th>10 Years</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Example row. This should be generated based on the data from your backend -->
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td><strong>Create</strong></td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$100</td>
|
||||
<td><button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#priceModal">Edit</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Renew</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$10</td>
|
||||
<td>$100</td>
|
||||
<td><button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#priceModal">Edit</button></td>
|
||||
</tr>
|
||||
<!-- ... other rows ... -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer footer-transparent d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
|
||||
<ul class="list-inline list-inline-dots mb-0">
|
||||
<li class="list-inline-item">
|
||||
Copyright © 2023
|
||||
<a href="https://namingo.org" target="_blank" class="link-secondary">Namingo</a>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -6,7 +6,7 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>{% block title %}{% endblock %} | Namingo</title>
|
||||
<!-- CSS files -->
|
||||
{% if route_is('domains') or route_is('contacts') or route_is('hosts') or route_is('epphistory') or route_is('registrars') or route_is('transactions') or route_is('overview') or route_is('reports') or route_is('transfers') or route_is('users') or route_is('support') or route_is('poll') or route_is('log') %}
|
||||
{% if route_is('domains') or route_is('contacts') or route_is('hosts') or route_is('epphistory') or route_is('registrars') or route_is('transactions') or route_is('overview') or route_is('reports') or route_is('transfers') or route_is('users') or route_is('support') or route_is('poll') or route_is('log') or route_is('invoices') %}
|
||||
{% include 'partials/css-tables.twig' %}
|
||||
{% else %}
|
||||
{% include 'partials/css.twig' %}
|
||||
|
@ -172,7 +172,7 @@
|
|||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li {{ is_current_url('deposit') or is_current_url('transactions') or is_current_url('overview') ? 'class="nav-item dropdown active"' : 'class="nav-item dropdown"' }}>
|
||||
<li {{ is_current_url('deposit') or is_current_url('transactions') or is_current_url('overview') or is_current_url('invoices') ? 'class="nav-item dropdown active"' : 'class="nav-item dropdown"' }}>
|
||||
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false">
|
||||
<span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 9m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path><path d="M14 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path><path d="M17 9v-2a2 2 0 0 0 -2 -2h-10a2 2 0 0 0 -2 2v6a2 2 0 0 0 2 2h2"></path></svg>
|
||||
</span>
|
||||
|
@ -187,7 +187,7 @@
|
|||
<a class="dropdown-item" href="{{route('transactions')}}">
|
||||
{{ __('Transactions') }}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#">
|
||||
<a class="dropdown-item" href="{{route('invoices')}}">
|
||||
{{ __('Invoices') }}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{{route('deposit')}}">
|
||||
|
@ -385,6 +385,8 @@
|
|||
{% include 'partials/js-poll.twig' %}
|
||||
{% elseif route_is('log') %}
|
||||
{% include 'partials/js-log.twig' %}
|
||||
{% elseif route_is('invoices') %}
|
||||
{% include 'partials/js-invoices.twig' %}
|
||||
{% else %}
|
||||
{% include 'partials/js.twig' %}
|
||||
{% endif %}
|
||||
|
|
75
cp/resources/views/partials/js-invoices.twig
Normal file
75
cp/resources/views/partials/js-invoices.twig
Normal file
|
@ -0,0 +1,75 @@
|
|||
<script src="/assets/js/tabulator.min.js" defer></script>
|
||||
<script src="/assets/js/tabler.min.js" defer></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.6.0/jspdf.plugin.autotable.min.js"></script>
|
||||
<script>
|
||||
var table;
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
|
||||
table = new Tabulator("#invoicesTable", {
|
||||
ajaxURL:"/api/records/invoices?join=registrar", // Set the URL for your JSON data
|
||||
ajaxConfig:"GET",
|
||||
pagination:"local",
|
||||
paginationSize:10,
|
||||
ajaxResponse:function(url, params, response){
|
||||
return response.records;
|
||||
},
|
||||
layout:"fitDataFill",
|
||||
responsiveLayout: "collapse",
|
||||
responsiveLayoutCollapseStartOpen:false,
|
||||
resizableColumns:false,
|
||||
initialSort:[
|
||||
{column:"invoice_number", dir:"desc"},
|
||||
],
|
||||
columns:[
|
||||
{formatter:"responsiveCollapse", width:30, minWidth:30, hozAlign:"center", resizable:false, headerSort:false, responsive:0},
|
||||
{title:"Number", field:"invoice_number", width:200, headerSort:true, responsive:0},
|
||||
{title:"Registrar", field:"registrar_id.name", width:300, headerSort:true, responsive:0},
|
||||
{title:"Date", field:"issue_date", width:300, headerSort:true, responsive:0},
|
||||
{title:"Amount", field:"total_amount", width:200, headerSort:true, responsive:0},
|
||||
],
|
||||
placeholder:function(){
|
||||
return this.getHeaderFilters().length ? "No Matching Data" : "No Data"; //set placeholder based on if there are currently any header filters
|
||||
}
|
||||
});
|
||||
var searchInput = document.getElementById("search-input");
|
||||
searchInput.addEventListener("input", function () {
|
||||
var term = searchInput.value.toLowerCase();
|
||||
|
||||
if (term) { // Only apply the filter when there's a term to search for
|
||||
table.setFilter(function (data) {
|
||||
// Check if any of the fields contain the search term
|
||||
return (
|
||||
String(data.invoice_number).toLowerCase().includes(term) ||
|
||||
String(data.registrar_id.name).toLowerCase().includes(term) ||
|
||||
String(data.issue_date).toLowerCase().includes(term) ||
|
||||
String(data.total_amount).toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
table.clearFilter(); // Clear the filter when the search box is emptied
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function downloadCSV() {
|
||||
table.download("csv", "data.csv");
|
||||
}
|
||||
|
||||
function downloadJSON() {
|
||||
table.download("json", "data.json");
|
||||
}
|
||||
|
||||
function downloadXLSX() {
|
||||
table.download("xlsx", "data.xlsx", {sheetName:"My Invoices"});
|
||||
}
|
||||
|
||||
function downloadPDF() {
|
||||
table.download("pdf", "data.pdf", {
|
||||
orientation:"portrait",
|
||||
title:"My Invoices",
|
||||
jsPDF:{unit:"mm", format:"a4", orientation:"p"}
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -67,7 +67,7 @@ $app->group('', function ($route) {
|
|||
$route->get('/log', LogsController::class .':log')->setName('log');
|
||||
$route->get('/reports', ReportsController::class .':view')->setName('reports');
|
||||
|
||||
$route->get('/pricing', FinancialsController::class .':pricing')->setName('pricing');
|
||||
$route->get('/invoices', FinancialsController::class .':invoices')->setName('invoices');
|
||||
$route->map(['GET', 'POST'], '/deposit', FinancialsController::class .':deposit')->setName('deposit');
|
||||
$route->get('/transactions', FinancialsController::class .':transactions')->setName('transactions');
|
||||
$route->get('/overview', FinancialsController::class .':overview')->setName('overview');
|
||||
|
|
|
@ -173,6 +173,22 @@ CREATE TABLE IF NOT EXISTS `registry`.`statement` (
|
|||
CONSTRAINT `statement_ibfk_1` FOREIGN KEY (`registrar_id`) REFERENCES `registrar` (`id`) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='financial statement';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `registry`.`invoices` (
|
||||
id INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
invoice_number VARCHAR(20),
|
||||
registrar_id INT(10) UNSIGNED,
|
||||
billing_contact_id INT(10) UNSIGNED,
|
||||
issue_date DATETIME(3),
|
||||
due_date DATETIME(3) default NULL,
|
||||
total_amount DECIMAL(10,2),
|
||||
payment_status ENUM('unpaid', 'paid', 'overdue', 'cancelled') DEFAULT 'unpaid',
|
||||
notes TEXT default NULL,
|
||||
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
FOREIGN KEY (registrar_id) REFERENCES registrar(id),
|
||||
FOREIGN KEY (billing_contact_id) REFERENCES registrar_contact(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='invoices';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `registry`.`contact` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`identifier` varchar(255) NOT NULL,
|
||||
|
|
|
@ -178,6 +178,23 @@ CREATE TABLE registry.statement (
|
|||
primary key ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE registry.invoices (
|
||||
id SERIAL PRIMARY KEY,
|
||||
invoice_number VARCHAR(20),
|
||||
registrar_id INT,
|
||||
billing_contact_id INT,
|
||||
issue_date TIMESTAMP(3),
|
||||
due_date TIMESTAMP(3) DEFAULT NULL,
|
||||
total_amount NUMERIC(10,2),
|
||||
payment_status VARCHAR(10) DEFAULT 'unpaid' CHECK (payment_status IN ('unpaid', 'paid', 'overdue', 'cancelled')),
|
||||
notes TEXT DEFAULT NULL,
|
||||
created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (registrar_id) REFERENCES registrar(id),
|
||||
FOREIGN KEY (billing_contact_id) REFERENCES registrar_contact(id)
|
||||
);
|
||||
|
||||
CREATE TABLE registry.contact (
|
||||
"id" serial8,
|
||||
"identifier" varchar(255) NOT NULL,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue