Lucas ARRIESSE commited on
Commit
e4ce2a0
·
1 Parent(s): 9829679

Rework JS + add settings btn

Browse files
static/index.html CHANGED
@@ -22,10 +22,16 @@
22
  </div>
23
 
24
  <div class="container mx-auto p-6">
25
- <h1 class="text-3xl font-bold text-center mb-8">Requirements Extractor</h1>
 
 
 
 
 
26
  <div id="selection-container" class="mb-6">
27
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
28
- <!-- Working Group -->
 
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
- <select id="meeting-select" class="w-full p-2 border border-gray-300 rounded-md">
55
- <option value="">Select a meeting</option>
56
- </select>
 
 
 
 
 
 
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" onclick="switchTab('doc-table-tab')">📝
76
  Documents</a>
77
- <a role="tab" class="tab tab-disabled" id="requirements-tab" onclick="switchTab('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" onclick="switchTab('solutions-tab')">Group and
84
  Solve</a>
85
- <a role="tab" class="tab tab-disabled" id="query-tab" onclick="switchTab('query-tab')">🔎 Find relevant
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
- <!-- Right side: 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,7 +186,7 @@
186
  </div>
187
 
188
 
189
- <!-- Data 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,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
- <script src="js/sse.js"></script>
337
- <script src="js/ui-utils.js"></script>
338
- <script src="js/script.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
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
- applyFilters();
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
+ }