158 lines
5.8 KiB
PHP
158 lines
5.8 KiB
PHP
<?php
|
|
require_once "config/session.php";
|
|
|
|
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|
header("location: index.php");
|
|
exit;
|
|
}
|
|
|
|
|
|
include('navbar.php');
|
|
?>
|
|
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>ANJUMA • Rechnungen</title>
|
|
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.10/css/dataTables.bootstrap5.min.css">
|
|
<style>
|
|
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
|
@media (max-width: 768px){ .d-md-table-cell{display:none!important;} }
|
|
</style>
|
|
</head>
|
|
<body class="bg-light">
|
|
|
|
<div class="container-fluid py-4">
|
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
|
<div>
|
|
<h3 class="mb-0">Rechnungen</h3>
|
|
</div>
|
|
<div class="d-flex gap-2 flex-wrap">
|
|
<button id="btnRefresh" class="btn btn-outline-secondary btn-sm">↻ Refresh</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-body">
|
|
<div class="row g-2 align-items-end">
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label mb-1">Von</label>
|
|
<input type="date" class="form-control" id="fromDate">
|
|
</div>
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label mb-1">Bis</label>
|
|
<input type="date" class="form-control" id="toDate">
|
|
</div>
|
|
<div class="col-12 col-md-6 d-flex gap-2">
|
|
<button id="btnThisMonth" class="btn btn-outline-primary">Dieser Monat</button>
|
|
<button id="btnThisYear" class="btn btn-outline-primary">Dieses Jahr</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table id="tblInv" class="table table-striped table-hover align-middle w-100">
|
|
<thead>
|
|
<tr>
|
|
<th>Nr</th>
|
|
<th>Datum</th>
|
|
<th class="d-md-table-cell">Leistung</th>
|
|
<th>Kunde</th>
|
|
<th>Status</th>
|
|
<th>Summe</th>
|
|
<th>PDF</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="text-muted small mt-2" id="info"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/datatables.net@1.13.10/js/jquery.dataTables.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.10/js/dataTables.bootstrap5.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<script>
|
|
const API_LIST = '/api/invoice_list.php';
|
|
const PDF_URL = '/api/invoice_pdf.php?id=';
|
|
|
|
let dt;
|
|
|
|
function isoLocal(d){ const y=d.getFullYear(), m=String(d.getMonth()+1).padStart(2,'0'), day=String(d.getDate()).padStart(2,'0'); return `${y}-${m}-${day}`; }
|
|
function thisMonthRange(){ const d=new Date(); return [isoLocal(new Date(d.getFullYear(), d.getMonth(), 1)), isoLocal(new Date(d.getFullYear(), d.getMonth()+1, 0))]; }
|
|
function thisYearRange(){ const d=new Date(); return [isoLocal(new Date(d.getFullYear(), 0, 1)), isoLocal(new Date(d.getFullYear(), 11, 31))]; }
|
|
function fmtDateDE(iso){ const [y,m,d]=String(iso||'').split('-'); return (y && m && d) ? `${d}.${m}.${y}` : ''; }
|
|
function eur(n){ return (Number(n)||0).toLocaleString('de-DE',{style:'currency',currency:'EUR',maximumFractionDigits:0}); }
|
|
|
|
function statusBadge(s){
|
|
s = String(s||'draft');
|
|
if (s==='paid') return '<span class="badge text-bg-success">PAID</span>';
|
|
if (s==='sent') return '<span class="badge text-bg-primary">SENT</span>';
|
|
if (s==='canceled') return '<span class="badge text-bg-danger">CANCELED</span>';
|
|
return '<span class="badge text-bg-secondary">DRAFT</span>';
|
|
}
|
|
|
|
function pdfBtn(row){
|
|
return `<a class="btn btn-outline-secondary btn-sm" href="${PDF_URL}${row.id}" target="_blank">PDF</a>`;
|
|
}
|
|
|
|
async function fetchJSON(url){
|
|
const res = await fetch(url, { cache:'no-store' });
|
|
const text = await res.text();
|
|
const json = JSON.parse(text);
|
|
if (!json.ok) throw new Error(json.error || 'API error');
|
|
return json;
|
|
}
|
|
|
|
async function refresh(){
|
|
const qs = new URLSearchParams({ from: $('#fromDate').val(), to: $('#toDate').val() }).toString();
|
|
const json = await fetchJSON(`${API_LIST}?${qs}`);
|
|
const items = json.items || [];
|
|
dt.clear().rows.add(items).draw(false);
|
|
$('#info').text(`Treffer: ${items.length} • Zeitraum: ${json.from} bis ${json.to}`);
|
|
}
|
|
|
|
$(function(){
|
|
const [f,t] = thisMonthRange();
|
|
$('#fromDate').val(f); $('#toDate').val(t);
|
|
|
|
dt = $('#tblInv').DataTable({
|
|
pageLength: 25,
|
|
order: [[1,'desc']],
|
|
columns: [
|
|
{ data:'invoice_no', className:'mono' },
|
|
{ data:'invoice_date', className:'mono', render:(d)=>fmtDateDE(d) },
|
|
{ data:'service_date', className:'mono d-md-table-cell', render:(d)=>fmtDateDE(d) },
|
|
{ data:'customer_name' },
|
|
{ data:'status', render:(d)=>statusBadge(d) },
|
|
{ data:'amount_net_eur', className:'mono', render:(d)=>eur(d) },
|
|
{ data:null, orderable:false, searchable:false, render:(row)=>pdfBtn(row) },
|
|
]
|
|
});
|
|
|
|
$('#btnRefresh').on('click', refresh);
|
|
$('#fromDate,#toDate').on('change', refresh);
|
|
|
|
$('#btnThisMonth').on('click', () => {
|
|
const [a,b]=thisMonthRange(); $('#fromDate').val(a); $('#toDate').val(b); refresh();
|
|
});
|
|
$('#btnThisYear').on('click', () => {
|
|
const [a,b]=thisYearRange(); $('#fromDate').val(a); $('#toDate').val(b); refresh();
|
|
});
|
|
|
|
refresh();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|