509 lines
28 KiB
HTML
509 lines
28 KiB
HTML
{% extends "base.html" %}
|
||
{% block content %}
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
|
||
|
||
:root {
|
||
--lp-primary: #2F5496;
|
||
--lp-primary-light: #4a7bc4;
|
||
--lp-primary-dark: #1a3055;
|
||
--lp-primary-glow: rgba(47,84,150,.25);
|
||
--lp-success: #27ae60;
|
||
--lp-bg: #f0f2f8;
|
||
--lp-card-bg: rgba(255,255,255,.85);
|
||
--lp-border: rgba(0,0,0,.06);
|
||
--lp-text: #1a1a2e;
|
||
--lp-text-light: #6b7280;
|
||
--lp-radius: 14px;
|
||
--lp-shadow: 0 1px 3px rgba(0,0,0,.04), 0 4px 16px rgba(0,0,0,.04);
|
||
--lp-shadow-hover: 0 4px 12px rgba(47,84,150,.12), 0 8px 32px rgba(0,0,0,.06);
|
||
--lp-transition: all .35s cubic-bezier(.25,.46,.45,.94);
|
||
}
|
||
body { background: var(--lp-bg); font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; }
|
||
.lp-page { max-width: 1000px; margin: 0 auto; padding: 0 16px; }
|
||
|
||
/* Hero */
|
||
.lp-hero {
|
||
display:flex;align-items:center;justify-content:space-between;
|
||
padding:32px 0 24px;flex-wrap:wrap;gap:16px;
|
||
}
|
||
.lp-hero-left{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
|
||
.lp-hero-left .proj-icon {
|
||
width:44px;height:44px;border-radius:12px;
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:1.3rem;flex-shrink:0;
|
||
background:linear-gradient(135deg,#eef2fa,#e0e7f5);
|
||
}
|
||
.lp-hero-left h1 {
|
||
font-size:1.45rem;font-weight:700;letter-spacing:-.02em;margin:0;
|
||
background:linear-gradient(135deg,var(--lp-primary-dark),var(--lp-primary-light));
|
||
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;
|
||
display:flex;align-items:center;gap:8px;
|
||
}
|
||
.lp-hero-left .hero-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
||
.lp-hero-left .status-pill{
|
||
padding:3px 14px;border-radius:20px;font-size:.72rem;font-weight:600;
|
||
}
|
||
.lp-hero-left .status-pill.aktiv{
|
||
background:linear-gradient(135deg,#e8f8f0,#d0f0e0);color:#1a8a4a;
|
||
box-shadow:0 0 0 1px rgba(39,174,96,.15);
|
||
}
|
||
.lp-hero-left .status-pill.archiv{background:#f0f2f8;color:#999}
|
||
.lp-hero-left .status-pill.storniert{background:#fde8e8;color:#c0392b}
|
||
.lp-hero-right{display:flex;gap:6px;flex-wrap:wrap}
|
||
.lp-btn{
|
||
padding:8px 16px;border-radius:10px;border:1.5px solid var(--lp-border);
|
||
background:var(--lp-card-bg);backdrop-filter:blur(8px);
|
||
font-size:.78rem;font-weight:500;cursor:pointer;text-decoration:none;
|
||
transition:var(--lp-transition);color:var(--lp-text);
|
||
display:inline-flex;align-items:center;gap:5px;font-family:inherit;
|
||
box-shadow:var(--lp-shadow);white-space:nowrap;
|
||
}
|
||
.lp-btn:hover{transform:translateY(-2px);box-shadow:var(--lp-shadow-hover);border-color:rgba(47,84,150,.2)}
|
||
.lp-btn:active{transform:scale(.97)}
|
||
.lp-btn.primary{
|
||
background:linear-gradient(135deg,var(--lp-primary),var(--lp-primary-light));
|
||
color:#fff;border-color:transparent;box-shadow:0 4px 14px var(--lp-primary-glow);
|
||
}
|
||
.lp-btn.primary:hover{box-shadow:0 8px 24px var(--lp-primary-glow);}
|
||
.lp-btn.danger:hover{border-color:#e74c3c;color:#e74c3c;background:#fef2f2}
|
||
|
||
/* Main Card */
|
||
.lp-main-card{
|
||
background:var(--lp-card-bg);backdrop-filter:blur(12px);
|
||
border:1px solid var(--lp-border);border-radius:var(--lp-radius);
|
||
box-shadow:var(--lp-shadow);overflow:hidden;
|
||
animation:cardIn .45s cubic-bezier(.25,.46,.45,.94) both;
|
||
}
|
||
@keyframes cardIn{from{opacity:0;transform:translateY(20px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||
|
||
.lp-card-header{
|
||
display:flex;align-items:center;justify-content:space-between;
|
||
padding:16px 20px;border-bottom:1px solid var(--lp-border);flex-wrap:wrap;gap:8px;
|
||
}
|
||
.lp-card-header h2{font-size:1rem;font-weight:600;margin:0;color:var(--lp-text);display:flex;align-items:center;gap:6px}
|
||
.lp-card-body{padding:14px 20px 20px}
|
||
|
||
/* Aufmass Cards */
|
||
.lp-aufmass-grid{display:flex;flex-direction:column;gap:6px}
|
||
.lp-aufmass-card{
|
||
display:flex;align-items:center;gap:10px;
|
||
padding:10px 14px;border-radius:10px;text-decoration:none;color:var(--lp-text);
|
||
transition:var(--lp-transition);cursor:pointer;position:relative;
|
||
background:rgba(255,255,255,.5);border:1px solid var(--lp-border);
|
||
}
|
||
.lp-aufmass-card:hover{
|
||
background:#fff;border-color:rgba(47,84,150,.12);
|
||
box-shadow:0 2px 8px rgba(0,0,0,.04);transform:translateX(4px);
|
||
}
|
||
.lp-aufmass-card:active{transform:translateX(2px) scale(.99)}
|
||
.lp-aufmass-card .row-dot{
|
||
width:8px;height:8px;border-radius:50%;flex-shrink:0;
|
||
background:var(--lp-primary);opacity:.25;transition:var(--lp-transition);
|
||
}
|
||
.lp-aufmass-card:hover .row-dot{opacity:.6;transform:scale(1.3)}
|
||
.lp-aufmass-card .a-name{font-weight:500;font-size:.85rem;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.lp-aufmass-card .a-meta{font-size:.72rem;color:var(--lp-text-light);display:flex;align-items:center;gap:6px;white-space:nowrap;flex-shrink:0}
|
||
.lp-aufmass-card .a-meta .tag{
|
||
font-size:.65rem;padding:2px 10px;border-radius:10px;font-weight:500;
|
||
}
|
||
.lp-aufmass-card .a-meta .tag-aktiv{background:rgba(39,174,96,.1);color:#1a8a4a}
|
||
.lp-aufmass-card .a-meta .tag-abgeschlossen{background:rgba(47,84,150,.08);color:var(--lp-primary)}
|
||
.lp-aufmass-card .a-meta .tag-storniert{background:#fde8e8;color:#c0392b}
|
||
.lp-aufmass-card .a-actions{
|
||
display:flex;gap:2px;opacity:0;transition:opacity .25s,transform .25s;transform:translateX(-6px);
|
||
}
|
||
.lp-aufmass-card:hover .a-actions{opacity:1;transform:translateX(0)}
|
||
.lp-aufmass-card .a-actions .act-btn{
|
||
width:28px;height:28px;border:none;background:transparent;
|
||
border-radius:6px;cursor:pointer;font-size:.78rem;
|
||
transition:var(--lp-transition);display:flex;align-items:center;justify-content:center;color:#bbb;
|
||
}
|
||
.lp-aufmass-card .a-actions .act-btn:hover{background:rgba(47,84,150,.08);color:var(--lp-primary);transform:scale(1.15)}
|
||
.lp-aufmass-card .a-actions .act-btn.danger:hover{background:#fde8e8;color:#e74c3c}
|
||
@media(max-width:768px){
|
||
.lp-aufmass-card .a-actions{opacity:1}
|
||
.lp-aufmass-card{flex-wrap:wrap}
|
||
}
|
||
|
||
/* Empty */
|
||
.lp-empty{
|
||
text-align:center;padding:50px 20px;
|
||
}
|
||
.lp-empty .empty-icon{font-size:3rem;margin-bottom:10px;display:block;animation:float 3s ease-in-out infinite}
|
||
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
|
||
.lp-empty p{color:var(--lp-text-light);font-size:.9rem;margin-bottom:16px}
|
||
|
||
/* Settings Section */
|
||
.lp-settings{
|
||
margin-top:12px;background:var(--lp-card-bg);backdrop-filter:blur(12px);
|
||
border:1px solid var(--lp-border);border-radius:var(--lp-radius);
|
||
box-shadow:var(--lp-shadow);overflow:hidden;animation:cardIn .45s both;animation-delay:.1s;
|
||
}
|
||
.lp-settings .lp-card-body{padding:20px}
|
||
.lp-settings-grid{display:flex;flex-direction:column;gap:14px}
|
||
.lp-setting-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
||
.lp-setting-row label{font-size:.8rem;font-weight:500;color:var(--lp-text-light);min-width:100px}
|
||
.lp-setting-row select{
|
||
padding:6px 10px;border-radius:8px;border:1.5px solid var(--lp-border);
|
||
font-size:.78rem;background:#fff;font-family:inherit;min-width:180px;
|
||
transition:var(--lp-transition);
|
||
}
|
||
.lp-setting-row select:focus{outline:none;border-color:var(--lp-primary);box-shadow:0 0 0 3px var(--lp-primary-glow)}
|
||
.lp-setting-row .lp-btn{padding:6px 14px;font-size:.75rem}
|
||
|
||
/* Form Styles */
|
||
.lp-neu-form{display:none;margin-top:12px;animation:formSlide .35s cubic-bezier(.25,.46,.45,.94)}
|
||
@keyframes formSlide{from{opacity:0;transform:translateY(-12px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||
.lp-form-wrap{
|
||
background:linear-gradient(135deg,#f8f9fd,#f0f2f8);
|
||
border-radius:12px;padding:20px;border:1px solid rgba(47,84,150,.08);
|
||
}
|
||
.lp-form-wrap .form-card{
|
||
background:#fff;border-radius:10px;padding:16px;margin-bottom:12px;
|
||
box-shadow:0 1px 4px rgba(0,0,0,.04);border:1px solid var(--lp-border);
|
||
}
|
||
.lp-form-wrap .form-card h3{font-size:.82rem;font-weight:600;margin-bottom:10px;color:var(--lp-primary-dark);display:flex;align-items:center;gap:6px}
|
||
.lp-form-wrap .form-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px}
|
||
.lp-form-wrap .form-field label{display:block;font-size:.7rem;font-weight:500;color:var(--lp-text-light);margin-bottom:3px}
|
||
.lp-form-wrap .form-field .input,.lp-form-wrap .form-field select{
|
||
width:100%;border-radius:8px;border:1.5px solid var(--lp-border);
|
||
padding:6px 10px;font-size:.8rem;font-family:inherit;
|
||
transition:var(--lp-transition);background:#fff;
|
||
}
|
||
.lp-form-wrap .form-field .input:focus,.lp-form-wrap .form-field select:focus{
|
||
outline:none;border-color:var(--lp-primary);box-shadow:0 0 0 3px var(--lp-primary-glow);
|
||
}
|
||
.lp-form-wrap .form-field-full{grid-column:1/-1}
|
||
.lp-form-wrap .form-footer{display:flex;justify-content:space-between;align-items:center;margin-top:12px}
|
||
|
||
/* Toast reuse */
|
||
.toast-container{position:fixed;bottom:30px;right:30px;z-index:9999;display:flex;flex-direction:column;gap:8px}
|
||
.toast{padding:14px 22px;border-radius:12px;color:#fff;font-size:.85rem;font-weight:500;box-shadow:0 8px 32px rgba(0,0,0,.15);animation:toastIn .4s cubic-bezier(.34,1.56,.64,1);display:flex;align-items:center;gap:8px;backdrop-filter:blur(12px);cursor:pointer;transition:opacity .3s,transform .3s;font-family:'Inter',sans-serif}
|
||
.toast:hover{transform:scale(1.03)}
|
||
.toast.toast-success{background:linear-gradient(135deg,#27ae60,#2ecc71)}
|
||
.toast.toast-error{background:linear-gradient(135deg,#e74c3c,#f06050)}
|
||
.toast.toast-info{background:linear-gradient(135deg,var(--lp-primary),var(--lp-primary-light))}
|
||
@keyframes toastIn{from{opacity:0;transform:translateX(40px) scale(.9)}to{opacity:1;transform:translateX(0) scale(1)}}
|
||
|
||
/* Inline edit */
|
||
.inline-edit-input{
|
||
border:2px solid var(--lp-primary);border-radius:8px;padding:4px 10px;
|
||
font-size:.82rem;font-family:inherit;background:#fff;
|
||
box-shadow:0 0 0 3px var(--lp-primary-glow);width:100%;
|
||
}
|
||
.inline-edit-input:focus{outline:none}
|
||
</style>
|
||
|
||
<div class="lp-page">
|
||
<!-- Hero -->
|
||
<div class="lp-hero">
|
||
<div class="lp-hero-left">
|
||
<span class="proj-icon">📁</span>
|
||
<div>
|
||
<h1 class="js-name-ctnr">
|
||
<span class="js-projekt-name">{{ project.bezeichnung or project.sm_nr }}</span>
|
||
<span class="js-name-edit-trigger" style="font-size:.65rem;cursor:pointer;opacity:0;transition:opacity .2s;padding:2px 6px;border-radius:4px;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0'" onclick="event.stopPropagation();projectNameEdit(this)">✎</span>
|
||
<span class="js-name-edit-form" style="display:none;font-size:.85rem">
|
||
<input type="text" value="{{ project.bezeichnung or project.sm_nr or '' }}" style="border:2px solid var(--lp-primary);border-radius:8px;padding:4px 10px;font-size:.82rem;font-family:inherit;width:220px;box-shadow:0 0 0 3px var(--lp-primary-glow)">
|
||
<button class="mini-btn" style="width:26px;height:26px;border-radius:6px;border:none;background:#27ae60;color:#fff;cursor:pointer;font-size:.65rem;transition:all .2s" onclick="projectNameSave(this)">✓</button>
|
||
<button class="mini-btn" style="width:26px;height:26px;border-radius:6px;border:none;background:#f0f2f8;color:#888;cursor:pointer;font-size:.65rem;transition:all .2s" onclick="projectNameCancel(this)">✕</button>
|
||
</span>
|
||
</h1>
|
||
<div class="hero-meta">
|
||
<span class="status-pill {{ project.status }}">{{ project.status }}</span>
|
||
<span style="font-size:.75rem;color:var(--lp-text-light)">{{ project.sm_nr or '–' }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="lp-hero-right">
|
||
<a class="lp-btn" href="{{ url_for('export.excel', project_id=project.id) }}">📊 Excel</a>
|
||
<a class="lp-btn" href="{{ url_for('export.pdf', project_id=project.id) }}">📄 PDF</a>
|
||
{% if current_user.is_firmadmin() or current_user.is_superadmin() %}
|
||
<a class="lp-btn" href="{{ url_for('aufmass.typen_liste') }}">🏷 Typen</a>
|
||
{% endif %}
|
||
<a class="lp-btn" href="{{ url_for('aufmass.index') }}">← Übersicht</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Card -->
|
||
<div class="lp-main-card">
|
||
<div class="lp-card-header">
|
||
<h2>📋 Aufmaße</h2>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
||
<button class="lp-btn primary js-aufmass-neu-btn">+ Neues Aufmaß</button>
|
||
<button class="lp-btn" onclick="document.getElementById('import-file').click()">📥 Import</button>
|
||
<form method="POST" action="{{ url_for('aufmass.aufmass_import', project_id=project.id) }}" enctype="multipart/form-data" style="display:none">
|
||
<input type="file" name="file" accept=".txt" id="import-file" onchange="this.form.submit()">
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<div class="lp-card-body">
|
||
<!-- Neu Form -->
|
||
<div class="lp-neu-form js-aufmass-neu-form">
|
||
<div class="lp-form-wrap">
|
||
<form method="POST" action="{{ url_for('aufmass.aufmass_neu_voll', project_id=project.id) }}">
|
||
<input type="hidden" name="ev_details_id" value="{{ project.ev_details_id or '' }}">
|
||
<input type="hidden" name="name" id="aufmass-name-{{ project.id }}">
|
||
<div class="form-card"><h3>Basisdaten</h3>
|
||
<div class="form-grid">
|
||
<div class="form-field"><label>Vertrag</label><select name="contract_id"><option value="">– Kein Vertrag –</option>{% for c in contracts %}<option value="{{ c.id }}" {{ 'selected' if project.contract_id == c.id }}>{{ c.name }}</option>{% endfor %}</select></div>
|
||
<div class="form-field"><label>LV-Name</label><input class="input" name="lv_name" value="{{ project.lv_name or '' }}"></div>
|
||
<div class="form-field"><label>Typ</label><select name="typ"><option value="">– Typ wählen –</option>{% for t in typen %}<option value="{{ t.name }}">{{ t.name }}</option>{% endfor %}</select></div>
|
||
<div class="form-field"><label>Aufmaß-Datum</label><input class="input" name="datum" type="date" value="{{ project.datum or '' }}"></div>
|
||
</div>
|
||
<div class="form-field form-field-full" style="margin-top:8px"><label>Projekt</label><input class="input js-aufmass-auto-name js-validate-name" name="bezeichnung" value=""><span class="js-name-warn" style="display:none;font-size:.7rem;color:#e74c3c">Ungültige Zeichen</span></div>
|
||
<div class="form-field form-field-full"><label>Baustelle</label><input class="input" name="baustelle" value="{{ project.baustelle or '' }}"></div>
|
||
<div class="form-field form-field-full"><label>Bauabschnitt</label><input class="input js-aufmass-auto-name js-validate-name" name="bauabschnitt" value="{{ project.bauabschnitt or '' }}"></div>
|
||
</div>
|
||
<div class="form-card"><h3>🕐 Zeitraum & Referenz</h3>
|
||
<div class="form-grid">
|
||
<div class="form-field"><label>SM-Nr.</label><input class="input js-aufmass-auto-name" name="sm_nr" value="{{ project.sm_nr or '' }}"></div>
|
||
<div class="form-field"><label>Abruf-Nr.</label><input class="input js-aufmass-auto-name" name="abruf_nr" value="{{ project.abruf_nr or '' }}"></div>
|
||
<div class="form-field"><label>Startdatum</label><input class="input" name="datum_start" type="date" value="{{ project.datum_start or '' }}"></div>
|
||
<div class="form-field"><label>Enddatum</label><input class="input" name="datum_ende" type="date" value="{{ project.datum_ende or '' }}"></div>
|
||
</div>
|
||
{% if current_user.darf_evergabe_nutzen and current_user.darf_kopfdaten_holen and company.evergabe_aktiviert and company.evergabe_benutzer and company.evergabe_passwort %}
|
||
<button class="lp-btn" type="button" style="margin-top:8px" onclick="kopfdatenHolen({{ project.id }})">⬇️ Kopfdaten EV holen</button>
|
||
{% endif %}
|
||
</div>
|
||
<div class="form-card"><h3>👤 Ansprechpartner</h3>
|
||
<div class="form-grid">
|
||
<div class="form-field"><label>Vorname</label><input class="input" name="ansprechpartner_vorname" value="{{ project.ansprechpartner_vorname or '' }}"></div>
|
||
<div class="form-field"><label>Nachname</label><input class="input" name="ansprechpartner_nachname" value="{{ project.ansprechpartner_nachname or '' }}"></div>
|
||
<div class="form-field"><label>Telefon</label><input class="input" name="ansprechpartner_tel" value="{{ project.ansprechpartner_tel or '' }}"></div>
|
||
<div class="form-field"><label>Email</label><input class="input" name="ansprechpartner_email" value="{{ project.ansprechpartner_email or '' }}"></div>
|
||
</div>
|
||
</div>
|
||
<div class="form-footer">
|
||
<button class="lp-btn" type="button" onclick="this.closest('.lp-neu-form').style.display='none'">Abbrechen</button>
|
||
<div style="display:flex;gap:8px">
|
||
<button class="lp-btn js-aufmass-neu-reset" type="button">🗑️ Zurücksetzen</button>
|
||
<button class="lp-btn primary" type="submit">Aufmaß anlegen</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
{% if aufmass_liste %}
|
||
<div class="lp-aufmass-grid">
|
||
{% for a in aufmass_liste %}
|
||
<div class="lp-aufmass-card" data-id="{{ a.id }}" data-name="{{ a.name }}" data-typ="{{ a.typ }}" data-status="{{ a.status }}">
|
||
<span class="row-dot"></span>
|
||
<span class="a-name" data-field="name">{{ a.name }}</span>
|
||
<span class="a-meta">
|
||
{% if a.typ %}<span class="tag" style="background:rgba(240,192,64,.12);color:#b8941a">{{ a.typ }}</span>{% endif %}
|
||
<span class="tag tag-{{ a.status }}">{{ a.status }}</span>
|
||
<span>{{ a.positionen.count() }} Pos.</span>
|
||
{% if preise_sichtbar %}<span>{{ aufmass_preise.get(a.id, 0)|german_number }} €</span>{% endif %}
|
||
</span>
|
||
<span class="a-actions">
|
||
<button class="act-btn js-edit-btn" title="Bearbeiten">✎</button>
|
||
<a class="act-btn" href="{{ url_for('aufmass.bearbeiten', project_id=project.id, aufmass_id=a.id) }}" title="Öffnen">↗</a>
|
||
<form method="POST" action="{{ url_for('aufmass.aufmass_duplizieren', project_id=project.id, aufmass_id=a.id) }}" style="display:inline" onclick="event.stopPropagation()">
|
||
<button class="act-btn" title="Duplizieren">📋</button>
|
||
</form>
|
||
<form method="POST" action="{{ url_for('aufmass.aufmass_loeschen', project_id=project.id, aufmass_id=a.id) }}" style="display:inline" onsubmit="return confirm('Aufmaß wirklich löschen?')" onclick="event.stopPropagation()">
|
||
<button class="act-btn danger" title="Löschen">✕</button>
|
||
</form>
|
||
</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="lp-empty">
|
||
<span class="empty-icon">📋</span>
|
||
<p>Noch keine Aufmaße vorhanden.</p>
|
||
<button class="lp-btn primary js-aufmass-neu-btn">+ Erstes Aufmaß anlegen</button>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Settings -->
|
||
<div class="lp-settings">
|
||
<div class="lp-card-header"><h2>⚙ Projekt-Einstellungen</h2></div>
|
||
<div class="lp-card-body">
|
||
<div class="lp-settings-grid">
|
||
<div class="lp-setting-row">
|
||
<label>Vertrag & LV</label>
|
||
<form method="POST" action="{{ url_for('aufmass.project_lv_set', project_id=project.id) }}" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||
<select name="contract_id" id="settings-contract-select" onchange="loadSettingsLV(this.value)">
|
||
<option value="">– Kein Vertrag –</option>
|
||
{% for c in contracts %}
|
||
<option value="{{ c.id }}" {{ 'selected' if project.contract_id == c.id }}>{{ c.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<select name="lv_name">
|
||
<option value="">– LV wählen –</option>
|
||
{% for n in lv_names %}
|
||
<option value="{{ n }}" {{ 'selected' if project.lv_name == n }}>{{ n }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<button class="lp-btn primary" style="padding:6px 14px;font-size:.75rem">Speichern</button>
|
||
</form>
|
||
</div>
|
||
<div class="lp-setting-row">
|
||
<label>Status</label>
|
||
<form method="POST" action="{{ url_for('aufmass.status_aendern', project_id=project.id) }}" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||
<select name="status">
|
||
<option value="aktiv" {{ 'selected' if project.status == 'aktiv' }}>Aktiv</option>
|
||
<option value="abgeschlossen" {{ 'selected' if project.status == 'abgeschlossen' }}>Abgeschlossen</option>
|
||
<option value="storniert" {{ 'selected' if project.status == 'storniert' }}>Storniert</option>
|
||
</select>
|
||
<button class="lp-btn" style="padding:6px 14px;font-size:.75rem">Status ändern</button>
|
||
</form>
|
||
</div>
|
||
<div class="lp-setting-row" style="justify-content:flex-end">
|
||
<form method="POST" action="{{ url_for('aufmass.project_loeschen', project_id=project.id) }}" onsubmit="return confirm('WIRKLICH das ganze Projekt löschen?')">
|
||
<button class="lp-btn danger" style="padding:6px 14px;font-size:.75rem">Projekt löschen</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast-container" id="toast-container"></div>
|
||
|
||
<script>
|
||
/* Toast */
|
||
function showToast(msg,type){
|
||
type=type||'success';
|
||
var c=document.getElementById('toast-container');
|
||
var t=document.createElement('div');t.className='toast toast-'+type;
|
||
var icons={success:'✓',error:'✕',info:'ℹ'};
|
||
t.innerHTML=(icons[type]||'')+' '+msg;
|
||
c.appendChild(t);
|
||
setTimeout(function(){t.style.opacity='0';t.style.transform='translateX(40px) scale(.9)';setTimeout(function(){if(t.parentNode)t.remove()},400)},3000);
|
||
t.addEventListener('click',function(){t.style.opacity='0';t.style.transform='translateX(40px) scale(.9)';setTimeout(function(){if(t.parentNode)t.remove()},400)});
|
||
}
|
||
|
||
/* Flash toasts */
|
||
(function(){
|
||
document.querySelectorAll('.notification').forEach(function(n){
|
||
var msg=n.textContent.trim();var cat='info';
|
||
if(n.classList.contains('is-success'))cat='success';
|
||
else if(n.classList.contains('is-danger'))cat='error';
|
||
showToast(msg,cat);n.style.display='none';
|
||
});
|
||
})();
|
||
|
||
/* Toggle neu form */
|
||
document.querySelectorAll('.js-aufmass-neu-btn').forEach(function(btn){
|
||
btn.addEventListener('click',function(e){
|
||
e.preventDefault();
|
||
var f=document.querySelector('.js-aufmass-neu-form');
|
||
if(f)f.style.display=f.style.display==='none'?'block':'none';
|
||
});
|
||
});
|
||
|
||
/* Auto name */
|
||
function updateAufmassName(pid){
|
||
var nameInput=document.getElementById('aufmass-name-'+pid);
|
||
if(!nameInput)return;
|
||
var f=nameInput.closest('form');
|
||
var parts=[f.querySelector('[name="bezeichnung"]').value.trim(),f.querySelector('[name="bauabschnitt"]').value.trim(),f.querySelector('[name="sm_nr"]').value.trim(),f.querySelector('[name="abruf_nr"]').value.trim()].filter(Boolean);
|
||
nameInput.value=parts.join(' - ').replace(/[<>:"\/\\|?*&#%{}~\[\]]/g,'').replace(/\s+/g,' ').trim();
|
||
}
|
||
document.querySelectorAll('.js-aufmass-auto-name').forEach(function(inp){
|
||
inp.addEventListener('input',function(){var f=inp.closest('form');var ni=f.querySelector('[name="name"]');if(ni)updateAufmassName(ni.id.replace('aufmass-name-',''));});
|
||
});
|
||
document.querySelectorAll('.js-validate-name').forEach(function(inp){
|
||
inp.addEventListener('input',function(){
|
||
var warn=inp.closest('.lp-form-wrap').querySelector('.js-name-warn');
|
||
if(warn)warn.style.display=/[<>:"\/\\|?*&#%{}~\[\]]/.test(inp.value)?'block':'none';
|
||
});
|
||
});
|
||
|
||
/* Reset */
|
||
document.querySelector('.js-aufmass-neu-reset')?.addEventListener('click',function(e){
|
||
e.preventDefault();
|
||
var rf=this.closest('form');
|
||
if(rf){rf.querySelectorAll('input[name]:not([type=hidden])').forEach(function(i){i.value=''});updateAufmassName({{ project.id }});}
|
||
});
|
||
|
||
/* Inline edit aufmass */
|
||
document.querySelector('.lp-aufmass-grid')?.addEventListener('click',function(e){
|
||
var btn=e.target.closest('.js-edit-btn');
|
||
if(!btn)return;
|
||
e.stopPropagation();
|
||
var card=btn.closest('.lp-aufmass-card');
|
||
var nameEl=card.querySelector('.a-name');
|
||
var old=nameEl.textContent.trim();
|
||
var inp=document.createElement('input');
|
||
inp.className='inline-edit-input';inp.value=old;
|
||
nameEl.style.display='none';
|
||
nameEl.parentNode.insertBefore(inp,nameEl);
|
||
inp.focus();
|
||
inp.addEventListener('blur',save);
|
||
inp.addEventListener('keydown',function(ev){
|
||
if(ev.key==='Enter'){ev.preventDefault();save()}
|
||
if(ev.key==='Escape'){ev.preventDefault();inp.remove();nameEl.style.display=''}
|
||
});
|
||
function save(){
|
||
var v=inp.value.trim();if(!v){inp.remove();nameEl.style.display='';return}
|
||
nameEl.textContent=v;inp.remove();nameEl.style.display='';
|
||
fetch('/projekt/'+card.closest('[data-project-id]')?.dataset.projectId||{{ project.id }}+'/aufmass/'+card.dataset.id+'/umbenennen',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:v})})
|
||
.then(function(r){if(!r.ok)throw new Error('Fehler');showToast('Aufmaß umbenannt','success')})
|
||
.catch(function(){nameEl.textContent=old;showToast('Fehler beim Umbenennen','error')});
|
||
}
|
||
});
|
||
|
||
/* Projekt name inline edit */
|
||
function projectNameEdit(btn){
|
||
var c=btn.closest('.js-name-ctnr');
|
||
c.querySelector('.js-projekt-name').style.display='none';
|
||
btn.style.display='none';
|
||
c.querySelector('.js-name-edit-form').style.display='inline';
|
||
c.querySelector('.js-name-edit-form input').focus();
|
||
}
|
||
function projectNameSave(btn){
|
||
var c=btn.closest('.js-name-ctnr'),i=c.querySelector('input'),n=i.value;
|
||
fetch('/projekt/{{ project.id }}/update-name',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'name='+encodeURIComponent(n)})
|
||
.then(function(r){if(!r.ok)return r.json().then(function(e){throw new Error(e.error)});return r.json()})
|
||
.then(function(dt){c.querySelector('.js-projekt-name').textContent=dt.name;c.querySelector('.js-projekt-name').style.display='';btn.style.display='';c.querySelector('.js-name-edit-form').style.display='none';showToast('Projekt umbenannt','success')})
|
||
.catch(function(e){alert('Fehler: '+e.message)});
|
||
}
|
||
function projectNameCancel(btn){
|
||
var c=btn.closest('.js-name-ctnr');
|
||
c.querySelector('.js-projekt-name').style.display='';
|
||
btn.style.display='';
|
||
c.querySelector('.js-name-edit-form').style.display='none';
|
||
}
|
||
|
||
/* Load LV names */
|
||
function loadSettingsLV(contractId){
|
||
var url=contractId?'/contracts/api/lv-names?contract_id='+contractId:'/contracts/api/lv-names';
|
||
fetch(url).then(function(r){return r.json()}).then(function(names){
|
||
var sel=document.querySelector('[name="lv_name"]');
|
||
var current=sel.value;
|
||
sel.innerHTML='<option value="">– LV wählen –</option>';
|
||
names.forEach(function(n){sel.innerHTML+='<option value="'+n.replace(/"/g,'"')+'">'+n+'</option>'});
|
||
sel.value=current;
|
||
});
|
||
}
|
||
|
||
/* Kopfdaten holen */
|
||
function kopfdatenHolen(pid){
|
||
var form=document.querySelector('.lp-neu-form form');
|
||
if(!form)return;
|
||
var smNr=form.querySelector('[name="sm_nr"]').value.trim();
|
||
if(!smNr){alert('Bitte SM-Nr eingeben.');return}
|
||
var btn=form.querySelector('[onclick*="kopfdatenHolen"]');if(btn){btn.disabled=true;btn.textContent='Laden...'}
|
||
fetch('/projekt/'+pid+'/kopfdaten-ev-holen',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({sm_nr:smNr})})
|
||
.then(function(r){return r.json()})
|
||
.then(function(data){
|
||
if(btn){btn.disabled=false;btn.textContent='⬇️ Kopfdaten EV holen'}
|
||
if(data.error){alert(data.error);return}
|
||
['bauabschnitt','sm_nr','abruf_nr','datum_start','datum_ende','datum','ev_details_id','ansprechpartner_vorname','ansprechpartner_nachname','ansprechpartner_tel','ansprechpartner_email'].forEach(function(f){
|
||
var inp=form.querySelector('[name="'+f+'"]');
|
||
if(inp&&data[f])inp.value=data[f];
|
||
});
|
||
updateAufmassName(pid);
|
||
}).catch(function(err){if(btn){btn.disabled=false;btn.textContent='⬇️ Kopfdaten EV holen'}alert('Fehler: '+err)});
|
||
}
|
||
</script>
|
||
{% endblock %}
|