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
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>
|
|
|