Files

202 lines
7.7 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="level">
<div class="level-left">
<h1 class="title is-3">Dashboard</h1>
</div>
<div class="level-right">
<div class="field has-addons">
<div class="control">
<div class="select is-small">
<select id="profile-select" onchange="loadProfile(this.value)">
{% for p in profile_list or [] %}
<option value="{{ p.id }}" {{ 'selected' if active_profile and active_profile.id == p.id }}>{{ p.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="control">
<button class="button is-small is-info" onclick="saveProfile()">Speichern</button>
</div>
<div class="control">
<button class="button is-small is-light" onclick="newProfile()">+ Neu</button>
</div>
</div>
</div>
</div>
<div id="dashboard-cards" class="columns is-multiline mt-3">
<div class="column is-one-third" data-card="projekte">
<div class="box has-text-centered">
<p class="heading">Projekte (aktiv)</p>
<p class="title">{{ projekte_anzahl or 0 }}</p>
<a href="{{ url_for('aufmass.index') }}" class="button is-small is-link is-outlined mt-2">Alle Projekte</a>
</div>
</div>
<div class="column is-one-third" data-card="module">
<div class="box has-text-centered">
<p class="heading">Module verfügbar</p>
<p class="title">{{ modules|length }}</p>
<a href="{{ url_for('admin.firma') }}" class="button is-small is-link is-outlined mt-2">Module verwalten</a>
</div>
</div>
<div class="column is-one-third" data-card="mitarbeiter">
<div class="box has-text-centered">
<p class="heading">Mitarbeiter</p>
<p class="title">{{ mitarbeiter_anzahl }}</p>
<a href="{{ url_for('admin.firma') }}" class="button is-small is-link is-outlined mt-2">Mitarbeiter verwalten</a>
</div>
</div>
<div class="column is-one-third" data-card="projektwerte">
<div class="box">
<p class="heading">Projektwerte (€)</p>
{% if gesamt_summe > 0 %}
<p class="is-size-4 has-text-weight-bold has-text-primary">Gesamt: {{ gesamt_summe|german_number }} €</p>
<table class="table is-fullwidth is-hoverable mt-2" style="font-size:0.85rem">
<thead><tr><th>Bezeichnung</th><th style="text-align:right">Wert (€)</th></tr></thead>
<tbody>
{% for p, summe in projekte_mit_summe %}
<tr>
<td><a href="{{ url_for('aufmass.aufmass_list', project_id=p.id) }}">{{ p.bezeichnung or p.sm_nr }}</a></td>
<td style="text-align:right">{{ summe|german_number }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="has-text-grey is-size-7">Keine Projekte mit Werten.</p>
{% endif %}
</div>
</div>
</div>
<div class="box" data-card="lizenzen">
<h2 class="title is-5">Lizenzen</h2>
<div class="columns is-multiline">
<div class="column is-one-third">
<div class="notification is-light has-text-centered" style="margin-bottom:0">
<p class="heading">Mitarbeiter</p>
<p class="title is-4">{{ mitarbeiter_anzahl }}/{{ license_max_ma }}</p>
<p class="is-size-7 has-text-grey">belegt/verfügbar</p>
</div>
</div>
<div class="column is-one-third">
<div class="notification is-light has-text-centered" style="margin-bottom:0">
<p class="heading">Module</p>
<p class="title is-4">{{ license_module_used }}/{{ license_module_count }}</p>
<p class="is-size-7 has-text-grey">belegt/verfügbar</p>
</div>
</div>
<div class="column is-one-third">
<div class="notification is-light has-text-centered" style="margin-bottom:0">
<p class="heading">Lizenzen</p>
<p class="title is-4">{{ license_count }}</p>
<p class="is-size-7 has-text-grey">vorhanden</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
<script>
var cardContainer = document.getElementById('dashboard-cards');
new Sortable(cardContainer, {
animation: 150,
handle: '.box',
onEnd: function() {
var order = [];
cardContainer.querySelectorAll('.column[data-card]').forEach(function(col) {
order.push(col.dataset.card);
});
localStorage.setItem('dash_order_' + {{ current_user.id }}, JSON.stringify(order));
}
});
function loadOrder() {
var saved = localStorage.getItem('dash_order_' + {{ current_user.id }});
if (!saved) return;
var order = JSON.parse(saved);
var cards = {};
cardContainer.querySelectorAll('.column[data-card]').forEach(function(col) {
cards[col.dataset.card] = col;
});
order.forEach(function(key) {
if (cards[key]) cardContainer.appendChild(cards[key]);
});
}
loadOrder();
function saveProfile() {
var order = [];
cardContainer.querySelectorAll('.column[data-card]').forEach(function(col) {
order.push(col.dataset.card);
});
var lizOrder = document.querySelector('[data-card="lizenzen"]');
var sel = document.getElementById('profile-select');
var profileId = sel.value;
fetch('/api/profiles/' + (profileId || ''), {
method: profileId ? 'PUT' : 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Dashboard',
view_type: 'dashboard',
config_json: {card_order: order}
})
}).then(function(r){return r.json()}).then(function(data){
if (data.id) {
var opt = sel.querySelector('option[value="'+profileId+'"]');
if (!opt) {
opt = document.createElement('option');
opt.value = data.id;
opt.textContent = data.name || 'Dashboard';
sel.appendChild(opt);
}
sel.value = data.id;
}
alert('Gespeichert');
});
}
function loadProfile(profileId) {
if (!profileId) return;
fetch('/api/profiles/' + profileId)
.then(function(r){return r.json()})
.then(function(data){
var cfg = data.config_json || {};
if (cfg.card_order) {
var cards = {};
cardContainer.querySelectorAll('.column[data-card]').forEach(function(col) {
cards[col.dataset.card] = col;
});
cfg.card_order.forEach(function(key) {
if (cards[key]) cardContainer.appendChild(cards[key]);
});
}
});
}
function newProfile() {
var name = prompt('Profil-Name:');
if (!name) return;
fetch('/api/profiles', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: name,
view_type: 'dashboard',
config_json: {card_order: []}
})
}).then(function(r){return r.json()}).then(function(data){
if (data.id) {
var sel = document.getElementById('profile-select');
var opt = document.createElement('option');
opt.value = data.id;
opt.textContent = data.name;
opt.selected = true;
sel.appendChild(opt);
}
});
}
</script>
{% endblock %}