mirror of
https://github.com/itsdave-de/msp.git
synced 2026-09-28 04:02:42 -03:00
fix(msp): Import-UI — Frappé-Tokens, Paddings, Kundennamen, Icons in Cards
Überarbeitung der Dropzone + Wizard auf Basis Benutzer-Feedback: - Horizontale Seitenpaddings (var(--padding-lg)) — vorher lag der Inhalt am Rand (page-body hat padding: 0). - Durchgängig Frappé-CSS-Variablen statt eigener Hex-Farben (--text-color, --heading-color, --text-muted, --card-bg, --subtle-fg, --border-color, --primary, --red-500 / --green-500 / --yellow-500 / --blue-500, --alert-bg-* / --alert-text-*). Dark Mode funktioniert damit out-of-the-box. - Icons in jeder Statistik-/Counter-Card (tonale Icon-Kacheln statt nur Akzentstreifen — optische Abstufung Icon → Label → Value). - Kundennamen in Top-Kunden-Liste und Dokument-Tabelle: Anzeigename oben, Kunden-Ref (CUST-…) als kleiner Zusatz in Monospace. - run_summary / run_documents / customer_context liefern jetzt customer_name per LEFT JOIN auf tabCustomer. - Dokumenten-Tabelle in einem Card-Rahmen mit Subtle-BG-Header. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9a973d92ee
commit
97971fa7ca
+25
-19
@@ -105,15 +105,17 @@ def run_documents(run_name: str) -> list[dict]:
|
||||
return []
|
||||
rows = frappe.db.sql(
|
||||
"""
|
||||
SELECT customer, target_doc_type, target_doc_name,
|
||||
COUNT(*) AS line_count, SUM(COALESCE(amount, 0)) AS total_amount
|
||||
FROM `tabSupplier Import Line`
|
||||
WHERE parent = %(run)s
|
||||
AND line_status = 'Created'
|
||||
AND target_doc_name IS NOT NULL
|
||||
AND target_doc_name != ''
|
||||
GROUP BY customer, target_doc_type, target_doc_name
|
||||
ORDER BY customer
|
||||
SELECT l.customer, c.customer_name,
|
||||
l.target_doc_type, l.target_doc_name,
|
||||
COUNT(*) AS line_count, SUM(COALESCE(l.amount, 0)) AS total_amount
|
||||
FROM `tabSupplier Import Line` l
|
||||
LEFT JOIN `tabCustomer` c ON c.name = l.customer
|
||||
WHERE l.parent = %(run)s
|
||||
AND l.line_status = 'Created'
|
||||
AND l.target_doc_name IS NOT NULL
|
||||
AND l.target_doc_name != ''
|
||||
GROUP BY l.customer, c.customer_name, l.target_doc_type, l.target_doc_name
|
||||
ORDER BY COALESCE(c.customer_name, l.customer)
|
||||
""",
|
||||
{"run": run_name},
|
||||
as_dict=True,
|
||||
@@ -258,10 +260,12 @@ def customer_context(customer_refs: str | list) -> list[dict]:
|
||||
)
|
||||
cov_map = {r["customer"]: r for r in coverage}
|
||||
|
||||
customers_exist = set(frappe.db.sql_list(
|
||||
"SELECT name FROM tabCustomer WHERE name IN %(refs)s",
|
||||
customer_rows = frappe.db.sql(
|
||||
"SELECT name, customer_name FROM `tabCustomer` WHERE name IN %(refs)s",
|
||||
{"refs": tuple(refs)},
|
||||
))
|
||||
as_dict=True,
|
||||
)
|
||||
customer_name_map = {r["name"]: r["customer_name"] for r in customer_rows}
|
||||
|
||||
rows = []
|
||||
for ref in refs:
|
||||
@@ -269,7 +273,8 @@ def customer_context(customer_refs: str | list) -> list[dict]:
|
||||
c = cov_map.get(ref, {})
|
||||
rows.append({
|
||||
"customer": ref,
|
||||
"exists": ref in customers_exist,
|
||||
"customer_name": customer_name_map.get(ref),
|
||||
"exists": ref in customer_name_map,
|
||||
"subscriptions": int(s.get("n_subscriptions") or 0),
|
||||
"subscription_qty": int(s.get("qty_total") or 0),
|
||||
"coverages": int(c.get("n_coverages") or 0),
|
||||
@@ -299,13 +304,14 @@ def run_summary(run_name: str, top_n: int = 5) -> dict:
|
||||
)
|
||||
top = frappe.db.sql(
|
||||
"""
|
||||
SELECT customer,
|
||||
COUNT(DISTINCT target_doc_name) AS documents,
|
||||
SELECT l.customer, c.customer_name,
|
||||
COUNT(DISTINCT l.target_doc_name) AS documents,
|
||||
COUNT(*) AS line_count,
|
||||
COALESCE(SUM(amount), 0) AS total_amount
|
||||
FROM `tabSupplier Import Line`
|
||||
WHERE parent = %(run)s AND line_status = 'Created'
|
||||
GROUP BY customer
|
||||
COALESCE(SUM(l.amount), 0) AS total_amount
|
||||
FROM `tabSupplier Import Line` l
|
||||
LEFT JOIN `tabCustomer` c ON c.name = l.customer
|
||||
WHERE l.parent = %(run)s AND l.line_status = 'Created'
|
||||
GROUP BY l.customer, c.customer_name
|
||||
ORDER BY total_amount DESC
|
||||
LIMIT %(limit)s
|
||||
""",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* 2. Import läuft
|
||||
* 3. Ergebnis mit Counters, Top-Kunden, Dokumenten-Tabelle
|
||||
*
|
||||
* Design: Editorial Data. Große Zahlen als Anker, dezente Akzentfarben.
|
||||
* Nutzt Frappé-CSS-Variablen (--text-color, --card-bg, --primary, --red-500 …)
|
||||
* für Light-/Dark-Mode-Kompatibilität und konsistente Optik mit dem Desk.
|
||||
*/
|
||||
|
||||
frappe.pages["import-assistant"].on_page_load = function (wrapper) {
|
||||
@@ -22,7 +23,7 @@ frappe.pages["import-assistant"].on_page_load = function (wrapper) {
|
||||
root.innerHTML = `
|
||||
<div class="msp-wizard">
|
||||
<div class="msp-empty">
|
||||
<div class="msp-empty__icon">↩</div>
|
||||
<div class="msp-empty__icon">${ICON.arrow_left}</div>
|
||||
<h2 class="msp-empty__title">${__("Kein Kontext")}</h2>
|
||||
<p class="msp-empty__text">
|
||||
${__("Bitte starte den Import-Flow über die Dropzone.")}
|
||||
@@ -178,35 +179,32 @@ function render_config_panel(ctx) {
|
||||
|
||||
function render_hero_stats(p) {
|
||||
const cells = [];
|
||||
if (p.line_count != null) cells.push(stat_cell(__("Zeilen"), p.line_count));
|
||||
if (p.customer_count != null) cells.push(stat_cell(__("Kunden"), p.customer_count));
|
||||
if (p.product_count != null) cells.push(stat_cell(__("Produkte"), p.product_count));
|
||||
if (p.period_label) cells.push(stat_cell(__("Zeitraum"), p.period_label, true));
|
||||
if (p.credit_note_lines) cells.push(stat_cell(__("Gutschriften"), p.credit_note_lines, false, "accent"));
|
||||
if (p.line_count != null) cells.push(stat_cell(__("Zeilen"), p.line_count, false, "primary", ICON.rows));
|
||||
if (p.customer_count != null) cells.push(stat_cell(__("Kunden"), p.customer_count, false, "success", ICON.users));
|
||||
if (p.product_count != null) cells.push(stat_cell(__("Produkte"), p.product_count, false, "info", ICON.box));
|
||||
if (p.period_label) cells.push(stat_cell(__("Zeitraum"), p.period_label, true, "neutral", ICON.calendar));
|
||||
if (p.credit_note_lines) cells.push(stat_cell(__("Gutschriften"), p.credit_note_lines, false, "accent", ICON.refund));
|
||||
if (!cells.length) return "";
|
||||
return `<div class="msp-hero-grid">${cells.join("")}</div>`;
|
||||
}
|
||||
|
||||
function stat_cell(label, value, is_text, accent) {
|
||||
function stat_cell(label, value, is_text, tone, icon) {
|
||||
const v = is_text ? frappe.utils.escape_html(String(value)) : format_int(value);
|
||||
return `
|
||||
<div class="msp-hero-stat ${accent ? `msp-hero-stat--${accent}` : ""}">
|
||||
<div class="msp-hero-stat msp-hero-stat--${tone || "neutral"}">
|
||||
<div class="msp-hero-stat__head">
|
||||
<span class="msp-hero-stat__icon">${icon || ""}</span>
|
||||
<span class="msp-hero-stat__label">${frappe.utils.escape_html(label)}</span>
|
||||
</div>
|
||||
<div class="msp-hero-stat__value">${v}</div>
|
||||
<div class="msp-hero-stat__label">${frappe.utils.escape_html(label)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function enhance_with_customer_context(root, ctx) {
|
||||
// Der Preview kennt nur customer_count, nicht die einzelnen Refs. Wir
|
||||
// holen uns die Liste indirekt aus der Datei (erneute Analyse reicht nicht
|
||||
// — wir haben die Rows dort nicht). Um den Wizard trotzdem aussagekräftig
|
||||
// zu machen: neuer Endpoint analyze_file hat das nicht. Also machen wir
|
||||
// hier einen schnellen SQL-Call per whitelisted Helper: wir nutzen die
|
||||
// Preview-Customers nicht, weil sie nicht übermittelt werden. Stattdessen
|
||||
// lassen wir den Block dynamisch blenden, wenn das Frontend die Refs
|
||||
// nicht hat. (Erweiterung für später: analyze_file liefert auch die Liste.)
|
||||
// Bleibt vorerst hidden.
|
||||
// Vorerst deaktiviert — die Preview liefert uns keine einzelnen Kunden-Refs.
|
||||
// Erweiterungspunkt für später: analyze_file könnte die Refs mit zurückgeben,
|
||||
// dann zeigen wir hier bekannte vs. neue Kunden an.
|
||||
void root; void ctx;
|
||||
}
|
||||
|
||||
@@ -329,20 +327,20 @@ async function show_result(root, state, ctx) {
|
||||
|
||||
<div class="msp-counters">
|
||||
<div class="msp-counter msp-counter--primary">
|
||||
<div class="msp-counter__head">${ICON.doc} <span>${__("Dokumente")}</span></div>
|
||||
<div class="msp-counter__value">${format_int(r.document_count || 0)}</div>
|
||||
<div class="msp-counter__label">${__("Dokumente")}</div>
|
||||
</div>
|
||||
<div class="msp-counter">
|
||||
<div class="msp-counter msp-counter--neutral">
|
||||
<div class="msp-counter__head">${ICON.rows} <span>${__("Zeilen gesamt")}</span></div>
|
||||
<div class="msp-counter__value">${format_int(r.line_count || 0)}</div>
|
||||
<div class="msp-counter__label">${__("Zeilen gesamt")}</div>
|
||||
</div>
|
||||
<div class="msp-counter">
|
||||
<div class="msp-counter msp-counter--muted">
|
||||
<div class="msp-counter__head">${ICON.skip} <span>${__("Übersprungen")}</span></div>
|
||||
<div class="msp-counter__value">${format_int(r.skipped_count || 0)}</div>
|
||||
<div class="msp-counter__label">${__("Übersprungen")}</div>
|
||||
</div>
|
||||
<div class="msp-counter ${r.error_count ? 'msp-counter--bad' : ''}">
|
||||
<div class="msp-counter ${r.error_count ? 'msp-counter--bad' : 'msp-counter--ok'}">
|
||||
<div class="msp-counter__head">${r.error_count ? ICON.warning : ICON.check} <span>${__("Fehler")}</span></div>
|
||||
<div class="msp-counter__value">${format_int(r.error_count || 0)}</div>
|
||||
<div class="msp-counter__label">${__("Fehler")}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -387,11 +385,14 @@ async function load_summary(panel, run_name) {
|
||||
const max = Math.max(...top.map((c) => Number(c.total_amount) || 0), 1);
|
||||
const html = top.map((c, idx) => {
|
||||
const pct = ((Number(c.total_amount) || 0) / max * 100).toFixed(1);
|
||||
const display = c.customer_name || c.customer;
|
||||
const ref = c.customer_name ? c.customer : "";
|
||||
return `
|
||||
<div class="msp-top-row">
|
||||
<div class="msp-top-row__rank">${idx + 1}</div>
|
||||
<a class="msp-top-row__customer" href="/app/customer/${encodeURIComponent(c.customer)}">
|
||||
${frappe.utils.escape_html(c.customer)}
|
||||
<span class="msp-top-row__name">${frappe.utils.escape_html(display)}</span>
|
||||
${ref ? `<span class="msp-top-row__ref">${frappe.utils.escape_html(ref)}</span>` : ""}
|
||||
</a>
|
||||
<div class="msp-top-row__bar">
|
||||
<div class="msp-top-row__bar-fill" style="width: ${pct}%"></div>
|
||||
@@ -423,7 +424,7 @@ async function load_documents(panel, run_name) {
|
||||
args: { run_name },
|
||||
});
|
||||
const entries = (response.message || []).sort((a, b) =>
|
||||
String(a.customer || "").localeCompare(String(b.customer || ""))
|
||||
String(a.customer_name || a.customer || "").localeCompare(String(b.customer_name || b.customer || ""))
|
||||
);
|
||||
const body = panel.querySelector(".msp-documents__body");
|
||||
if (!entries.length) {
|
||||
@@ -431,6 +432,7 @@ async function load_documents(panel, run_name) {
|
||||
return;
|
||||
}
|
||||
body.innerHTML = `
|
||||
<div class="msp-table-wrap">
|
||||
<table class="msp-table">
|
||||
<thead><tr>
|
||||
<th>${__("Kunde")}</th>
|
||||
@@ -439,23 +441,52 @@ async function load_documents(panel, run_name) {
|
||||
<th class="num">${__("Summe (netto)")}</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
${entries.map((e) => `
|
||||
<tr>
|
||||
<td><a href="/app/customer/${encodeURIComponent(e.customer || "")}">${frappe.utils.escape_html(e.customer || "")}</a></td>
|
||||
<td><a href="/app/${slug(e.target_doc_type)}/${encodeURIComponent(e.target_doc_name)}">
|
||||
<span class="msp-doc-type">${frappe.utils.escape_html(e.target_doc_type)}</span>
|
||||
<span class="msp-doc-name">${frappe.utils.escape_html(e.target_doc_name)}</span>
|
||||
</a></td>
|
||||
<td class="num">${format_int(e.line_count || 0)}</td>
|
||||
<td class="num">${format_eur(e.total_amount)}</td>
|
||||
</tr>
|
||||
`).join("")}
|
||||
${entries.map((e) => {
|
||||
const name = e.customer_name || e.customer || "";
|
||||
const ref = e.customer_name && e.customer ? e.customer : "";
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
<a class="msp-cust-link" href="/app/customer/${encodeURIComponent(e.customer || "")}">
|
||||
<span class="msp-cust-name">${frappe.utils.escape_html(name)}</span>
|
||||
${ref ? `<span class="msp-cust-ref">${frappe.utils.escape_html(ref)}</span>` : ""}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/app/${slug(e.target_doc_type)}/${encodeURIComponent(e.target_doc_name)}">
|
||||
<span class="msp-doc-type">${frappe.utils.escape_html(e.target_doc_type)}</span>
|
||||
<span class="msp-doc-name">${frappe.utils.escape_html(e.target_doc_name)}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="num">${format_int(e.line_count || 0)}</td>
|
||||
<td class="num">${format_eur(e.total_amount)}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
} catch (err) { console.error(err); }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Icons
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const ICON = {
|
||||
arrow_left: `<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>`,
|
||||
rows: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/></svg>`,
|
||||
users: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9.5" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9"/><path d="M16 3.1a4 4 0 0 1 0 7.8"/></svg>`,
|
||||
box: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="M3.3 7l8.7 5 8.7-5"/><path d="M12 22V12"/></svg>`,
|
||||
calendar: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>`,
|
||||
refund: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9h13a5 5 0 0 1 0 10h-2"/><path d="M7 5L3 9l4 4"/></svg>`,
|
||||
doc: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8M8 17h6"/></svg>`,
|
||||
skip: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 4l10 8-10 8V4z"/><path d="M19 5v14"/></svg>`,
|
||||
warning: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`,
|
||||
check: `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5L20 7"/></svg>`,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Utils
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -478,8 +509,8 @@ function filename_of(url) {
|
||||
function icon_for(url) {
|
||||
const n = (url || "").toLowerCase();
|
||||
if (n.endsWith(".zip"))
|
||||
return "<svg viewBox='0 0 24 24' width='20' height='20' fill='none' stroke='currentColor' stroke-width='1.6'><rect x='4' y='3' width='16' height='18' rx='2'/><path d='M10 3v4h4V3'/><path d='M10 9v2h4v2h-4v2h4v2h-4'/></svg>";
|
||||
return "<svg viewBox='0 0 24 24' width='20' height='20' fill='none' stroke='currentColor' stroke-width='1.6'><path d='M6 3h9l4 4v14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z'/><path d='M15 3v4h4'/><path d='M8 14h8M8 18h6'/></svg>";
|
||||
return "<svg viewBox='0 0 24 24' width='22' height='22' fill='none' stroke='currentColor' stroke-width='1.6'><rect x='4' y='3' width='16' height='18' rx='2'/><path d='M10 3v4h4V3'/><path d='M10 9v2h4v2h-4v2h4v2h-4'/></svg>";
|
||||
return "<svg viewBox='0 0 24 24' width='22' height='22' fill='none' stroke='currentColor' stroke-width='1.6'><path d='M6 3h9l4 4v14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z'/><path d='M15 3v4h4'/><path d='M8 14h8M8 18h6'/></svg>";
|
||||
}
|
||||
|
||||
function slug(doctype) {
|
||||
@@ -498,57 +529,48 @@ function format_eur(v) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Styles
|
||||
// Styles — Frappé-Variablen (Light/Dark-kompatibel)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function inject_style(root) {
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
.msp-wizard {
|
||||
--ink: #0f172a;
|
||||
--ink-soft: #475569;
|
||||
--mute: #94a3b8;
|
||||
--line: #e2e8f0;
|
||||
--surface: #ffffff;
|
||||
--surface-alt: #f8fafc;
|
||||
--primary: var(--primary, #2490ef);
|
||||
--primary-ink: #0b4a8a;
|
||||
--accent: #e73249;
|
||||
--ok: #047857;
|
||||
--warn: #b45309;
|
||||
--danger: #b91c1c;
|
||||
--muted-bg: #f1f5f9;
|
||||
--msp-accent: #e73249;
|
||||
--msp-radius: 10px;
|
||||
--msp-shadow-card: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
|
||||
max-width: 1080px;
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
padding: 4px 4px 40px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
color: var(--ink);
|
||||
padding: var(--padding-sm) var(--padding-lg) var(--padding-2xl);
|
||||
color: var(--text-color);
|
||||
font-family: inherit;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.msp-wizard { padding: var(--padding-xs) var(--padding-md) var(--padding-xl); }
|
||||
}
|
||||
|
||||
.msp-empty {
|
||||
min-height: 360px;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 14px;
|
||||
text-align: center;
|
||||
gap: 14px; text-align: center;
|
||||
}
|
||||
.msp-empty__icon { font-size: 32px; color: var(--mute); }
|
||||
.msp-empty__title { font-size: 20px; font-weight: 600; margin: 0; color: var(--ink); }
|
||||
.msp-empty__text { color: var(--ink-soft); font-size: 14px; margin: 0; }
|
||||
.msp-empty__icon { color: var(--text-muted); }
|
||||
.msp-empty__title { font-size: 20px; font-weight: 600; margin: 0; color: var(--heading-color); }
|
||||
.msp-empty__text { color: var(--text-muted); font-size: 14px; margin: 0; }
|
||||
|
||||
/* ---- Header + Stepper -------------------------------------- */
|
||||
/* ---------- Header + Stepper ---------- */
|
||||
.msp-wiz-head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 32px;
|
||||
gap: var(--padding-xl);
|
||||
align-items: center;
|
||||
padding: 24px 0 20px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
margin-bottom: 24px;
|
||||
padding: var(--padding-lg) 0 var(--padding-md);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
margin-bottom: var(--padding-lg);
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.msp-wiz-head { grid-template-columns: 1fr; gap: 16px; }
|
||||
.msp-wiz-head { grid-template-columns: 1fr; gap: var(--padding-md); }
|
||||
}
|
||||
.msp-wiz-file {
|
||||
display: flex; gap: 12px; align-items: center;
|
||||
@@ -556,100 +578,125 @@ function inject_style(root) {
|
||||
}
|
||||
.msp-wiz-file__icon {
|
||||
width: 44px; height: 44px; border-radius: 10px;
|
||||
background: var(--muted-bg);
|
||||
background: var(--subtle-fg);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: var(--ink-soft);
|
||||
color: var(--text-muted);
|
||||
flex: none;
|
||||
}
|
||||
.msp-wiz-file__meta { min-width: 0; }
|
||||
.msp-wiz-file__name {
|
||||
font-size: 15px; font-weight: 600; color: var(--ink);
|
||||
font-size: 15px; font-weight: 600; color: var(--heading-color);
|
||||
letter-spacing: -0.01em;
|
||||
white-space: nowrap; text-overflow: ellipsis; overflow: hidden;
|
||||
}
|
||||
.msp-wiz-file__handler {
|
||||
font-size: 12.5px; color: var(--ink-soft);
|
||||
}
|
||||
.msp-wiz-file__handler { font-size: 12.5px; color: var(--text-muted); }
|
||||
|
||||
.msp-steps {
|
||||
list-style: none;
|
||||
display: flex; gap: 24px;
|
||||
display: flex; gap: var(--padding-lg);
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
.msp-step {
|
||||
display: flex; gap: 10px; align-items: center;
|
||||
font-size: 13px; color: var(--mute);
|
||||
font-size: 13px; color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.msp-step span {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
min-width: 26px; height: 26px;
|
||||
padding: 0 7px;
|
||||
border: 1px solid var(--line);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-family: "SF Mono", Menlo, Consolas, monospace;
|
||||
color: var(--mute);
|
||||
font-family: var(--font-stack-monospace, "SF Mono", Menlo, Consolas, monospace);
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
background: var(--surface);
|
||||
background: var(--card-bg);
|
||||
}
|
||||
.msp-step.is-active { color: var(--heading-color); font-weight: 600; }
|
||||
.msp-step.is-active span {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
background: var(--alert-bg-info);
|
||||
}
|
||||
.msp-step.is-done span {
|
||||
background: var(--green-500);
|
||||
border-color: var(--green-500);
|
||||
color: #fff;
|
||||
}
|
||||
.msp-step.is-active { color: var(--ink); font-weight: 600; }
|
||||
.msp-step.is-active span { border-color: var(--primary); color: var(--primary); background: rgba(36, 144, 239, 0.06); }
|
||||
.msp-step.is-done span { background: var(--ok); border-color: var(--ok); color: #fff; }
|
||||
|
||||
/* ---- Config Panel ----------------------------------------- */
|
||||
/* ---------- Config Panel ---------- */
|
||||
.msp-config { animation: msp-fade-in .3s ease both; }
|
||||
.msp-h3 {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .12em;
|
||||
color: var(--mute);
|
||||
letter-spacing: .1em;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
margin: 0 0 10px 0;
|
||||
margin: 0 0 var(--padding-sm) 0;
|
||||
}
|
||||
|
||||
.msp-hero-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 28px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: var(--padding-sm);
|
||||
margin-bottom: var(--padding-xl);
|
||||
}
|
||||
.msp-hero-stat {
|
||||
position: relative;
|
||||
padding: 14px 14px 12px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: var(--padding-md);
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--msp-radius);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--msp-shadow-card);
|
||||
}
|
||||
.msp-hero-stat::before {
|
||||
content: "";
|
||||
position: absolute; left: 0; top: 0; bottom: 0;
|
||||
width: 3px; background: var(--line);
|
||||
content: ""; position: absolute; left: 0; top: 0; bottom: 0;
|
||||
width: 3px; background: var(--border-color);
|
||||
}
|
||||
.msp-hero-stat--accent::before { background: var(--accent); }
|
||||
.msp-hero-stat__value {
|
||||
font-size: 26px; font-weight: 600; line-height: 1.1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
.msp-hero-stat--primary::before { background: var(--primary); }
|
||||
.msp-hero-stat--success::before { background: var(--green-500); }
|
||||
.msp-hero-stat--info::before { background: var(--blue-500); }
|
||||
.msp-hero-stat--accent::before { background: var(--msp-accent); }
|
||||
.msp-hero-stat--neutral::before { background: var(--border-color); }
|
||||
|
||||
.msp-hero-stat__head {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.msp-hero-stat--accent .msp-hero-stat__value { color: var(--accent); }
|
||||
.msp-hero-stat__icon {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 22px; height: 22px; border-radius: 5px;
|
||||
background: var(--subtle-fg);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.msp-hero-stat--primary .msp-hero-stat__icon { background: var(--alert-bg-info); color: var(--alert-text-info); }
|
||||
.msp-hero-stat--success .msp-hero-stat__icon { background: var(--alert-bg-success); color: var(--alert-text-success); }
|
||||
.msp-hero-stat--info .msp-hero-stat__icon { background: var(--alert-bg-info); color: var(--alert-text-info); }
|
||||
.msp-hero-stat--accent .msp-hero-stat__icon { background: var(--alert-bg-danger); color: var(--msp-accent); }
|
||||
|
||||
.msp-hero-stat__label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
color: var(--mute);
|
||||
letter-spacing: .08em;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.msp-hero-stat__value {
|
||||
font-size: 24px; font-weight: 700; line-height: 1.1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
.msp-hero-stat--accent .msp-hero-stat__value { color: var(--msp-accent); }
|
||||
|
||||
.msp-context { margin: 28px 0; }
|
||||
.msp-context { margin: var(--padding-lg) 0; }
|
||||
|
||||
.msp-config__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
gap: var(--padding-lg);
|
||||
max-width: 720px;
|
||||
}
|
||||
@media (max-width: 640px) { .msp-config__grid { grid-template-columns: 1fr; } }
|
||||
@@ -657,32 +704,32 @@ function inject_style(root) {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
color: var(--mute);
|
||||
letter-spacing: .08em;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.msp-field--wide { grid-column: 1 / -1; }
|
||||
.msp-check { display: flex; align-items: center; gap: 8px; cursor: pointer; }
|
||||
.msp-check span { font-size: 13.5px; color: var(--ink); font-weight: 500; text-transform: none; letter-spacing: 0; }
|
||||
.msp-check span { font-size: 13.5px; color: var(--text-color); font-weight: 500; text-transform: none; letter-spacing: 0; }
|
||||
.msp-hint {
|
||||
font-size: 12px; color: var(--mute); margin-top: 6px;
|
||||
font-size: 12px; color: var(--text-muted); margin-top: 6px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.msp-hint--warn { color: var(--warn); }
|
||||
.msp-hint--warn { color: var(--alert-text-warning); }
|
||||
|
||||
.msp-config__foot {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-top: 32px; padding-top: 20px;
|
||||
border-top: 1px solid var(--line);
|
||||
margin-top: var(--padding-xl); padding-top: var(--padding-md);
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
.msp-back {
|
||||
font-size: 13px; color: var(--ink-soft); text-decoration: none;
|
||||
font-size: 13px; color: var(--text-muted); text-decoration: none;
|
||||
}
|
||||
.msp-back:hover { color: var(--primary); }
|
||||
.arr { margin-left: 4px; }
|
||||
|
||||
/* ---- Running Panel ---------------------------------------- */
|
||||
/* ---------- Running Panel ---------- */
|
||||
.msp-running {
|
||||
min-height: 320px;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
@@ -691,92 +738,117 @@ function inject_style(root) {
|
||||
}
|
||||
.msp-running__spin {
|
||||
width: 40px; height: 40px; border-radius: 50%;
|
||||
border: 3px solid var(--line); border-top-color: var(--primary);
|
||||
border: 3px solid var(--border-color); border-top-color: var(--primary);
|
||||
animation: msp-spin .9s linear infinite;
|
||||
}
|
||||
.msp-running__label { font-size: 18px; font-weight: 600; color: var(--ink); }
|
||||
.msp-running__hint { font-size: 13px; color: var(--ink-soft); }
|
||||
.msp-running__label { font-size: 18px; font-weight: 600; color: var(--heading-color); }
|
||||
.msp-running__hint { font-size: 13px; color: var(--text-muted); }
|
||||
|
||||
/* ---- Result Panel ----------------------------------------- */
|
||||
/* ---------- Result Panel ---------- */
|
||||
.msp-result { animation: msp-fade-in .3s ease both; }
|
||||
.msp-result__head {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
gap: 20px; flex-wrap: wrap;
|
||||
padding-bottom: 20px; border-bottom: 1px solid var(--line);
|
||||
margin-bottom: 24px;
|
||||
gap: var(--padding-md); flex-wrap: wrap;
|
||||
padding-bottom: var(--padding-md); border-bottom: 1px solid var(--border-color);
|
||||
margin-bottom: var(--padding-lg);
|
||||
}
|
||||
.msp-result__status { display: flex; gap: 14px; align-items: center; }
|
||||
.msp-result__icon {
|
||||
width: 54px; height: 54px; border-radius: 50%;
|
||||
width: 52px; height: 52px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: var(--muted-bg); color: var(--ink-soft);
|
||||
background: var(--subtle-fg); color: var(--text-muted);
|
||||
flex: none;
|
||||
}
|
||||
.msp-result--ok .msp-result__icon { background: #dcfce7; color: var(--ok); }
|
||||
.msp-result--warn .msp-result__icon { background: #fef3c7; color: var(--warn); }
|
||||
.msp-result--error .msp-result__icon { background: #fee2e2; color: var(--danger); }
|
||||
.msp-result__title { font-size: 18px; font-weight: 600; color: var(--ink); }
|
||||
.msp-result__sub { font-size: 13px; color: var(--ink-soft); margin-top: 2px; }
|
||||
.msp-result__sub a { font-family: "SF Mono", Menlo, Consolas, monospace; }
|
||||
.msp-result--ok .msp-result__icon { background: var(--alert-bg-success); color: var(--alert-text-success); }
|
||||
.msp-result--warn .msp-result__icon { background: var(--alert-bg-warning); color: var(--alert-text-warning); }
|
||||
.msp-result--error .msp-result__icon { background: var(--alert-bg-danger); color: var(--alert-text-danger); }
|
||||
.msp-result__title { font-size: 18px; font-weight: 600; color: var(--heading-color); }
|
||||
.msp-result__sub { font-size: 13px; color: var(--text-muted); margin-top: 2px; }
|
||||
.msp-result__sub a { font-family: var(--font-stack-monospace, "SF Mono", Menlo, Consolas, monospace); }
|
||||
.msp-result__cta { display: flex; gap: 8px; }
|
||||
.msp-result__msg {
|
||||
background: #fef2f2; color: var(--danger);
|
||||
padding: 10px 14px; border-radius: 6px; margin-bottom: 16px;
|
||||
background: var(--alert-bg-danger); color: var(--alert-text-danger);
|
||||
padding: 10px 14px; border-radius: 6px; margin-bottom: var(--padding-md);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ---------- Counters ---------- */
|
||||
.msp-counters {
|
||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
|
||||
margin-bottom: 28px;
|
||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--padding-sm);
|
||||
margin-bottom: var(--padding-xl);
|
||||
}
|
||||
@media (max-width: 640px) { .msp-counters { grid-template-columns: repeat(2, 1fr); } }
|
||||
.msp-counter {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 18px 16px 16px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--msp-radius);
|
||||
padding: var(--padding-md);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--msp-shadow-card);
|
||||
}
|
||||
.msp-counter::before {
|
||||
content: ""; position: absolute; left: 0; top: 0; bottom: 0;
|
||||
width: 3px; background: var(--line);
|
||||
width: 3px; background: var(--border-color);
|
||||
}
|
||||
.msp-counter--primary::before { background: var(--primary); }
|
||||
.msp-counter--bad::before { background: var(--danger); }
|
||||
.msp-counter--ok::before { background: var(--green-500); }
|
||||
.msp-counter--bad::before { background: var(--red-500); }
|
||||
.msp-counter--muted::before { background: var(--border-color); }
|
||||
|
||||
.msp-counter__head {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase; letter-spacing: .08em;
|
||||
color: var(--text-muted); font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.msp-counter__head svg { color: var(--text-muted); }
|
||||
.msp-counter--primary .msp-counter__head svg { color: var(--primary); }
|
||||
.msp-counter--ok .msp-counter__head svg { color: var(--green-500); }
|
||||
.msp-counter--bad .msp-counter__head svg { color: var(--red-500); }
|
||||
.msp-counter__value {
|
||||
font-size: 34px; font-weight: 600; line-height: 1;
|
||||
font-size: 32px; font-weight: 700; line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
.msp-counter--bad .msp-counter__value { color: var(--danger); }
|
||||
.msp-counter__label {
|
||||
font-size: 11px; text-transform: uppercase; letter-spacing: .1em;
|
||||
color: var(--mute); font-weight: 600; margin-top: 6px;
|
||||
}
|
||||
.msp-counter--bad .msp-counter__value { color: var(--red-600, var(--red-500)); }
|
||||
|
||||
/* ---- Summary / Top customers ------------------------------ */
|
||||
.msp-summary { margin: 28px 0 32px; }
|
||||
/* ---------- Top-Customer-Liste ---------- */
|
||||
.msp-summary { margin: var(--padding-lg) 0 var(--padding-xl); }
|
||||
.msp-top-row {
|
||||
display: grid;
|
||||
grid-template-columns: 28px minmax(150px, 1fr) minmax(120px, 2fr) auto auto;
|
||||
gap: 12px;
|
||||
grid-template-columns: 28px minmax(180px, 1.4fr) minmax(120px, 2fr) auto auto;
|
||||
gap: var(--padding-sm);
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-size: 13.5px;
|
||||
}
|
||||
.msp-top-row:last-child { border-bottom: 0; }
|
||||
.msp-top-row__rank {
|
||||
font-family: "SF Mono", Menlo, Consolas, monospace;
|
||||
color: var(--mute);
|
||||
font-family: var(--font-stack-monospace, "SF Mono", Menlo, Consolas, monospace);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.msp-top-row__customer { color: var(--ink); text-decoration: none; font-weight: 500; }
|
||||
.msp-top-row__customer:hover { color: var(--primary); }
|
||||
.msp-top-row__customer {
|
||||
display: flex; flex-direction: column; gap: 1px;
|
||||
text-decoration: none; color: var(--text-color);
|
||||
min-width: 0;
|
||||
}
|
||||
.msp-top-row__customer:hover .msp-top-row__name { color: var(--primary); }
|
||||
.msp-top-row__name {
|
||||
font-weight: 600; color: var(--text-color);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.msp-top-row__ref {
|
||||
font-size: 11px; color: var(--text-muted);
|
||||
font-family: var(--font-stack-monospace, "SF Mono", Menlo, Consolas, monospace);
|
||||
}
|
||||
.msp-top-row__bar {
|
||||
height: 6px; background: var(--muted-bg); border-radius: 999px;
|
||||
height: 6px; background: var(--subtle-fg); border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.msp-top-row__bar-fill {
|
||||
@@ -784,59 +856,78 @@ function inject_style(root) {
|
||||
border-radius: 999px;
|
||||
transition: width .4s ease;
|
||||
}
|
||||
.msp-top-row__sub { color: var(--mute); font-size: 12px; white-space: nowrap; }
|
||||
.msp-top-row__sub { color: var(--text-muted); font-size: 12px; white-space: nowrap; }
|
||||
.msp-top-row__amount {
|
||||
font-weight: 600; font-variant-numeric: tabular-nums;
|
||||
min-width: 100px; text-align: right;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
.msp-top-foot {
|
||||
display: flex; gap: 18px; justify-content: flex-end;
|
||||
padding-top: 12px; font-size: 12.5px; color: var(--ink-soft);
|
||||
display: flex; gap: var(--padding-md); justify-content: flex-end;
|
||||
padding-top: 12px; font-size: 12.5px; color: var(--text-muted);
|
||||
}
|
||||
.msp-top-foot strong { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.msp-top-foot strong { color: var(--heading-color); font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
|
||||
/* ---- Documents table -------------------------------------- */
|
||||
.msp-documents { margin-top: 16px; }
|
||||
/* ---------- Documents table ---------- */
|
||||
.msp-documents { margin-top: var(--padding-md); }
|
||||
.msp-table-wrap {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--msp-radius);
|
||||
overflow: hidden;
|
||||
background: var(--card-bg);
|
||||
box-shadow: var(--msp-shadow-card);
|
||||
}
|
||||
.msp-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.msp-table th {
|
||||
text-align: left;
|
||||
font-size: 11px; text-transform: uppercase; letter-spacing: .1em;
|
||||
color: var(--mute); font-weight: 600;
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
|
||||
color: var(--text-muted); font-weight: 600;
|
||||
padding: 10px var(--padding-md);
|
||||
background: var(--subtle-accent);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
.msp-table td {
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--ink);
|
||||
padding: 10px var(--padding-md);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.msp-table tr:hover td { background: var(--surface-alt); }
|
||||
.msp-table tbody tr:last-child td { border-bottom: 0; }
|
||||
.msp-table tr:hover td { background: var(--highlight-color); }
|
||||
.msp-table td.num, .msp-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.msp-table a { color: var(--ink); text-decoration: none; }
|
||||
.msp-table a { color: var(--text-color); text-decoration: none; }
|
||||
.msp-table a:hover { color: var(--primary); }
|
||||
|
||||
.msp-cust-link { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
||||
.msp-cust-name { font-weight: 500; color: var(--text-color); }
|
||||
.msp-cust-ref {
|
||||
font-size: 11px; color: var(--text-muted);
|
||||
font-family: var(--font-stack-monospace, "SF Mono", Menlo, Consolas, monospace);
|
||||
}
|
||||
|
||||
.msp-doc-type {
|
||||
display: inline-block;
|
||||
padding: 1px 7px;
|
||||
background: var(--muted-bg);
|
||||
color: var(--ink-soft);
|
||||
background: var(--subtle-fg);
|
||||
color: var(--text-muted);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.msp-doc-name {
|
||||
font-family: "SF Mono", Menlo, Consolas, monospace;
|
||||
font-family: var(--font-stack-monospace, "SF Mono", Menlo, Consolas, monospace);
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-soft);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.muted { color: var(--mute); }
|
||||
.muted { color: var(--text-muted); }
|
||||
.num { text-align: right; }
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 10px; height: 10px; border-radius: 50%;
|
||||
border: 1.5px solid var(--line);
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-top-color: var(--primary);
|
||||
animation: msp-spin .8s linear infinite;
|
||||
}
|
||||
@@ -844,11 +935,11 @@ function inject_style(root) {
|
||||
|
||||
@keyframes msp-fade-in {
|
||||
from { opacity: 0; transform: translateY(4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes msp-spin {
|
||||
from { transform: rotate(0); }
|
||||
to { transform: rotate(360deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
`;
|
||||
root.appendChild(style);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Frappe-Page „Lieferanten-Import" — Drag-&-Drop-Dropzone mit Dashboard.
|
||||
*
|
||||
* Layout-Konzept: Editorial Data. Große Zahlen als visueller Anker,
|
||||
* klare Typografie-Hierarchie, Grautöne für Struktur, ERPNext-Blau als
|
||||
* Primär, itsdave-Rot als sparsam eingesetzter Akzent.
|
||||
* Nutzt durchgehend Frappé-CSS-Variablen (--text-color, --card-bg, --primary,
|
||||
* --red-500, --green-500 …), damit Dark Mode und Theme-Switch out-of-the-box
|
||||
* funktionieren. Horizontale Seitenpaddings per var(--padding-lg).
|
||||
*/
|
||||
|
||||
frappe.pages["import-dropzone"].on_page_load = function (wrapper) {
|
||||
@@ -32,7 +32,9 @@ frappe.pages["import-dropzone"].on_page_load = function (wrapper) {
|
||||
|
||||
<section class="msp-dropzone" tabindex="0">
|
||||
<div class="msp-dropzone__inner">
|
||||
<div class="msp-dropzone__orb" aria-hidden="true"></div>
|
||||
<div class="msp-dropzone__orb" aria-hidden="true">
|
||||
${ICON.upload}
|
||||
</div>
|
||||
<div class="msp-dropzone__copy">
|
||||
<div class="msp-dropzone__label">
|
||||
${__("Dateien ablegen")}
|
||||
@@ -104,12 +106,8 @@ frappe.pages["import-dropzone"].on_page_load = function (wrapper) {
|
||||
);
|
||||
els.dz.addEventListener("drop", (e) => handle_files(e.dataTransfer.files));
|
||||
els.dz.addEventListener("click", (e) => {
|
||||
if (e.target.classList.contains("msp-dropzone__browse") ||
|
||||
e.target.closest(".msp-dropzone__inner") === e.currentTarget.querySelector(".msp-dropzone__inner")) {
|
||||
if (e.target.tagName !== "BUTTON" && !e.target.classList.contains("msp-dropzone__browse")) {
|
||||
// Klick auf die Zone selbst → File-Dialog
|
||||
els.fileInput.click();
|
||||
}
|
||||
if (e.target.tagName !== "BUTTON" && !e.target.classList.contains("msp-dropzone__browse")) {
|
||||
els.fileInput.click();
|
||||
}
|
||||
});
|
||||
root.querySelector(".msp-dropzone__browse").addEventListener("click", (e) => {
|
||||
@@ -129,16 +127,34 @@ frappe.pages["import-dropzone"].on_page_load = function (wrapper) {
|
||||
const t = data.totals || {};
|
||||
const s = data.subscriptions || {};
|
||||
const stats = [
|
||||
{ label: __("Läufe (30 Tage)"), value: t.runs || 0,
|
||||
sub: build_status_strip(t), accent: "primary" },
|
||||
{ label: __("Erzeugte Belege"), value: t.documents || 0,
|
||||
sub: fmt_line(t.lines, __("Zeilen verarbeitet")), accent: "neutral" },
|
||||
{ label: __("Fehler"), value: t.errors || 0,
|
||||
sub: t.errors ? __("bitte prüfen") : __("keine auffälligen"),
|
||||
accent: t.errors ? "danger" : "neutral" },
|
||||
{ label: __("Aktive Subscriptions"), value: s.active || 0,
|
||||
sub: `${s.total_qty || 0} ${__("Slots insgesamt")}`,
|
||||
accent: "accent" },
|
||||
{
|
||||
label: __("Läufe (30 Tage)"),
|
||||
value: t.runs || 0,
|
||||
sub: build_status_strip(t),
|
||||
tone: "primary",
|
||||
icon: ICON.stack,
|
||||
},
|
||||
{
|
||||
label: __("Erzeugte Belege"),
|
||||
value: t.documents || 0,
|
||||
sub: fmt_line(t.lines, __("Zeilen verarbeitet")),
|
||||
tone: "success",
|
||||
icon: ICON.doc,
|
||||
},
|
||||
{
|
||||
label: __("Fehler"),
|
||||
value: t.errors || 0,
|
||||
sub: t.errors ? __("bitte prüfen") : __("keine auffälligen"),
|
||||
tone: t.errors ? "danger" : "neutral",
|
||||
icon: t.errors ? ICON.warning : ICON.check,
|
||||
},
|
||||
{
|
||||
label: __("Aktive Subscriptions"),
|
||||
value: s.active || 0,
|
||||
sub: `${format_int(s.total_qty || 0)} ${__("Slots insgesamt")}`,
|
||||
tone: "accent",
|
||||
icon: ICON.users,
|
||||
},
|
||||
];
|
||||
els.stats.innerHTML = stats.map(render_stat).join("");
|
||||
|
||||
@@ -150,9 +166,12 @@ frappe.pages["import-dropzone"].on_page_load = function (wrapper) {
|
||||
|
||||
function render_stat(s) {
|
||||
return `
|
||||
<div class="msp-stat msp-stat--${s.accent}">
|
||||
<div class="msp-stat msp-stat--${s.tone}">
|
||||
<div class="msp-stat__head">
|
||||
<span class="msp-stat__icon">${s.icon || ""}</span>
|
||||
<span class="msp-stat__label">${s.label}</span>
|
||||
</div>
|
||||
<div class="msp-stat__value">${format_int(s.value)}</div>
|
||||
<div class="msp-stat__label">${s.label}</div>
|
||||
<div class="msp-stat__sub">${s.sub || ""}</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -412,150 +431,185 @@ frappe.pages["import-dropzone"].on_page_load = function (wrapper) {
|
||||
|
||||
function icon_for(filename) {
|
||||
const name = (filename || "").toLowerCase();
|
||||
if (name.endsWith(".zip")) return "<svg viewBox='0 0 24 24' width='20' height='20' fill='none' stroke='currentColor' stroke-width='1.6'><rect x='4' y='3' width='16' height='18' rx='2'/><path d='M10 3v4h4V3'/><path d='M10 9v2h4v2h-4v2h4v2h-4'/></svg>";
|
||||
if (name.endsWith(".csv")) return "<svg viewBox='0 0 24 24' width='20' height='20' fill='none' stroke='currentColor' stroke-width='1.6'><path d='M6 3h9l4 4v14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z'/><path d='M15 3v4h4'/><path d='M8 14h2M12 14h2M16 14h1'/><path d='M8 18h2M12 18h2M16 18h1'/></svg>";
|
||||
return "<svg viewBox='0 0 24 24' width='20' height='20' fill='none' stroke='currentColor' stroke-width='1.6'><path d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/><path d='M14 2v6h6'/></svg>";
|
||||
if (name.endsWith(".zip")) return ICON.zip;
|
||||
if (name.endsWith(".csv")) return ICON.csv;
|
||||
return ICON.file;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Styles
|
||||
// Icon-Set — schlanke 1.6er-Stroke-SVGs, erben currentColor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const ICON = {
|
||||
upload: `<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 16V4"/><path d="M6 10l6-6 6 6"/><path d="M4 20h16"/></svg>`,
|
||||
stack: `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l9 5-9 5-9-5 9-5z"/><path d="M3 13l9 5 9-5"/><path d="M3 17l9 5 9-5"/></svg>`,
|
||||
doc: `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8M8 17h6"/></svg>`,
|
||||
warning: `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`,
|
||||
check: `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5L20 7"/></svg>`,
|
||||
users: `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9.5" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9"/><path d="M16 3.1a4 4 0 0 1 0 7.8"/></svg>`,
|
||||
zip: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M10 3v4h4V3"/><path d="M10 9v2h4v2h-4v2h4v2h-4"/></svg>`,
|
||||
csv: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3h9l4 4v14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M15 3v4h4"/><path d="M8 14h2M12 14h2M16 14h1"/><path d="M8 18h2M12 18h2M16 18h1"/></svg>`,
|
||||
file: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>`,
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Styles — nutzen Frappé-Design-Tokens (--text-color, --card-bg, --primary,
|
||||
// --red-500/--green-500/--yellow-500/--blue-500, --padding-*)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function inject_style(root) {
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
.msp-importer {
|
||||
--ink: #0f172a;
|
||||
--ink-soft: #475569;
|
||||
--mute: #94a3b8;
|
||||
--line: #e2e8f0;
|
||||
--surface: #ffffff;
|
||||
--surface-alt: #f8fafc;
|
||||
--primary: var(--primary, #2490ef);
|
||||
--primary-ink: #0b4a8a;
|
||||
--accent: #e73249; /* itsdave rot */
|
||||
--ok: #047857;
|
||||
--warn: #b45309;
|
||||
--danger: #b91c1c;
|
||||
--muted-bg: #f1f5f9;
|
||||
--msp-accent: #e73249; /* itsdave-Rot — sparsam als Signal */
|
||||
--msp-radius: 10px;
|
||||
--msp-shadow-card: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
|
||||
max-width: 1080px;
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
padding: 4px 4px 40px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
color: var(--ink);
|
||||
padding: var(--padding-sm) var(--padding-lg) var(--padding-2xl);
|
||||
color: var(--text-color);
|
||||
font-family: inherit;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.msp-importer { padding: var(--padding-xs) var(--padding-md) var(--padding-xl); }
|
||||
}
|
||||
|
||||
/* ---------- Hero + Stats ---------- */
|
||||
.msp-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.4fr);
|
||||
gap: 40px;
|
||||
gap: var(--padding-2xl);
|
||||
align-items: end;
|
||||
padding: 36px 0 28px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
margin-bottom: 28px;
|
||||
padding: var(--padding-xl) 0 var(--padding-lg);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
margin-bottom: var(--padding-xl);
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.msp-hero { grid-template-columns: 1fr; gap: var(--padding-lg); align-items: start; }
|
||||
}
|
||||
@media (max-width: 880px) { .msp-hero { grid-template-columns: 1fr; gap: 24px; } }
|
||||
|
||||
.msp-eyebrow {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .14em;
|
||||
color: var(--mute);
|
||||
color: var(--text-muted);
|
||||
margin: 0 0 10px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.msp-title {
|
||||
font-size: 34px;
|
||||
font-weight: 600;
|
||||
line-height: 1.15;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.015em;
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--ink);
|
||||
margin: 0 0 10px 0;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
.msp-sub {
|
||||
font-size: 14.5px;
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
max-width: 46ch;
|
||||
color: var(--text-muted);
|
||||
max-width: 48ch;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.msp-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
gap: var(--padding-sm);
|
||||
}
|
||||
@media (max-width: 640px) { .msp-stats { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 720px) { .msp-stats { grid-template-columns: repeat(2, 1fr); } }
|
||||
|
||||
.msp-stat {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 14px 14px 12px;
|
||||
position: relative;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--msp-radius);
|
||||
padding: var(--padding-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
position: relative;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--msp-shadow-card);
|
||||
animation: msp-fade-in .4s ease both;
|
||||
}
|
||||
.msp-stat::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0; top: 0; bottom: 0;
|
||||
width: 3px;
|
||||
content: ""; position: absolute;
|
||||
left: 0; top: 0; bottom: 0; width: 3px;
|
||||
background: var(--border-color);
|
||||
}
|
||||
.msp-stat--primary::before { background: var(--primary); }
|
||||
.msp-stat--accent::before { background: var(--accent); }
|
||||
.msp-stat--neutral::before { background: var(--line); }
|
||||
.msp-stat--danger::before { background: var(--danger); }
|
||||
.msp-stat--skeleton {
|
||||
background: linear-gradient(90deg, var(--muted-bg) 0%, #e8eef5 50%, var(--muted-bg) 100%);
|
||||
background-size: 200% 100%;
|
||||
animation: msp-shimmer 1.2s linear infinite;
|
||||
min-height: 92px;
|
||||
.msp-stat--success::before { background: var(--green-500); }
|
||||
.msp-stat--danger::before { background: var(--red-500); }
|
||||
.msp-stat--accent::before { background: var(--msp-accent); }
|
||||
.msp-stat--neutral::before { background: var(--border-color); }
|
||||
|
||||
.msp-stat__head {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.msp-stat__value {
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--ink);
|
||||
.msp-stat__icon {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 24px; height: 24px; border-radius: 6px;
|
||||
background: var(--subtle-fg);
|
||||
color: var(--text-muted);
|
||||
flex: none;
|
||||
}
|
||||
.msp-stat--primary .msp-stat__icon { background: var(--alert-bg-info); color: var(--alert-text-info); }
|
||||
.msp-stat--success .msp-stat__icon { background: var(--alert-bg-success); color: var(--alert-text-success); }
|
||||
.msp-stat--danger .msp-stat__icon { background: var(--alert-bg-danger); color: var(--alert-text-danger); }
|
||||
.msp-stat--accent .msp-stat__icon { background: var(--alert-bg-info); color: var(--alert-text-info); }
|
||||
|
||||
.msp-stat__label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
color: var(--mute);
|
||||
letter-spacing: .08em;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
.msp-stat__sub { font-size: 12px; color: var(--ink-soft); margin-top: 2px; }
|
||||
.msp-stat__value {
|
||||
font-size: 28px; font-weight: 700; line-height: 1;
|
||||
color: var(--heading-color);
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.msp-stat--danger .msp-stat__value { color: var(--red-600, var(--red-500)); }
|
||||
.msp-stat__sub { font-size: 12px; color: var(--text-muted); margin-top: 2px; min-height: 18px; }
|
||||
|
||||
.msp-stat--skeleton {
|
||||
background: linear-gradient(90deg, var(--subtle-fg) 0%, var(--fg-hover-color) 50%, var(--subtle-fg) 100%);
|
||||
background-size: 200% 100%;
|
||||
animation: msp-shimmer 1.2s linear infinite;
|
||||
min-height: 104px;
|
||||
}
|
||||
.msp-stat--skeleton::before { display: none; }
|
||||
|
||||
/* ---------- Pills ---------- */
|
||||
.pill {
|
||||
display: inline-block;
|
||||
padding: 2px 7px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.pill--ok { background: #dcfce7; color: #14532d; }
|
||||
.pill--warn { background: #fef3c7; color: #92400e; }
|
||||
.pill--danger { background: #fee2e2; color: #991b1b; }
|
||||
.pill--muted { background: var(--muted-bg); color: var(--mute); }
|
||||
.pill--ok { background: var(--alert-bg-success); color: var(--alert-text-success); }
|
||||
.pill--warn { background: var(--alert-bg-warning); color: var(--alert-text-warning); }
|
||||
.pill--danger { background: var(--alert-bg-danger); color: var(--alert-text-danger); }
|
||||
.pill--muted { background: var(--subtle-fg); color: var(--text-muted); }
|
||||
|
||||
/* ---------- Dropzone ---------- */
|
||||
.msp-dropzone {
|
||||
position: relative;
|
||||
background: var(--surface);
|
||||
border: 1.5px dashed #cbd5e1;
|
||||
border-radius: 14px;
|
||||
padding: 36px 28px;
|
||||
background: var(--card-bg);
|
||||
border: 1.5px dashed var(--dark-border-color, var(--border-color));
|
||||
border-radius: var(--msp-radius);
|
||||
padding: var(--padding-2xl) var(--padding-lg);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color .18s, border-color .18s, transform .18s;
|
||||
@@ -565,12 +619,13 @@ function inject_style(root) {
|
||||
.msp-dropzone::before {
|
||||
content: "";
|
||||
position: absolute; inset: 0;
|
||||
background: radial-gradient(circle at 50% -10%, rgba(36, 144, 239, 0.06), transparent 55%);
|
||||
background: radial-gradient(circle at 50% -10%, var(--alert-bg-info), transparent 55%);
|
||||
opacity: .6;
|
||||
pointer-events: none;
|
||||
}
|
||||
.msp-dropzone.is-dragover {
|
||||
border-color: var(--primary);
|
||||
background: rgba(36, 144, 239, 0.04);
|
||||
background: var(--alert-bg-info);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.msp-dropzone__inner {
|
||||
@@ -578,95 +633,86 @@ function inject_style(root) {
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.msp-dropzone__orb {
|
||||
width: 46px; height: 46px; border-radius: 50%;
|
||||
background: linear-gradient(145deg, var(--primary) 0%, var(--primary-ink) 100%);
|
||||
position: relative;
|
||||
box-shadow: 0 4px 14px rgba(36, 144, 239, 0.25);
|
||||
}
|
||||
.msp-dropzone__orb::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%; top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 14px; height: 14px;
|
||||
border-top: 2.5px solid #fff; border-left: 2.5px solid #fff;
|
||||
border-radius: 2px;
|
||||
transform-origin: center;
|
||||
transform: translate(-50%, -44%) rotate(45deg);
|
||||
width: 54px; height: 54px; border-radius: 14px;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
background: var(--primary);
|
||||
color: var(--neutral-white, #fff);
|
||||
box-shadow: 0 6px 18px rgba(36, 144, 239, 0.25);
|
||||
}
|
||||
.msp-dropzone__copy { display: flex; flex-direction: column; gap: 4px; }
|
||||
.msp-dropzone__label {
|
||||
font-size: 18px; font-weight: 600; color: var(--ink);
|
||||
font-size: 18px; font-weight: 600; color: var(--heading-color);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.msp-dropzone__hint { font-size: 13px; color: var(--ink-soft); }
|
||||
.msp-dropzone__browse {
|
||||
margin-top: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.msp-dropzone__hint { font-size: 13px; color: var(--text-muted); }
|
||||
.msp-dropzone__browse { margin-top: 8px; font-weight: 500; }
|
||||
|
||||
/* ---------- Upload-Cards ---------- */
|
||||
.msp-cards {
|
||||
display: flex; flex-direction: column; gap: 10px;
|
||||
margin-top: 18px;
|
||||
display: flex; flex-direction: column; gap: var(--padding-sm);
|
||||
margin-top: var(--padding-md);
|
||||
}
|
||||
|
||||
.msp-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 40px 1fr;
|
||||
grid-template-columns: 44px 1fr;
|
||||
gap: 14px;
|
||||
padding: 16px 18px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: var(--padding-md);
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--msp-radius);
|
||||
box-shadow: var(--msp-shadow-card);
|
||||
animation: msp-fade-in .3s ease both;
|
||||
}
|
||||
.msp-card__badge {
|
||||
width: 40px; height: 40px;
|
||||
width: 44px; height: 44px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: var(--muted-bg);
|
||||
background: var(--subtle-fg);
|
||||
color: var(--text-muted);
|
||||
border-radius: 8px;
|
||||
color: var(--ink-soft);
|
||||
flex: none;
|
||||
}
|
||||
.msp-card__head {
|
||||
display: flex; gap: 8px; align-items: baseline;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.msp-card__name { font-size: 15px; font-weight: 600; margin: 0; color: var(--ink); letter-spacing: -0.01em; }
|
||||
.msp-card__size { font-size: 12px; color: var(--mute); }
|
||||
.msp-card__state { font-size: 13px; color: var(--ink-soft); display: flex; align-items: center; gap: 6px; }
|
||||
.msp-card__name { font-size: 15px; font-weight: 600; margin: 0; color: var(--heading-color); letter-spacing: -0.01em; }
|
||||
.msp-card__size { font-size: 12px; color: var(--text-muted); }
|
||||
.msp-card__state { font-size: 13px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; }
|
||||
.msp-card__state .spinner {
|
||||
width: 10px; height: 10px; border-radius: 50%;
|
||||
border: 1.5px solid var(--line);
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-top-color: var(--primary);
|
||||
animation: msp-spin .8s linear infinite;
|
||||
display: inline-block;
|
||||
}
|
||||
.msp-card__state em { font-style: normal; color: var(--mute); }
|
||||
.msp-card__state strong { font-weight: 600; color: var(--ink); }
|
||||
.msp-card__state em { font-style: normal; color: var(--text-muted); }
|
||||
.msp-card__state strong { font-weight: 600; color: var(--text-color); }
|
||||
.msp-card__state .check {
|
||||
color: #fff;
|
||||
background: var(--ok);
|
||||
background: var(--green-500);
|
||||
width: 16px; height: 16px;
|
||||
border-radius: 50%;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
font-size: 10px;
|
||||
}
|
||||
.msp-card__state .cross {
|
||||
color: var(--danger);
|
||||
color: var(--red-600, var(--red-500));
|
||||
width: 16px; height: 16px;
|
||||
border-radius: 50%;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
background: #fee2e2;
|
||||
background: var(--alert-bg-danger);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.msp-card__state .cross--warn { color: var(--warn); background: #fef3c7; }
|
||||
.msp-card__state .cross--warn {
|
||||
color: var(--alert-text-warning);
|
||||
background: var(--alert-bg-warning);
|
||||
}
|
||||
|
||||
.msp-card--matched { border-left: 3px solid var(--ok); padding-left: 16px; }
|
||||
.msp-card--unmatched { border-left: 3px solid var(--warn); padding-left: 16px; }
|
||||
.msp-card--error { border-left: 3px solid var(--danger); padding-left: 16px; }
|
||||
.msp-card--matched { border-left: 3px solid var(--green-500); padding-left: 14px; }
|
||||
.msp-card--unmatched { border-left: 3px solid var(--yellow-500); padding-left: 14px; }
|
||||
.msp-card--error { border-left: 3px solid var(--red-500); padding-left: 14px; }
|
||||
|
||||
.msp-card__stats {
|
||||
display: flex; flex-wrap: wrap; gap: 6px;
|
||||
@@ -675,28 +721,28 @@ function inject_style(root) {
|
||||
.msp-chip {
|
||||
display: inline-flex; gap: 6px; align-items: baseline;
|
||||
padding: 4px 10px;
|
||||
background: var(--muted-bg);
|
||||
background: var(--subtle-fg);
|
||||
border-radius: 7px;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.msp-chip__label { color: var(--mute); text-transform: lowercase; }
|
||||
.msp-chip__value { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.msp-chip--warn { background: #fef3c7; }
|
||||
.msp-chip__label { color: var(--text-muted); text-transform: lowercase; }
|
||||
.msp-chip__value { color: var(--text-color); font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.msp-chip--warn { background: var(--alert-bg-warning); }
|
||||
.msp-chip--warn .msp-chip__label,
|
||||
.msp-chip--warn .msp-chip__value { color: #92400e; }
|
||||
.msp-chip--warn .msp-chip__value { color: var(--alert-text-warning); }
|
||||
|
||||
.msp-card__warn {
|
||||
font-size: 12.5px;
|
||||
color: var(--warn);
|
||||
background: #fffbeb;
|
||||
color: var(--alert-text-warning);
|
||||
background: var(--alert-bg-warning);
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.msp-card__error {
|
||||
font-size: 12.5px;
|
||||
color: var(--danger);
|
||||
background: #fef2f2;
|
||||
color: var(--alert-text-danger);
|
||||
background: var(--alert-bg-danger);
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
@@ -706,61 +752,65 @@ function inject_style(root) {
|
||||
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.msp-card__actions .msp-input {
|
||||
width: auto; min-width: 260px;
|
||||
}
|
||||
.msp-card__actions .msp-input { width: auto; min-width: 260px; }
|
||||
.btn-open-assistant .arr { margin-left: 4px; }
|
||||
|
||||
.msp-recent { margin-top: 32px; }
|
||||
/* ---------- Recent runs ---------- */
|
||||
.msp-recent { margin-top: var(--padding-xl); }
|
||||
.msp-section-head {
|
||||
display: flex; align-items: baseline; justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: var(--padding-sm);
|
||||
}
|
||||
.msp-section-title {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
color: var(--mute);
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
.msp-section-link {
|
||||
font-size: 13px;
|
||||
color: var(--ink-soft);
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
.msp-section-link:hover { color: var(--primary); }
|
||||
|
||||
.msp-recent__list {
|
||||
display: flex; flex-direction: column;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--msp-radius);
|
||||
overflow: hidden;
|
||||
background: var(--surface);
|
||||
background: var(--card-bg);
|
||||
box-shadow: var(--msp-shadow-card);
|
||||
}
|
||||
.msp-recent__row {
|
||||
display: grid;
|
||||
grid-template-columns: 130px 1fr 1.3fr 1.6fr auto;
|
||||
grid-template-columns: 130px 1fr 1.2fr 1.5fr auto;
|
||||
gap: 18px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 12px var(--padding-md);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
text-decoration: none;
|
||||
color: var(--ink);
|
||||
color: var(--text-color);
|
||||
font-size: 13px;
|
||||
transition: background-color .12s;
|
||||
}
|
||||
.msp-recent__row:last-child { border-bottom: 0; }
|
||||
.msp-recent__row:hover { background: var(--surface-alt); }
|
||||
.msp-recent__row:hover { background: var(--highlight-color); }
|
||||
.msp-recent__status { display: inline-flex; align-items: center; gap: 6px; font-weight: 500; }
|
||||
.msp-recent__name { color: var(--ink-soft); font-family: "SF Mono", Menlo, Consolas, monospace; font-size: 12.5px; }
|
||||
.msp-recent__profile { color: var(--ink-soft); }
|
||||
.msp-recent__nums { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
|
||||
.msp-recent__nums strong { color: var(--ink); font-weight: 600; }
|
||||
.msp-recent__nums .danger { color: var(--danger); }
|
||||
.msp-recent__time { color: var(--mute); font-size: 12px; justify-self: end; }
|
||||
.msp-recent__name { color: var(--text-muted); font-family: var(--font-stack-monospace, "SF Mono", Menlo, Consolas, monospace); font-size: 12.5px; }
|
||||
.msp-recent__profile { color: var(--text-muted); }
|
||||
.msp-recent__nums { color: var(--text-muted); font-variant-numeric: tabular-nums; }
|
||||
.msp-recent__nums strong { color: var(--text-color); font-weight: 600; }
|
||||
.msp-recent__nums .danger { color: var(--red-500); }
|
||||
.msp-recent__time { color: var(--text-muted); font-size: 12px; justify-self: end; }
|
||||
.msp-recent__empty {
|
||||
padding: 26px; text-align: center;
|
||||
color: var(--mute); font-size: 13px;
|
||||
padding: var(--padding-xl); text-align: center;
|
||||
color: var(--text-muted); font-size: 13px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.msp-recent__row { grid-template-columns: auto 1fr; grid-auto-flow: row; gap: 6px 12px; }
|
||||
.msp-recent__profile, .msp-recent__time { grid-column: 2; }
|
||||
}
|
||||
|
||||
.dot {
|
||||
@@ -768,25 +818,26 @@ function inject_style(root) {
|
||||
width: 7px; height: 7px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot--ok { background: var(--ok); }
|
||||
.dot--warn { background: var(--warn); }
|
||||
.dot--danger { background: var(--danger); }
|
||||
.dot--ok { background: var(--green-500); }
|
||||
.dot--warn { background: var(--yellow-500); }
|
||||
.dot--danger { background: var(--red-500); }
|
||||
.dot--accent { background: var(--primary); }
|
||||
.dot--muted { background: var(--mute); }
|
||||
.dot--muted { background: var(--text-muted); }
|
||||
|
||||
.muted { color: var(--mute); }
|
||||
.muted { color: var(--text-muted); }
|
||||
.danger { color: var(--red-500); }
|
||||
|
||||
@keyframes msp-fade-in {
|
||||
from { opacity: 0; transform: translateY(4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes msp-shimmer {
|
||||
from { background-position: 0 0; }
|
||||
to { background-position: -200% 0; }
|
||||
to { background-position: -200% 0; }
|
||||
}
|
||||
@keyframes msp-spin {
|
||||
from { transform: rotate(0); }
|
||||
to { transform: rotate(360deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
`;
|
||||
root.appendChild(style);
|
||||
|
||||
Reference in New Issue
Block a user