diff --git a/css/planner.css b/css/planner.css new file mode 100644 index 0000000..33f27bb --- /dev/null +++ b/css/planner.css @@ -0,0 +1,220 @@ +/* ── Planner Layout ──────────────────────────────────────────────────────────── */ +.planner-layout { + display: grid; + grid-template-columns: 280px 1fr; + gap: 16px; + align-items: start; +} + +/* ── Plan Sidebar ────────────────────────────────────────────────────────────── */ +.plan-sidebar { + background: var(--bgcard); + border: 1px solid var(--border); + border-radius: var(--rl); + padding: 18px; + position: sticky; + top: 74px; +} + +.plan-sidebar-header { + display: flex; + align-items: center; + margin-bottom: 14px; +} +.plan-sidebar-header .card-title { margin-bottom: 0; flex: 1; } + +.plan-new-form { + background: var(--bg2); + border: 1px solid var(--borderem); + border-radius: var(--r); + padding: 10px; + margin-bottom: 12px; +} +.plan-new-form input { + margin-bottom: 8px; + font-size: 14px; + padding: 6px 10px; +} +.plan-new-actions { + display: flex; + gap: 6px; +} + +.plan-list-empty { + font-size: 13px; + color: var(--t3); + text-align: center; + padding: 24px 0; +} + +/* ── Plan Item ───────────────────────────────────────────────────────────────── */ +.plan-item { + display: flex; + align-items: center; + gap: 8px; + padding: 9px 10px; + border-radius: var(--r); + border: 1px solid transparent; + cursor: pointer; + transition: all 0.12s; + margin-bottom: 4px; +} +.plan-item:hover { background: var(--bg2); border-color: var(--border); } +.plan-item.active { background: var(--bg2); border-color: var(--gold); } + +.plan-item-info { flex: 1; min-width: 0; } + +.plan-item-name { + font-size: 14px; + color: var(--t1); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.plan-item.active .plan-item-name { color: var(--gold); } + +.plan-item-meta { + font-size: 12px; + color: var(--t3); + margin-top: 2px; +} + +.plan-item-actions { + display: flex; + gap: 4px; + flex-shrink: 0; + opacity: 0; + transition: opacity 0.12s; +} +.plan-item:hover .plan-item-actions { opacity: 1; } + +.plan-btn { + background: none; + border: 1px solid var(--borderem); + border-radius: var(--r); + color: var(--t2); + font-size: 13px; + padding: 2px 7px; + cursor: pointer; + transition: all 0.12s; + line-height: 1.6; +} +.plan-btn:hover { background: var(--bg3); color: var(--t1); } +.plan-btn.plan-btn-danger { color: var(--red); border-color: rgba(224,92,92,0.3); } +.plan-btn.plan-btn-danger:hover { background: var(--redbg); } + +/* ── Plan Detail ─────────────────────────────────────────────────────────────── */ +.plan-detail-header { + display: flex; + flex-direction: column; + gap: 5px; +} + +.plan-name-wrap { + display: flex; + align-items: center; + gap: 8px; +} + +.plan-name-text { + font-family: var(--font-d); + font-size: 18px; + color: var(--gold); + letter-spacing: 0.04em; +} + +.plan-name-input { + font-size: 18px !important; + font-family: var(--font-d) !important; + padding: 2px 8px !important; + width: auto !important; + min-width: 200px; + color: var(--gold) !important; +} + +.plan-detail-meta { + font-size: 13px; + color: var(--t3); +} + +/* ── Job Slots Placeholder ───────────────────────────────────────────────────── */ +.job-slots-placeholder { + padding: 20px; + text-align: center; + color: var(--t3); + font-size: 13px; + background: var(--bg2); + border: 1px dashed var(--border); + border-radius: var(--r); +} + +/* ── Mechanic Cards ──────────────────────────────────────────────────────────── */ +.mechanic-card { + display: grid; + grid-template-columns: 52px 1fr; + gap: 14px; + padding: 12px 0; + border-bottom: 1px solid var(--border); + align-items: start; +} +.mechanic-card:last-child { border-bottom: none; } + +.mechanic-time { + font-family: var(--font-d); + font-size: 14px; + color: var(--gold); + letter-spacing: 0.03em; + padding-top: 3px; +} + +.mechanic-body { + display: flex; + flex-direction: column; + gap: 4px; +} + +.mechanic-phase { + font-size: 11px; + color: var(--blue); + text-transform: uppercase; + letter-spacing: 0.07em; +} + +.mechanic-name { + font-size: 15px; + color: var(--t1); + font-weight: 500; +} + +.mechanic-dmg { + font-size: 13px; + color: var(--t2); +} + +.mechanic-assignments { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 2px; +} + +.mechanic-no-assign { + font-size: 12px; + color: var(--t3); +} + +.badge-assign { + background: var(--bg2); + border: 1px solid var(--borderem); + border-radius: var(--r); + padding: 2px 8px; + font-size: 12px; + color: var(--t2); +} + +.mechanic-notes { + font-size: 12px; + color: var(--t3); + font-style: italic; + margin-top: 2px; +} diff --git a/js/planner.js b/js/planner.js new file mode 100644 index 0000000..c7549e5 --- /dev/null +++ b/js/planner.js @@ -0,0 +1,333 @@ +// ── Storage ─────────────────────────────────────────────────────────────────── + +const PLANNER_KEY = 'ff14-planner-plans'; + +function loadPlans() { + try { return JSON.parse(localStorage.getItem(PLANNER_KEY) || '[]'); } + catch { return []; } +} + +function savePlans(plans) { + localStorage.setItem(PLANNER_KEY, JSON.stringify(plans)); +} + +// ── CRUD ────────────────────────────────────────────────────────────────────── + +function createPlan(name) { + const plan = { + id: crypto.randomUUID(), + name: name.trim() || 'Unbenannter Plan', + createdAt: Date.now(), + updatedAt: Date.now(), + source: null, + jobComposition: Array(8).fill(''), + mechanics: [] + }; + const all = loadPlans(); + all.push(plan); + savePlans(all); + return plan; +} + +function getPlan(id) { + return loadPlans().find(p => p.id === id) ?? null; +} + +function updatePlan(id, changes) { + const all = loadPlans(); + const idx = all.findIndex(p => p.id === id); + if (idx === -1) return null; + all[idx] = { ...all[idx], ...changes, updatedAt: Date.now() }; + savePlans(all); + return all[idx]; +} + +function deletePlan(id) { + savePlans(loadPlans().filter(p => p.id !== id)); +} + +function copyPlan(id) { + const all = loadPlans(); + const orig = all.find(p => p.id === id); + if (!orig) return null; + const copy = { + ...JSON.parse(JSON.stringify(orig)), + id: crypto.randomUUID(), + name: orig.name + ' (Kopie)', + createdAt: Date.now(), + updatedAt: Date.now() + }; + all.push(copy); + savePlans(all); + return copy; +} + +// ── State ───────────────────────────────────────────────────────────────────── + +let activePlanId = null; + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +function escHtml(str) { + return String(str ?? '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +function fmtDate(ts) { + return new Date(ts).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); +} + +function fmtTimestamp(ms) { + const s = Math.floor(ms / 1000); + return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`; +} + +function fmtNumber(n) { + return Number(n).toLocaleString('de-DE'); +} + +// ── Rendering: Plan List ────────────────────────────────────────────────────── + +function renderPlanList() { + const el = document.getElementById('plan-list'); + if (!el) return; + + const plans = loadPlans(); + + if (plans.length === 0) { + el.innerHTML = '
+ Importiere einen Log aus dem Analyse-Tab +
+Erstelle einen neuen Plan oder wähle einen bestehenden aus
+