- Changes to the list views - now using tabulator with filtering and sorting, client-side pagination, ...

- Adaptation of all list views in the app
This commit is contained in:
Josako
2025-07-14 18:58:54 +02:00
parent acad28b623
commit 000636a229
50 changed files with 2162 additions and 2174 deletions

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="en" itemscope itemtype="http://schema.org/WebPage">
{% include 'head.html' %}
<body class="presentation-page bg-gray-200">

View File

@@ -1,27 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Documents{% endblock %}
{% block content_title %}Catalogs{% endblock %}
{% block content_description %}View Catalogs for Tenant{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('document_bp.handle_catalog_selection') }}" id="catalogsForm">
{{ render_selectable_table(headers=["Catalog ID", "Name", "Type"], rows=rows, selectable=True, id="catalogsTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="set_session_catalog" class="btn btn-primary" onclick="return validateTableSelection('catalogsForm')">Set Session Catalog</button>
<button type="submit" name="action" value="edit_catalog" class="btn btn-primary" onclick="return validateTableSelection('catalogsForm')">Edit Catalog</button>
</div>
<button type="submit" name="action" value="create_catalog" class="btn btn-success">Register Catalog</button>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.catalogs') }}
{% endblock %}

View File

@@ -1,27 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Document Versions{% endblock %}
{% block content_title %}Document Versions{% endblock %}
{% block content_description %}View Versions for Document <b>{{ document }}</b>{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('document_bp.handle_document_version_selection') }}" id="documentVersionsForm">
{{ render_selectable_table(headers=["ID", "File Type", "File Size", "Process.", "Proces. Start", "Proces. Finish", "Proces. Error"], rows=rows, selectable=True, id="versionsTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_document_version" class="btn btn-primary" onclick="return validateTableSelection('documentVersionsForm')">Edit Document Version</button>
<button type="submit" name="action" value="view_document_version_markdown" class="btn btn-danger" onclick="return validateTableSelection('documentVersionsForm')">View Processed Document</button>
<button type="submit" name="action" value="process_document_version" class="btn btn-danger" onclick="return validateTableSelection('documentVersionsForm')">Process Document Version</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.documents') }}
{% endblock %}

View File

@@ -1,94 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination, render_filter_field, render_date_filter_field, render_collapsible_section, render_selectable_sortable_table %}
{% block title %}Documents{% endblock %}
{% block content_title %}Document Versions{% endblock %}
{% block content_description %}View Document Versions for Tenant{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<!-- Filter Form -->
{% set filter_form %}
<form method="GET" action="{{ url_for('document_bp.document_versions_list') }}">
{{ render_filter_field('file_type', 'File Type', filter_options['file_type'], filters.get('file_type')) }}
{{ render_filter_field('processing', 'Processing Status', filter_options['processing'], filters.get('processing')) }}
{{ render_filter_field('processing_error', 'Error Status', filter_options['processing_error'], filters.get('processing_error')) }}
{{ render_date_filter_field('start_date', 'Processing Start Date', filters.get('start_date')) }}
{{ render_date_filter_field('end_date', 'Processing End Date', filters.get('end_date')) }}
<button type="submit" class="btn btn-primary">Apply Filters</button>
</form>
{% endset %}
{{ render_collapsible_section('Filter', 'Filter Options', filter_form) }}
<div class="form-group mt-3">
<form method="POST" action="{{ url_for('document_bp.handle_document_version_selection') }}" id="documentVersionsForm">
<!-- Document Versions Table -->
{{ render_selectable_sortable_table(
headers=["ID", "File Type", "Processing", "Processing Start", "Processing Finish", "Processing Error"],
rows=rows,
selectable=True,
id="documentVersionsTable",
sort_by=sort_by,
sort_order=sort_order
) }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_document_version" class="btn btn-primary" onclick="return validateTableSelection('documentVersionsForm')">Edit Document Version</button>
<button type="submit" name="action" value="view_document_version_markdown" class="btn btn-danger" onclick="return validateTableSelection('documentVersionsForm')">View Processed Document</button>
<button type="submit" name="action" value="process_document_version" class="btn btn-danger" onclick="return validateTableSelection('documentVersionsForm')">Process Document Version</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.document_versions_list') }}
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const table = document.getElementById('documentVersionsTable');
const headers = table.querySelectorAll('th.sortable');
headers.forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.dataset.sort;
let sortOrder = 'asc';
if (this.querySelector('.fa-sort-up')) {
sortOrder = 'desc';
} else if (this.querySelector('.fa-sort-down')) {
sortOrder = 'none';
}
window.location.href = updateQueryStringParameter(window.location.href, 'sort_by', sortBy);
window.location.href = updateQueryStringParameter(window.location.href, 'sort_order', sortOrder);
});
});
function updateQueryStringParameter(uri, key, value) {
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
if (uri.match(re)) {
return uri.replace(re, '$1' + key + "=" + value + '$2');
}
else {
return uri + separator + key + "=" + value;
}
}
table.addEventListener('change', function(event) {
if (event.target.type === 'radio') {
var selectedRow = event.target.closest('tr');
var documentVersionId = selectedRow.cells[1].textContent;
console.log('Selected Document Version ID:', documentVersionId);
}
});
});
</script>
{% endblock %}

View File

@@ -1,95 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination, render_filter_field, render_date_filter_field, render_collapsible_section, render_selectable_sortable_table_with_dict_headers %}
{% block title %}Documents{% endblock %}
{% block content_title %}Documents{% endblock %}
{% block content_description %}View Documents for Catalog <b>{% if session.catalog_name %}{{ session.catalog_name }}{% else %}No Catalog{% endif %}</b>{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<!-- Filter Form -->
{% set filter_form %}
<form method="GET" action="{{ url_for('document_bp.documents') }}">
{{ render_filter_field('validity', 'Validity', filter_options['validity'], filters.get('validity', [])) }}
<button type="submit" class="btn btn-primary">Apply Filters</button>
</form>
{% endset %}
{{ render_collapsible_section('Filter', 'Filter Options', filter_form) }}
<div class="form-group mt-3">
<form method="POST" action="{{ url_for('document_bp.handle_document_selection') }}" id="documentsForm">
<!-- Documents Table -->
{{ render_selectable_sortable_table_with_dict_headers(
headers=[
{"text": "ID", "sort": "id"},
{"text": "Name", "sort": "name"},
{"text": "Valid From", "sort": "valid_from"},
{"text": "Valid To", "sort": "valid_to"}
],
rows=rows,
selectable=True,
id="documentsTable",
sort_by=sort_by,
sort_order=sort_order
) }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_document" class="btn btn-primary" onclick="return validateTableSelection('documentsForm')">Edit Document</button>
<button type="submit" name="action" value="document_versions" class="btn btn-secondary" onclick="return validateTableSelection('documentsForm')">Show Document Versions</button>
<button type="submit" name="action" value="refresh_document" class="btn btn-secondary" onclick="return validateTableSelection('documentsForm')">Refresh Document (new version)</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.documents') }}
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const table = document.getElementById('documentsTable');
const headers = table.querySelectorAll('th.sortable');
headers.forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.dataset.sort;
let sortOrder = 'asc';
if (this.querySelector('.fa-sort-up')) {
sortOrder = 'desc';
} else if (this.querySelector('.fa-sort-down')) {
sortOrder = 'none';
}
window.location.href = updateQueryStringParameter(window.location.href, 'sort_by', sortBy);
window.location.href = updateQueryStringParameter(window.location.href, 'sort_order', sortOrder);
});
});
function updateQueryStringParameter(uri, key, value) {
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
if (uri.match(re)) {
return uri.replace(re, '$1' + key + "=" + value + '$2');
}
else {
return uri + separator + key + "=" + value;
}
}
table.addEventListener('change', function(event) {
if (event.target.type === 'radio') {
var selectedRow = event.target.closest('tr');
var documentId = selectedRow.cells[1].textContent;
console.log('Selected Document ID:', documentId);
}
});
});
</script>
{% endblock %}

