538 lines
27 KiB
HTML
538 lines
27 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>EVE Web Helper – Strukturen</title>
|
||
<style>
|
||
:root { color-scheme: light dark; }
|
||
html{scroll-behavior:smooth}
|
||
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:2rem}
|
||
.container{max-width:1440px;margin:0 auto}
|
||
|
||
/* Haupt-Tabs oben */
|
||
.nav{display:flex;gap:.6rem;margin-bottom:1rem}
|
||
.tab{padding:.5rem .8rem;border:1px solid #e5e7eb;border-radius:.6rem;text-decoration:none;color:#111827}
|
||
.tab.active{background:#111827;color:#fff;border-color:#111827}
|
||
|
||
/* Linke Unterreiter (untergeordnete Seite) */
|
||
.subtabs-left{
|
||
position:fixed; left:.8rem; top:96px; display:flex; flex-direction:column; gap:.5rem;
|
||
width:170px; padding:.6rem; border:1px solid #e5e7eb; border-radius:.6rem; background:#fff;
|
||
box-shadow:0 2px 8px rgba(0,0,0,.05); z-index:50
|
||
}
|
||
.subtabs-left a{display:block;text-decoration:none;padding:.5rem .6rem;border:1px solid #e5e7eb;border-radius:.6rem;color:#111827;background:#fff}
|
||
.subtabs-left a.active{background:#111827;color:#fff;border-color:#111827}
|
||
@media (max-width:1200px){.subtabs-left{display:none}}
|
||
|
||
.card{border:1px solid #e5e7eb;border-radius:.8rem;padding:1rem 1.2rem;margin-bottom:1rem;background:#fff}
|
||
.wide{max-width:1200px}
|
||
.full{width:100%}
|
||
h1{font-size:1.35rem;margin:.2rem 0 1rem}
|
||
label{font-weight:600;display:block;margin-bottom:.35rem}
|
||
select,input,textarea,button,a.button{padding:.5rem .7rem;border:1px solid #d1d5db;border-radius:.45rem}
|
||
textarea{width:100%;min-height:90px;resize:vertical}
|
||
button,a.button{background:#111827;color:#fff;cursor:pointer;text-decoration:none;display:inline-block}
|
||
.muted{color:#6b7280;font-size:.9rem}
|
||
.row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.8rem;margin-top:.8rem}
|
||
@media (max-width:1000px){.row{grid-template-columns:1fr 1fr}}
|
||
|
||
table{width:100%;border-collapse:collapse;table-layout:fixed}
|
||
th,td{padding:.5rem .45rem;border-bottom:1px solid #e5e7eb;text-align:left;vertical-align:top;word-break:break-word;overflow-wrap:anywhere}
|
||
th{font-weight:700}
|
||
.id{font-family:ui-monospace,SFMono-Regular,Consolas,Menlo,monospace;font-size:.85em;max-width:90px}
|
||
.num{text-align:right}
|
||
.actions form{display:inline}
|
||
.section-title{font-weight:700;margin-bottom:.6rem}
|
||
.empty{padding:.65rem .8rem;border:1px dashed #d1d5db;border-radius:.6rem;color:#6b7280;background:transparent}
|
||
.table-wrap{overflow:auto;border:1px solid #e5e7eb;border-radius:.6rem}
|
||
.table-wrap thead th{position:sticky;top:0;background:#fff;z-index:1}
|
||
.scroll-5{max-height:260px}
|
||
@media (max-width:1600px){ .col-notes{display:none} }
|
||
@media (max-width:1450px){ .col-react-rig{display:none} }
|
||
@media (max-width:1350px){ .col-ind-rig{display:none} }
|
||
@media (max-width:1250px){ .col-ind-struct,.col-react-struct{display:none} }
|
||
|
||
/* Modal */
|
||
.modal{position:fixed;inset:0;display:none;align-items:flex-start;justify-content:center;padding:4vh 1rem;background:rgba(0,0,0,.45);z-index:999}
|
||
.modal:target{display:flex}
|
||
.dialog{max-width:1100px;width:min(92vw,1100px);background:#fff;border-radius:14px;padding:1rem 1.2rem;box-shadow:0 20px 45px rgba(0,0,0,.35);max-height:92vh;overflow:auto;position:relative;z-index:1000;pointer-events:auto}
|
||
.modal *{pointer-events:auto}
|
||
.modal h2{margin:.2rem 0 1rem;font-size:1.2rem}
|
||
.close{position:sticky;top:0;float:right;font-size:1.4rem;text-decoration:none;color:#111827;padding:.2rem .5rem;border-radius:.4rem;z-index:2}
|
||
.kv{display:grid;grid-template-columns:180px 1fr;gap:.35rem .8rem}
|
||
hr.sep{border:none;border-top:1px solid #e5e7eb;margin:.8rem 0}
|
||
.pill{display:inline-block;background:#111827;color:#fff;border-radius:999px;padding:.2rem .55rem;font-size:.85rem}
|
||
.result{border:1px solid #e5e7eb;border-radius:.6rem;padding:.7rem .8rem;background:#fafafa;margin-top:.6rem}
|
||
.err{color:#b91c1c}
|
||
|
||
@media (prefers-color-scheme:dark){
|
||
body{color:#e5e7eb;background:#0b0f19}
|
||
.tab{border-color:#374151;color:#e5e7eb}
|
||
.tab.active{background:#1f2937;border-color:#374151}
|
||
.card,.dialog,.subtabs-left{background:#0f1423;border-color:#374151}
|
||
th,td{border-bottom-color:#374151}
|
||
.table-wrap{border-color:#374151}
|
||
.table-wrap thead th{background:#0f1423}
|
||
.result{background:#111318;border-color:#374151}
|
||
.close{color:#e5e7eb}
|
||
.subtabs-left a{color:#e5e7eb;border-color:#374151;background:#0f1423}
|
||
.subtabs-left a.active{background:#1f2937}
|
||
}
|
||
|
||
body{overflow-x:hidden}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Unterreiter links – AKTIV-Status serverseitig über request.path -->
|
||
<nav class="subtabs-left" aria-label="Unterreiter">
|
||
<a href="/strukturen"
|
||
class="{{ 'active' if request.path == '/strukturen' else '' }}">Aufträge</a>
|
||
<a href="/strukturen/mineralien"
|
||
class="{{ 'active' if request.path.startswith('/strukturen/mineralien') else '' }}">Mineralien</a>
|
||
</nav>
|
||
|
||
<div class="container">
|
||
|
||
<nav class="nav">
|
||
<a class="tab" href="/">Home</a>
|
||
<a class="tab active" href="/strukturen">Strukturen</a>
|
||
<a class="tab" href="/archiv">Archiv</a>
|
||
</nav>
|
||
|
||
<!-- Formular -->
|
||
<section class="card wide">
|
||
<h1>Struktur-Auftrag anlegen</h1>
|
||
<form method="post">
|
||
<div class="row">
|
||
<div>
|
||
<label for="structure-select">Struktur</label>
|
||
<select id="structure-select" name="structure" required>
|
||
<option value="" disabled selected>— bitte wählen —</option>
|
||
{% for s in structures %}<option value="{{ s }}">{{ s }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="quantity">Menge</label>
|
||
<input id="quantity" name="quantity" type="number" min="1" step="1" value="{{ quantity or 1 }}" required />
|
||
</div>
|
||
<div>
|
||
<label for="me">ME %</label>
|
||
<input id="me" name="me" type="number" min="0" max="10" step="1" value="{{ me_percent or 0 }}" />
|
||
</div>
|
||
<div>
|
||
<label for="system">System</label>
|
||
<select id="system" name="system">
|
||
{% for sys in systems %}<option value="{{ sys }}" {{ 'selected' if selected_system == sys else '' }}>{{ sys }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div>
|
||
<label for="industry_structure">Industrie-Struktur</label>
|
||
<select id="industry_structure" name="industry_structure">
|
||
{% for x in ind_structs %}<option value="{{ x }}" {{ 'selected' if selected_ind_struct == x else '' }}>{{ x }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="industry_rig">Industrie-Rig</label>
|
||
<select id="industry_rig" name="industry_rig">
|
||
{% for x in ind_rigs %}<option value="{{ x }}" {{ 'selected' if selected_ind_rig == x else '' }}>{{ x }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="reaction_structure">Reaktions-Struktur</label>
|
||
<select id="reaction_structure" name="reaction_structure">
|
||
{% for x in reac_structs %}<option value="{{ x }}" {{ 'selected' if selected_reac_struct == x else '' }}>{{ x }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="reaction_rig">Reaktions-Rig</label>
|
||
<select id="reaction_rig" name="reaction_rig">
|
||
{% for x in reac_rigs %}<option value="{{ x }}" {{ 'selected' if selected_reac_rig == x else '' }}>{{ x }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div>
|
||
<label for="facility_tax">Steuersatz % (facilityTax)</label>
|
||
<input id="facility_tax" name="facility_tax" type="number" min="0" max="50" step="0.1" value="0" />
|
||
</div>
|
||
<div style="grid-column: span 3">
|
||
<label for="notes">Notizen</label>
|
||
<textarea id="notes" name="notes" placeholder="Optionale Notizen zum Auftrag (max. ~2000 Zeichen)"></textarea>
|
||
<div class="muted" style="margin-top:.4rem">ME in 1er Schritten (0–10). Steuersatz in Prozent.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="submit" style="margin-top:.8rem">Auftrag hinzufügen</button>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Offene Aufträge -->
|
||
<section class="card full">
|
||
<div class="section-title">Offene Aufträge</div>
|
||
{% if open_orders %}
|
||
<div class="table-wrap scroll-5">
|
||
<table aria-label="Offene Aufträge">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Struktur</th>
|
||
<th>System</th>
|
||
<th class="col-ind-struct">Ind.-Struktur</th>
|
||
<th class="col-ind-rig">Ind.-Rig</th>
|
||
<th class="col-react-struct">Reakt.-Struktur</th>
|
||
<th class="col-react-rig">Reakt.-Rig</th>
|
||
<th class="num">Menge</th>
|
||
<th class="num">ME %</th>
|
||
<th class="col-notes">Notizen</th>
|
||
<th>Erstellt</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for o in open_orders %}
|
||
<tr>
|
||
<td class="id">{{ o.id[:8] }}…</td>
|
||
<td>{{ o.structure }}</td>
|
||
<td>{{ o.system|default('Jita') }}</td>
|
||
<td class="col-ind-struct">{{ o.industry_structure|default('Station') }}</td>
|
||
<td class="col-ind-rig">{{ o.industry_rig|default('None') }}</td>
|
||
<td class="col-react-struct">{{ o.reaction_structure|default('Athanor') }}</td>
|
||
<td class="col-react-rig">{{ o.reaction_rig|default('None') }}</td>
|
||
<td class="num">{{ o.quantity }}</td>
|
||
<td class="num">{{ o.me }}</td>
|
||
<td class="col-notes">{{ o.notes|default('') }}</td>
|
||
<td>{{ o.created_at|fmt_ts }}</td>
|
||
<td class="actions">
|
||
<a class="button" href="#m-{{ o.id }}">Details</a>
|
||
<button type="button" onclick="refreshOne('{{ o.id }}')">Aktualisieren</button>
|
||
<form method="post" action="/strukturen/done/{{ o.id }}"><button type="submit">Fertig</button></form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="empty">Keine offenen Aufträge.</div>
|
||
{% endif %}
|
||
</section>
|
||
|
||
<!-- Fertige Aufträge -->
|
||
<section class="card full">
|
||
<div class="section-title">Fertige Aufträge</div>
|
||
{% if done_orders %}
|
||
<div class="table-wrap scroll-5">
|
||
<table aria-label="Fertige Aufträge">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Struktur</th>
|
||
<th>System</th>
|
||
<th class="col-ind-struct">Ind.-Struktur</th>
|
||
<th class="col-ind-rig">Ind.-Rig</th>
|
||
<th class="col-react-struct">Reakt.-Struktur</th>
|
||
<th class="col-react-rig">Reakt.-Rig</th>
|
||
<th class="num">Menge</th>
|
||
<th class="num">ME %</th>
|
||
<th class="col-notes">Notizen</th>
|
||
<th>Erstellt</th>
|
||
<th>Fertig am</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for o in done_orders %}
|
||
<tr>
|
||
<td class="id">{{ o.id[:8] }}…</td>
|
||
<td>{{ o.structure }}</td>
|
||
<td>{{ o.system|default('Jita') }}</td>
|
||
<td class="col-ind-struct">{{ o.industry_structure|default('Station') }}</td>
|
||
<td class="col-ind-rig">{{ o.industry_rig|default('None') }}</td>
|
||
<td class="col-react-struct">{{ o.reaction_structure|default('Athanor') }}</td>
|
||
<td class="col-react-rig">{{ o.reaction_rig|default('None') }}</td>
|
||
<td class="num">{{ o.quantity }}</td>
|
||
<td class="num">{{ o.me }}</td>
|
||
<td class="col-notes">{{ o.notes|default('') }}</td>
|
||
<td>{{ o.created_at|fmt_ts }}</td>
|
||
<td>{{ o.done_at|fmt_ts }}</td>
|
||
<td class="actions">
|
||
<a class="button" href="#m-{{ o.id }}">Details</a>
|
||
<form method="post" action="/archiv/add/{{ o.id }}"><button type="submit">Archivieren</button></form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="empty">Noch keine fertigen Aufträge.</div>
|
||
{% endif %}
|
||
</section>
|
||
|
||
<!-- Kostenübersicht -->
|
||
<section class="card">
|
||
<div class="section-title" style="display:flex;justify-content:space-between;align-items:center">
|
||
<span>Kostenübersicht – Offene Aufträge</span>
|
||
<button id="btn-refresh-all" type="button" onclick="refreshAll()">Aktualisieren</button>
|
||
</div>
|
||
<div id="costs-box" class="empty">Lade Kosten …</div>
|
||
<div id="costs-table" style="display:none">
|
||
<div class="table-wrap">
|
||
<table aria-label="Kostenübersicht">
|
||
<thead>
|
||
<tr>
|
||
<th>Struktur</th><th class="num">M</th><th class="num">ME</th><th>System</th><th>Industrie</th><th>Reaktion</th><th class="num">€/E</th><th class="num">Gesamt</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="costs-body"></tbody>
|
||
<tfoot>
|
||
<tr><th colspan="7" class="num">Summe</th><th class="num"><strong id="costs-total">0 ISK</strong></th></tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
<div class="muted" id="costs-ts" style="margin-top:.4rem"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Materialübersicht -->
|
||
<section class="card">
|
||
<div class="section-title" style="display:flex;justify-content:space-between;align-items:center">
|
||
<span>Materialübersicht – Offene Aufträge</span>
|
||
<button id="btn-refresh-mats" type="button" onclick="refreshAll()">Aktualisieren</button>
|
||
</div>
|
||
<div id="mats-box" class="empty">Lade Materialien …</div>
|
||
<div id="mats-table" style="display:none">
|
||
<div class="table-wrap">
|
||
<table aria-label="Materialübersicht">
|
||
<thead>
|
||
<tr><th>Item</th><th class="num">Menge</th><th class="num">Kosten</th></tr>
|
||
</thead>
|
||
<tbody id="mats-body"></tbody>
|
||
<tfoot>
|
||
<tr><th colspan="2" class="num">Gesamtkosten</th><th class="num"><strong id="mats-total">0 ISK</strong></th></tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
<div class="muted" id="mats-ts" style="margin-top:.4rem"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Modals -->
|
||
{% for o in open_orders + done_orders %}
|
||
<div id="m-{{ o.id }}" class="modal" aria-hidden="true">
|
||
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="h-{{ o.id }}">
|
||
<a class="close" href="#" aria-label="Schließen">×</a>
|
||
<h2 id="h-{{ o.id }}">Auftrag – {{ o.structure }}</h2>
|
||
|
||
<div class="kv">
|
||
<div><strong>ID</strong></div><div class="id">{{ o.id }}</div>
|
||
<div><strong>Struktur</strong></div><div data-field="structure">{{ o.structure }}</div>
|
||
<div><strong>System</strong></div><div data-field="system">{{ o.system|default('Jita') }}</div>
|
||
<div><strong>Industrie-Struktur</strong></div><div data-field="industry_structure">{{ o.industry_structure|default('Station') }}</div>
|
||
<div><strong>Industrie-Rig</strong></div><div data-field="industry_rig">{{ o.industry_rig|default('None') }}</div>
|
||
<div><strong>Reaktions-Struktur</strong></div><div data-field="reaction_structure">{{ o.reaction_structure|default('Athanor') }}</div>
|
||
<div><strong>Reaktions-Rig</strong></div><div data-field="reaction_rig">{{ o.reaction_rig|default('None') }}</div>
|
||
<div><strong>Menge</strong></div><div data-field="quantity">{{ o.quantity }}</div>
|
||
<div><strong>ME %</strong></div><div data-field="me">{{ o.me }}</div>
|
||
<div><strong>Steuersatz %</strong></div><div data-field="facility_tax">{{ o.facility_tax|default(0) }}</div>
|
||
<div><strong>Notizen</strong></div><div class="note">{{ o.notes|default('') }}</div>
|
||
<div><strong>Erstellt</strong></div><div>{{ o.created_at|fmt_ts }}</div>
|
||
|
||
<div><strong>Blueprint ID</strong></div>
|
||
<div>
|
||
<input id="bp-{{ o.id }}" type="text" style="width:11rem"
|
||
value="{{ o.blueprint_type_id or '' }}" {{ '' if not o.blueprint_type_id else 'readonly' }} />
|
||
<button type="button" onclick="toggleEditBp('{{ o.id }}')">Bearbeiten</button>
|
||
<button type="button" onclick="saveBp('{{ o.id }}')">Speichern</button>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="sep" />
|
||
|
||
<h3 style="margin:.2rem 0 .4rem">Cookbook – Kosten & Materialien</h3>
|
||
<div class="muted" style="margin-bottom:.4rem">Die Blueprint-ID ist vorausgefüllt und kann bei Bedarf überschrieben werden.</div>
|
||
<div style="display:flex;gap:.5rem;flex-wrap:wrap;align-items:center;margin-bottom:.4rem">
|
||
<button type="button" onclick="fetchCookbook('{{ o.id }}')">Kosten abrufen</button>
|
||
<button type="button" onclick="fetchMaterials('{{ o.id }}')">Materialien abrufen</button>
|
||
<button type="button" onclick="refreshOne('{{ o.id }}')">Aktualisieren & speichern</button>
|
||
<span id="cb-status-{{ o.id }}" class="muted"></span>
|
||
</div>
|
||
|
||
<div id="cb-total-{{ o.id }}" class="pill" style="display:none;margin-bottom:.5rem"></div>
|
||
<div id="cb-result-{{ o.id }}" class="result" style="display:none"></div>
|
||
<div id="cb-mats-{{ o.id }}" class="result" style="display:none"></div>
|
||
|
||
<div style="margin-top:.8rem">
|
||
{% if o.status == 'open' %}
|
||
<form method="post" action="/strukturen/done/{{ o.id }}" style="display:inline"><button type="submit">Als fertig markieren</button></form>
|
||
{% elif o.status == 'done' %}
|
||
<form method="post" action="/archiv/add/{{ o.id }}" style="display:inline"><button type="submit">Archivieren</button></form>
|
||
{% endif %}
|
||
<a class="button" href="#" style="margin-left:.4rem">Schließen</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
</div>
|
||
|
||
<script>
|
||
function formatISK(x){const n=Number(x);if(!isFinite(n))return String(x);return n.toLocaleString('de-DE')+" ISK";}
|
||
function formatTS(s){
|
||
if(!s) return ""; const d=new Date(s);
|
||
if(isNaN(d.getTime())){ const t=String(s); return t.slice(0,16); }
|
||
const y=d.getFullYear(), m=String(d.getMonth()+1).padStart(2,'0'), da=String(d.getDate()).padStart(2,'0'),
|
||
hh=String(d.getHours()).padStart(2,'0'), mm=String(d.getMinutes()).padStart(2,'0');
|
||
return `${y}-${m}-${da} ${hh}:${mm}`;
|
||
}
|
||
function textOf(c,s){const el=c.querySelector(s);return el?el.textContent.trim():"";}
|
||
function toggleEditBp(id){const i=document.getElementById("bp-"+id);i.readOnly=!i.readOnly;i.focus();}
|
||
function saveBp(id){
|
||
const i=document.getElementById("bp-"+id), v=(i.value||"").trim();
|
||
if(!/^[0-9]+$/.test(v)){alert("Bitte gültige Blueprint-TypeID eingeben.");return;}
|
||
fetch("/api/order/"+encodeURIComponent(id)+"/bp",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({blueprint_type_id:v})}).then(()=>{i.readOnly=true;});
|
||
}
|
||
|
||
/* ===== Kosten ===== */
|
||
function pullTotal(d){
|
||
if(d&&d.total!=null) return d.total;
|
||
if(d&&d.totalCost!=null) return d.totalCost;
|
||
if(d&&d.message&&d.message.totalCost!=null) return d.message.totalCost;
|
||
return null;
|
||
}
|
||
function renderCost(root,data){
|
||
const m=(data&&data.message&&typeof data.message==='object')?data.message:data;
|
||
const rows=[
|
||
["Kosten / Einheit", m.buildCostPerUnit],
|
||
["Produzierte Menge", m.producedQuantity],
|
||
["Materialkosten", m.materialCost],
|
||
["Jobkosten", m.jobCost],
|
||
["Überschüssige Materialien", m.excessMaterialsValue],
|
||
["Zusatzkosten", m.additionalCost],
|
||
["Gesamtkosten", pullTotal(data)]
|
||
].filter(r=>r[1]!=null);
|
||
let html='<table style="width:100%;border-collapse:collapse"><tbody>';
|
||
rows.forEach(([k,v],i)=>{const strong=i===rows.length-1?' style="font-weight:700"':'';html+=`<tr><th style="text-align:left;padding:.25rem .35rem">${k}</th><td class="num" style="padding:.25rem .35rem"${strong}>${typeof v==='number'?formatISK(v):String(v)}</td></tr>`;});
|
||
html+='</tbody></table>';
|
||
const det=document.createElement('details'); const sum=document.createElement('summary'); sum.textContent='Rohdaten';
|
||
const pre=document.createElement('pre'); pre.style.whiteSpace='pre-wrap'; pre.textContent=JSON.stringify(data,null,2);
|
||
det.appendChild(sum); det.appendChild(pre);
|
||
root.innerHTML=html; root.appendChild(det);
|
||
}
|
||
|
||
/* ===== Materialien ===== (robustes Parsing) */
|
||
function looksLikeMatRow(o){
|
||
if(!o || typeof o!=='object') return false;
|
||
const hasId = ('type_id' in o) || ('typeId' in o);
|
||
const hasQty = ('quantity' in o) || ('qty' in o) || ('amount' in o) || ('count' in o);
|
||
return !!(hasId && hasQty);
|
||
}
|
||
function dictRowsToArray(obj){
|
||
const vals = Object.values(obj||{});
|
||
if(!vals.length) return null;
|
||
if(vals.every(looksLikeMatRow)) return vals;
|
||
return null;
|
||
}
|
||
function extractMaterials(data){
|
||
if(!data) return null;
|
||
if(data.materials){
|
||
if(Array.isArray(data.materials)) return data.materials;
|
||
const arr = dictRowsToArray(data.materials); if(arr) return arr;
|
||
}
|
||
if(data.message && typeof data.message==='object'){
|
||
const m = data.message;
|
||
if(m.materials){
|
||
if(Array.isArray(m.materials)) return m.materials;
|
||
const arr = dictRowsToArray(m.materials); if(arr) return arr;
|
||
}
|
||
}
|
||
let found=null;
|
||
(function walk(x){
|
||
if(found||!x) return;
|
||
if(Array.isArray(x)){
|
||
if(x.length && looksLikeMatRow(x[0])){ found=x; return; }
|
||
for(const y of x){ walk(y); if(found) return; }
|
||
}else if(typeof x==='object'){
|
||
const asArr = dictRowsToArray(x);
|
||
if(asArr){ found=asArr; return; }
|
||
for(const v of Object.values(x)){ walk(v); if(found) return; }
|
||
}
|
||
})(data);
|
||
return found;
|
||
}
|
||
function renderMaterials(root,data){
|
||
const mats = extractMaterials(data);
|
||
let html = "";
|
||
if(!mats || !mats.length){
|
||
html += "Keine Materialien gefunden.";
|
||
} else {
|
||
const rows=mats.map(m=>{
|
||
const typeId = m.type_id ?? m.typeId ?? "";
|
||
const name = m.name ?? m.item_name ?? m.typeName ?? ('Type '+typeId);
|
||
const qty = (m.quantity ?? m.qty ?? m.amount ?? m.count ?? "");
|
||
const cost = (m.cost ?? m.cost_per_unit ?? m.unit_cost);
|
||
const costHtml = (cost!=null)?('<strong>'+formatISK(cost)+'</strong>'):"";
|
||
return `<tr><td>${name} ${typeId?`<span class="muted">(${typeId})</span>`:""}</td><td class="num">${qty}</td><td class="num">${costHtml}</td></tr>`;
|
||
}).join("");
|
||
html += '<h4 style="margin:.2rem 0 .5rem">Materialien (Manufacturing)</h4>'+
|
||
`<table style="width:100%;border-collapse:collapse"><thead><tr><th>Item</th><th class="num">Menge</th><th class="num">Kosten</th></tr></thead><tbody>${rows}</tbody></table>`;
|
||
}
|
||
const det=document.createElement('details'); const sum=document.createElement('summary'); sum.textContent='Rohdaten';
|
||
const pre=document.createElement('pre'); pre.style.whiteSpace='pre-wrap'; pre.textContent=JSON.stringify(data,null,2);
|
||
det.appendChild(sum); det.appendChild(pre);
|
||
root.innerHTML=html; root.appendChild(det);
|
||
}
|
||
|
||
function saveCache(id,payload){ fetch("/api/order/"+encodeURIComponent(id)+"/cache",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)}).catch(()=>{}); }
|
||
|
||
function loadCostsOverview(){
|
||
const box=document.getElementById("costs-box"), tbl=document.getElementById("costs-table"), body=document.getElementById("costs-body"), total=document.getElementById("costs-total"), ts=document.getElementById("costs-ts");
|
||
box.style.display="block"; box.textContent="Lade Kosten …"; tbl.style.display="none";
|
||
fetch("/api/cookbook/open_costs").then(r=>{if(!r.ok){return r.text().then(t=>{throw new Error('HTTP '+r.status+' - '+t.slice(0,200));});}return r.json();})
|
||
.then(d=>{
|
||
const items=d.items||[]; if(!items.length){box.textContent="Keine offenen Aufträge (oder keine Blueprint-IDs).";return;}
|
||
body.innerHTML=items.map(it=>{
|
||
const unit=(it.unit_cost!=null)?formatISK(it.unit_cost):"", tot=(it.total_cost!=null)?"<strong>"+formatISK(it.total_cost)+"</strong>":"";
|
||
const ind=(it.industry_structure||"")+((it.industry_rig&&it.industry_rig!=="None")?" / "+it.industry_rig:"");
|
||
const reac=(it.reaction_structure||"")+((it.reaction_rig&&it.reaction_rig!=="None")?" / "+it.reaction_rig:"");
|
||
const err=it.error?` <span class="err">(${it.error})</span>`:"";
|
||
return `<tr><td>${(it.structure||"")+err}</td><td class="num">${it.quantity||""}</td><td class="num">${it.me||""}</td><td>${it.system||""}</td><td>${ind}</td><td>${reac}</td><td class="num">${unit}</td><td class="num">${tot}</td></tr>`;
|
||
}).join("");
|
||
total.textContent=formatISK(d.total_cost||0);
|
||
ts.textContent=d.refreshed_at?("Stand: "+formatTS(d.refreshed_at)):"";
|
||
box.style.display="none"; tbl.style.display="block";
|
||
}).catch(err=>{box.innerHTML='<span class="err">Fehler: '+(err&&err.message?err.message:err)+'</span>'; tbl.style.display="none";});
|
||
}
|
||
function loadMatsOverview(){
|
||
const box=document.getElementById("mats-box"), tbl=document.getElementById("mats-table"), body=document.getElementById("mats-body"), total=document.getElementById("mats-total"), ts=document.getElementById("mats-ts");
|
||
box.style.display="block"; box.textContent="Lade Materialien …"; tbl.style.display="none";
|
||
fetch("/api/cookbook/open_materials").then(r=>{if(!r.ok){return r.text().then(t=>{throw new Error('HTTP '+r.status+' - '+t.slice(0,200));});}return r.json();})
|
||
.then(d=>{
|
||
const items=d.items||[]; if(!items.length){box.textContent="Keine Daten vorhanden.";return;}
|
||
body.innerHTML=items.map(it=>`<tr><td>${it.name||("Type "+it.type_id)} <span class="muted">(${it.type_id})</span></td><td class="num">${it.quantity!=null?it.quantity:""}</td><td class="num">${it.cost!=null?("<strong>"+formatISK(it.cost)+"</strong>"):""}</td></tr>`).join("");
|
||
total.textContent=formatISK(d.total_cost||0);
|
||
ts.textContent=d.refreshed_at?("Stand: "+formatTS(d.refreshed_at)):"";
|
||
box.style.display="none"; tbl.style.display="block";
|
||
}).catch(err=>{box.innerHTML='<span class="err">Fehler: '+(err&&err.message?err.message:err)+'</span>'; tbl.style.display="none";});
|
||
}
|
||
function refreshAll(){
|
||
const b1=document.getElementById("btn-refresh-all"), b2=document.getElementById("btn-refresh-mats");
|
||
if(b1) b1.disabled=true; if(b2) b2.disabled=true;
|
||
fetch("/api/orders/refresh_all",{method:"POST"}).then(r=>r.json()).then(()=>{
|
||
loadCostsOverview(); loadMatsOverview();
|
||
}).finally(()=>{ if(b1) b1.disabled=false; if(b2) b2.disabled=false; });
|
||
}
|
||
function refreshOne(id){
|
||
fetch("/api/order/"+encodeURIComponent(id)+"/refresh",{method:"POST"}).then(r=>r.json()).then(()=>{ loadCostsOverview(); loadMatsOverview(); });
|
||
}
|
||
document.addEventListener("DOMContentLoaded",function(){ loadCostsOverview(); loadMatsOverview(); });
|
||
</script>
|
||
</body>
|
||
</html>
|