Lucas ARRIESSE
commited on
Commit
·
e4ce2a0
1
Parent(s):
9829679
Rework JS + add settings btn
Browse files- static/index.html +74 -39
- static/js/{script.js → app.js} +21 -7
- static/js/sse.js +1 -3
- static/js/ui-utils.js +39 -15
static/index.html
CHANGED
|
@@ -22,10 +22,16 @@
|
|
| 22 |
</div>
|
| 23 |
|
| 24 |
<div class="container mx-auto p-6">
|
| 25 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
<div id="selection-container" class="mb-6">
|
| 27 |
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4
|
| 28 |
-
|
|
|
|
| 29 |
<div>
|
| 30 |
<label for="working-group-select" class="block text-sm font-medium text-gray-700 mb-2">Working
|
| 31 |
Group</label>
|
|
@@ -48,41 +54,36 @@
|
|
| 48 |
</select>
|
| 49 |
</div>
|
| 50 |
|
| 51 |
-
<!-- Meeting -->
|
| 52 |
<div>
|
| 53 |
<label for="meeting-select" class="block text-sm font-medium text-gray-700 mb-2">Meeting</label>
|
| 54 |
-
<
|
| 55 |
-
<
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
</div>
|
| 58 |
</div>
|
| 59 |
-
|
| 60 |
-
<!-- Buttons -->
|
| 61 |
-
<div class="flex flex-col sm:flex-row gap-2">
|
| 62 |
-
<!-- <button id="get-meetings-btn" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
|
| 63 |
-
Get Meetings
|
| 64 |
-
</button> -->
|
| 65 |
-
<button id="get-tdocs-btn" class="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600">
|
| 66 |
-
Get TDocs
|
| 67 |
-
</button>
|
| 68 |
-
</div>
|
| 69 |
</div>
|
| 70 |
|
| 71 |
-
<!-- Add an horizontal separation -->
|
| 72 |
<hr>
|
| 73 |
<!-- Tab list for subsections -->
|
| 74 |
<div role="tablist" class="tabs tabs-border tabs-xl" id="tab-container">
|
| 75 |
-
<a role="tab" class="tab tab-active" id="doc-table-tab"
|
| 76 |
Documents</a>
|
| 77 |
-
<a role="tab" class="tab tab-disabled" id="requirements-tab"
|
| 78 |
<div class="flex items-center gap-1">
|
| 79 |
<div class="badge badge-neutral badge-outline badge-xs" id="requirements-tab-badge">0</div>
|
| 80 |
<span>Requirements</span>
|
| 81 |
</div>
|
| 82 |
</a>
|
| 83 |
-
<a role="tab" class="tab tab-disabled" id="solutions-tab"
|
| 84 |
Solve</a>
|
| 85 |
-
<a role="tab" class="tab tab-disabled" id="query-tab"
|
| 86 |
requirements</a>
|
| 87 |
</div>
|
| 88 |
|
|
@@ -155,7 +156,6 @@
|
|
| 155 |
|
| 156 |
<!-- Data Table Informations -->
|
| 157 |
<div class="flex justify-between items-center mb-2 pt-5" id="data-table-info-container">
|
| 158 |
-
<!-- Left side: buttons -->
|
| 159 |
<div class="flex gap-2 items-center">
|
| 160 |
<div class="tooltip" data-tip="Extract requirements from selected documents">
|
| 161 |
<button id="extract-requirements-btn"
|
|
@@ -174,7 +174,7 @@
|
|
| 174 |
</button>
|
| 175 |
</div>
|
| 176 |
|
| 177 |
-
<!--
|
| 178 |
<div class="flex gap-2 items-center">
|
| 179 |
<span id="displayed-count" class="text-sm text-gray-700 bg-white rounded px-3 py-1 shadow">
|
| 180 |
0 total documents
|
|
@@ -186,7 +186,7 @@
|
|
| 186 |
</div>
|
| 187 |
|
| 188 |
|
| 189 |
-
<!--
|
| 190 |
<div id="data-table-container" class="mb-6">
|
| 191 |
<table id="data-table" class="w-full bg-white rounded-lg shadow overflow-hidden">
|
| 192 |
<thead class="bg-gray-50">
|
|
@@ -207,13 +207,12 @@
|
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
|
|
|
|
| 210 |
<div id="requirements-tab-contents" class="hidden pt-10">
|
| 211 |
-
<!-- Requirement list container -->
|
| 212 |
<div id="requirements-container" class="mb-6">
|
| 213 |
<div class="flex">
|
| 214 |
<h2 class="text-2xl font-bold mb-4">Extracted requirement list</h2>
|
| 215 |
<div class="justify-end pl-5">
|
| 216 |
-
<!--Copy ALL reqs button-->
|
| 217 |
<div class="tooltip" data-tip="Copy ALL requirements to clipboard">
|
| 218 |
<button class="btn btn-square" id="copy-all-reqs-btn" aria-label="Copy">
|
| 219 |
📋
|
|
@@ -270,15 +269,6 @@
|
|
| 270 |
Categorize
|
| 271 |
</button>
|
| 272 |
</div>
|
| 273 |
-
<!-- <div class="flex flex-wrap justify-end items-center gap-6">
|
| 274 |
-
<div class="tooltip" data-tip="Use Insight Finder's API to generate solutions">
|
| 275 |
-
<label class="label">
|
| 276 |
-
<span class="label-text text-base-content">Use insight finder solver</span>
|
| 277 |
-
</label>
|
| 278 |
-
<input type="checkbox" class="toggle toggle-primary" id="use-insight-finder-solver"
|
| 279 |
-
checked="false" />
|
| 280 |
-
</div>
|
| 281 |
-
</div> -->
|
| 282 |
</div>
|
| 283 |
<div id="categorized-requirements-container pt-10" class="mb-6">
|
| 284 |
<div class="flex mb-4 pt-10">
|
|
@@ -333,9 +323,54 @@
|
|
| 333 |
</div>
|
| 334 |
</div>
|
| 335 |
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
</body>
|
| 340 |
|
| 341 |
</html>
|
|
|
|
| 22 |
</div>
|
| 23 |
|
| 24 |
<div class="container mx-auto p-6">
|
| 25 |
+
<div class="relative flex justify-center items-center p-4">
|
| 26 |
+
<h1 class="text-3xl font-bold">Requirements Extractor</h1>
|
| 27 |
+
<button class="absolute right-4 btn btn-sm btn-circle btn-outline" id="settings-btn"
|
| 28 |
+
onclick="settings_modal.showModal()" title="Settings">🔧</button>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
<div id="selection-container" class="mb-6">
|
| 32 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 33 |
+
|
| 34 |
+
<!-- WG selection-->
|
| 35 |
<div>
|
| 36 |
<label for="working-group-select" class="block text-sm font-medium text-gray-700 mb-2">Working
|
| 37 |
Group</label>
|
|
|
|
| 54 |
</select>
|
| 55 |
</div>
|
| 56 |
|
| 57 |
+
<!-- Meeting & Action Button Group -->
|
| 58 |
<div>
|
| 59 |
<label for="meeting-select" class="block text-sm font-medium text-gray-700 mb-2">Meeting</label>
|
| 60 |
+
<div class="flex items-end gap-2">
|
| 61 |
+
<select id="meeting-select" class="w-full p-2 border border-gray-300 rounded-md">
|
| 62 |
+
<option value="">Select a meeting</option>
|
| 63 |
+
</select>
|
| 64 |
+
<button id="get-tdocs-btn"
|
| 65 |
+
class="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 flex-shrink-0">
|
| 66 |
+
Get TDocs
|
| 67 |
+
</button>
|
| 68 |
+
</div>
|
| 69 |
</div>
|
| 70 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
</div>
|
| 72 |
|
|
|
|
| 73 |
<hr>
|
| 74 |
<!-- Tab list for subsections -->
|
| 75 |
<div role="tablist" class="tabs tabs-border tabs-xl" id="tab-container">
|
| 76 |
+
<a role="tab" class="tab tab-active" id="doc-table-tab">📝
|
| 77 |
Documents</a>
|
| 78 |
+
<a role="tab" class="tab tab-disabled" id="requirements-tab">
|
| 79 |
<div class="flex items-center gap-1">
|
| 80 |
<div class="badge badge-neutral badge-outline badge-xs" id="requirements-tab-badge">0</div>
|
| 81 |
<span>Requirements</span>
|
| 82 |
</div>
|
| 83 |
</a>
|
| 84 |
+
<a role="tab" class="tab tab-disabled" id="solutions-tab">Group and
|
| 85 |
Solve</a>
|
| 86 |
+
<a role="tab" class="tab tab-disabled" id="query-tab">🔎 Find relevant
|
| 87 |
requirements</a>
|
| 88 |
</div>
|
| 89 |
|
|
|
|
| 156 |
|
| 157 |
<!-- Data Table Informations -->
|
| 158 |
<div class="flex justify-between items-center mb-2 pt-5" id="data-table-info-container">
|
|
|
|
| 159 |
<div class="flex gap-2 items-center">
|
| 160 |
<div class="tooltip" data-tip="Extract requirements from selected documents">
|
| 161 |
<button id="extract-requirements-btn"
|
|
|
|
| 174 |
</button>
|
| 175 |
</div>
|
| 176 |
|
| 177 |
+
<!-- document counts -->
|
| 178 |
<div class="flex gap-2 items-center">
|
| 179 |
<span id="displayed-count" class="text-sm text-gray-700 bg-white rounded px-3 py-1 shadow">
|
| 180 |
0 total documents
|
|
|
|
| 186 |
</div>
|
| 187 |
|
| 188 |
|
| 189 |
+
<!-- TDoc table -->
|
| 190 |
<div id="data-table-container" class="mb-6">
|
| 191 |
<table id="data-table" class="w-full bg-white rounded-lg shadow overflow-hidden">
|
| 192 |
<thead class="bg-gray-50">
|
|
|
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
|
| 210 |
+
<!-- Requirement list tab-->
|
| 211 |
<div id="requirements-tab-contents" class="hidden pt-10">
|
|
|
|
| 212 |
<div id="requirements-container" class="mb-6">
|
| 213 |
<div class="flex">
|
| 214 |
<h2 class="text-2xl font-bold mb-4">Extracted requirement list</h2>
|
| 215 |
<div class="justify-end pl-5">
|
|
|
|
| 216 |
<div class="tooltip" data-tip="Copy ALL requirements to clipboard">
|
| 217 |
<button class="btn btn-square" id="copy-all-reqs-btn" aria-label="Copy">
|
| 218 |
📋
|
|
|
|
| 269 |
Categorize
|
| 270 |
</button>
|
| 271 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
</div>
|
| 273 |
<div id="categorized-requirements-container pt-10" class="mb-6">
|
| 274 |
<div class="flex mb-4 pt-10">
|
|
|
|
| 323 |
</div>
|
| 324 |
</div>
|
| 325 |
|
| 326 |
+
|
| 327 |
+
<!--App settings modal container-->
|
| 328 |
+
<dialog id="settings_modal" class="modal">
|
| 329 |
+
<div class="modal-box w-11/12 max-w-5xl">
|
| 330 |
+
<h3 class="text-lg font-bold">Reqxtract settings</h3>
|
| 331 |
+
<p class="py-4">Enter your LLM provider URL and key to enable using private solution assessment and
|
| 332 |
+
refining</p>
|
| 333 |
+
<div class="modal-action">
|
| 334 |
+
<form method="dialog">
|
| 335 |
+
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
| 336 |
+
</form>
|
| 337 |
+
</div>
|
| 338 |
+
<h2 class="text-lg font-bold">Private generation settings</h2>
|
| 339 |
+
<form class="space-y-4">
|
| 340 |
+
<div>
|
| 341 |
+
<label for="provider-url" class="block mb-2 text-sm font-medium">LLM provider URL</label>
|
| 342 |
+
<input id="settings-provider-url" name="provider-url" class="input input-bordered w-full">
|
| 343 |
+
</div>
|
| 344 |
+
|
| 345 |
+
<div>
|
| 346 |
+
<label for="provider-token" class="block mb-2 text-sm font-medium">LLM provider token</label>
|
| 347 |
+
<input id="settings-provider-token" name="provider-token" class="input input-bordered w-full"
|
| 348 |
+
type="password">
|
| 349 |
+
</div>
|
| 350 |
+
|
| 351 |
+
<div>
|
| 352 |
+
<label for="assessment-rules" class="block mb-2 text-sm font-medium">Assessment rules</label>
|
| 353 |
+
<textarea id="settings-assessment-rules" name="assessment-rules"
|
| 354 |
+
class="textarea textarea-bordered w-full h-48"
|
| 355 |
+
placeholder="Enter your rules here..."></textarea>
|
| 356 |
+
</div>
|
| 357 |
+
|
| 358 |
+
<div>
|
| 359 |
+
<label for="portfolio-info" class="block mb-2 text-sm font-medium">Portfolio information</label>
|
| 360 |
+
<textarea id="settings-portfolio" name="portfolio-info"
|
| 361 |
+
class="textarea textarea-bordered w-full h-48"
|
| 362 |
+
placeholder="Enter your portfolio info here..."></textarea>
|
| 363 |
+
</div>
|
| 364 |
+
</form>
|
| 365 |
+
<div class="flex">
|
| 366 |
+
<button class="btn btn-success" id="test-btn">Save config</button>
|
| 367 |
+
</div>
|
| 368 |
+
</div>
|
| 369 |
+
</dialog>
|
| 370 |
+
|
| 371 |
+
<script type="module" src="js/sse.js"></script>
|
| 372 |
+
<script type="module" src="js/ui-utils.js"></script>
|
| 373 |
+
<script type="module" src="js/app.js"></script>
|
| 374 |
</body>
|
| 375 |
|
| 376 |
</html>
|
static/js/{script.js → app.js}
RENAMED
|
@@ -1,4 +1,12 @@
|
|
| 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
// ==================================== Variables globales ========================================
|
| 3 |
let requirements = [];
|
| 4 |
|
|
@@ -79,6 +87,7 @@ async function getTDocs() {
|
|
| 79 |
'doc-table-tab-contents',
|
| 80 |
], true);
|
| 81 |
|
|
|
|
| 82 |
hasRequirementsExtracted = false;
|
| 83 |
} catch (error) {
|
| 84 |
console.error('Error while getting TDocs:', error);
|
|
@@ -139,10 +148,10 @@ function setupFilters(data) {
|
|
| 139 |
});
|
| 140 |
|
| 141 |
// Status (checkbox multiselect)
|
| 142 |
-
populateCheckboxDropdown('status-options', statuses, 'status', 'status-filter-label', selectedStatus);
|
| 143 |
|
| 144 |
// Agenda (checkbox multiselect)
|
| 145 |
-
populateCheckboxDropdown('agenda-options', agendaItems, 'agenda', 'agenda-filter-label', selectedAgenda);
|
| 146 |
|
| 147 |
// Initialisation des labels (optionnel)
|
| 148 |
document.getElementById('doc-type-filter-label').textContent = 'Type';
|
|
@@ -180,7 +189,7 @@ function updateSelectedAndDisplayedCount() {
|
|
| 180 |
/**
|
| 181 |
* Applique les filtres au tableau
|
| 182 |
*/
|
| 183 |
-
function applyFilters() {
|
| 184 |
const rows = document.querySelectorAll('#data-table tbody tr');
|
| 185 |
rows.forEach(row => {
|
| 186 |
const typeVal = row.getAttribute('data-type');
|
|
@@ -352,7 +361,7 @@ async function extractRequirements() {
|
|
| 352 |
|
| 353 |
displayRequirements(requirements);
|
| 354 |
|
| 355 |
-
toggleContainersVisibility(['requirements-container', 'query-requirements-container'], true);
|
| 356 |
toggleContainersVisibility(['categorize-requirements-btn'], true);
|
| 357 |
|
| 358 |
// we got some requirements to the other tabs can be enabled
|
|
@@ -911,7 +920,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 911 |
|
| 912 |
// ===================================== Section sources ================================
|
| 913 |
|
| 914 |
-
createEl = (tag, properties) => {
|
| 915 |
const element = document.createElement(tag);
|
| 916 |
Object.assign(element, properties);
|
| 917 |
return element;
|
|
@@ -1099,6 +1108,7 @@ async function workflow(steps = 1) {
|
|
| 1099 |
// =============================================================================
|
| 1100 |
|
| 1101 |
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
| 1102 |
// Événements des boutons principaux
|
| 1103 |
// document.getElementById('get-meetings-btn').addEventListener('click', getMeetings);
|
| 1104 |
document.getElementById('working-group-select').addEventListener('change', (ev) => {
|
|
@@ -1121,7 +1131,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
| 1121 |
const n_steps = document.getElementById('solution-gen-nsteps').value;
|
| 1122 |
workflow(n_steps);
|
| 1123 |
});
|
| 1124 |
-
|
| 1125 |
workflow(1);
|
| 1126 |
});
|
| 1127 |
});
|
|
@@ -1139,4 +1149,8 @@ document.getElementById('copy-reqs-btn').addEventListener('click', (ev) => {
|
|
| 1139 |
copySelectedRequirementsAsMarkdown();
|
| 1140 |
});
|
| 1141 |
|
| 1142 |
-
document.getElementById('copy-all-reqs-btn').addEventListener('click', copyAllRequirementsAsMarkdown);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
|
| 2 |
+
import {
|
| 3 |
+
toggleElementsEnabled, toggleContainersVisibility, showLoadingOverlay, hideLoadingOverlay, populateSelect,
|
| 4 |
+
populateCheckboxDropdown, updateCheckboxDropdownLabel, updateSelectedFilters, populateDaisyDropdown, updateFilterLabel,
|
| 5 |
+
extractTableData, switchTab, enableTabSwitching, debounceAutoCategoryCount,
|
| 6 |
+
bindTabs, checkCanUsePrivateGen
|
| 7 |
+
} from "./ui-utils.js";
|
| 8 |
+
import { postWithSSE } from "./sse.js";
|
| 9 |
+
|
| 10 |
// ==================================== Variables globales ========================================
|
| 11 |
let requirements = [];
|
| 12 |
|
|
|
|
| 87 |
'doc-table-tab-contents',
|
| 88 |
], true);
|
| 89 |
|
| 90 |
+
switchTab('doc-table-tab');
|
| 91 |
hasRequirementsExtracted = false;
|
| 92 |
} catch (error) {
|
| 93 |
console.error('Error while getting TDocs:', error);
|
|
|
|
| 148 |
});
|
| 149 |
|
| 150 |
// Status (checkbox multiselect)
|
| 151 |
+
populateCheckboxDropdown('status-options', statuses, 'status', 'status-filter-label', selectedStatus, applyFilters);
|
| 152 |
|
| 153 |
// Agenda (checkbox multiselect)
|
| 154 |
+
populateCheckboxDropdown('agenda-options', agendaItems, 'agenda', 'agenda-filter-label', selectedAgenda, applyFilters);
|
| 155 |
|
| 156 |
// Initialisation des labels (optionnel)
|
| 157 |
document.getElementById('doc-type-filter-label').textContent = 'Type';
|
|
|
|
| 189 |
/**
|
| 190 |
* Applique les filtres au tableau
|
| 191 |
*/
|
| 192 |
+
export function applyFilters() {
|
| 193 |
const rows = document.querySelectorAll('#data-table tbody tr');
|
| 194 |
rows.forEach(row => {
|
| 195 |
const typeVal = row.getAttribute('data-type');
|
|
|
|
| 361 |
|
| 362 |
displayRequirements(requirements);
|
| 363 |
|
| 364 |
+
// toggleContainersVisibility(['requirements-container', 'query-requirements-container'], true);
|
| 365 |
toggleContainersVisibility(['categorize-requirements-btn'], true);
|
| 366 |
|
| 367 |
// we got some requirements to the other tabs can be enabled
|
|
|
|
| 920 |
|
| 921 |
// ===================================== Section sources ================================
|
| 922 |
|
| 923 |
+
const createEl = (tag, properties) => {
|
| 924 |
const element = document.createElement(tag);
|
| 925 |
Object.assign(element, properties);
|
| 926 |
return element;
|
|
|
|
| 1108 |
// =============================================================================
|
| 1109 |
|
| 1110 |
document.addEventListener('DOMContentLoaded', function () {
|
| 1111 |
+
bindTabs();
|
| 1112 |
// Événements des boutons principaux
|
| 1113 |
// document.getElementById('get-meetings-btn').addEventListener('click', getMeetings);
|
| 1114 |
document.getElementById('working-group-select').addEventListener('change', (ev) => {
|
|
|
|
| 1131 |
const n_steps = document.getElementById('solution-gen-nsteps').value;
|
| 1132 |
workflow(n_steps);
|
| 1133 |
});
|
| 1134 |
+
document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
|
| 1135 |
workflow(1);
|
| 1136 |
});
|
| 1137 |
});
|
|
|
|
| 1149 |
copySelectedRequirementsAsMarkdown();
|
| 1150 |
});
|
| 1151 |
|
| 1152 |
+
document.getElementById('copy-all-reqs-btn').addEventListener('click', copyAllRequirementsAsMarkdown);
|
| 1153 |
+
|
| 1154 |
+
document.getElementById('test-btn').addEventListener('click', _ => {
|
| 1155 |
+
console.log(checkCanUsePrivateGen());
|
| 1156 |
+
});
|
static/js/sse.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
// sse-fetch.js
|
| 2 |
-
|
| 3 |
/**
|
| 4 |
* Performs a POST request and handles the response as a Server-Sent Events (SSE) stream.
|
| 5 |
* The standard EventSource API does not support POST requests, so we use fetch.
|
|
@@ -10,7 +8,7 @@
|
|
| 10 |
* @param {(data: object) => void} callbacks.onMessage A function called for each message received.
|
| 11 |
* @param {(error: Error) => void} callbacks.onError A function called if an error occurs.
|
| 12 |
*/
|
| 13 |
-
async function postWithSSE(url, body, callbacks) {
|
| 14 |
const { onMessage, onError } = callbacks;
|
| 15 |
|
| 16 |
try {
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
* Performs a POST request and handles the response as a Server-Sent Events (SSE) stream.
|
| 3 |
* The standard EventSource API does not support POST requests, so we use fetch.
|
|
|
|
| 8 |
* @param {(data: object) => void} callbacks.onMessage A function called for each message received.
|
| 9 |
* @param {(error: Error) => void} callbacks.onError A function called if an error occurs.
|
| 10 |
*/
|
| 11 |
+
export async function postWithSSE(url, body, callbacks) {
|
| 12 |
const { onMessage, onError } = callbacks;
|
| 13 |
|
| 14 |
try {
|
static/js/ui-utils.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
| 7 |
* @param {string[]} elementIds - Liste des IDs des éléments à activer
|
| 8 |
* @param {boolean} enabled - true pour activer, false pour désactiver
|
| 9 |
*/
|
| 10 |
-
function toggleElementsEnabled(elementIds, enabled = true) {
|
| 11 |
elementIds.forEach(id => {
|
| 12 |
const element = document.getElementById(id);
|
| 13 |
if (element) {
|
|
@@ -25,7 +25,7 @@ function toggleElementsEnabled(elementIds, enabled = true) {
|
|
| 25 |
* @param {string[]} containerIds - Liste des IDs des conteneurs à afficher
|
| 26 |
* @param {boolean} visible - true pour afficher, false pour masquer
|
| 27 |
*/
|
| 28 |
-
function toggleContainersVisibility(containerIds, visible = true) {
|
| 29 |
containerIds.forEach(id => {
|
| 30 |
const container = document.getElementById(id);
|
| 31 |
if (container) {
|
|
@@ -42,7 +42,7 @@ function toggleContainersVisibility(containerIds, visible = true) {
|
|
| 42 |
* Affiche le loading overlay avec un message personnalisé
|
| 43 |
* @param {string} message - Message à afficher
|
| 44 |
*/
|
| 45 |
-
function showLoadingOverlay(message = 'Chargement en cours...') {
|
| 46 |
document.getElementById('progress-text').textContent = message;
|
| 47 |
toggleContainersVisibility(['loading-overlay'], true);
|
| 48 |
}
|
|
@@ -50,7 +50,7 @@ function showLoadingOverlay(message = 'Chargement en cours...') {
|
|
| 50 |
/**
|
| 51 |
* Masque le loading overlay
|
| 52 |
*/
|
| 53 |
-
function hideLoadingOverlay() {
|
| 54 |
toggleContainersVisibility(['loading-overlay'], false);
|
| 55 |
}
|
| 56 |
|
|
@@ -60,7 +60,7 @@ function hideLoadingOverlay() {
|
|
| 60 |
* @param {Object} options - Objet avec les options {value: text}
|
| 61 |
* @param {string} defaultText - Texte par défaut
|
| 62 |
*/
|
| 63 |
-
function populateSelect(selectId, options, defaultText = 'Sélectionner...') {
|
| 64 |
const select = document.getElementById(selectId);
|
| 65 |
if (select) {
|
| 66 |
select.innerHTML = `<option value="">${defaultText}</option>`;
|
|
@@ -73,7 +73,7 @@ function populateSelect(selectId, options, defaultText = 'Sélectionner...') {
|
|
| 73 |
}
|
| 74 |
}
|
| 75 |
|
| 76 |
-
function populateCheckboxDropdown(optionsContainerId, options, filterType, labelId, selectionSet) {
|
| 77 |
const container = document.getElementById(optionsContainerId);
|
| 78 |
container.innerHTML = '';
|
| 79 |
selectionSet.clear(); // reset all
|
|
@@ -93,6 +93,7 @@ function populateCheckboxDropdown(optionsContainerId, options, filterType, label
|
|
| 93 |
} else {
|
| 94 |
selectionSet.delete(this.value);
|
| 95 |
}
|
|
|
|
| 96 |
// Gestion du label "Tous"
|
| 97 |
updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length);
|
| 98 |
// Gestion du "Tous" global
|
|
@@ -100,7 +101,7 @@ function populateCheckboxDropdown(optionsContainerId, options, filterType, label
|
|
| 100 |
if (allBox && allBox.checked) allBox.checked = false;
|
| 101 |
// Si plus rien n'est coché, recoche "Tous"
|
| 102 |
if (selectionSet.size === 0 && allBox) allBox.checked = true;
|
| 103 |
-
|
| 104 |
});
|
| 105 |
container.appendChild(label);
|
| 106 |
});
|
|
@@ -124,7 +125,7 @@ function populateCheckboxDropdown(optionsContainerId, options, filterType, label
|
|
| 124 |
}
|
| 125 |
}
|
| 126 |
|
| 127 |
-
function updateCheckboxDropdownLabel(type, labelId, set, totalCount) {
|
| 128 |
const label = document.getElementById(labelId);
|
| 129 |
if (!set.size) {
|
| 130 |
label.textContent = type.charAt(0).toUpperCase() + type.slice(1) + " (Tous)";
|
|
@@ -135,7 +136,7 @@ function updateCheckboxDropdownLabel(type, labelId, set, totalCount) {
|
|
| 135 |
}
|
| 136 |
}
|
| 137 |
|
| 138 |
-
function updateSelectedFilters(filterType, value, isChecked) {
|
| 139 |
if (isChecked) {
|
| 140 |
selectedFilters[filterType].add(value);
|
| 141 |
} else {
|
|
@@ -143,7 +144,7 @@ function updateSelectedFilters(filterType, value, isChecked) {
|
|
| 143 |
}
|
| 144 |
}
|
| 145 |
|
| 146 |
-
function populateDaisyDropdown(menuId, options, labelId, onSelect) {
|
| 147 |
const menu = document.getElementById(menuId);
|
| 148 |
menu.innerHTML = '';
|
| 149 |
// Option "Tous"
|
|
@@ -169,7 +170,7 @@ function populateDaisyDropdown(menuId, options, labelId, onSelect) {
|
|
| 169 |
});
|
| 170 |
}
|
| 171 |
|
| 172 |
-
function updateFilterLabel(filterType) {
|
| 173 |
const selectedCount = selectedFilters[filterType].size;
|
| 174 |
const labelElement = document.getElementById(`${filterType}-filter-label`);
|
| 175 |
|
|
@@ -185,7 +186,7 @@ function updateFilterLabel(filterType) {
|
|
| 185 |
* @param {Object} mapping - Mapping des colonnes {columnName: propertyName}
|
| 186 |
* @returns {Array} Données extraites
|
| 187 |
*/
|
| 188 |
-
function extractTableData(mapping) {
|
| 189 |
const tbody = document.querySelector('#data-table tbody');
|
| 190 |
const rows = tbody.querySelectorAll('tr');
|
| 191 |
const data = [];
|
|
@@ -222,7 +223,7 @@ const TABS = {
|
|
| 222 |
* Bascule l'affichage sur le nouveau tab
|
| 223 |
* @param {*} newTab
|
| 224 |
*/
|
| 225 |
-
function switchTab(newTab) {
|
| 226 |
// Remove active tab style from all tabs
|
| 227 |
Object.keys(TABS).forEach(tabId => {
|
| 228 |
const tabElement = document.getElementById(tabId);
|
|
@@ -249,10 +250,20 @@ function switchTab(newTab) {
|
|
| 249 |
}
|
| 250 |
}
|
| 251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
/**
|
| 253 |
* Bascule l'affichage vers la tab uniquement si les requirements sont
|
| 254 |
*/
|
| 255 |
-
function enableTabSwitching() {
|
| 256 |
Object.keys(TABS).forEach(tabId => {
|
| 257 |
const tab = document.getElementById(tabId);
|
| 258 |
if (tab)
|
|
@@ -264,7 +275,20 @@ function enableTabSwitching() {
|
|
| 264 |
/**
|
| 265 |
* Change l'état d'activation du number box de choix de nb de catégories.
|
| 266 |
*/
|
| 267 |
-
function debounceAutoCategoryCount(state) {
|
| 268 |
document.getElementById('category-count').disabled = state;
|
| 269 |
}
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
* @param {string[]} elementIds - Liste des IDs des éléments à activer
|
| 8 |
* @param {boolean} enabled - true pour activer, false pour désactiver
|
| 9 |
*/
|
| 10 |
+
export function toggleElementsEnabled(elementIds, enabled = true) {
|
| 11 |
elementIds.forEach(id => {
|
| 12 |
const element = document.getElementById(id);
|
| 13 |
if (element) {
|
|
|
|
| 25 |
* @param {string[]} containerIds - Liste des IDs des conteneurs à afficher
|
| 26 |
* @param {boolean} visible - true pour afficher, false pour masquer
|
| 27 |
*/
|
| 28 |
+
export function toggleContainersVisibility(containerIds, visible = true) {
|
| 29 |
containerIds.forEach(id => {
|
| 30 |
const container = document.getElementById(id);
|
| 31 |
if (container) {
|
|
|
|
| 42 |
* Affiche le loading overlay avec un message personnalisé
|
| 43 |
* @param {string} message - Message à afficher
|
| 44 |
*/
|
| 45 |
+
export function showLoadingOverlay(message = 'Chargement en cours...') {
|
| 46 |
document.getElementById('progress-text').textContent = message;
|
| 47 |
toggleContainersVisibility(['loading-overlay'], true);
|
| 48 |
}
|
|
|
|
| 50 |
/**
|
| 51 |
* Masque le loading overlay
|
| 52 |
*/
|
| 53 |
+
export function hideLoadingOverlay() {
|
| 54 |
toggleContainersVisibility(['loading-overlay'], false);
|
| 55 |
}
|
| 56 |
|
|
|
|
| 60 |
* @param {Object} options - Objet avec les options {value: text}
|
| 61 |
* @param {string} defaultText - Texte par défaut
|
| 62 |
*/
|
| 63 |
+
export function populateSelect(selectId, options, defaultText = 'Sélectionner...') {
|
| 64 |
const select = document.getElementById(selectId);
|
| 65 |
if (select) {
|
| 66 |
select.innerHTML = `<option value="">${defaultText}</option>`;
|
|
|
|
| 73 |
}
|
| 74 |
}
|
| 75 |
|
| 76 |
+
export function populateCheckboxDropdown(optionsContainerId, options, filterType, labelId, selectionSet, onSelect) {
|
| 77 |
const container = document.getElementById(optionsContainerId);
|
| 78 |
container.innerHTML = '';
|
| 79 |
selectionSet.clear(); // reset all
|
|
|
|
| 93 |
} else {
|
| 94 |
selectionSet.delete(this.value);
|
| 95 |
}
|
| 96 |
+
|
| 97 |
// Gestion du label "Tous"
|
| 98 |
updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length);
|
| 99 |
// Gestion du "Tous" global
|
|
|
|
| 101 |
if (allBox && allBox.checked) allBox.checked = false;
|
| 102 |
// Si plus rien n'est coché, recoche "Tous"
|
| 103 |
if (selectionSet.size === 0 && allBox) allBox.checked = true;
|
| 104 |
+
onSelect?.();
|
| 105 |
});
|
| 106 |
container.appendChild(label);
|
| 107 |
});
|
|
|
|
| 125 |
}
|
| 126 |
}
|
| 127 |
|
| 128 |
+
export function updateCheckboxDropdownLabel(type, labelId, set, totalCount) {
|
| 129 |
const label = document.getElementById(labelId);
|
| 130 |
if (!set.size) {
|
| 131 |
label.textContent = type.charAt(0).toUpperCase() + type.slice(1) + " (Tous)";
|
|
|
|
| 136 |
}
|
| 137 |
}
|
| 138 |
|
| 139 |
+
export function updateSelectedFilters(filterType, value, isChecked) {
|
| 140 |
if (isChecked) {
|
| 141 |
selectedFilters[filterType].add(value);
|
| 142 |
} else {
|
|
|
|
| 144 |
}
|
| 145 |
}
|
| 146 |
|
| 147 |
+
export function populateDaisyDropdown(menuId, options, labelId, onSelect) {
|
| 148 |
const menu = document.getElementById(menuId);
|
| 149 |
menu.innerHTML = '';
|
| 150 |
// Option "Tous"
|
|
|
|
| 170 |
});
|
| 171 |
}
|
| 172 |
|
| 173 |
+
export function updateFilterLabel(filterType) {
|
| 174 |
const selectedCount = selectedFilters[filterType].size;
|
| 175 |
const labelElement = document.getElementById(`${filterType}-filter-label`);
|
| 176 |
|
|
|
|
| 186 |
* @param {Object} mapping - Mapping des colonnes {columnName: propertyName}
|
| 187 |
* @returns {Array} Données extraites
|
| 188 |
*/
|
| 189 |
+
export function extractTableData(mapping) {
|
| 190 |
const tbody = document.querySelector('#data-table tbody');
|
| 191 |
const rows = tbody.querySelectorAll('tr');
|
| 192 |
const data = [];
|
|
|
|
| 223 |
* Bascule l'affichage sur le nouveau tab
|
| 224 |
* @param {*} newTab
|
| 225 |
*/
|
| 226 |
+
export function switchTab(newTab) {
|
| 227 |
// Remove active tab style from all tabs
|
| 228 |
Object.keys(TABS).forEach(tabId => {
|
| 229 |
const tabElement = document.getElementById(tabId);
|
|
|
|
| 250 |
}
|
| 251 |
}
|
| 252 |
|
| 253 |
+
/**
|
| 254 |
+
* Setup les boutons pour basculer vers un autre tab
|
| 255 |
+
*/
|
| 256 |
+
export function bindTabs() {
|
| 257 |
+
Object.keys(TABS).forEach(tabId => {
|
| 258 |
+
const tabElement = document.getElementById(tabId);
|
| 259 |
+
tabElement.addEventListener('click', _ => switchTab(tabId));
|
| 260 |
+
});
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
/**
|
| 264 |
* Bascule l'affichage vers la tab uniquement si les requirements sont
|
| 265 |
*/
|
| 266 |
+
export function enableTabSwitching() {
|
| 267 |
Object.keys(TABS).forEach(tabId => {
|
| 268 |
const tab = document.getElementById(tabId);
|
| 269 |
if (tab)
|
|
|
|
| 275 |
/**
|
| 276 |
* Change l'état d'activation du number box de choix de nb de catégories.
|
| 277 |
*/
|
| 278 |
+
export function debounceAutoCategoryCount(state) {
|
| 279 |
document.getElementById('category-count').disabled = state;
|
| 280 |
}
|
| 281 |
|
| 282 |
+
|
| 283 |
+
/**
|
| 284 |
+
* Vérifie si les paramètres sont bien renseignés pour utiliser la génération privée.
|
| 285 |
+
*/
|
| 286 |
+
export function checkCanUsePrivateGen() {
|
| 287 |
+
const provider_url = document.getElementById('settings-provider-url').value;
|
| 288 |
+
const provider_token = document.getElementById('settings-provider-token').value;
|
| 289 |
+
const assessment_rules = document.getElementById('settings-assessment-rules').value;
|
| 290 |
+
const portfolio_info = document.getElementById('settings-portfolio').value;
|
| 291 |
+
|
| 292 |
+
const isEmpty = (str) => (!str?.length);
|
| 293 |
+
return !isEmpty(provider_url) && !isEmpty(provider_token) && !isEmpty(assessment_rules) && !isEmpty(portfolio_info);
|
| 294 |
+
}
|