View File

@@ -1,114 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination, render_filter_field, render_date_filter_field, render_collapsible_section, render_selectable_sortable_table_with_dict_headers %}
{% block title %}Complete Document Overview{% endblock %}
{% block content_title %}Complete Document Overview{% endblock %}
{% block content_description %}View Documents with Latest Version for Catalog <b>{% if session.catalog_name %}{{ session.catalog_name }}{% else %}No Catalog{% endif %}</b>{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<!-- Filter Form -->
{% set filter_form %}
<form method="GET" action="{{ url_for('document_bp.full_documents') }}">
{{ render_filter_field('validity', 'Validity', filter_options['validity'], filters.get('validity', [])) }}
{{ render_filter_field('file_type', 'File Type', filter_options['file_type'], filters.get('file_type', [])) }}
{{ render_filter_field('processing', 'Processing Status', filter_options['processing'], filters.get('processing', [])) }}
{{ render_filter_field('processing_error', 'Error Status', filter_options['processing_error'], filters.get('processing_error', [])) }}
{{ render_date_filter_field('start_date', 'Processing Start Date', filters.get('start_date', [])) }}
{{ render_date_filter_field('end_date', 'Processing End Date', filters.get('end_date', [])) }}
<button type="submit" class="btn btn-primary">Apply Filters</button>
</form>
{% endset %}
{{ render_collapsible_section('Filter', 'Filter Options', filter_form) }}
<div class="form-group mt-3">
<form method="POST" action="{{ url_for('document_bp.handle_full_document_selection') }}" id="fullDocumentsForm">
<!-- Hidden field to store the selected version ID -->
<input type="hidden" name="version_id" id="selectedVersionId" value="">
<!-- Documents Table -->
{{ render_selectable_sortable_table_with_dict_headers(
headers=[
{"text": "Document ID", "sort": "id"},
{"text": "Name", "sort": "name"},
{"text": "Valid From", "sort": "valid_from"},
{"text": "Valid To", "sort": "valid_to"},
{"text": "Version ID", "sort": ""},
{"text": "File Type", "sort": "file_type"},
{"text": "Processing", "sort": "processing"},
{"text": "Error", "sort": "processing_error"}
],
rows=rows,
selectable=True,
id="fullDocumentsTable",
sort_by=sort_by,
sort_order=sort_order
) }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_document" class="btn btn-primary" onclick="return validateTableSelection('fullDocumentsForm')">Edit Document</button>
<button type="submit" name="action" value="edit_document_version" class="btn btn-primary" onclick="return validateTableSelection('fullDocumentsForm')">Edit Document Version</button>
<button type="submit" name="action" value="document_versions" class="btn btn-secondary" onclick="return validateTableSelection('fullDocumentsForm')">Show All Document Versions</button>
<button type="submit" name="action" value="refresh_document" class="btn btn-secondary" onclick="return validateTableSelection('fullDocumentsForm')">Refresh Document (new version)</button>
<button type="submit" name="action" value="view_document_version_markdown" class="btn btn-danger" onclick="return validateTableSelection('fullDocumentsForm')">View Processed Document</button>
<button type="submit" name="action" value="process_document_version" class="btn btn-danger" onclick="return validateTableSelection('fullDocumentsForm')">Process Document Version</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.full_documents') }}
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const table = document.getElementById('fullDocumentsTable');
const headers = table.querySelectorAll('th.sortable');
headers.forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.dataset.sort;
let sortOrder = 'asc';
if (this.querySelector('.fa-sort-up')) {
sortOrder = 'desc';
} else if (this.querySelector('.fa-sort-down')) {
sortOrder = 'none';
}
window.location.href = updateQueryStringParameter(window.location.href, 'sort_by', sortBy);
window.location.href = updateQueryStringParameter(window.location.href, 'sort_order', sortOrder);
});
});
function updateQueryStringParameter(uri, key, value) {
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
if (uri.match(re)) {
return uri.replace(re, '$1' + key + "=" + value + '$2');
}
else {
return uri + separator + key + "=" + value;
}
}
table.addEventListener('change', function(event) {
if (event.target.type === 'radio') {
var selectedRow = event.target.closest('tr');
var documentId = selectedRow.cells[1].textContent;
var versionId = selectedRow.cells[5].textContent;
console.log('Selected Document ID:', documentId, 'Version ID:', versionId);
// Update the hidden field with the version ID
document.getElementById('selectedVersionId').value = versionId;
}
});
});
</script>
{% endblock %}

View File

