billing frontend for mariadb. setup as otb_billing for outsidethebox.top accounting. also involved with outsidethedb
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.
 
 
 

376 lines
7.7 KiB

<style>
:root {
--otb-bg: #f5f7fb;
--otb-text: #1e293b;
--otb-card: #ffffff;
--otb-border: #cbd5e1;
--otb-link: #1d4ed8;
--otb-muted: #64748b;
--otb-shadow: 0 2px 8px rgba(15,23,42,0.08);
--otb-hover: #eef4ff;
--otb-success-bg: #dcfce7;
--otb-success-text: #166534;
--otb-warning-bg: #fef3c7;
--otb-warning-text: #92400e;
--otb-danger-bg: #fee2e2;
--otb-danger-text: #991b1b;
--otb-info-bg: #dbeafe;
--otb-info-text: #1d4ed8;
--otb-neutral-bg: #e5e7eb;
--otb-neutral-text: #374151;
}
body.otb-dark {
--otb-bg: #0f172a;
--otb-text: #e5e7eb;
--otb-card: #111827;
--otb-border: #334155;
--otb-link: #93c5fd;
--otb-muted: #94a3b8;
--otb-shadow: 0 2px 10px rgba(0,0,0,0.35);
--otb-hover: #172554;
--otb-success-bg: #052e16;
--otb-success-text: #86efac;
--otb-warning-bg: #451a03;
--otb-warning-text: #fcd34d;
--otb-danger-bg: #450a0a;
--otb-danger-text: #fca5a5;
--otb-info-bg: #172554;
--otb-info-text: #93c5fd;
--otb-neutral-bg: #1f2937;
--otb-neutral-text: #d1d5db;
}
body {
background: var(--otb-bg);
color: var(--otb-text);
font-family: Arial, Helvetica, sans-serif;
margin: 0;
padding: 24px;
transition: background 0.2s ease, color 0.2s ease;
}
a {
color: var(--otb-link);
}
img {
max-width: 100%;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
cursor: pointer;
}
.otb-page {
max-width: 1240px;
margin: 0 auto;
background: var(--otb-card);
border: 1px solid var(--otb-border);
box-shadow: var(--otb-shadow);
padding: 20px;
}
.otb-table,
table {
border-collapse: collapse;
width: 100%;
margin-top: 14px;
background: var(--otb-card);
}
.otb-table th,
.otb-table td,
table th,
table td {
border: 1px solid var(--otb-border);
padding: 8px 10px;
vertical-align: top;
}
.otb-table th,
table th {
text-align: left;
}
.otb-table tbody tr:hover,
table tbody tr:hover,
table tr:hover td {
background: var(--otb-hover);
}
.otb-money {
text-align: right;
white-space: nowrap;
}
.otb-alert {
padding: 10px;
margin-bottom: 15px;
max-width: 950px;
border: 1px solid;
}
.otb-alert-success {
background: var(--otb-success-bg);
border-color: var(--otb-success-text);
color: var(--otb-success-text);
}
.otb-alert-error {
background: var(--otb-danger-bg);
border-color: var(--otb-danger-text);
color: var(--otb-danger-text);
}
.otb-footer {
margin: 18px auto 0 auto;
max-width: 1240px;
font-size: 12px;
color: var(--otb-muted);
}
.otb-nav {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 18px 0 20px 0;
}
.otb-nav a,
.otb-nav button {
display: inline-block;
text-decoration: none;
padding: 9px 14px;
border: 1px solid var(--otb-border);
background: var(--otb-card);
color: var(--otb-text);
border-radius: 10px;
box-shadow: var(--otb-shadow);
}
.otb-nav a:hover,
.otb-nav button:hover {
background: var(--otb-hover);
}
.otb-section-title {
margin-top: 0;
}
.otb-status {
display: inline-block;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: bold;
letter-spacing: 0.02em;
white-space: nowrap;
}
.otb-status-paid,
.otb-status-active,
.otb-status-confirmed,
.otb-status-sent {
background: var(--otb-success-bg);
color: var(--otb-success-text);
}
.otb-status-pending,
.otb-status-partial,
.otb-status-warning,
.otb-status-paused {
background: var(--otb-warning-bg);
color: var(--otb-warning-text);
}
.otb-status-overdue,
.otb-status-cancelled,
.otb-status-reversed,
.otb-status-failed {
background: var(--otb-danger-bg);
color: var(--otb-danger-text);
}
.otb-status-draft,
.otb-status-current,
.otb-status-info {
background: var(--otb-info-bg);
color: var(--otb-info-text);
}
.otb-status-neutral {
background: var(--otb-neutral-bg);
color: var(--otb-neutral-text);
}
/* Toggle switch */
.otb-theme-toggle-wrap {
position: fixed;
top: 12px;
right: 12px;
z-index: 9999;
display: flex;
align-items: center;
gap: 8px;
background: var(--otb-card);
color: var(--otb-text);
border: 1px solid var(--otb-border);
box-shadow: var(--otb-shadow);
border-radius: 999px;
padding: 8px 12px;
}
.otb-theme-toggle-wrap .label {
font-size: 12px;
color: var(--otb-muted);
}
.otb-switch {
position: relative;
display: inline-block;
width: 46px;
height: 24px;
}
.otb-switch input {
opacity: 0;
width: 0;
height: 0;
}
.otb-slider {
position: absolute;
inset: 0;
background: #cbd5e1;
border-radius: 999px;
transition: 0.2s ease;
}
.otb-slider:before {
content: "";
position: absolute;
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background: white;
border-radius: 50%;
transition: 0.2s ease;
}
.otb-switch input:checked + .otb-slider {
background: #2563eb;
}
.otb-switch input:checked + .otb-slider:before {
transform: translateX(22px);
}
</style>
<div class="otb-theme-toggle-wrap">
<span class="label">Theme</span>
<label class="otb-switch">
<input type="checkbox" id="otbThemeToggle">
<span class="otb-slider"></span>
</label>
</div>
<hr>
<div class="otb-footer">
OTB Billing v{{ app_version }}
</div>
<script>
(function () {
const themeKey = "otb_theme";
const toggle = document.getElementById("otbThemeToggle");
function applyTheme(theme) {
if (theme === "dark") {
document.body.classList.add("otb-dark");
if (toggle) toggle.checked = true;
} else {
document.body.classList.remove("otb-dark");
if (toggle) toggle.checked = false;
}
}
const savedTheme = localStorage.getItem(themeKey) || "light";
applyTheme(savedTheme);
if (toggle) {
toggle.addEventListener("change", function () {
const nextTheme = toggle.checked ? "dark" : "light";
localStorage.setItem(themeKey, nextTheme);
applyTheme(nextTheme);
});
}
function normalizeStatusText(text) {
return (text || "")
.trim()
.toLowerCase()
.replace(/\s+/g, "-");
}
function statusClassFor(value) {
const normalized = normalizeStatusText(value);
if (["paid", "active", "confirmed", "sent"].includes(normalized)) {
return "otb-status otb-status-paid";
}
if (["pending", "partial", "paused"].includes(normalized)) {
return "otb-status otb-status-pending";
}
if (["overdue", "cancelled", "reversed", "failed"].includes(normalized)) {
return "otb-status otb-status-overdue";
}
if (["draft", "current"].includes(normalized)) {
return "otb-status otb-status-draft";
}
return "";
}
function enhanceStatusCells() {
const cells = document.querySelectorAll("td, span, div");
cells.forEach(function (el) {
if (el.dataset.otbStatusDone === "1") return;
if (el.children.length > 0) return;
const text = (el.textContent || "").trim();
const klass = statusClassFor(text);
if (!klass) return;
if (el.tagName.toLowerCase() === "span") {
el.className = (el.className ? el.className + " " : "") + klass;
} else if (el.tagName.toLowerCase() === "td" || el.tagName.toLowerCase() === "div") {
el.innerHTML = '<span class="' + klass + '">' + text + '</span>';
}
el.dataset.otbStatusDone = "1";
});
}
enhanceStatusCells();
})();
</script>