1339 lines
55 KiB
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')">×</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')">×</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>
|