Add favicon, refactor UiBadge rendering logic, update layout and mobile styles, and adjust Docker volumes configuration

This commit is contained in:
2026-05-17 08:07:48 -04:00
parent 8d74f3e520
commit fb67c76f45
4 changed files with 47 additions and 42 deletions
+1
View File
@@ -55,6 +55,7 @@ services:
- "localhost:host-gateway" - "localhost:host-gateway"
volumes: volumes:
- uploads:/uploads - uploads:/uploads
- ./web:/app/web
depends_on: depends_on:
mssql: mssql:
condition: service_healthy condition: service_healthy
+27 -24
View File
@@ -4,21 +4,7 @@ const STATUS_LABELS = {
}; };
const PRIORITY_LABELS = { low: 'Low', normal: 'Normal', high: 'High', urgent: 'Urgent' }; const PRIORITY_LABELS = { low: 'Low', normal: 'Normal', high: 'High', urgent: 'Urgent' };
class UiBadge extends HTMLElement { const CSS = `
connectedCallback() { this.#render(); }
static get observedAttributes() { return ['type', 'value']; }
attributeChangedCallback() { this.#render(); }
#render() {
const type = this.getAttribute('type') || 'status';
const value = this.getAttribute('value') || '';
const label = type === 'priority'
? (PRIORITY_LABELS[value] || value)
: (STATUS_LABELS[value] || value.replace('_', ' '));
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host { display: inline-flex; } :host { display: inline-flex; }
.badge { .badge {
display: inline-flex; align-items: center; gap: .35rem; display: inline-flex; align-items: center; gap: .35rem;
@@ -27,25 +13,42 @@ class UiBadge extends HTMLElement {
letter-spacing: .04em; white-space: nowrap; letter-spacing: .04em; white-space: nowrap;
} }
.dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } .dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
/* Status variants */
.s-draft { background:var(--status-draft-bg); color:var(--status-draft); } .s-draft { background:var(--status-draft-bg); color:var(--status-draft); }
.s-assigned { background:var(--status-assigned-bg); color:var(--status-assigned); } .s-assigned { background:var(--status-assigned-bg); color:var(--status-assigned); }
.s-scheduled { background:var(--status-scheduled-bg); color:var(--status-scheduled); } .s-scheduled { background:var(--status-scheduled-bg); color:var(--status-scheduled); }
.s-in_progress { background:var(--status-in_progress-bg); color:var(--status-in_progress); } .s-in_progress { background:var(--status-in_progress-bg); color:var(--status-in_progress); }
.s-pending_review { background:var(--status-pending_review-bg); color:var(--status-pending_review); } .s-pending_review { background:var(--status-pending_review-bg); color:var(--status-pending_review); }
.s-closed { background:var(--status-closed-bg); color:var(--status-closed); } .s-closed { background:var(--status-closed-bg); color:var(--status-closed); }
/* Priority variants */
.p-low { background:#F1F5F9; color:var(--priority-low); } .p-low { background:#F1F5F9; color:var(--priority-low); }
.p-normal { background:#E0F2FE; color:var(--priority-normal); } .p-normal { background:#E0F2FE; color:var(--priority-normal); }
.p-high { background:#FFF7ED; color:var(--priority-high); } .p-high { background:#FFF7ED; color:var(--priority-high); }
.p-urgent { background:#FEF2F2; color:var(--priority-urgent); } .p-urgent { background:#FEF2F2; color:var(--priority-urgent); }
</style> `;
<span class="badge ${type === 'priority' ? 'p' : 's'}-${value}">
<span class="dot" style="background:currentColor"></span> class UiBadge extends HTMLElement {
${label} static get observedAttributes() { return ['type', 'value']; }
</span>`;
connectedCallback() {
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<style>${CSS}</style><span class="badge"></span>`;
}
this.#update();
}
attributeChangedCallback() {
if (this.shadowRoot) this.#update();
}
#update() {
const type = this.getAttribute('type') || 'status';
const value = this.getAttribute('value') || '';
const label = type === 'priority'
? (PRIORITY_LABELS[value] || value)
: (STATUS_LABELS[value] || value.replace('_', ' '));
const span = this.shadowRoot.querySelector('.badge');
span.className = `badge ${type === 'priority' ? 'p' : 's'}-${value}`;
span.innerHTML = `<span class="dot" style="background:currentColor"></span>${label}`;
} }
} }
+1
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Work Orders</title> <title>Work Orders</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%230A7EA4'/><path d='M8 10h16M8 16h16M8 22h10' stroke='white' stroke-width='2.5' stroke-linecap='round'/></svg>">
<!-- Fonts: Inter (UI) + JetBrains Mono (WO numbers) --> <!-- Fonts: Inter (UI) + JetBrains Mono (WO numbers) -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
+6 -6
View File
@@ -166,15 +166,15 @@ app-root.collapsed {
app-mobile-nav { display: none; } app-mobile-nav { display: none; }
@media (max-width: 768px) { @media (max-width: 768px) {
app-root { app-root,
app-root.collapsed {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: 56px 1fr 56px; grid-template-rows: 56px 1fr;
} }
.app-shell { grid-template-rows: 1fr; } .app-shell { grid-template-rows: 56px 1fr; }
app-sidebar { display: none; } app-sidebar { display: none !important; }
app-mobile-nav { display: flex; } app-mobile-nav { display: flex; }
app-topbar { display: none; } #main-content { padding: 1rem; padding-bottom: calc(56px + 1rem); }
#main-content { padding: 1rem; }
} }
/* ── Sidebar (Light DOM) ──────────────────────────────────────────────────────── */ /* ── Sidebar (Light DOM) ──────────────────────────────────────────────────────── */