wiki/z-archive/ui_nyla/feature-trustee/doc_trustee_feature_ui_demo.html

1339 lines
55 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trustee Feature - UI Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 8px;
margin-bottom: 30px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 2em;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
font-size: 1.1em;
}
.tabs {
display: flex;
background: white;
border-radius: 8px 8px 0 0;
overflow-x: auto;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 0;
}
.tab {
padding: 15px 25px;
cursor: pointer;
border: none;
background: white;
color: #666;
font-size: 1em;
font-weight: 500;
transition: all 0.3s;
border-bottom: 3px solid transparent;
white-space: nowrap;
}
.tab:hover {
background: #f8f9fa;
color: #333;
}
.tab.active {
color: #667eea;
border-bottom-color: #667eea;
background: #f8f9ff;
}
.tab-content {
display: none;
background: white;
padding: 30px;
border-radius: 0 0 8px 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
min-height: 500px;
}
.tab-content.active {
display: block;
}
.view-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 2px solid #e9ecef;
}
.view-header h2 {
color: #333;
font-size: 1.5em;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.95em;
font-weight: 500;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
}
.controls {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.search-box {
flex: 1;
min-width: 250px;
padding: 10px 15px;
border: 2px solid #e9ecef;
border-radius: 6px;
font-size: 0.95em;
transition: border-color 0.3s;
}
.search-box:focus {
outline: none;
border-color: #667eea;
}
.filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.filter-select {
padding: 8px 12px;
border: 2px solid #e9ecef;
border-radius: 6px;
font-size: 0.9em;
background: white;
cursor: pointer;
}
.filter-select:focus {
outline: none;
border-color: #667eea;
}
.table-container {
overflow-x: auto;
border-radius: 6px;
border: 1px solid #e9ecef;
}
table {
width: 100%;
border-collapse: collapse;
background: white;
}
thead {
background: #f8f9fa;
position: sticky;
top: 0;
}
th {
padding: 15px;
text-align: left;
font-weight: 600;
color: #495057;
border-bottom: 2px solid #dee2e6;
cursor: pointer;
user-select: none;
position: relative;
}
th:hover {
background: #e9ecef;
}
th.sortable::after {
content: ' ↕';
opacity: 0.5;
font-size: 0.8em;
}
th.sort-asc::after {
content: ' ↑';
opacity: 1;
color: #667eea;
}
th.sort-desc::after {
content: ' ↓';
opacity: 1;
color: #667eea;
}
td {
padding: 12px 15px;
border-bottom: 1px solid #e9ecef;
}
tr:hover {
background: #f8f9fa;
}
.badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.85em;
font-weight: 500;
}
.badge-success {
background: #d4edda;
color: #155724;
}
.badge-danger {
background: #f8d7da;
color: #721c24;
}
.badge-warning {
background: #fff3cd;
color: #856404;
}
.badge-info {
background: #d1ecf1;
color: #0c5460;
}
.actions {
display: flex;
gap: 8px;
}
.btn-icon {
padding: 6px 10px;
border: none;
background: transparent;
cursor: pointer;
border-radius: 4px;
font-size: 1.1em;
transition: all 0.2s;
}
.btn-icon:hover {
background: #e9ecef;
}
.btn-edit {
color: #007bff;
}
.btn-delete {
color: #dc3545;
}
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #e9ecef;
}
.pagination-info {
color: #6c757d;
font-size: 0.9em;
}
.pagination-controls {
display: flex;
gap: 5px;
}
.pagination-btn {
padding: 6px 12px;
border: 1px solid #dee2e6;
background: white;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
}
.pagination-btn:hover:not(:disabled) {
background: #f8f9fa;
border-color: #adb5bd;
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-btn.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #6c757d;
}
.empty-state-icon {
font-size: 3em;
margin-bottom: 15px;
opacity: 0.5;
}
.loading {
text-align: center;
padding: 40px;
color: #6c757d;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.info-box {
background: #e7f3ff;
border-left: 4px solid #2196F3;
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
}
.info-box strong {
color: #1976D2;
}
.demo-note {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
font-size: 0.9em;
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
animation: fadeIn 0.3s;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background: white;
border-radius: 8px;
padding: 30px;
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
animation: slideUp 0.3s;
}
@keyframes slideUp {
from {
transform: translateY(50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 2px solid #e9ecef;
}
.modal-header h3 {
margin: 0;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 1.5em;
cursor: pointer;
color: #6c757d;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.close-btn:hover {
background: #f8f9fa;
color: #333;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #495057;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px;
border: 2px solid #e9ecef;
border-radius: 6px;
font-size: 0.95em;
transition: border-color 0.3s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #667eea;
}
.form-group input[readonly] {
background: #f8f9fa;
cursor: not-allowed;
}
.form-group .help-text {
font-size: 0.85em;
color: #6c757d;
margin-top: 5px;
}
.form-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 25px;
padding-top: 20px;
border-top: 1px solid #e9ecef;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🏢 Trustee Feature - UI Demo</h1>
<p>Demonstration der UI-Komponenten basierend auf FormGenerator-Pattern</p>
</div>
<div class="demo-note">
<strong>Hinweis:</strong> Dies ist eine statische Demo zur Visualisierung der UI-Struktur.
Die tatsächliche Implementierung verwendet React/TypeScript mit FormGenerator-Komponente.
</div>
<div class="tabs">
<button class="tab active" onclick="showTab('organisationen')">Organisationen</button>
<button class="tab" onclick="showTab('rollen')">Rollen</button>
<button class="tab" onclick="showTab('access')">Access</button>
<button class="tab" onclick="showTab('contracts')">Contracts</button>
<button class="tab" onclick="showTab('documents')">Documents</button>
<button class="tab" onclick="showTab('positions')">Positions</button>
<button class="tab" onclick="showTab('positiondocuments')">Position-Documents</button>
</div>
<!-- Organisationen View -->
<div id="organisationen" class="tab-content active">
<div class="view-header">
<h2>Organisationen</h2>
<button class="btn btn-primary" onclick="openModal('org-modal', {})">+ Neue Organisation</button>
</div>
<div class="info-box">
<strong>RBAC:</strong> sysadmin kann alle verwalten, admin kann für Gruppe verwalten<br>
<strong>Komponente:</strong> FormGenerator mit Logic-Hook
</div>
<div class="controls">
<input type="text" class="search-box" placeholder="Suche..." id="search-org" onkeyup="filterTable('org-table', this.value)">
<div class="filters">
<select class="filter-select" onchange="filterByColumn('org-table', 2, this.value)">
<option value="">Alle Status</option>
<option value="true">Aktiv</option>
<option value="false">Inaktiv</option>
</select>
</div>
</div>
<div class="table-container">
<table id="org-table">
<thead>
<tr>
<th class="sortable" onclick="sortTable('org-table', 0)">ID</th>
<th class="sortable" onclick="sortTable('org-table', 1)">Label</th>
<th class="sortable" onclick="sortTable('org-table', 2)">Enabled</th>
<th class="sortable" onclick="sortTable('org-table', 3)">Mandate</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr>
<td>acme-corp</td>
<td>ACME Corporation</td>
<td><span class="badge badge-success">Ja</span></td>
<td>mandate-001</td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="openModal('org-modal', {id: 'acme-corp', label: 'ACME Corporation', enabled: true})" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="if(confirm('Organisation löschen?')) alert('Delete Organisation')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>tech-solutions</td>
<td>Tech Solutions AG</td>
<td><span class="badge badge-success">Ja</span></td>
<td>mandate-001</td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="openModal('org-modal', {id: 'acme-corp', label: 'ACME Corporation', enabled: true})" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="if(confirm('Organisation löschen?')) alert('Delete Organisation')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>global-trust</td>
<td>Global Trust Ltd.</td>
<td><span class="badge badge-danger">Nein</span></td>
<td>mandate-002</td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="openModal('org-modal', {id: 'acme-corp', label: 'ACME Corporation', enabled: true})" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="if(confirm('Organisation löschen?')) alert('Delete Organisation')" title="Löschen">🗑️</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<div class="pagination-info">Zeige 1-3 von 3 Einträgen</div>
<div class="pagination-controls">
<button class="pagination-btn" disabled>««</button>
<button class="pagination-btn" disabled>«</button>
<button class="pagination-btn active">1</button>
<button class="pagination-btn" disabled>»</button>
<button class="pagination-btn" disabled>»»</button>
</div>
</div>
</div>
<!-- Rollen View -->
<div id="rollen" class="tab-content">
<div class="view-header">
<h2>Rollen</h2>
<button class="btn btn-primary" onclick="alert('Create Role - nur sysadmin')">+ Neue Rolle</button>
</div>
<div class="info-box">
<strong>RBAC:</strong> Nur sysadmin kann Rollen verwalten<br>
<strong>Initiale Rollen:</strong> userreport, admin, operate (werden automatisch erstellt)
</div>
<div class="controls">
<input type="text" class="search-box" placeholder="Suche..." id="search-roles" onkeyup="filterTable('roles-table', this.value)">
</div>
<div class="table-container">
<table id="roles-table">
<thead>
<tr>
<th class="sortable" onclick="sortTable('roles-table', 0)">ID</th>
<th class="sortable" onclick="sortTable('roles-table', 1)">Beschreibung</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr>
<td>userreport</td>
<td>Kann Benutzerdokumente an das System liefern</td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Role')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Role - prüft ob in Verwendung')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>admin</td>
<td>Kann den Zugriff administrieren</td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Role')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Role - prüft ob in Verwendung')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>operate</td>
<td>Kann Daten für Operationen verwenden</td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Role')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Role - prüft ob in Verwendung')" title="Löschen">🗑️</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Access View -->
<div id="access" class="tab-content">
<div class="view-header">
<h2>Access</h2>
<button class="btn btn-primary" onclick="alert('Create Access - würde Modal öffnen')">+ Neuer Access</button>
</div>
<div class="info-box">
<strong>RBAC:</strong> sysadmin kann alle verwalten, admin kann für Gruppe verwalten<br>
<strong>Besonderheit:</strong> Dropdowns zeigen nur erlaubte Optionen (RBAC-gefiltert)<br>
<strong>Contract-Zugriff:</strong> contractId ist optional - wenn leer, Zugriff auf gesamte Organisation; wenn gesetzt, nur auf diesen Contract
</div>
<div class="controls">
<input type="text" class="search-box" placeholder="Suche..." id="search-access" onkeyup="filterTable('access-table', this.value)">
<div class="filters">
<select class="filter-select" onchange="filterByColumn('access-table', 1, this.value)">
<option value="">Alle Organisationen</option>
<option value="acme-corp">ACME Corporation</option>
<option value="tech-solutions">Tech Solutions AG</option>
</select>
<select class="filter-select" onchange="filterByColumn('access-table', 2, this.value)">
<option value="">Alle Rollen</option>
<option value="admin">admin</option>
<option value="operate">operate</option>
<option value="userreport">userreport</option>
</select>
<select class="filter-select" onchange="filterByColumn('access-table', 4, this.value)">
<option value="">Alle Contracts</option>
<option value="">Gesamte Organisation</option>
<option value="c1d2e3f4">Muster AG 2026</option>
<option value="g5h6i7j8">Vertrag 2025</option>
</select>
</div>
</div>
<div class="table-container">
<table id="access-table">
<thead>
<tr>
<th class="sortable" onclick="sortTable('access-table', 0)">ID</th>
<th class="sortable" onclick="sortTable('access-table', 1)">Organisation</th>
<th class="sortable" onclick="sortTable('access-table', 2)">Rolle</th>
<th class="sortable" onclick="sortTable('access-table', 3)">User ID</th>
<th class="sortable" onclick="sortTable('access-table', 4)">Contract</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr>
<td>a1b2c3d4-...</td>
<td>ACME Corporation</td>
<td><span class="badge badge-info">admin</span></td>
<td>user-123</td>
<td><span class="badge badge-success">Gesamte Organisation</span></td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Access')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Access')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>e5f6g7h8-...</td>
<td>Tech Solutions AG</td>
<td><span class="badge badge-warning">operate</span></td>
<td>user-456</td>
<td><span class="badge badge-success">Gesamte Organisation</span></td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Access')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Access')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>i9j0k1l2-...</td>
<td>ACME Corporation</td>
<td><span class="badge badge-success">userreport</span></td>
<td>user-789</td>
<td>Muster AG 2026</td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Access')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Access')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>m3n4o5p6-...</td>
<td>ACME Corporation</td>
<td><span class="badge badge-warning">operate</span></td>
<td>user-999</td>
<td>Muster AG 2026</td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Access - nur für diesen Contract')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Access')" title="Löschen">🗑️</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Contracts View -->
<div id="contracts" class="tab-content">
<div class="view-header">
<h2>Contracts</h2>
<button class="btn btn-primary" onclick="alert('Create Contract - würde Modal öffnen')">+ Neuer Contract</button>
</div>
<div class="info-box">
<strong>RBAC:</strong> sysadmin, admin (für Gruppe), trustee.admin (für zugewiesene Organisationen)<br>
<strong>Besonderheit:</strong> organisationId ist immutable nach Erstellung (readonly wenn id vorhanden)
</div>
<div class="controls">
<input type="text" class="search-box" placeholder="Suche..." id="search-contracts" onkeyup="filterTable('contracts-table', this.value)">
<div class="filters">
<select class="filter-select" onchange="filterByColumn('contracts-table', 1, this.value)">
<option value="">Alle Organisationen</option>
<option value="acme-corp">ACME Corporation</option>
<option value="tech-solutions">Tech Solutions AG</option>
</select>
<select class="filter-select" onchange="filterByColumn('contracts-table', 3, this.value)">
<option value="">Alle Status</option>
<option value="true">Aktiv</option>
<option value="false">Inaktiv</option>
</select>
</div>
</div>
<div class="table-container">
<table id="contracts-table">
<thead>
<tr>
<th class="sortable" onclick="sortTable('contracts-table', 0)">ID</th>
<th class="sortable" onclick="sortTable('contracts-table', 1)">Organisation</th>
<th class="sortable" onclick="sortTable('contracts-table', 2)">Label</th>
<th class="sortable" onclick="sortTable('contracts-table', 3)">Enabled</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr>
<td>c1d2e3f4-...</td>
<td>ACME Corporation</td>
<td>Muster AG 2026</td>
<td><span class="badge badge-success">Ja</span></td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Contract - organisationId ist readonly')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Contract')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>g5h6i7j8-...</td>
<td>Tech Solutions AG</td>
<td>Vertrag 2025</td>
<td><span class="badge badge-success">Ja</span></td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="alert('Edit Contract - organisationId ist readonly')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Contract')" title="Löschen">🗑️</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Documents View -->
<div id="documents" class="tab-content">
<div class="view-header">
<h2>Documents</h2>
<button class="btn btn-primary" onclick="alert('Create Document - File Upload über Workflow-System')">+ Neues Document</button>
</div>
<div class="info-box">
<strong>RBAC:</strong> sysadmin, admin (für Gruppe), trustee.operate (für Organisationen), trustee.userreport (eigene Records)<br>
<strong>Besonderheit:</strong> File Upload/Download erfolgt über Workflow-System, nicht direkt integriert
</div>
<div class="controls">
<input type="text" class="search-box" placeholder="Suche..." id="search-documents" onkeyup="filterTable('documents-table', this.value)">
<div class="filters">
<select class="filter-select" onchange="filterByColumn('documents-table', 1, this.value)">
<option value="">Alle Organisationen</option>
<option value="acme-corp">ACME Corporation</option>
<option value="tech-solutions">Tech Solutions AG</option>
</select>
</div>
</div>
<div class="table-container">
<table id="documents-table">
<thead>
<tr>
<th class="sortable" onclick="sortTable('documents-table', 0)">ID</th>
<th class="sortable" onclick="sortTable('documents-table', 1)">Organisation</th>
<th class="sortable" onclick="sortTable('documents-table', 2)">Contract</th>
<th class="sortable" onclick="sortTable('documents-table', 3)">Document Name</th>
<th class="sortable" onclick="sortTable('documents-table', 4)">MIME Type</th>
<th>Verknüpfte Positionen</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr>
<td>d1e2f3a4-...</td>
<td>ACME Corporation</td>
<td>Muster AG 2026</td>
<td>Beleg_2026_01.pdf</td>
<td>application/pdf</td>
<td><a href="#" onclick="alert('Zeige verknüpfte Positionen'); return false;">2 Positionen</a></td>
<td class="actions">
<button class="btn-icon" onclick="alert('Download Document')" title="Download">⬇️</button>
<button class="btn-icon btn-edit" onclick="alert('Edit Document')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Document')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>b5c6d7e8-...</td>
<td>Tech Solutions AG</td>
<td>Vertrag 2025</td>
<td>Rechnung_2025_12.pdf</td>
<td>application/pdf</td>
<td><a href="#" onclick="alert('Zeige verknüpfte Positionen'); return false;">1 Position</a></td>
<td class="actions">
<button class="btn-icon" onclick="alert('Download Document')" title="Download">⬇️</button>
<button class="btn-icon btn-edit" onclick="alert('Edit Document')" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="alert('Delete Document')" title="Löschen">🗑️</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Positions View -->
<div id="positions" class="tab-content">
<div class="view-header">
<h2>Positions</h2>
<button class="btn btn-primary" onclick="openModal('position-modal', {})">+ Neue Position</button>
</div>
<div class="info-box">
<strong>RBAC:</strong> sysadmin, admin (für Gruppe), trustee.operate (für Organisationen), trustee.userreport (eigene Records)<br>
<strong>Besonderheit:</strong> MwSt-Berechnung automatisch (vatAmount = bookingAmount * vatPercentage / 100)
</div>
<div class="controls">
<input type="text" class="search-box" placeholder="Suche..." id="search-positions" onkeyup="filterTable('positions-table', this.value)">
<div class="filters">
<select class="filter-select" onchange="filterByColumn('positions-table', 1, this.value)">
<option value="">Alle Organisationen</option>
<option value="acme-corp">ACME Corporation</option>
<option value="tech-solutions">Tech Solutions AG</option>
</select>
</div>
</div>
<div class="table-container">
<table id="positions-table">
<thead>
<tr>
<th class="sortable" onclick="sortTable('positions-table', 0)">ID</th>
<th class="sortable" onclick="sortTable('positions-table', 1)">Organisation</th>
<th class="sortable" onclick="sortTable('positions-table', 2)">Contract</th>
<th class="sortable" onclick="sortTable('positions-table', 3)">Valuta</th>
<th class="sortable" onclick="sortTable('positions-table', 4)">Company</th>
<th class="sortable" onclick="sortTable('positions-table', 5)">Booking Amount</th>
<th class="sortable" onclick="sortTable('positions-table', 6)">MwSt %</th>
<th class="sortable" onclick="sortTable('positions-table', 7)">MwSt Betrag</th>
<th>Verknüpfte Documents</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr>
<td>p1q2r3s4-...</td>
<td>ACME Corporation</td>
<td>Muster AG 2026</td>
<td>2026-01-15</td>
<td>Hotel ABC</td>
<td>150.00 CHF</td>
<td>7.7%</td>
<td>11.55 CHF</td>
<td><a href="#" onclick="alert('Zeige verknüpfte Documents'); return false;">2 Documents</a></td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="openModal('position-modal', {id: 'p1q2r3s4', bookingAmount: 150.00, vatPercentage: 7.7, vatAmount: 11.55})" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="if(confirm('Position löschen?')) alert('Delete Position')" title="Löschen">🗑️</button>
</td>
</tr>
<tr>
<td>t5u6v7w8-...</td>
<td>Tech Solutions AG</td>
<td>Vertrag 2025</td>
<td>2025-12-20</td>
<td>Restaurant XYZ</td>
<td>85.50 EUR</td>
<td>19.0%</td>
<td>16.25 EUR</td>
<td><a href="#" onclick="alert('Zeige verknüpfte Documents'); return false;">1 Document</a></td>
<td class="actions">
<button class="btn-icon btn-edit" onclick="openModal('position-modal', {id: 'p1q2r3s4', bookingAmount: 150.00, vatPercentage: 7.7, vatAmount: 11.55})" title="Bearbeiten">✏️</button>
<button class="btn-icon btn-delete" onclick="if(confirm('Position löschen?')) alert('Delete Position')" title="Löschen">🗑️</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Position-Documents View -->
<div id="positiondocuments" class="tab-content">
<div class="view-header">
<h2>Position-Document Verknüpfungen</h2>
<button class="btn btn-primary" onclick="alert('Create Verknüpfung - würde Modal öffnen')">+ Neue Verknüpfung</button>
</div>
<div class="info-box">
<strong>RBAC:</strong> sysadmin, admin (für Gruppe), trustee.operate (für Organisationen), trustee.userreport (eigene Records)<br>
<strong>Besonderheit:</strong> Verknüpfungen sind optional - Positionen/Dokumente können unabhängig existieren
</div>
<div class="controls">
<input type="text" class="search-box" placeholder="Suche..." id="search-pd" onkeyup="filterTable('pd-table', this.value)">
</div>
<div class="table-container">
<table id="pd-table">
<thead>
<tr>
<th class="sortable" onclick="sortTable('pd-table', 0)">ID</th>
<th class="sortable" onclick="sortTable('pd-table', 1)">Organisation</th>
<th class="sortable" onclick="sortTable('pd-table', 2)">Position ID</th>
<th class="sortable" onclick="sortTable('pd-table', 3)">Document ID</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr>
<td>pd1-...</td>
<td>ACME Corporation</td>
<td>p1q2r3s4-...</td>
<td>d1e2f3a4-...</td>
<td class="actions">
<button class="btn-icon btn-delete" onclick="alert('Unlink Position-Document')" title="Verknüpfung löschen">🗑️</button>
</td>
</tr>
<tr>
<td>pd2-...</td>
<td>ACME Corporation</td>
<td>p1q2r3s4-...</td>
<td>b5c6d7e8-...</td>
<td class="actions">
<button class="btn-icon btn-delete" onclick="alert('Unlink Position-Document')" title="Verknüpfung löschen">🗑️</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Edit Modal for Organisationen -->
<div id="org-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="org-modal-title">Neue Organisation</h3>
<button class="close-btn" onclick="closeModal('org-modal')">&times;</button>
</div>
<form onsubmit="event.preventDefault(); saveOrganisation();">
<div class="form-group">
<label for="org-id">ID <span style="color: red;">*</span></label>
<input type="text" id="org-id" name="id" required pattern="[a-zA-Z0-9_-]{3,50}"
placeholder="z.B. acme-corp"
oninvalid="this.setCustomValidity('ID muss 3-50 Zeichen lang sein (alphanumerisch, Bindestrich, Unterstrich)')"
oninput="this.setCustomValidity('')">
<div class="help-text">Alphanumerisch + Bindestrich/Unterstrich, 3-50 Zeichen</div>
</div>
<div class="form-group">
<label for="org-label">Label <span style="color: red;">*</span></label>
<input type="text" id="org-label" name="label" required placeholder="z.B. ACME Corporation">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="org-enabled" name="enabled" checked>
Enabled
</label>
</div>
<div class="form-group">
<label for="org-mandate">Mandate</label>
<input type="text" id="org-mandate" name="mandate" readonly value="mandate-001">
<div class="help-text">Wird automatisch gesetzt</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="closeModal('org-modal')">Abbrechen</button>
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</form>
</div>
</div>
<!-- Edit Modal for Positions (mit MwSt-Berechnung) -->
<div id="position-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="position-modal-title">Neue Position</h3>
<button class="close-btn" onclick="closeModal('position-modal')">&times;</button>
</div>
<form onsubmit="event.preventDefault(); savePosition();">
<div class="form-group">
<label for="pos-organisation">Organisation <span style="color: red;">*</span></label>
<select id="pos-organisation" name="organisationId" required>
<option value="">Bitte wählen...</option>
<option value="acme-corp">ACME Corporation</option>
<option value="tech-solutions">Tech Solutions AG</option>
</select>
</div>
<div class="form-group">
<label for="pos-contract">Contract <span style="color: red;">*</span></label>
<select id="pos-contract" name="contractId" required>
<option value="">Bitte wählen...</option>
</select>
</div>
<div class="form-group">
<label for="pos-valuta">Valuta <span style="color: red;">*</span></label>
<input type="date" id="pos-valuta" name="valuta" required>
</div>
<div class="form-group">
<label for="pos-company">Company</label>
<input type="text" id="pos-company" name="company" placeholder="z.B. Hotel ABC">
</div>
<div class="form-group">
<label for="pos-booking-amount">Booking Amount <span style="color: red;">*</span></label>
<input type="number" id="pos-booking-amount" name="bookingAmount" step="0.01" required
oninput="calculateVAT()" placeholder="0.00">
</div>
<div class="form-group">
<label for="pos-booking-currency">Booking Currency <span style="color: red;">*</span></label>
<select id="pos-booking-currency" name="bookingCurrency" required>
<option value="CHF">CHF</option>
<option value="EUR">EUR</option>
<option value="USD">USD</option>
</select>
</div>
<div class="form-group">
<label for="pos-vat-percentage">MwSt %</label>
<input type="number" id="pos-vat-percentage" name="vatPercentage" step="0.1" value="0"
oninput="calculateVAT()" placeholder="0.0">
<div class="help-text">Wird automatisch berechnet</div>
</div>
<div class="form-group">
<label for="pos-vat-amount">MwSt Betrag</label>
<input type="number" id="pos-vat-amount" name="vatAmount" step="0.01"
oninput="checkVATOverride()" placeholder="0.00">
<div class="help-text" id="vat-warning" style="color: #ffc107; display: none;">
⚠️ MwSt-Betrag wurde manuell überschrieben
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="closeModal('position-modal')">Abbrechen</button>
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</form>
</div>
</div>
<script>
function openModal(modalId, data) {
const modal = document.getElementById(modalId);
modal.classList.add('active');
if (modalId === 'org-modal') {
if (data.id) {
document.getElementById('org-modal-title').textContent = 'Organisation bearbeiten';
document.getElementById('org-id').value = data.id;
document.getElementById('org-id').readOnly = true;
document.getElementById('org-label').value = data.label || '';
document.getElementById('org-enabled').checked = data.enabled !== false;
} else {
document.getElementById('org-modal-title').textContent = 'Neue Organisation';
document.getElementById('org-id').value = '';
document.getElementById('org-id').readOnly = false;
document.getElementById('org-label').value = '';
document.getElementById('org-enabled').checked = true;
}
} else if (modalId === 'position-modal') {
if (data.id) {
document.getElementById('position-modal-title').textContent = 'Position bearbeiten';
} else {
document.getElementById('position-modal-title').textContent = 'Neue Position';
}
}
}
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
function saveOrganisation() {
const id = document.getElementById('org-id').value;
const label = document.getElementById('org-label').value;
const enabled = document.getElementById('org-enabled').checked;
alert(`Organisation gespeichert:\nID: ${id}\nLabel: ${label}\nEnabled: ${enabled}`);
closeModal('org-modal');
}
function calculateVAT() {
const bookingAmount = parseFloat(document.getElementById('pos-booking-amount').value) || 0;
const vatPercentage = parseFloat(document.getElementById('pos-vat-percentage').value) || 0;
const calculatedVAT = bookingAmount * (vatPercentage / 100);
document.getElementById('pos-vat-amount').value = calculatedVAT.toFixed(2);
checkVATOverride();
}
function checkVATOverride() {
const bookingAmount = parseFloat(document.getElementById('pos-booking-amount').value) || 0;
const vatPercentage = parseFloat(document.getElementById('pos-vat-percentage').value) || 0;
const vatAmount = parseFloat(document.getElementById('pos-vat-amount').value) || 0;
const calculatedVAT = bookingAmount * (vatPercentage / 100);
const warning = document.getElementById('vat-warning');
if (Math.abs(vatAmount - calculatedVAT) > 0.01 && vatAmount > 0) {
warning.style.display = 'block';
} else {
warning.style.display = 'none';
}
}
function savePosition() {
alert('Position gespeichert - MwSt wurde automatisch berechnet');
closeModal('position-modal');
}
// Close modal when clicking outside
window.onclick = function(event) {
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
if (event.target === modal) {
modal.classList.remove('active');
}
});
}
let sortStates = {};
function showTab(tabName) {
// Hide all tab contents
const contents = document.querySelectorAll('.tab-content');
contents.forEach(content => content.classList.remove('active'));
// Remove active class from all tabs
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => tab.classList.remove('active'));
// Show selected tab content
document.getElementById(tabName).classList.add('active');
// Add active class to clicked tab
event.target.classList.add('active');
}
function filterTable(tableId, searchTerm) {
const table = document.getElementById(tableId);
const rows = table.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
const searchLower = searchTerm.toLowerCase();
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const cells = row.getElementsByTagName('td');
let found = false;
for (let j = 0; j < cells.length - 1; j++) { // Exclude actions column
const cellText = cells[j].textContent.toLowerCase();
if (cellText.includes(searchLower)) {
found = true;
break;
}
}
row.style.display = found ? '' : 'none';
}
}
function filterByColumn(tableId, columnIndex, filterValue) {
const table = document.getElementById(tableId);
const rows = table.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const cell = row.getElementsByTagName('td')[columnIndex];
if (!cell) continue;
const cellText = cell.textContent.toLowerCase();
const cellValue = cellText.includes('badge-success') ? 'true' :
cellText.includes('badge-danger') ? 'false' :
cellText.toLowerCase();
if (!filterValue || cellValue.includes(filterValue.toLowerCase())) {
row.style.display = '';
} else {
row.style.display = 'none';
}
}
}
function sortTable(tableId, columnIndex) {
const table = document.getElementById(tableId);
const tbody = table.getElementsByTagName('tbody')[0];
const rows = Array.from(tbody.getElementsByTagName('tr'));
const header = table.getElementsByTagName('thead')[0].getElementsByTagName('th')[columnIndex];
// Remove sort indicators from all headers
const headers = table.getElementsByTagName('thead')[0].getElementsByTagName('th');
for (let i = 0; i < headers.length; i++) {
headers[i].classList.remove('sort-asc', 'sort-desc');
}
// Determine sort direction
const currentState = sortStates[tableId + '-' + columnIndex] || 'none';
let sortDirection = 'asc';
if (currentState === 'asc') {
sortDirection = 'desc';
header.classList.add('sort-desc');
} else {
sortDirection = 'asc';
header.classList.add('sort-asc');
}
sortStates[tableId + '-' + columnIndex] = sortDirection;
// Sort rows
rows.sort((a, b) => {
const aCell = a.getElementsByTagName('td')[columnIndex];
const bCell = b.getElementsByTagName('td')[columnIndex];
if (!aCell || !bCell) return 0;
const aText = aCell.textContent.trim();
const bText = bCell.textContent.trim();
// Try to parse as number
const aNum = parseFloat(aText);
const bNum = parseFloat(bText);
if (!isNaN(aNum) && !isNaN(bNum)) {
return sortDirection === 'asc' ? aNum - bNum : bNum - aNum;
}
// Compare as strings
if (sortDirection === 'asc') {
return aText.localeCompare(bText);
} else {
return bText.localeCompare(aText);
}
});
// Re-append sorted rows
rows.forEach(row => tbody.appendChild(row));
}
</script>
</body>
</html>