@@ -1,26 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Processors{% endblock %}
{% block content_title %}Processors{% endblock %}
{% block content_description %}View Processors for Catalog <b>{% if session.catalog_name %}{{ session.catalog_name }}{% else %}No Catalog{% endif %}</b>{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('document_bp.handle_processor_selection') }}" id="processorsForm">
{{ render_selectable_table(headers=["Processor ID", "Name", "Type", "Active"], rows=rows, selectable=True, id="retrieversTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_processor" class="btn btn-primary" onclick="return validateTableSelection('processorsForm')">Edit Processor</button>
</div>
<button type="submit" name="action" value="create_processor" class="btn btn-success">Register Processor</button>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.processors') }}
{% endblock %}

View File

@@ -1,26 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Retrievers{% endblock %}
{% block content_title %}Retrievers{% endblock %}
{% block content_description %}View Retrievers for Catalog <b>{% if session.catalog_name %}{{ session.catalog_name }}{% else %}No Catalog{% endif %}</b>{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('document_bp.handle_retriever_selection') }}" id="retrieversForm">
{{ render_selectable_table(headers=["Retriever ID", "Name", "Type"], rows=rows, selectable=True, id="retrieversTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_retriever" class="btn btn-primary" onclick="return validateTableSelection('retrieversForm')">Edit Retriever</button>
</div>
<button type="submit" name="action" value="create_retriever" class="btn btn-success">Register Retriever</button>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.retrievers') }}
{% endblock %}

View File

@@ -1,36 +0,0 @@
{% extends 'base.html' %}
{% from "macros.html" import render_selectable_table, render_pagination, render_field %}
{% block title %}License Tier Selection{% endblock %}
{% block content_title %}Select a License Tier{% endblock %}
{% block content_description %}Select a License Tier to continue{% endblock %}
{% block content %}
<!-- License Tier Selection Form -->
<form method="POST" action="{{ url_for('entitlements_bp.handle_license_tier_selection') }}" id="licenseTiersForm">
{{ render_selectable_table(headers=["ID", "Name", "Version", "Start Date", "End Date"], rows=rows, selectable=True, id="licenseTierTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
{% if current_user.has_role('Super User') %}
<button type="submit" name="action" value="edit_license_tier" class="btn btn-primary" onclick="return validateTableSelection('licenseTiersForm')">Edit License Tier</button>
{% endif %}
{% if current_user.has_role('Super User') or (current_user.has_role('Partner Admin') and can_assign_license) %}
<button type="submit" name="action" value="create_license_for_tenant" class="btn btn-secondary" onclick="return validateTableSelection('licenseTiersForm')">Create Tenant License</button>
{% endif %}
{% if current_user.has_role('Super User') %}
<button type="submit" name="action" value="associate_license_tier_to_partner" class="btn btn-secondary" onclick="return validateTableSelection('licenseTiersForm')">Associate to Partner</button>
{% endif %}
</div>
{% if current_user.has_role('Super User') %}
<button type="submit" name="action" value="create_license_tier" class="btn btn-success">Register License Tier</button>
{% endif %}
</div>
</form>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'user_bp.select_tenant') }}
{% endblock %}

View File

@@ -1,28 +0,0 @@
{% extends 'base.html' %}
{% from "macros.html" import render_selectable_table, render_pagination %}
{% block title %}View Licenses{% endblock %}
{% block content_title %}View Licenses{% endblock %}
{% block content_description %}View Licenses{% endblock %}
{% block content %}
<form action="{{ url_for('entitlements_bp.handle_license_selection') }}" method="POST" id="licensesForm">
{{ render_selectable_table(headers=["License ID", "Name", "Start Date", "Nr of Periods", "Active"], rows=rows, selectable=True, id="licensesTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_license" class="btn btn-primary" onclick="return validateTableSelection('licensesForm')">Edit License</button>
<button type="submit" name="action" value="view_periods" class="btn btn-info" onclick="return validateTableSelection('licensesForm')">View Periods</button>
</div>
<!-- Additional buttons can be added here for other actions -->
</div>
</form>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'entitlements_bp.view_licenses') }}
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@@ -1,301 +1,493 @@
<script type="module">
window.EveAI = window.EveAI || {};
window.EveAI.ListView = {
instances: {},
<style>
button.disabled, button:disabled {
opacity: 0.65;
cursor: not-allowed;
pointer-events: none;
}
</style>
<div class="container">
<input type="hidden" id="{{ table_id }}-selected-row" name="selected_row" value="">
<input type="hidden" id="{{ table_id }}-action" name="action" value="">
initialize: function(containerId, options = {}) {
const container = document.getElementById(containerId);
if (!container) {
console.error(`Container ${containerId} not found`);
return null;
<div id="{{ table_id }}" class="tabulator-list-view"></div>
<div class="row mt-3">
{% set right_actions = actions|selectattr('position', 'equalto', 'right')|list %}
<div class="{% if right_actions %}col{% else %}col-12{% endif %}">
{% for action in actions if action.position != 'right' %}
<button type="button"
onclick="handleListViewAction('{{ action.value }}', {{ action.requiresSelection|tojson }})"
class="btn {{ action.class|default('btn-primary') }} me-2 {% if action.requiresSelection %}requires-selection{% endif %}"
{% if action.requiresSelection %}disabled{% endif %}>
{{ action.text }}
</button>
{% endfor %}
</div>
{% if right_actions %}
<div class="col-auto text-end">
{% for action in actions if action.position == 'right' %}
<button type="button"
onclick="handleListViewAction('{{ action.value }}', {{ action.requiresSelection|tojson }})"
class="btn {{ action.class|default('btn-primary') }} ms-2 {% if action.requiresSelection %}requires-selection{% endif %}"
{% if action.requiresSelection %}disabled{% endif %}>
{{ action.text }}
</button>
{% endfor %}
</div>
{% endif %}
</div>
</div>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
// Zorg ervoor dat de ListView-module beschikbaar is
window.EveAI = window.EveAI || {};
window.EveAI.ListView = window.EveAI.ListView || {};
/* ListView-module functionaliteit:
* - Initialiseert een Tabulator tabel met de gegeven configuratie
* - Beheert rijselectie (maximaal 1 rij tegelijk)
* - Zorgt ervoor dat knoppen met requiresSelection=true inactief zijn wanneer er geen rij is geselecteerd
* - Ondersteunt verschillende Tabulator versies (5.x en 6.x)
*/
// Direct alle buttons met requiresSelection uitschakelen bij laden pagina
document.querySelectorAll('button[onclick*="handleListViewAction"]').forEach(button => {
const onclickAttr = button.getAttribute('onclick');
const match = onclickAttr.match(/handleListViewAction\('([^']+)',\s*(true|false)\)/i);
if (match && match[2].toLowerCase() === 'true') {
button.disabled = true;
button.classList.add('disabled');
}
});
// Default configuration
const config = {
data: [],
columns: [],
initialSort: [],
initialFilters: [],
filterableColumns: [],
selectable: true,
actions: [],
usePagination: true, // Nieuwe optie om paginering aan/uit te zetten
tableHeight: 700, // Standaard tabelhoogte als deze niet wordt gespecificeerd
...options
};
// Voeg de benodigde functies toe als ze nog niet bestaan
if (!window.EveAI.ListView.initialize) {
window.EveAI.ListView.instances = {};
// Check if Tabulator is available
if (typeof window.Tabulator !== 'function') {
console.error('Tabulator not loaded (window.Tabulator missing).');
container.innerHTML = `<div class="alert alert-danger">
<strong>Error:</strong> Tabulator not loaded
</div>`;
return null;
}
// Initialize functie
window.EveAI.ListView.initialize = function(containerId, options = {}) {
const container = document.getElementById(containerId);
if (!container) {
console.error(`Container ${containerId} not found`);
return null;
}
try {
// Default configuration
const config = {
data: [],
columns: [],
initialSort: [],
initialFilters: [],
filterableColumns: [],
selectable: true,
actions: [],
usePagination: true,
tableHeight: 700,
...options
};
// Create Tabulator table
const tableContainer = document.createElement('div');
tableContainer.className = 'tabulator-container tabulator-list-view mt-3';
container.appendChild(tableContainer);
// Check if Tabulator is available
if (typeof window.Tabulator !== 'function') {
console.error('Tabulator not loaded (window.Tabulator missing).');
container.innerHTML = `<div class="alert alert-danger">
<strong>Error:</strong> Tabulator not loaded
</div>`;
return null;
}
// Basisinstellingen voor tabel configuratie
const tableConfig = {
data: config.data,
columns: this._buildColumns(config.columns, config.selectable),
layout: "fitColumns",
try {
// Create Tabulator table
const tableContainer = document.createElement('div');
tableContainer.className = 'tabulator-container tabulator-list-view mt-3';
container.appendChild(tableContainer);
// Conditionele paginatie of progressieve lading
...(config.usePagination ? {
pagination: "local",
paginationSize: 25,
paginationSizeSelector: [10, 25, 50, 100]
} : {
progressiveLoad: "scroll" // Gebruik progressieve lading voor grote datasets wanneer paginatie uitstaat
}),
// Basisinstellingen voor tabel configuratie
const tableConfig = {
data: config.data,
columns: this._buildColumns(config.columns, config.selectable),
layout: "fitColumns",
// Gemeenschappelijke configuratie voor beide modi
movableColumns: true,
resizableRows: false, // Schakel dit uit om prestatieproblemen te voorkomen
initialSort: config.initialSort,
initialFilter: config.initialFilters,
selectable: 1, // Beperk tot maximaal 1 rij selectie
selectableRangeMode: "click",
selectableCheck: function() { return true; }, // Zorg ervoor dat alle cellen selecteerbaar zijn
rowClick: function(e, row) {
// Selecteer de rij bij klikken op willekeurige cel
// Conditionele paginatie of progressieve lading
...(config.usePagination ? {
pagination: "local",
paginationSize: 25,
paginationSizeSelector: [10, 25, 50, 100]
} : {
progressiveLoad: "scroll" // Gebruik progressieve lading voor grote datasets wanneer paginatie uitstaat
}),
// Gemeenschappelijke configuratie voor beide modi
movableColumns: true,
resizableRows: false, // Schakel dit uit om prestatieproblemen te voorkomen
initialSort: config.initialSort,
initialFilter: config.initialFilters,
selectable: config.selectable ? 1 : false, // Beperk tot maximaal 1 rij selectie of schakel uit
selectableRangeMode: "click",
selectableCheck: function() { return true; }, // Zorg ervoor dat alle cellen selecteerbaar zijn
rowClick: function(e, row) {
// Selecteer de rij bij klikken op willekeurige cel
if (config.selectable) {
console.log('Row clicked!', row.getData());
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen als extra maatregel
const instance = EveAI.ListView.instances[containerId];
if (instance) {
// Expliciet de geselecteerde rij instellen
instance.selectedRow = row.getData();
// Direct UI updaten voor betere gebruikerservaring
console.log('Updating buttons from rowClick handler');
EveAI.ListView._updateActionButtons(containerId);
}
}
},
cellClick: function(e, cell) {
// Gebruik ook cellClick om ervoor te zorgen dat klikken op een cel de rij selecteert
if (config.selectable && !e.target.matches('a, input, button, select, .tabulator-cell-editing')) {
console.log('Cell clicked!', cell.getData());
const row = cell.getRow();
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen als extra maatregel
const instance = EveAI.ListView.instances[containerId];
if (instance) {
instance.selectedRow = row.getData();
// Direct forceer een update van de buttons - ook als de rowSelectionChanged niet triggert
console.log('Updating buttons from cellClick handler');
setTimeout(() => EveAI.ListView._updateActionButtons(containerId), 0);
}
}
},
cellTap: function(e, cell) {
// Extra handler voor touch devices
if (config.selectable && !e.target.matches('a, input, button, select, .tabulator-cell-editing')) {
console.log('Cell tapped!', cell.getData());
const row = cell.getRow();
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen
const instance = EveAI.ListView.instances[containerId];
if (instance) {
instance.selectedRow = row.getData();
EveAI.ListView._updateActionButtons(containerId);
}
}
},
rowSelectionChanged: function(data, rows) {
console.log("Aantal geselecteerde rijen:", rows.length);
console.log("Geselecteerde rijen:", rows.map(r => r.getData()));
// Update de geselecteerde rij (enkelvoud)
const selectedData = rows.length > 0 ? rows[0].getData() : null;
console.log('rowSelectionChanged met data:', selectedData);
// Controleer of de instance bestaat
if (EveAI.ListView.instances[containerId]) {
EveAI.ListView.instances[containerId].selectedRow = selectedData;
// Met kleine timeout om zeker te zijn dat alles is bijgewerkt
setTimeout(() => {
console.log('Updating buttons from rowSelectionChanged handler');
EveAI.ListView._updateActionButtons(containerId);
}, 10);
} else {
console.warn(`Instance voor ${containerId} niet gevonden in rowSelectionChanged`);
}
},
rowFormatter: function(row) {
// Voeg cursor-style toe om aan te geven dat rijen klikbaar zijn
if (config.selectable) {
row.getElement().style.cursor = 'pointer';
}
},
placeholder: "No data available",
tooltips: false, // Schakel tooltips uit voor betere prestaties
responsiveLayout: false, // Schakel responsiveLayout uit om recursieve problemen te voorkomen
renderVerticalBuffer: 20, // Optimaliseer virtuele rendering
virtualDomBuffer: 80, // Optimaliseer virtuele DOM buffer
height: config.tableHeight // Gebruik de geconfigureerde tabel hoogte
};
// Maak Tabulator instantie met de geconfigureerde instellingen
const table = new Tabulator(tableContainer, tableConfig);
// Store instance - maar maak GEEN action buttons meer in JavaScript
this.instances[containerId] = {
table: table,
config: config,
selectedRow: null
};
// Direct bij initialisatie: zorg ervoor dat knoppen met requiresSelection=True inactief zijn
// Gebruik een groter timeout om te zorgen dat alle componenten zijn geladen
setTimeout(() => {
console.log('Initialiseren van action buttons...');
this._updateActionButtons(containerId);
}, 100);
// Registreer events met de Tabulator API voor betere compatibiliteit
table.on("rowClick", function(e, row){
console.log("Tabulator API: Row clicked!", row.getData());
if (config.selectable) {
console.log('Row clicked!', row.getData());
// Stop event propagation om te voorkomen dat andere handlers interfereren
e.stopPropagation();
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen als extra maatregel
const instance = EveAI.ListView.instances[containerId];
if (instance) {
instance.selectedRow = row.getData();
EveAI.ListView._updateActionButtons(containerId);
}
}
},
cellClick: function(e, cell) {
// Gebruik ook cellClick om ervoor te zorgen dat klikken op een cel de rij selecteert
if (config.selectable && !e.target.matches('a, input, button, select, .tabulator-cell-editing')) {
console.log('Cell clicked!', cell.getData());
const row = cell.getRow();
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen als extra maatregel
const instance = EveAI.ListView.instances[containerId];
if (instance) {
instance.selectedRow = row.getData();
EveAI.ListView._updateActionButtons(containerId);
}
}
},
cellTap: function(e, cell) {
// Extra handler voor touch devices
if (config.selectable && !e.target.matches('a, input, button, select, .tabulator-cell-editing')) {
console.log('Cell tapped!', cell.getData());
const row = cell.getRow();
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen
const instance = EveAI.ListView.instances[containerId];
if (instance) {
instance.selectedRow = row.getData();
EveAI.ListView._updateActionButtons(containerId);
}
}
},
rowSelectionChanged: function(data, rows) {
console.log("Aantal geselecteerde rijen:", rows.length);
console.log("Geselecteerde rijen:", rows.map(r => r.getData()));
});
// Row selection event met de Tabulator API
table.on("rowSelectionChanged", function(data, rows){
console.log("Tabulator API: Aantal geselecteerde rijen:", rows.length);
console.log("Tabulator API: Geselecteerde rijen:", rows.map(r => r.getData()));
// Update de geselecteerde rij (enkelvoud)
EveAI.ListView.instances[containerId].selectedRow = rows.length > 0 ? rows[0].getData() : null;
EveAI.ListView._updateActionButtons(containerId);
},
rowFormatter: function(row) {
// Voeg cursor-style toe om aan te geven dat rijen klikbaar zijn
if (config.selectable) {
row.getElement().style.cursor = 'pointer';
});
return table;
} catch (error) {
console.error('Error initializing ListView:', error);
container.innerHTML = `<div class="alert alert-danger">
<strong>Error loading data:</strong> ${error.message}
</div>`;
return null;
}
};
// Bouw kolommen functie
window.EveAI.ListView._buildColumns = function(columns, selectable) {
const tabulatorColumns = [];
// Detecteer Tabulator versie (6.x heeft een andere aanpak voor column definities)
const isTabulator6Plus = typeof Tabulator === 'function' && Tabulator.version &&
parseInt(Tabulator.version.split('.')[0]) >= 6;
console.log('Tabulator versie detectie:', isTabulator6Plus ? '6+' : 'Pre-6');
// Add data columns
columns.forEach(col => {
// Maak een nieuwe schone kolom met alleen geldige Tabulator-eigenschappen
// voor de gedetecteerde versie
const column = {
title: col.title,
field: col.field,
width: col.width,
hozAlign: col.hozAlign || 'left',
vertAlign: col.vertAlign || 'middle'
};
// Voeg sorteren toe volgens de juiste manier per versie
if (isTabulator6Plus) {
// Tabulator 6+ gebruikt sorteerbaarheid via kolom-opties
column.sorter = col.field === 'id' || col.field.endsWith('_id') || col.type === 'number' ?
'number' : 'string';
column.sorterParams = {};
column.sorteringActive = col.sortable !== false;
} else {
// Pre-6 versies gebruiken headerSort
column.headerSort = col.sortable !== false;
// Zorg voor juiste numerieke sortering voor ID velden
if (col.field === 'id' || col.field.endsWith('_id') || col.type === 'number') {
column.sorter = 'number';
}
},
placeholder: "No data available",
tooltips: false, // Schakel tooltips uit voor betere prestaties
responsiveLayout: false, // Schakel responsiveLayout uit om recursieve problemen te voorkomen
renderVerticalBuffer: 20, // Optimaliseer virtuele rendering
virtualDomBuffer: 80, // Optimaliseer virtuele DOM buffer
height: config.tableHeight // Gebruik de geconfigureerde tabel hoogte
};
}
// Maak Tabulator instantie met de geconfigureerde instellingen
const table = new Tabulator(tableContainer, tableConfig);
// Voeg formattering toe volgens de juiste manier per versie
if (col.formatter) {
if (isTabulator6Plus) {
column.formatterParams = { formatter: col.formatter };
} else {
column.formatter = col.formatter;
}
}
// Create action buttons
if (config.actions.length > 0) {
this._createActionButtons(container, config.actions, table);
// Voeg filtering toe volgens de juiste manier per versie
if (isTabulator6Plus) {
// Tabulator 6+ gebruikt verschillende eigenschappen voor filtering
column.filterable = col.filterable !== false;
column.filterParams = {};
if (col.type === 'date') {
column.filterParams.type = 'date';
} else if (col.type === 'number') {
column.filterParams.type = 'number';
} else if (col.filterValues) {
column.filterParams.values = col.filterValues;
}
} else {
// Pre-6 versies gebruiken headerFilter
column.headerFilter = col.filterable !== false ? 'input' : false;
// Set appropriate header filter based on the data type
if (col.type === 'date') {
column.headerFilter = "input";
column.headerFilterParams = {type: "date"};
} else if (col.type === 'number') {
column.headerFilter = "number";
} else if (col.filterValues) {
column.headerFilter = "select";
column.headerFilterParams = {values: col.filterValues};
}
}
tabulatorColumns.push(column);
});
return tabulatorColumns;
};
// Update action buttons functie
window.EveAI.ListView._updateActionButtons = function(containerId) {
const instance = this.instances[containerId];
if (!instance) {
console.warn(`Kan buttons niet updaten: geen instance voor ${containerId}`);
return;
}
// Store instance
this.instances[containerId] = {
table: table,
config: config,
selectedRow: null
};
// Zoek buttons in de volledige formulier context (niet alleen de container)
const form = document.getElementById(`${containerId}-form`) || document.querySelector('form');
const buttons = form ? form.querySelectorAll('button[onclick*="handleListViewAction"]') :
document.querySelectorAll('button[onclick*="handleListViewAction"]');
// Registreer events met de Tabulator API voor betere compatibiliteit
table.on("rowClick", function(e, row){
console.log("Tabulator API: Row clicked!", row.getData());
if (config.selectable) {
// Stop event propagation om te voorkomen dat andere handlers interfereren
e.stopPropagation();
row.getTable().deselectRow();
row.select();
console.log(`Updating buttons voor ${containerId}, ${buttons.length} buttons gevonden, selectedRow:`, instance.selectedRow);
buttons.forEach(button => {
// Parse the onclick attribute to get the action value and requiresSelection parameter
const onclickAttr = button.getAttribute('onclick');
const match = onclickAttr.match(/handleListViewAction\('([^']+)',\s*(true|false)\)/i);
if (match) {
const actionValue = match[1];
const requiresSelection = match[2].toLowerCase() === 'true';
// Direct toepassen van requiresSelection-check
if (requiresSelection) {
// Controleer of er een geselecteerde rij is
const isDisabled = !instance.selectedRow;
button.disabled = isDisabled;
// Voeg/verwijder disabled class voor styling
if (isDisabled) {
button.classList.add('disabled');
} else {
button.classList.remove('disabled');
}
console.log(`Button ${actionValue} updated: disabled=${isDisabled}`);
}
// Backup check op basis van actions in config (voor achterwaartse compatibiliteit)
const action = instance.config.actions.find(a => a.value === actionValue);
if (action && action.requiresSelection === true && !requiresSelection) {
// Ook controleren op basis van action config
const isDisabled = !instance.selectedRow;
button.disabled = isDisabled;
// Voeg/verwijder disabled class voor styling
if (isDisabled) {
button.classList.add('disabled');
} else {
button.classList.remove('disabled');
}
}
}
});
// Row selection event met de Tabulator API
table.on("rowSelectionChanged", function(data, rows){
console.log("Tabulator API: Aantal geselecteerde rijen:", rows.length);
console.log("Tabulator API: Geselecteerde rijen:", rows.map(r => r.getData()));
// Update hidden input with selected row data
this._updateSelectedRowInput(containerId);
};
// Update de geselecteerde rij (enkelvoud)
EveAI.ListView.instances[containerId].selectedRow = rows.length > 0 ? rows[0].getData() : null;
EveAI.ListView._updateActionButtons(containerId);
});
// Update selected row input functie
window.EveAI.ListView._updateSelectedRowInput = function(containerId) {
const instance = this.instances[containerId];
let hiddenInput = document.getElementById(`${containerId}-selected-row`);
// Opmerking: row selection event wordt nu afgehandeld via de Tabulator API
return table;
} catch (error) {
console.error('Error initializing ListView:', error);
container.innerHTML = `<div class="alert alert-danger">
<strong>Error loading data:</strong> ${error.message}
</div>`;
return null;
}
},
_buildColumns: function(columns, selectable) {
const tabulatorColumns = [];
// Add selection column if needed
{#if (selectable) {#}
{# tabulatorColumns.push({#}
{# formatter: "rowSelection", #}
{# titleFormatter: "rowSelection", #}
{# hozAlign: "center", #}
{# headerSort: false, #}
{# width: 60#}
{# });#}
{#}#}
// Add data columns
columns.forEach(col => {
// Maak een nieuwe schone kolom met alleen geldige Tabulator-eigenschappen
const column = {
title: col.title,
field: col.field,
headerSort: col.sortable !== false,
headerFilter: col.filterable !== false,
formatter: col.formatter,
width: col.width,
hozAlign: col.hozAlign,
vertAlign: col.vertAlign
};
// Zorg voor juiste numerieke sortering voor ID velden
if (col.field === 'id' || col.field.endsWith('_id') || col.type === 'number') {
column.sorter = 'number';
if (!hiddenInput) {
hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'selected_row';
hiddenInput.id = `${containerId}-selected-row`;
document.getElementById(containerId).appendChild(hiddenInput);
}
// Set appropriate header filter based on the data type
if (col.type === 'date') {
column.headerFilter = "input";
column.headerFilterParams = {type: "date"};
} else if (col.type === 'number') {
column.headerFilter = "number";
} else if (col.filterValues) {
column.headerFilter = "select";
column.headerFilterParams = {values: col.filterValues};
if (instance.selectedRow) {
// Bewaar de geselecteerde rij-ID
hiddenInput.value = JSON.stringify({value: instance.selectedRow.id});
} else {
column.headerFilter = "input";
hiddenInput.value = '';
}
tabulatorColumns.push(column);
});
return tabulatorColumns;
},
_createActionButtons: function(container, actions, table) {
const actionSection = document.createElement('div');
actionSection.className = 'mt-3 d-flex justify-content-between';
const leftActions = document.createElement('div');
const rightActions = document.createElement('div');
actions.forEach(action => {
const button = document.createElement('button');
button.type = 'submit';
button.name = 'action';
button.value = action.value;
button.className = `btn ${action.class || 'btn-primary'} me-2`;
button.textContent = action.text;
button.disabled = action.requiresSelection !== false; // Disabled by default if requires selection
if (action.position === 'right') {
rightActions.appendChild(button);
} else {
leftActions.appendChild(button);
}
});
actionSection.appendChild(leftActions);
actionSection.appendChild(rightActions);
container.appendChild(actionSection);
},
_updateActionButtons: function(containerId) {
const instance = this.instances[containerId];
const container = document.getElementById(containerId);
const buttons = container.querySelectorAll('button[name="action"]');
buttons.forEach(button => {
// Enable/disable based on selection requirement
const action = instance.config.actions.find(a => a.value === button.value);
if (action && action.requiresSelection !== false) {
// Controleer of er een geselecteerde rij is
button.disabled = !instance.selectedRow;
}
});
// Update hidden input with selected row data
this._updateSelectedRowInput(containerId);
},
_updateSelectedRowInput: function(containerId) {
const instance = this.instances[containerId];
let hiddenInput = document.getElementById(`${containerId}-selected-row`);
if (!hiddenInput) {
hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'selected_row';
hiddenInput.id = `${containerId}-selected-row`;
document.getElementById(containerId).appendChild(hiddenInput);
}
if (instance.selectedRow) {
// Bewaar de geselecteerde rij-ID
hiddenInput.value = JSON.stringify({value: instance.selectedRow.id});
} else {
hiddenInput.value = '';
}
};
}
};
</script>
// Definieer de handleListViewAction functie als deze nog niet bestaat
if (typeof window.handleListViewAction !== 'function') {
window.handleListViewAction = function(action, requiresSelection, e) {
// Gebruik explicit de event parameter om de browser event warning te vermijden
const evt = e || window.event;
const target = evt?.target || evt?.srcElement;
// Voorkom acties vanuit gedisabled buttons
if (target && (target.disabled || target.classList.contains('disabled'))) {
console.log('Button actie geblokkeerd: button is disabled');
return false;
}
// Vind het tableId op basis van het formulier waarin we zitten
const form = target ? target.closest('form') : null;
const tableId = form ? form.id.replace('-form', '') : document.querySelector('.tabulator-list-view')?.id;
if (!tableId) {
console.error('Kan tableId niet bepalen voor action:', action);
return false;
}
// Controleer direct of de button disabled zou moeten zijn
if (requiresSelection === true) {
const instance = window.EveAI.ListView.instances[tableId];
if (!instance || !instance.selectedRow) {
console.log('Button actie geblokkeerd: geen rij geselecteerd');
return false;
}
}
// Als EveAI.ListView beschikbaar is, gebruik dan de handleAction functie
if (window.EveAI && window.EveAI.ListView && typeof window.EveAI.ListView.handleAction === 'function') {
return window.EveAI.ListView.handleAction(action, requiresSelection, tableId);
}
// Fallback naar de originele implementatie
const actionInput = document.getElementById(`${tableId}-action`);
if (actionInput) {
actionInput.value = action;
}
// Controleer of er een rij geselecteerd is indien nodig
if (requiresSelection) {
const selectedRowInput = document.getElementById(`${tableId}-selected-row`);
if (!selectedRowInput || !selectedRowInput.value) {
alert('Selecteer eerst een item uit de lijst.');
return false;
}
}
// Verstuur het formulier met behoud van de originele form action
if (form) {
// Controleer of de form action correct is ingesteld
if (!form.action || form.action === '' || form.action === window.location.href || form.action === window.location.pathname) {
console.warn('Form action is mogelijk niet correct ingesteld:', form.action);
// Als er geen action is ingesteld, gebruik dan de huidige URL
form.action = window.location.href;
}
console.log(`Form action is: ${form.action}`);
form.submit();
return true;
}
return false;
};
}
console.log('EveAI List View component geladen');
});
</script>

View File

@@ -1,97 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination, render_filter_field, render_date_filter_field, render_collapsible_section, render_selectable_sortable_table_with_dict_headers %}
{% block title %}Assets{% endblock %}
{% block content_title %}Assets{% endblock %}
{% block content_description %}View Assets{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<!-- Filter Form -->
{% set filter_form %}
<form method="GET" action="{{ url_for('interaction_bp.assets') }}">
{{ render_filter_field('type', 'Type', filter_options['type'], filters.get('type', [])) }}
{{ render_filter_field('file_type', 'Bestandstype', filter_options['file_type'], filters.get('file_type', [])) }}
<button type="submit" class="btn btn-primary">Apply Filters</button>
</form>
{% endset %}
{{ render_collapsible_section('Filter', 'Filter Options', filter_form) }}
<div class="form-group mt-3">
<form method="POST" action="{{ url_for('interaction_bp.handle_asset_selection') }}" id="assetsForm">
<!-- Assets Table -->
{{ render_selectable_sortable_table_with_dict_headers(
headers=[
{"text": "ID", "sort": "id"},
{"text": "Naam", "sort": "name"},
{"text": "Type", "sort": "type"},
{"text": "Type Versie", "sort": "type_version"},
{"text": "Bestandstype", "sort": "file_type"},
{"text": "Laatst Gebruikt", "sort": "last_used_at"}
],
rows=rows,
selectable=True,
id="assetsTable",
sort_by=sort_by,
sort_order=sort_order
) }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_asset" class="btn btn-primary" onclick="return validateTableSelection('assetsForm')">Edit Asset</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'interaction_bp.assets') }}
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const table = document.getElementById('assetsTable');
const headers = table.querySelectorAll('th.sortable');
headers.forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.dataset.sort;
let sortOrder = 'asc';
if (this.querySelector('.fa-sort-up')) {
sortOrder = 'desc';
} else if (this.querySelector('.fa-sort-down')) {
sortOrder = 'none';
}
window.location.href = updateQueryStringParameter(window.location.href, 'sort_by', sortBy);
window.location.href = updateQueryStringParameter(window.location.href, 'sort_order', sortOrder);
});
});
function updateQueryStringParameter(uri, key, value) {
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
if (uri.match(re)) {
return uri.replace(re, '$1' + key + "=" + value + '$2');
}
else {
return uri + separator + key + "=" + value;
}
}
table.addEventListener('change', function(event) {
if (event.target.type === 'radio') {
var selectedRow = event.target.closest('tr');
var assetId = selectedRow.cells[1].textContent;
console.log('Selected Asset ID:', assetId);
}
});
});
</script>
{% endblock %}

View File

@@ -1,26 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Chat Sessions{% endblock %}
{% block content_title %}Chat Sessions{% endblock %}
{% block content_description %}View Chat Sessions for Tenant{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('interaction_bp.handle_chat_session_selection') }}" id="chatSessionsForm">
{{ render_selectable_table(headers=["ID", "Session ID", "Session Start", "Session End"], rows=rows, selectable=True, id="chatSessionsTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="view_chat_session" class="btn btn-primary" onclick="return validateTableSelection('chatSessionsForm')">View Chat Session</button>
<button type="submit" name="action" value="chat_session_interactions" class="btn btn-primary" onclick="return validateTableSelection('chatSessionsForm')">View Chat Session interactions</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'interaction_bp.chat_sessions') }}
{% endblock %}

View File

@@ -1,26 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Specialist Magic Links{% endblock %}
{% block content_title %}Specialist Magic Links{% endblock %}
{% block content_description %}View Specialists Magic Links{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('interaction_bp.handle_specialist_magic_link_selection') }}" id="specialistMagicLinksForm">
{{ render_selectable_table(headers=["Specialist ML ID", "Name", "Magic Link Code"], rows=rows, selectable=True, id="specialistMagicLinksTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_specialist_magic_link" class="btn btn-primary" onclick="return validateTableSelection('specialistMagicLinksForm')">Edit Specialist Magic Link</button>
</div>
<button type="submit" name="action" value="create_specialist_magic_link" class="btn btn-success">Register Specialist Magic Link</button>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'interaction_bp.specialist_magic_links') }}
{% endblock %}

View File

@@ -1,77 +0,0 @@
{% extends 'base.html' %}
{% block title %}Specialists{% endblock %}
{% block content_title %}Specialists{% endblock %}
{% block content_description %}View Specialists for Tenant{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('interaction_bp.handle_specialist_selection') }}" id="specialistsForm" onsubmit="return validateSpecialistSelection()">
<div id="specialists-list-view" class="tabulator-list-view"></div>
</form>
</div>
<!-- Include the list view component -->
{% include 'eveai_list_view.html' %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Laad data met een kleine vertraging om de pagina eerst te laten renderen
setTimeout(() => {
// Initialize the list view met gepagineerde data en optimalisaties
const tabulatorTable = EveAI.ListView.initialize('specialists-list-view', {
data: {{ specialists_data | tojson }},
columns: {{ columns | tojson }},
initialSort: {{ initial_sort | tojson }},
actions: {{ actions | tojson }},
selectable: true,
usePagination: true, // Gebruik paginatie in plaats van progressieve lading
tableHeight: 800 // Hogere tabel voor specialists view
});
// Extra debug event listeners
if (tabulatorTable) {
console.log('Tabulator tabel succesvol geïnitialiseerd');
// Voeg een klik-event toe aan de hele tabel container voor een betere gebruikerservaring
document.querySelector('.tabulator-container').addEventListener('click', function(e) {
console.log('Tabulator container click event');
// De specifieke row click events worden afgehandeld door Tabulator zelf
});
} else {
console.error('Tabulator tabel kon niet worden geïnitialiseerd');
}
}, 50);
});
// Validation function for form submission
function validateSpecialistSelection() {
const selectedRow = document.getElementById('specialists-list-view-selected-row');
const actionButton = document.activeElement;
// Als de actie geen selectie vereist, ga dan altijd door
if (actionButton && actionButton.classList.contains('btn-success')) {
return true;
}
if (!selectedRow || !selectedRow.value) {
alert('Selecteer a.u.b. eerst een specialist.');
return false;
}
try {
const selection = JSON.parse(selectedRow.value);
// Controleer of er een specialist is geselecteerd
if (!selection.value) {
alert('Selecteer a.u.b. eerst een specialist.');
return false;
}
} catch (e) {
alert('Er is een fout opgetreden bij het verwerken van de selectie.');
return false;
}
return true;
}
</script>
{% endblock %}

View File

@@ -0,0 +1,82 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content_title %}{{ title }}{% endblock %}
{% block content_description %}{{ description|default('') }}{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ form_action }}" id="{{ table_id }}-form">
{% include 'eveai_list_view.html' %}
</form>
</div>
<script>
// Wacht tot de pagina volledig geladen is
document.addEventListener('DOMContentLoaded', function() {
// Functie om knoppen met requiresSelection te beheren
const updateSelectionButtons = function(hasSelection) {
document.querySelectorAll('.requires-selection').forEach(button => {
if (hasSelection) {
button.disabled = false;
button.classList.remove('disabled');
} else {
button.disabled = true;
button.classList.add('disabled');
}
});
};
// Direct alle requires-selection buttons disablen bij start
updateSelectionButtons(false);
// Wacht tot de EveAI.ListView module beschikbaar is
function checkAndInitialize() {
if (window.EveAI && window.EveAI.ListView && window.EveAI.ListView.initialize) {
// Configureer de tabel
const tableConfig = {
data: {{ data | tojson }},
columns: {{ columns | tojson }},
initialSort: {{ initial_sort | tojson }},
actions: {{ actions | tojson }},
tableHeight: {{ table_height|default(600) }},
selectable: true
};
// Initialiseer de tabel
const tabulatorTable = window.EveAI.ListView.initialize('{{ table_id }}', tableConfig);
if (tabulatorTable) {
console.log('Tabulator tabel succesvol geïnitialiseerd');
// Luister naar selectie-events
tabulatorTable.on("rowSelectionChanged", function(data, rows) {
const hasSelection = rows.length > 0;
updateSelectionButtons(hasSelection);
});
// Luister naar deselect events
tabulatorTable.on("rowDeselected", function(row) {
const selectedRows = tabulatorTable.getSelectedRows();
updateSelectionButtons(selectedRows.length > 0);
});
// Luister naar select events
tabulatorTable.on("rowSelected", function(row) {
updateSelectionButtons(true);
});
} else {
console.error('Kon de Tabulator tabel niet initialiseren');
}
} else {
// Probeer opnieuw na een korte vertraging
setTimeout(checkAndInitialize, 100);
}
}
// Start de initialisatie
setTimeout(checkAndInitialize, 50);
});
</script>
{% endblock %}

View File

@@ -69,10 +69,9 @@
<ul class="navbar-nav navbar-nav-hover mx-auto">
{% if current_user.is_authenticated %}
{{ dropdown('Tenants', 'source_environment', [
{'name': 'Tenants', 'url': '/user/select_tenant', 'roles': ['Super User', 'Partner Admin']},
{'name': 'Tenants', 'url': '/user/tenants', 'roles': ['Super User', 'Partner Admin']},
{'name': 'Tenant Overview', 'url': '/user/tenant_overview', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Edit Tenant', 'url': '/user/tenant/' ~ session['tenant'].get('id'), 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Tenant Domains', 'url': '/user/view_tenant_domains', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Tenant Makes', 'url': '/user/tenant_makes', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Tenant Projects', 'url': '/user/tenant_projects', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Users', 'url': '/user/view_users', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
@@ -100,8 +99,7 @@
{'name': 'Add Document', 'url': '/document/add_document', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Add URL', 'url': '/document/add_url', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Documents', 'url': '/document/documents', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Full Documents', 'url': '/document/full_documents', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Document Versions', 'url': '/document/document_versions_list', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Document Processing', 'url': '/document/documents_processing', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Library Operations', 'url': '/document/library_operations', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
]) }}
{% endif %}
@@ -115,9 +113,9 @@
{% endif %}
{% if current_user.is_authenticated %}
{{ dropdown('Entitlements', 'settings', [
{'name': 'License Tiers', 'url': '/entitlements/view_license_tiers', 'roles': ['Super User', 'Partner Admin']},
{'name': 'License Tiers', 'url': '/entitlements/license_tiers', 'roles': ['Super User', 'Partner Admin']},
{'name': 'Trigger Actions', 'url': '/administration/trigger_actions', 'roles': ['Super User']},
{'name': 'Licenses', 'url': '/entitlements/view_licenses', 'roles': ['Super User', 'Tenant Admin', 'Partner Admin']},
{'name': 'Licenses', 'url': '/entitlements/licenses', 'roles': ['Super User', 'Tenant Admin', 'Partner Admin']},
{'name': 'Active Usage', 'url': '/entitlements/active_usage', 'roles': ['Super User', 'Tenant Admin', 'Partner Admin']},
]) }}
{% endif %}

View File

@@ -1,27 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Partner Services{% endblock %}
{% block content_title %}Partner Services{% endblock %}
{% block content_description %}View Partner Services for active Partner{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('partner_bp.handle_partner_service_selection') }}" id="partnerServicesForm">
{{ render_selectable_table(headers=["Partner Service ID", "Name", "Type"], rows=rows, selectable=True, id="retrieversTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_partner_service" class="btn btn-primary" onclick="return validateTableSelection('partnerServicesForm')">Edit Partner Service</button>
<button type="submit" name="action" value="add_partner_service_for_tenant" class="btn btn-primary" onclick="return validateTableSelection('partnerServicesForm')">Add Partner Service for Tenant</button>
</div>
<button type="submit" name="action" value="create_partner_service" class="btn btn-success">Register Partner Service</button>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.retrievers') }}
{% endblock %}

View File

@@ -1,27 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Partners{% endblock %}
{% block content_title %}Partners{% endblock %}
{% block content_description %}View Partners{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('partner_bp.handle_partner_selection') }}" id="partnersForm">
{{ render_selectable_table(headers=["Partner ID", "Name"], rows=rows, selectable=True, id="partnersTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_partner" class="btn btn-primary" onclick="return validateTableSelection('partnersForm')">Edit Partner</button>
<button type="submit" name="action" value="set_session_partner" class="btn btn-primary" onclick="return validateTableSelection('partnersForm')">Set Session Partner</button>
</div>
<button type="submit" name="action" value="create_partner" class="btn btn-success">Register Partner for Tenant</button>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'document_bp.retrievers') }}
{% endblock %}

View File

@@ -1,14 +1,9 @@
{#dist/main.js contains all used javascript libraries#}
<script src="{{url_for('static', filename='dist/main.js')}}"></script>
<!-- Chat client bundle (alleen op chat paginas) -->
{% if 'chat' in request.endpoint or request.path.startswith('/chat') %}
<script src="{{url_for('static', filename='dist/chat-client.js')}}"></script>
{% endif %}
{% include 'eveai_json_editor.html' %}
{% include 'eveai_ordered_list_editor.html' %}
{% include 'eveai_list_view.html' %}
<!-- ListView component is now included in base.html -->
<script>
document.addEventListener('DOMContentLoaded', function() {

View File

@@ -1,55 +0,0 @@
{% extends 'base.html' %}
{% from "macros.html" import render_selectable_table, render_pagination, render_field %}
{% block title %}Tenant Selection{% endblock %}
{% block content_title %}Select a Tenant{% endblock %}
{% block content_description %}Select the active tenant for the current session{% endblock %}
{% block content %}
<!-- Filter Form -->
<form method="POST" action="{{ url_for('user_bp.select_tenant') }}" class="mb-4">
{{ filter_form.hidden_tag() }}
<div class="row">
<div class="col-md-4">
{{ render_field(filter_form.types, class="select2") }}
</div>
<div class="col-md-4">
{{ render_field(filter_form.search) }}
</div>
<div class="col-md-4">
{{ filter_form.submit(class="btn btn-primary") }}
</div>
</div>
</form>
<!-- Tenant Selection Form -->
<form method="POST" action="{{ url_for('user_bp.handle_tenant_selection') }}">
{{ render_selectable_table(headers=["Tenant ID", "Tenant Name", "Website", "Type"], rows=rows, selectable=True, id="tenantsTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="select_tenant" class="btn btn-primary">Set Session Tenant</button>
<button type="submit" name="action" value="edit_tenant" class="btn btn-secondary">Edit Tenant</button>
</div>
<button type="submit" name="action" value="create_tenant" class="btn btn-success">Register Tenant</button>
</div>
</form>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'user_bp.select_tenant') }}
{% endblock %}
{% block scripts %}
<script>
$(document).ready(function() {
$('.select2').select2({
placeholder: "Select tenant types",
allowClear: true,
minimumResultsForSearch: Infinity, // Hides the search box
dropdownCssClass: 'select2-dropdown-hidden', // Custom class for dropdown
containerCssClass: 'select2-container-hidden' // Custom class for container
});
});
</script>
{% endblock %}

View File

@@ -1,26 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Tenant Makes{% endblock %}
{% block content_title %}Tenant Makes{% endblock %}
{% block content_description %}View Tenant Makes for Tenant{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('user_bp.handle_tenant_make_selection') }}" id="tenantMakesForm">
{{ render_selectable_table(headers=["Tenant Make ID", "Name", "Website", "Active"], rows=rows, selectable=True, id="tenantMakesTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_tenant_make" class="btn btn-primary" onclick="return validateTableSelection('tenantMakesForm')">Edit Tenant Make</button>
</div>
<button type="submit" name="action" value="create_tenant_make" class="btn btn-success">Register Tenant Make</button>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, "user_bp.tenant_makes") }}
{% endblock %}

View File

@@ -1,28 +0,0 @@
{% extends 'base.html' %}
{% from 'macros.html' import render_selectable_table, render_pagination %}
{% block title %}Documents{% endblock %}
{% block content_title %}Tenant Projects{% endblock %}
{% block content_description %}View Tenant Projects for Tenant{% endblock %}
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
{% block content %}
<div class="container">
<form method="POST" action="{{ url_for('user_bp.handle_tenant_project_selection') }}" id="tenantProjectsForm">
{{ render_selectable_table(headers=["Tenant Project ID", "Name", "API Clue", "Responsible", "Active"], rows=rows, selectable=True, id="catalogsTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_tenant_project" class="btn btn-primary" onclick="return validateTableSelection('tenantProjectsForm')">Edit Tenant Project</button>
<button type="submit" name="action" value="invalidate_tenant_project" class="btn btn-primary" onclick="return validateTableSelection('tenantProjectsForm')">Invalidate Tenant Project</button>
<button type="submit" name="action" value="delete_tenant_project" class="btn btn-danger" onclick="return validateTableSelection('tenantProjectsForm')">Delete Tenant Project</button>
</div>
<button type="submit" name="action" value="create_tenant_project" class="btn btn-success">Register Project</button>
</div>
</form>
</div>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, "user_bp.tenant_projects") }}
{% endblock %}

View File

@@ -21,7 +21,7 @@
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'user_bp.view_tenant_domains') }}
{{ render_pagination(pagination, 'user_bp.tenant_domains') }}
{% endblock %}
{% block scripts %}

View File

@@ -1,27 +0,0 @@
{% extends 'base.html' %}
{% from "macros.html" import render_selectable_table, render_pagination %}
{% block title %}View Users{% endblock %}
{% block content_title %}Select a user{% endblock %}
{% block content_description %}Select the user you'd like to action upon{% endblock %}
{% block content %}
<form action="{{ url_for('user_bp.handle_user_action') }}" method="POST" id="usersForm">
{{ render_selectable_table(headers=["User ID", "User Name", "Email"], rows=rows, selectable=True, id="usersTable") }}
<div class="form-group mt-3 d-flex justify-content-between">
<div>
<button type="submit" name="action" value="edit_user" class="btn btn-primary" onclick="return validateTableSelection('usersForm')">Edit User</button>
<button type="submit" name="action" value="resend_confirmation_email" class="btn btn-secondary" onclick="return validateTableSelection('usersForm')">Resend Confirmation</button>
<button type="submit" name="action" value="send_password_reset_email" class="btn btn-secondary" onclick="return validateTableSelection('usersForm')">Password Reset</button>
<button type="submit" name="action" value="reset_uniquifier" class="btn btn-secondary" onclick="return validateTableSelection('usersForm')">Reset Uniquifier</button>
</div>
<button type="submit" name="action" value="create_user" class="btn btn-success">Register User</button>
<!-- Additional buttons can be added here for other actions -->
</div>
</form>
{% endblock %}
{% block content_footer %}
{{ render_pagination(pagination, 'user_bp.select_tenant') }}
{% endblock %}