genelastic 0.8.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- genelastic/api/.env +4 -0
- genelastic/api/cli_start_api.py +2 -2
- genelastic/api/errors.py +52 -0
- genelastic/api/extends/example.py +0 -6
- genelastic/api/extends/example.yml +0 -20
- genelastic/api/routes.py +313 -181
- genelastic/api/server.py +8 -3
- genelastic/api/specification.yml +343 -181
- genelastic/common/__init__.py +0 -44
- genelastic/common/cli.py +48 -0
- genelastic/common/elastic.py +374 -46
- genelastic/common/exceptions.py +34 -2
- genelastic/common/server.py +9 -1
- genelastic/common/types.py +1 -14
- genelastic/import_data/__init__.py +0 -27
- genelastic/import_data/checker.py +99 -0
- genelastic/import_data/checker_observer.py +13 -0
- genelastic/import_data/cli/__init__.py +0 -0
- genelastic/import_data/cli/cli_check.py +136 -0
- genelastic/import_data/{cli_gen_data.py → cli/gen_data.py} +4 -4
- genelastic/import_data/cli/import_data.py +346 -0
- genelastic/import_data/cli/info.py +247 -0
- genelastic/import_data/{cli_integrity.py → cli/integrity.py} +29 -7
- genelastic/import_data/cli/validate.py +146 -0
- genelastic/import_data/collect.py +185 -0
- genelastic/import_data/constants.py +136 -11
- genelastic/import_data/import_bundle.py +102 -59
- genelastic/import_data/import_bundle_factory.py +70 -149
- genelastic/import_data/importers/__init__.py +0 -0
- genelastic/import_data/importers/importer_base.py +131 -0
- genelastic/import_data/importers/importer_factory.py +85 -0
- genelastic/import_data/importers/importer_types.py +223 -0
- genelastic/import_data/logger.py +2 -1
- genelastic/import_data/models/__init__.py +0 -0
- genelastic/import_data/models/analyses.py +178 -0
- genelastic/import_data/models/analysis.py +144 -0
- genelastic/import_data/models/data_file.py +110 -0
- genelastic/import_data/models/process.py +45 -0
- genelastic/import_data/models/processes.py +84 -0
- genelastic/import_data/models/tags.py +170 -0
- genelastic/import_data/models/unique_list.py +109 -0
- genelastic/import_data/models/validate.py +26 -0
- genelastic/import_data/patterns.py +90 -0
- genelastic/import_data/random_bundle.py +10 -8
- genelastic/import_data/resolve.py +157 -0
- genelastic/ui/.env +1 -0
- genelastic/ui/cli_start_ui.py +4 -2
- genelastic/ui/routes.py +289 -42
- genelastic/ui/static/cea-cnrgh.ico +0 -0
- genelastic/ui/static/cea.ico +0 -0
- genelastic/ui/static/layout.ico +0 -0
- genelastic/ui/static/novaseq6000.png +0 -0
- genelastic/ui/static/style.css +430 -0
- genelastic/ui/static/ui.js +458 -0
- genelastic/ui/templates/analyses.html +96 -9
- genelastic/ui/templates/analysis_detail.html +44 -0
- genelastic/ui/templates/bi_process_detail.html +129 -0
- genelastic/ui/templates/bi_processes.html +114 -9
- genelastic/ui/templates/explorer.html +356 -0
- genelastic/ui/templates/home.html +205 -2
- genelastic/ui/templates/layout.html +148 -29
- genelastic/ui/templates/version.html +19 -7
- genelastic/ui/templates/wet_process_detail.html +131 -0
- genelastic/ui/templates/wet_processes.html +114 -9
- genelastic-0.9.0.dist-info/METADATA +686 -0
- genelastic-0.9.0.dist-info/RECORD +76 -0
- genelastic-0.9.0.dist-info/WHEEL +4 -0
- genelastic-0.9.0.dist-info/entry_points.txt +10 -0
- genelastic-0.9.0.dist-info/licenses/LICENSE +519 -0
- genelastic/import_data/analyses.py +0 -69
- genelastic/import_data/analysis.py +0 -205
- genelastic/import_data/bi_process.py +0 -27
- genelastic/import_data/bi_processes.py +0 -49
- genelastic/import_data/cli_import.py +0 -379
- genelastic/import_data/cli_info.py +0 -256
- genelastic/import_data/cli_validate.py +0 -54
- genelastic/import_data/data_file.py +0 -87
- genelastic/import_data/filename_pattern.py +0 -57
- genelastic/import_data/tags.py +0 -123
- genelastic/import_data/wet_process.py +0 -28
- genelastic/import_data/wet_processes.py +0 -53
- genelastic-0.8.0.dist-info/METADATA +0 -109
- genelastic-0.8.0.dist-info/RECORD +0 -52
- genelastic-0.8.0.dist-info/WHEEL +0 -5
- genelastic-0.8.0.dist-info/entry_points.txt +0 -8
- genelastic-0.8.0.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{% extends "layout.html" %}
|
|
2
|
+
{% block title %}
|
|
3
|
+
Détails - {{ proc_id }} - BEx
|
|
4
|
+
{% endblock title %}
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="container mt-5">
|
|
7
|
+
<h2 class="mb-4 text-center text-success">
|
|
8
|
+
Détails du bi process
|
|
9
|
+
<span class="condition-details">{{ proc_id }}</span>
|
|
10
|
+
</h2>
|
|
11
|
+
{% if not error %}
|
|
12
|
+
<!-- Formulaire de sélection pour comparaison -->
|
|
13
|
+
<form method="get" class="mb-4 d-flex align-items-center gap-2">
|
|
14
|
+
<label for="compare_with" class="form-label m-0">
|
|
15
|
+
Comparer la sélection avec une condition expérimentale
|
|
16
|
+
:
|
|
17
|
+
</label>
|
|
18
|
+
<select name="compare_with" id="compare_with" class="form-select w-auto">
|
|
19
|
+
<option value="">----------</option>
|
|
20
|
+
{% for wp in wet_processes %}
|
|
21
|
+
<option value="{{ wp }}" {% if selected_wet== wp %}selected{% endif %}>{{ wp }}</option>
|
|
22
|
+
{% endfor %}
|
|
23
|
+
</select>
|
|
24
|
+
<button type="submit" class="btn btn-success">Comparer</button>
|
|
25
|
+
</form>
|
|
26
|
+
<div class="row">
|
|
27
|
+
<!-- Bi Process -->
|
|
28
|
+
<div class="col-md-6 mb-4">
|
|
29
|
+
<div class="card shadow-lg border-0 h-100">
|
|
30
|
+
<div class="card-body">
|
|
31
|
+
<h5 class="card-title text-center">Bi Process</h5>
|
|
32
|
+
<div class="table-responsive">
|
|
33
|
+
<table class="details-table">
|
|
34
|
+
<thead>
|
|
35
|
+
<tr class="text-center">
|
|
36
|
+
<th scope="col">Champ</th>
|
|
37
|
+
<th scope="col">Valeur</th>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
<tbody>
|
|
41
|
+
{% for key, value in bi_process.items() %}
|
|
42
|
+
{% if key == 'steps' %}
|
|
43
|
+
<tr>
|
|
44
|
+
<td class="fw-bold text-capitalize">Steps</td>
|
|
45
|
+
<td>
|
|
46
|
+
<button id="toggleStepsBtn"
|
|
47
|
+
class="btn btn-sm btn-success"
|
|
48
|
+
type="button"
|
|
49
|
+
data-bs-toggle="collapse"
|
|
50
|
+
data-bs-target="#stepsCollapse"
|
|
51
|
+
aria-expanded="false"
|
|
52
|
+
aria-controls="stepsCollapse">Afficher les étapes</button>
|
|
53
|
+
<div class="collapse mt-2" id="stepsCollapse">
|
|
54
|
+
<table class="details-table table-sm mt-2 text-center">
|
|
55
|
+
<thead>
|
|
56
|
+
<tr>
|
|
57
|
+
<th>Commande</th>
|
|
58
|
+
<th>Nom</th>
|
|
59
|
+
<th>Version</th>
|
|
60
|
+
<th>Sortie</th>
|
|
61
|
+
</tr>
|
|
62
|
+
</thead>
|
|
63
|
+
<tbody>
|
|
64
|
+
{% for step in value %}
|
|
65
|
+
<tr>
|
|
66
|
+
<td>{{ step.cmd }}</td>
|
|
67
|
+
<td>{{ step.name }}</td>
|
|
68
|
+
<td>{{ step.version }}</td>
|
|
69
|
+
<td>{{ step.output if 'output' in step else '-' }}</td>
|
|
70
|
+
</tr>
|
|
71
|
+
{% endfor %}
|
|
72
|
+
</tbody>
|
|
73
|
+
</table>
|
|
74
|
+
</div>
|
|
75
|
+
</td>
|
|
76
|
+
</tr>
|
|
77
|
+
{% else %}
|
|
78
|
+
<tr>
|
|
79
|
+
<td class="fw-bold text-capitalize">{{ key.replace('_', ' ') }}</td>
|
|
80
|
+
<td>{{ value }}</td>
|
|
81
|
+
</tr>
|
|
82
|
+
{% endif %}
|
|
83
|
+
{% endfor %}
|
|
84
|
+
</tbody>
|
|
85
|
+
</table>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<!-- Wet Process (s’il y en a un sélectionné) -->
|
|
91
|
+
{% if wet_process_data %}
|
|
92
|
+
<div class="col-md-6 mb-4">
|
|
93
|
+
<div class="card shadow-lg border-0 h-100">
|
|
94
|
+
<div class="card-body">
|
|
95
|
+
<h5 class="card-title text-center">
|
|
96
|
+
Wet Process : <span class="select-compared-process">{{ selected_wet }}</span>
|
|
97
|
+
</h5>
|
|
98
|
+
{% if wet_process_data.error %}
|
|
99
|
+
<div class="alert alert-danger text-center">{{ wet_process_data.error }}</div>
|
|
100
|
+
{% else %}
|
|
101
|
+
<div class="table-responsive">
|
|
102
|
+
<table class="details-table">
|
|
103
|
+
<thead>
|
|
104
|
+
<tr class="text-center">
|
|
105
|
+
<th scope="col">Champ</th>
|
|
106
|
+
<th scope="col">Valeur</th>
|
|
107
|
+
</tr>
|
|
108
|
+
</thead>
|
|
109
|
+
<tbody>
|
|
110
|
+
{% for key, value in wet_process_data.items() %}
|
|
111
|
+
<tr>
|
|
112
|
+
<td class="fw-bold text-capitalize">{{ key.replace('_', ' ') }}</td>
|
|
113
|
+
<td>{{ value }}</td>
|
|
114
|
+
</tr>
|
|
115
|
+
{% endfor %}
|
|
116
|
+
</tbody>
|
|
117
|
+
</table>
|
|
118
|
+
</div>
|
|
119
|
+
{% endif %}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
{% endif %}
|
|
124
|
+
</div>
|
|
125
|
+
{% else %}
|
|
126
|
+
<div class="p-3 mb-2 bg-danger text-white text-center" role="alert">{{ error.message }}</div>
|
|
127
|
+
{% endif %}
|
|
128
|
+
</div>
|
|
129
|
+
{% endblock content %}
|
|
@@ -1,11 +1,116 @@
|
|
|
1
1
|
{% extends "layout.html" %}
|
|
2
|
-
{% block title %}
|
|
2
|
+
{% block title %}
|
|
3
|
+
Bi Processes - BEx
|
|
4
|
+
{% endblock title %}
|
|
3
5
|
{% block content %}
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
|
|
6
|
+
<div class="container mt-5">
|
|
7
|
+
<div class="text-center mb-4">
|
|
8
|
+
<h1 class="display-5 title">Liste des conditions bio-informatiques</h1>
|
|
9
|
+
<p class="lead">Sélectionner les conditions pour afficher les analyses associées.</p>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="card shadow-sm">
|
|
12
|
+
<div class="card-body">
|
|
13
|
+
<h3 class="mb-4">Conditions disponibles</h3>
|
|
14
|
+
<form id="biForm" method="get" action="{{ request.path }}">
|
|
15
|
+
<input type="text"
|
|
16
|
+
id="biSearch"
|
|
17
|
+
class="form-control mb-3"
|
|
18
|
+
placeholder="Rechercher un bi process..." />
|
|
19
|
+
<div class="row">
|
|
20
|
+
<div class="col-md-6">
|
|
21
|
+
<div class="list-group" id="biProcessList">
|
|
22
|
+
{% for bi_process in bi_processes %}
|
|
23
|
+
<div class="list-group-item bi-process-item">
|
|
24
|
+
<div class="form-check">
|
|
25
|
+
<input class="form-check-input"
|
|
26
|
+
type="checkbox"
|
|
27
|
+
id="bi_{{ loop.index }}"
|
|
28
|
+
name="bi_processes"
|
|
29
|
+
value="{{ bi_process }}"
|
|
30
|
+
{% if bi_process in selected_bi_processes %}checked{% endif %} />
|
|
31
|
+
<label class="form-check-label" for="bi_{{ loop.index }}">{{ bi_process }}</label>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
{% endfor %}
|
|
35
|
+
</div>
|
|
36
|
+
<div class="d-flex gap-2 mt-3">
|
|
37
|
+
<button type="button"
|
|
38
|
+
class="reset-error-btn btn"
|
|
39
|
+
id="resetBiSelection"
|
|
40
|
+
disabled>Réinitialiser la sélection</button>
|
|
41
|
+
<button type="button" class="btn btn-success" id="selectAllBi">Tout sélectionner</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
{% if selected_bi_processes %}
|
|
45
|
+
<div class="col-md-6">
|
|
46
|
+
{% if analyses %}
|
|
47
|
+
<h4 class="mt-4">Analyses associées :</h4>
|
|
48
|
+
<ul class="list-group mt-3">
|
|
49
|
+
{% for analysis in analyses %}
|
|
50
|
+
<li class="list-group-item">
|
|
51
|
+
<a href="{{ url_for('routes.show_analysis_detail', analysis_id=analysis, source='bi') }}"
|
|
52
|
+
class="text-decoration-none">
|
|
53
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
54
|
+
<span>{{ analysis }}</span>
|
|
55
|
+
<i class="bi bi-chevron-right"></i>
|
|
56
|
+
</div>
|
|
57
|
+
</a>
|
|
58
|
+
</li>
|
|
59
|
+
{% endfor %}
|
|
60
|
+
</ul>
|
|
61
|
+
{% else %}
|
|
62
|
+
<div class="alert alert-danger mt-4" role="alert">
|
|
63
|
+
<strong>Aucune analyse correspondante à ce Bi Process !</strong>
|
|
64
|
+
</div>
|
|
65
|
+
{% endif %}
|
|
66
|
+
</div>
|
|
67
|
+
{% endif %}
|
|
68
|
+
</div>
|
|
69
|
+
</form>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<script>
|
|
74
|
+
document.getElementById('biSearch').addEventListener('input', function() {
|
|
75
|
+
const filter = this.value.toLowerCase();
|
|
76
|
+
const items = document.querySelectorAll('.bi-process-item');
|
|
77
|
+
items.forEach((item) => {
|
|
78
|
+
const label = item.querySelector('label').innerText.toLowerCase();
|
|
79
|
+
if (label.startsWith(filter)) {
|
|
80
|
+
item.style.display = '';
|
|
81
|
+
} else {
|
|
82
|
+
item.style.display = 'none';
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
document
|
|
88
|
+
.getElementById('resetBiSelection')
|
|
89
|
+
.addEventListener('click', function() {
|
|
90
|
+
const checkboxes = document.querySelectorAll(
|
|
91
|
+
'#biForm input[type="checkbox"]',
|
|
92
|
+
);
|
|
93
|
+
checkboxes.forEach((cb) => (cb.checked = false));
|
|
94
|
+
document.getElementById('biSearch').value = '';
|
|
95
|
+
document.getElementById('biForm').submit();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
document.getElementById('selectAllBi').addEventListener('click', function() {
|
|
99
|
+
document
|
|
100
|
+
.querySelectorAll('#biProcessList .form-check-input')
|
|
101
|
+
.forEach((cb) => (cb.checked = true));
|
|
102
|
+
document.getElementById('biForm').submit();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const biCheckboxes = document.querySelectorAll('#biForm input[type="checkbox"]');
|
|
106
|
+
const resetBiBtn = document.getElementById('resetBiSelection');
|
|
107
|
+
|
|
108
|
+
function updateBiResetButton() {
|
|
109
|
+
const anyChecked = Array.from(biCheckboxes).some(cb => cb.checked);
|
|
110
|
+
resetBiBtn.disabled = !anyChecked;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
biCheckboxes.forEach(cb => cb.addEventListener('change', updateBiResetButton));
|
|
114
|
+
updateBiResetButton();
|
|
115
|
+
</script>
|
|
116
|
+
{% endblock content %}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
{% extends "layout.html" %}
|
|
2
|
+
{% block title %}
|
|
3
|
+
Explorer - BEx
|
|
4
|
+
{% endblock title %}
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="container py-5">
|
|
7
|
+
<h1 class="mb-4 text-primary fw-bold display-5">Explorer les métadonnées</h1>
|
|
8
|
+
<p class="mb-4 text-muted fs-5">Déroulez chaque catégorie et cochez les valeurs que vous souhaitez sélectionner.</p>
|
|
9
|
+
<div class="d-flex justify-content-end mb-3">
|
|
10
|
+
<button id="toggleAllBtn" class="btn btn-sm btn-outline-primary shadow-sm">Tout dérouler</button>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="accordion" id="metadataAccordion">
|
|
13
|
+
{% for field, values in metadata_counts.items() %}
|
|
14
|
+
{% set clean_name = field.replace('metadata.bi_process.', '')
|
|
15
|
+
.replace('metadata.wet_process.', '')
|
|
16
|
+
.replace('metadata.', '')
|
|
17
|
+
.replace('_', ' ')
|
|
18
|
+
.replace('.', ' ')
|
|
19
|
+
.title() %}
|
|
20
|
+
{% set collapse_id = 'collapse' + clean_name.replace(' ', '') %}
|
|
21
|
+
{% set is_empty = values|length == 0 %}
|
|
22
|
+
<div class="accordion-item mb-3 shadow-sm rounded {% if is_empty %}opacity-50{% endif %}"
|
|
23
|
+
style="border: 1px solid #dee2e6">
|
|
24
|
+
<h2 class="accordion-header" id="heading{{ collapse_id }}">
|
|
25
|
+
<button class="accordion-button collapsed fs-6"
|
|
26
|
+
type="button"
|
|
27
|
+
data-bs-toggle="collapse"
|
|
28
|
+
data-bs-target="#{{ collapse_id }}"
|
|
29
|
+
aria-expanded="false"
|
|
30
|
+
aria-controls="{{ collapse_id }}"
|
|
31
|
+
style="background-color: #f8f9fa">{{ clean_name }}</button>
|
|
32
|
+
</h2>
|
|
33
|
+
<div id="{{ collapse_id }}"
|
|
34
|
+
class="accordion-collapse collapse"
|
|
35
|
+
aria-labelledby="heading{{ collapse_id }}"
|
|
36
|
+
data-bs-parent="#metadataAccordion">
|
|
37
|
+
<div class="accordion-body p-3">
|
|
38
|
+
{% if not is_empty %}
|
|
39
|
+
<form class="metadata-values-form" data-field="{{ field }}">
|
|
40
|
+
<div class="list-group">
|
|
41
|
+
{% for value, count_val in values.items() %}
|
|
42
|
+
{% set input_id = (collapse_id + '_' + loop.index|string) %}
|
|
43
|
+
<label class="list-group-item d-flex justify-content-between align-items-center rounded"
|
|
44
|
+
for="{{ input_id }}"
|
|
45
|
+
style="cursor: pointer;
|
|
46
|
+
transition: background-color 0.15s ease">
|
|
47
|
+
<div>
|
|
48
|
+
<input type="checkbox"
|
|
49
|
+
class="form-check-input me-3 metadata-checkbox"
|
|
50
|
+
id="{{ input_id }}"
|
|
51
|
+
data-value="{{ value }}"
|
|
52
|
+
data-field="{{ field }}">
|
|
53
|
+
{% if 'wet_process' in field %}
|
|
54
|
+
<a href="{{ url_for('routes.wet_process_detail', wet_process_id=value) }}"
|
|
55
|
+
class="text-decoration-none link-primary fw-semibold"
|
|
56
|
+
style="transition: color 0.2s">{{ value or "—" }}</a>
|
|
57
|
+
{% elif 'bi_process' in field %}
|
|
58
|
+
<a href="{{ url_for('routes.bi_process_detail', bi_process_id=value) }}"
|
|
59
|
+
class="text-decoration-none link-primary fw-semibold"
|
|
60
|
+
style="transition: color 0.2s">{{ value or "—" }}</a>
|
|
61
|
+
{% else %}
|
|
62
|
+
<span class="fw-normal">{{ value or "—" }}</span>
|
|
63
|
+
{% endif %}
|
|
64
|
+
</div>
|
|
65
|
+
<span class="badge bg-primary rounded-pill px-3 py-2 fw-semibold">{{ count_val }}</span>
|
|
66
|
+
</label>
|
|
67
|
+
{% endfor %}
|
|
68
|
+
</div>
|
|
69
|
+
</form>
|
|
70
|
+
{% else %}
|
|
71
|
+
<p class="text-muted text-center fst-italic">Aucune donnée disponible</p>
|
|
72
|
+
{% endif %}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
{% endfor %}
|
|
77
|
+
</div>
|
|
78
|
+
<!-- Panier flottant -->
|
|
79
|
+
<div id="floatingCart"
|
|
80
|
+
class="position-fixed bottom-0 end-0 m-4 bg-white border rounded shadow p-4 d-flex flex-column"
|
|
81
|
+
style="width: 340px;
|
|
82
|
+
height: 380px;
|
|
83
|
+
z-index: 1050;
|
|
84
|
+
box-shadow: 0 8px 20px rgb(0 123 255 / 0.25);
|
|
85
|
+
overflow: hidden">
|
|
86
|
+
<h5 class="fw-bold text-primary d-flex align-items-center gap-2 mb-3"
|
|
87
|
+
style="font-size: 1.35rem">
|
|
88
|
+
🛒 Panier
|
|
89
|
+
<span class="badge bg-primary rounded-pill fs-6" id="cartCount">0</span>
|
|
90
|
+
</h5>
|
|
91
|
+
<div id="selectedMetadata"
|
|
92
|
+
class="flex-grow-1 d-flex flex-column gap-3"
|
|
93
|
+
style="overflow-y: auto;
|
|
94
|
+
padding-right: 4px;
|
|
95
|
+
margin-bottom: 1rem"></div>
|
|
96
|
+
<div class="pt-2 border-top" style="background-color: white">
|
|
97
|
+
<div class="d-flex flex-column gap-2 mt-3">
|
|
98
|
+
<button id="validateCart1Btn"
|
|
99
|
+
class="btn btn-outline-success fw-semibold shadow-sm"
|
|
100
|
+
style="font-size: 1rem">Valider panier 1</button>
|
|
101
|
+
<button id="validateCart2Btn"
|
|
102
|
+
class="btn btn-outline-warning fw-semibold shadow-sm"
|
|
103
|
+
style="font-size: 1rem"
|
|
104
|
+
disabled>Valider panier 2</button>
|
|
105
|
+
<button id="compareBtn"
|
|
106
|
+
class="btn btn-secondary fw-semibold shadow-sm"
|
|
107
|
+
style="font-size: 1rem"
|
|
108
|
+
disabled>Comparer mes deux paniers</button>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="d-flex gap-2 mt-3">
|
|
111
|
+
<button id="validateBtn"
|
|
112
|
+
class="btn btn-primary flex-grow-1 shadow-sm fw-semibold"
|
|
113
|
+
style="font-size: 1rem">Valider la sélection</button>
|
|
114
|
+
<button id="clearCartBtn"
|
|
115
|
+
class="btn btn-outline-secondary flex-grow-1 shadow-sm fw-semibold"
|
|
116
|
+
style="font-size: 1rem">Vider le panier</button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
<button id="toggleCartBtn"
|
|
121
|
+
class="btn btn-primary rounded-circle shadow position-fixed bottom-0 end-0 m-4 d-flex align-items-center justify-content-center"
|
|
122
|
+
style="width: 58px;
|
|
123
|
+
height: 58px;
|
|
124
|
+
z-index: 1060">🛒</button>
|
|
125
|
+
<!-- Modale de comparaison -->
|
|
126
|
+
<div class="modal fade"
|
|
127
|
+
id="compareModal"
|
|
128
|
+
tabindex="-1"
|
|
129
|
+
aria-labelledby="compareModalLabel"
|
|
130
|
+
aria-hidden="true">
|
|
131
|
+
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
|
132
|
+
<div class="modal-content">
|
|
133
|
+
<div class="modal-header">
|
|
134
|
+
<h5 class="modal-title fw-bold text-primary" id="compareModalLabel">
|
|
135
|
+
Comparaison des deux
|
|
136
|
+
paniers
|
|
137
|
+
</h5>
|
|
138
|
+
<button type="button"
|
|
139
|
+
class="btn-close"
|
|
140
|
+
data-bs-dismiss="modal"
|
|
141
|
+
aria-label="Fermer"></button>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="modal-body">
|
|
144
|
+
<div class="row">
|
|
145
|
+
<div class="col-md-6">
|
|
146
|
+
<h6 class="text-success">🛒 Panier 1</h6>
|
|
147
|
+
<ul id="cart1List" class="list-group list-group-flush small">
|
|
148
|
+
</ul>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="col-md-6">
|
|
151
|
+
<h6 class="text-warning">🛒 Panier 2</h6>
|
|
152
|
+
<ul id="cart2List" class="list-group list-group-flush small">
|
|
153
|
+
</ul>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
<hr>
|
|
157
|
+
<div>
|
|
158
|
+
<h6 class="text-secondary">🎯 Métadonnées communes</h6>
|
|
159
|
+
<ul id="commonList" class="list-group list-group-flush small">
|
|
160
|
+
</ul>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="modal-footer">
|
|
164
|
+
<button type="button"
|
|
165
|
+
class="btn btn-secondary fw-semibold"
|
|
166
|
+
data-bs-dismiss="modal">Fermer</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
<script>
|
|
173
|
+
const selectedSet = new Set();
|
|
174
|
+
let cart1 = null;
|
|
175
|
+
let cart2 = null;
|
|
176
|
+
|
|
177
|
+
const selectedContainer = document.getElementById('selectedMetadata');
|
|
178
|
+
const cartCountBadge = document.getElementById('cartCount');
|
|
179
|
+
const validateCart1Btn = document.getElementById('validateCart1Btn');
|
|
180
|
+
const validateCart2Btn = document.getElementById('validateCart2Btn');
|
|
181
|
+
const compareBtn = document.getElementById('compareBtn');
|
|
182
|
+
|
|
183
|
+
function capitalizeWords(str) {
|
|
184
|
+
return str.replace(/\b\w/g, c => c.toUpperCase());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function updateCartUI() {
|
|
188
|
+
selectedContainer.innerHTML = '';
|
|
189
|
+
|
|
190
|
+
if (selectedSet.size === 0) {
|
|
191
|
+
selectedContainer.innerHTML = '<p class="text-muted fst-italic text-center">Aucune sélection</p>';
|
|
192
|
+
} else {
|
|
193
|
+
selectedSet.forEach(item => {
|
|
194
|
+
const [field, value] = item.split('||');
|
|
195
|
+
const cleanName = field.replace('metadata.', '').replace(/[_\.]/g, ' ');
|
|
196
|
+
const displayText = `${capitalizeWords(cleanName.trim())}: ${value}`;
|
|
197
|
+
|
|
198
|
+
const badge = document.createElement('div');
|
|
199
|
+
badge.className = 'badge bg-primary d-flex justify-content-between align-items-center rounded-pill px-3 py-2';
|
|
200
|
+
badge.style.cursor = 'default';
|
|
201
|
+
badge.textContent = displayText;
|
|
202
|
+
|
|
203
|
+
const btnClose = document.createElement('button');
|
|
204
|
+
btnClose.type = 'button';
|
|
205
|
+
btnClose.className = 'btn-close btn-close-white btn-sm ms-3';
|
|
206
|
+
btnClose.setAttribute('aria-label', `Retirer ${displayText}`);
|
|
207
|
+
btnClose.style.filter = 'drop-shadow(0 0 1px rgba(0,0,0,0.3))';
|
|
208
|
+
btnClose.addEventListener('click', () => {
|
|
209
|
+
selectedSet.delete(item);
|
|
210
|
+
updateCartUI();
|
|
211
|
+
updateCheckboxes();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
badge.appendChild(btnClose);
|
|
215
|
+
selectedContainer.appendChild(badge);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
cartCountBadge.textContent = selectedSet.size;
|
|
220
|
+
|
|
221
|
+
validateCart1Btn.disabled = (selectedSet.size === 0) || (cart1 !== null);
|
|
222
|
+
validateCart2Btn.disabled = (selectedSet.size === 0) || (cart1 === null) || (cart2 !== null);
|
|
223
|
+
|
|
224
|
+
updateCheckboxes();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function updateCheckboxes() {
|
|
228
|
+
document.querySelectorAll('.metadata-checkbox').forEach(checkbox => {
|
|
229
|
+
const key = `${checkbox.dataset.field}||${checkbox.dataset.value}`;
|
|
230
|
+
checkbox.checked = selectedSet.has(key);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
document.querySelectorAll('.metadata-checkbox').forEach(checkbox => {
|
|
235
|
+
checkbox.addEventListener('change', () => {
|
|
236
|
+
const key = `${checkbox.dataset.field}||${checkbox.dataset.value}`;
|
|
237
|
+
if (checkbox.checked) {
|
|
238
|
+
selectedSet.add(key);
|
|
239
|
+
} else {
|
|
240
|
+
selectedSet.delete(key);
|
|
241
|
+
}
|
|
242
|
+
updateCartUI();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
document.getElementById('validateBtn').addEventListener('click', () => {
|
|
247
|
+
console.log('Sélection validée:', Array.from(selectedSet));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
document.getElementById('clearCartBtn').addEventListener('click', () => {
|
|
251
|
+
selectedSet.clear();
|
|
252
|
+
updateCartUI();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
document.getElementById('toggleAllBtn').addEventListener('click', () => {
|
|
256
|
+
const accordionItems = document.querySelectorAll('.accordion-collapse');
|
|
257
|
+
const allExpanded = Array.from(accordionItems).every(item => item.classList.contains('show'));
|
|
258
|
+
|
|
259
|
+
accordionItems.forEach(item => {
|
|
260
|
+
const bsCollapse = bootstrap.Collapse.getOrCreateInstance(item);
|
|
261
|
+
if (allExpanded) {
|
|
262
|
+
bsCollapse.hide();
|
|
263
|
+
} else {
|
|
264
|
+
bsCollapse.show();
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
document.getElementById('toggleAllBtn').textContent = allExpanded ? 'Tout dérouler' : 'Tout replier';
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
document.getElementById('toggleCartBtn').addEventListener('click', () => {
|
|
272
|
+
const cart = document.getElementById('floatingCart');
|
|
273
|
+
cart.style.display = (cart.style.display === 'none' || cart.style.display === '') ? 'block' : 'none';
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
validateCart1Btn.addEventListener('click', () => {
|
|
277
|
+
cart1 = new Set(selectedSet);
|
|
278
|
+
validateCart1Btn.disabled = true;
|
|
279
|
+
validateCart2Btn.disabled = false;
|
|
280
|
+
selectedSet.clear();
|
|
281
|
+
updateCartUI();
|
|
282
|
+
console.log('Panier 1 validé :', Array.from(cart1));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
validateCart2Btn.addEventListener('click', () => {
|
|
286
|
+
cart2 = new Set(selectedSet);
|
|
287
|
+
validateCart2Btn.disabled = true;
|
|
288
|
+
compareBtn.disabled = false;
|
|
289
|
+
selectedSet.clear();
|
|
290
|
+
updateCartUI();
|
|
291
|
+
console.log('Panier 2 validé :', Array.from(cart2));
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
compareBtn.addEventListener('click', () => {
|
|
295
|
+
const cart1List = document.getElementById('cart1List');
|
|
296
|
+
const cart2List = document.getElementById('cart2List');
|
|
297
|
+
const commonList = document.getElementById('commonList');
|
|
298
|
+
|
|
299
|
+
cart1List.innerHTML = '';
|
|
300
|
+
cart2List.innerHTML = '';
|
|
301
|
+
commonList.innerHTML = '';
|
|
302
|
+
|
|
303
|
+
const arr1 = Array.from(cart1);
|
|
304
|
+
const arr2 = Array.from(cart2);
|
|
305
|
+
const commons = arr1.filter(item => cart2.has(item));
|
|
306
|
+
|
|
307
|
+
function createListItems(arr, container) {
|
|
308
|
+
arr.forEach(item => {
|
|
309
|
+
const li = document.createElement('li');
|
|
310
|
+
li.className = 'list-group-item';
|
|
311
|
+
li.innerHTML = `<strong>${item.split('||')[0].replace('metadata.', '').replace(/[_\.]/g, ' ')}</strong>: ${item.split('||')[1]}`;
|
|
312
|
+
container.appendChild(li);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
createListItems(arr1, cart1List);
|
|
317
|
+
createListItems(arr2, cart2List);
|
|
318
|
+
createListItems(commons, commonList);
|
|
319
|
+
|
|
320
|
+
const compareModal = new bootstrap.Modal(document.getElementById('compareModal'));
|
|
321
|
+
compareModal.show();
|
|
322
|
+
});
|
|
323
|
+
const cart = document.getElementById('floatingCart');
|
|
324
|
+
const toggleBtn = document.getElementById('toggleCartBtn');
|
|
325
|
+
|
|
326
|
+
// Cache le panier au départ
|
|
327
|
+
cart.classList.add('d-none');
|
|
328
|
+
|
|
329
|
+
// Affiche le panier quand la souris entre sur le bouton
|
|
330
|
+
toggleBtn.addEventListener('mouseenter', () => {
|
|
331
|
+
cart.classList.remove('d-none');
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Masque le panier quand la souris quitte le bouton ET le panier (pour éviter flicker)
|
|
335
|
+
function hideCartIfNotHover() {
|
|
336
|
+
// On attend un petit délai pour voir si la souris est toujours sur le bouton ou panier
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
const isOverBtn = toggleBtn.matches(':hover');
|
|
339
|
+
const isOverCart = cart.matches(':hover');
|
|
340
|
+
if (!isOverBtn && !isOverCart) {
|
|
341
|
+
cart.classList.add('d-none');
|
|
342
|
+
}
|
|
343
|
+
}, 100); // délai 100ms, ajuste si besoin
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
toggleBtn.addEventListener('mouseleave', hideCartIfNotHover);
|
|
347
|
+
cart.addEventListener('mouseleave', hideCartIfNotHover);
|
|
348
|
+
|
|
349
|
+
// Si la souris entre sur le panier, on garde affiché
|
|
350
|
+
cart.addEventListener('mouseenter', () => {
|
|
351
|
+
cart.classList.remove('d-none');
|
|
352
|
+
});
|
|
353
|
+
// Initial update
|
|
354
|
+
updateCartUI();
|
|
355
|
+
</script>
|
|
356
|
+
{% endblock content %}
|