ositah 25.6.dev1__py3-none-any.whl → 25.9.dev2__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.

Potentially problematic release.


This version of ositah might be problematic. Click here for more details.

Files changed (42) hide show
  1. ositah/app.py +17 -17
  2. ositah/apps/analysis.py +785 -785
  3. ositah/apps/configuration/callbacks.py +916 -916
  4. ositah/apps/configuration/main.py +546 -546
  5. ositah/apps/configuration/parameters.py +74 -74
  6. ositah/apps/configuration/tools.py +112 -112
  7. ositah/apps/export.py +1209 -1191
  8. ositah/apps/validation/callbacks.py +240 -240
  9. ositah/apps/validation/main.py +89 -89
  10. ositah/apps/validation/parameters.py +25 -25
  11. ositah/apps/validation/tables.py +646 -646
  12. ositah/apps/validation/tools.py +552 -552
  13. ositah/assets/arrow_down_up.svg +3 -3
  14. ositah/assets/ositah.css +53 -53
  15. ositah/assets/sort_ascending.svg +4 -4
  16. ositah/assets/sort_descending.svg +5 -5
  17. ositah/assets/sorttable.js +499 -499
  18. ositah/main.py +449 -449
  19. ositah/ositah.example.cfg +229 -229
  20. ositah/static/style.css +53 -53
  21. ositah/templates/base.html +22 -22
  22. ositah/templates/bootstrap_login.html +38 -38
  23. ositah/templates/login_form.html +26 -26
  24. ositah/utils/agents.py +124 -124
  25. ositah/utils/authentication.py +287 -287
  26. ositah/utils/cache.py +19 -19
  27. ositah/utils/core.py +13 -13
  28. ositah/utils/exceptions.py +64 -64
  29. ositah/utils/hito_db.py +51 -51
  30. ositah/utils/hito_db_model.py +253 -253
  31. ositah/utils/menus.py +339 -339
  32. ositah/utils/period.py +139 -139
  33. ositah/utils/projects.py +1179 -1178
  34. ositah/utils/teams.py +42 -42
  35. ositah/utils/utils.py +474 -474
  36. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/METADATA +149 -150
  37. ositah-25.9.dev2.dist-info/RECORD +46 -0
  38. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/licenses/LICENSE +29 -29
  39. ositah-25.6.dev1.dist-info/RECORD +0 -46
  40. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/WHEEL +0 -0
  41. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/entry_points.txt +0 -0
  42. {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/top_level.txt +0 -0
ositah/utils/menus.py CHANGED
@@ -1,339 +1,339 @@
1
- # Module containing helper functions to build/manage the menus and graphic objects
2
-
3
- from datetime import datetime
4
-
5
- import dash_bootstrap_components as dbc
6
- from dash import dcc, html
7
- from dash.dependencies import MATCH, Input, Output, State
8
-
9
- from ositah.app import app
10
- from ositah.utils.cache import clear_cached_data
11
- from ositah.utils.exceptions import SessionDataMissing
12
- from ositah.utils.period import get_declaration_periods, get_default_period_date
13
- from ositah.utils.utils import GlobalParams, no_session_id_jumbotron
14
-
15
- DATA_SELECTED_SOURCE_ID = "project-declaration-source"
16
- DATA_SELECTION_SOURCE_ID = "project-declaration-source-button"
17
-
18
- TEAM_SELECTED_VALUE_ID = "team-selected"
19
- TEAM_SELECTION_MENU_ID = "team-selection-dropdown"
20
- TEAM_SELECTION_DATE_ID = "team-selection-date"
21
-
22
- VALIDATION_PERIOD_MENU_ID = "validation-period-dropdown"
23
- VALIDATION_PERIOD_SELECTED_ID = "validation-period-selected"
24
-
25
- # 'type' part of composite IDs
26
- TABLE_TYPE_TABLE = "ositah-table"
27
- TABLE_TYPE_DUMMY_STORE = "ositah-table-dummy-store"
28
-
29
- LOAD_PROGRESS_BAR_ID = "validation-progress-bar"
30
- LOAD_PROGRESS_BAR_INTERVAL_DURATION = 500 # Milliseconds
31
- LOAD_PROGRESS_BAR_MAX_DURATION = 15 # Seconds
32
-
33
- NEW_PAGE_INDICATOR_ID = "page-initial-load"
34
-
35
-
36
- def team_list_dropdown(menu_id=TEAM_SELECTION_MENU_ID):
37
- """
38
- Build a dropdown menu from the teams associated with the current user session.
39
-
40
- :param menu_id: menu ID for the created dropdown menu
41
- :return: dcc.Dropdown object or a jumbotron in case of errors
42
- """
43
-
44
- global_params = GlobalParams()
45
- try:
46
- session_data = global_params.session_data
47
- if session_data.agent_teams and len(session_data.agent_teams) > 1:
48
- default_team = ""
49
- else:
50
- default_team = session_data.agent_teams[0]
51
-
52
- except SessionDataMissing:
53
- return no_session_id_jumbotron()
54
-
55
- periods = session_data.declaration_periods
56
- if periods is None:
57
- periods = get_declaration_periods()
58
- session_data.declaration_periods = periods
59
- default_period = get_default_period_date(
60
- periods, global_params.declaration_options["default_date"]
61
- )
62
-
63
- return dbc.Row(
64
- [
65
- dbc.Col(
66
- [
67
- dbc.Label(html.B("Equipe")),
68
- dcc.Dropdown(
69
- id=menu_id,
70
- options=[
71
- {"label": team, "value": team} for team in session_data.agent_teams
72
- ],
73
- loading_state={"is_loading": True},
74
- value=default_team,
75
- placeholder="Sélectionner une équipe",
76
- ),
77
- ],
78
- width=6,
79
- class_name="team_list_dropdown",
80
- ),
81
- dbc.Col(
82
- [
83
- dbc.Label(html.B("Période")),
84
- dcc.Dropdown(
85
- id=VALIDATION_PERIOD_MENU_ID,
86
- options=[
87
- {
88
- "label": period.label,
89
- "value": period.start_date,
90
- }
91
- for period in periods
92
- ],
93
- value=default_period,
94
- placeholder="Sélectionner une période",
95
- ),
96
- ],
97
- width={"size": 4, "offset": 1},
98
- class_name="team_list_dropdown",
99
- ),
100
- ]
101
- )
102
-
103
-
104
- def build_accordion(button_number, button_content, hidden_text, tooltip=None, class_list=""):
105
- """
106
- Function to build an accordion associated with the component passed in button_contents.
107
-
108
- :param button_number: the button number, must be unique for each button
109
- :param button_content: what will be put inside the button
110
- :param hidden_text: text to be displayed when the accordion is open
111
- :param tooltip: text to be displayed as an optional tooltip
112
- :param class_list: optional class list to add to the dbc.Card
113
- :return: the accordion element
114
-
115
- """
116
-
117
- return html.Div(
118
- [
119
- dbc.Accordion(
120
- dbc.AccordionItem(
121
- hidden_text,
122
- title=button_content,
123
- class_name=class_list,
124
- ),
125
- id={"type": "accordion_toggle", "id": button_number},
126
- start_collapsed=True,
127
- ),
128
- dbc.Tooltip(
129
- tooltip,
130
- target={"type": "accordion_toggle", "id": button_number},
131
- placement="left",
132
- key=f"acccordion_tooltip_{button_number}",
133
- ),
134
- ]
135
- )
136
-
137
-
138
- def create_progress_bar(
139
- team: str = None,
140
- duration: int = LOAD_PROGRESS_BAR_MAX_DURATION,
141
- interval: float = LOAD_PROGRESS_BAR_INTERVAL_DURATION,
142
- ):
143
- """
144
- Create a Div with a progress bar
145
-
146
- :param team: currently selected team
147
- :param duration: progress bar duration (seconds)
148
- :param interval: interval duration (millisecondes
149
- :return: Div
150
- """
151
-
152
- max_intervals = int(duration * 1000 / interval)
153
-
154
- return html.Div(
155
- [
156
- (
157
- html.Div(f"Chargement des données de l'équipe {team} en cours...")
158
- if team
159
- else html.Div()
160
- ),
161
- dcc.Interval(
162
- id="progress-interval",
163
- max_intervals=max_intervals,
164
- n_intervals=0,
165
- interval=LOAD_PROGRESS_BAR_INTERVAL_DURATION,
166
- ),
167
- dbc.Progress(id="progress", striped=True),
168
- dcc.Store(id="progress-bar-max-intervals", data=max_intervals),
169
- ],
170
- id=LOAD_PROGRESS_BAR_ID,
171
- )
172
-
173
-
174
- @app.callback(
175
- [Output("progress", "value"), Output("progress", "label")],
176
- Input("progress-interval", "n_intervals"),
177
- State("progress-bar-max-intervals", "data"),
178
- prevent_initial_call=True,
179
- )
180
- def update_progress_bar(n, max_intervals):
181
- """
182
- Update the progress bar.
183
-
184
- :param n: number of intervals since the beginning
185
- :param max_intervals: maximum number of intervals (duration)
186
- :return:
187
- """
188
-
189
- progress = int(round(n * 100 / max_intervals))
190
- # only add text after 5% progress to ensure text isn't squashed too much
191
- return progress, f"{progress} %" if progress >= 5 else ""
192
-
193
-
194
- @app.callback(
195
- Output({"type": "accordion_collapse", "id": MATCH}, "is_open"),
196
- Input({"type": "accordion_toggle", "id": MATCH}, "n_clicks"),
197
- State({"type": "accordion_collapse", "id": MATCH}, "is_open"),
198
- prevent_initial_call=True,
199
- )
200
- def toggle_agent_accordion(n_clicks, is_open) -> bool:
201
- """
202
- Callback function for the agent accordion.
203
-
204
- :param value: number of times the link was clicked
205
- :param is_open: whether the accordion is open or closed
206
- :return: List of n Output
207
- """
208
-
209
- if n_clicks:
210
- return not is_open
211
-
212
-
213
- @app.callback(
214
- Output(TEAM_SELECTION_MENU_ID, "value"),
215
- Output(TEAM_SELECTED_VALUE_ID, "data"),
216
- Output(TEAM_SELECTION_DATE_ID, "data"),
217
- Output(VALIDATION_PERIOD_MENU_ID, "value"),
218
- Output(VALIDATION_PERIOD_SELECTED_ID, "data"),
219
- Output(TEAM_SELECTION_MENU_ID, "loading_state"),
220
- Input(TEAM_SELECTION_MENU_ID, "value"),
221
- Input(VALIDATION_PERIOD_MENU_ID, "value"),
222
- State(TEAM_SELECTED_VALUE_ID, "data"),
223
- State(DATA_SELECTED_SOURCE_ID, "children"),
224
- State(TEAM_SELECTION_DATE_ID, "data"),
225
- State(VALIDATION_PERIOD_SELECTED_ID, "data"),
226
- State(TEAM_SELECTION_MENU_ID, "loading_state"),
227
- )
228
- def save_team_and_period(
229
- selected_team,
230
- selected_period,
231
- previous_team,
232
- selected_source,
233
- team_selection_date,
234
- previous_period,
235
- loading_state,
236
- ):
237
- """
238
- Function to save the selected team and period in a dcc.Store. This will trigger other callbacks.
239
- Also clear the data cache. The value of the team selection dropdown is written back: it
240
- allows to update the initial value if one was saved into the dcc.Store.
241
-
242
- :param selected_team: team dropdown menu value
243
- :param selected_period: period dropdoan menu value
244
- :param previous_team: previously selected team
245
- :param selected_source: currently selected data source
246
- :param team_selection_date: date of the last team selection
247
- :param previous_period: previously selected period
248
- :param loading_state: loading state of the team selection menu
249
- :return: expected Output values
250
- """
251
-
252
- # Handle the initial display of the selection menu: if it was already displayed in another
253
- # page and a value was previously selected (saved in TEAM_SELECTED_VALUE_ID), use it as the
254
- # initial value (set it to this value through the callback). Do the same for the validation
255
- # period
256
-
257
- global_params = GlobalParams()
258
- try:
259
- session_data = global_params.session_data
260
- except SessionDataMissing:
261
- return no_session_id_jumbotron(), "", ""
262
-
263
- if "is_loading" in loading_state and loading_state["is_loading"]:
264
- team = previous_team
265
- # If previous_period is empty, means that it should be initialised with the pulldown
266
- # menu default value (in selected_period the first time the callback is executed)
267
- if previous_period == "":
268
- period = selected_period
269
- else:
270
- period = previous_period
271
- else:
272
- team = selected_team
273
- period = selected_period
274
-
275
- # Cache must be changed if the team has been changed or if the selected source doesn't match
276
- # the cached one
277
- if (
278
- team != previous_team
279
- or period != previous_period
280
- or session_data.project_declarations_source is None
281
- or selected_source != session_data.project_declarations_source
282
- ):
283
- clear_cached_data()
284
- selection_date = f"{datetime.now()}"
285
- else:
286
- selection_date = team_selection_date
287
-
288
- return team, team, selection_date, period, period, {"is_laoding": False}
289
-
290
-
291
- # Client-side callback used to mark a table as sortable. To be marked sortable, a table must have
292
- # an ID matching the ID type attribute TABLE_TYPE_TABLE and create a dcc.Store associated with
293
- # an ID type attribute TABLE_TYPE_DUMMY_STORE
294
- app.clientside_callback(
295
- """
296
- function make_table_sortable(dummy, table_id) {
297
- if (!(typeof table_id === 'string' || table_id instanceof String)) {
298
- table_id = JSON.stringify(table_id, Object.keys(table_id).sort());
299
- };
300
- /*alert('Mark sortable table with ID='+table_id);*/
301
- const tableObject = document.getElementById(table_id);
302
- sorttable.makeSortable(tableObject);
303
- return 0;
304
- }
305
- """,
306
- Output({"type": TABLE_TYPE_DUMMY_STORE, "id": MATCH}, "data"),
307
- Input({"type": TABLE_TYPE_TABLE, "id": MATCH}, "children"),
308
- State({"type": TABLE_TYPE_TABLE, "id": MATCH}, "id"),
309
- prevent_initial_call=True,
310
- )
311
-
312
-
313
- # Emulate former Jumbotron
314
- def ositah_jumbotron(title: str, main_text: str, details: str = None, title_class: str = None):
315
- """
316
- Emulate Jumbotron component available in Bootstrap v4
317
-
318
- :param title: Jumbotron title
319
- :param main_text: main text
320
- :param details: optional additional text
321
- :return: html.div()
322
- """
323
-
324
- return html.Div(
325
- dbc.Container(
326
- [
327
- html.H1(title, className=f"display-3 {title_class if (title_class) else ''}"),
328
- html.P(
329
- main_text,
330
- className="lead",
331
- ),
332
- html.Hr(className="my-2"),
333
- html.P(details),
334
- ],
335
- fluid=True,
336
- className="py-3",
337
- ),
338
- className="p-3 bg-light rounded-3",
339
- )
1
+ # Module containing helper functions to build/manage the menus and graphic objects
2
+
3
+ from datetime import datetime
4
+
5
+ import dash_bootstrap_components as dbc
6
+ from dash import dcc, html
7
+ from dash.dependencies import MATCH, Input, Output, State
8
+
9
+ from ositah.app import app
10
+ from ositah.utils.cache import clear_cached_data
11
+ from ositah.utils.exceptions import SessionDataMissing
12
+ from ositah.utils.period import get_declaration_periods, get_default_period_date
13
+ from ositah.utils.utils import GlobalParams, no_session_id_jumbotron
14
+
15
+ DATA_SELECTED_SOURCE_ID = "project-declaration-source"
16
+ DATA_SELECTION_SOURCE_ID = "project-declaration-source-button"
17
+
18
+ TEAM_SELECTED_VALUE_ID = "team-selected"
19
+ TEAM_SELECTION_MENU_ID = "team-selection-dropdown"
20
+ TEAM_SELECTION_DATE_ID = "team-selection-date"
21
+
22
+ VALIDATION_PERIOD_MENU_ID = "validation-period-dropdown"
23
+ VALIDATION_PERIOD_SELECTED_ID = "validation-period-selected"
24
+
25
+ # 'type' part of composite IDs
26
+ TABLE_TYPE_TABLE = "ositah-table"
27
+ TABLE_TYPE_DUMMY_STORE = "ositah-table-dummy-store"
28
+
29
+ LOAD_PROGRESS_BAR_ID = "validation-progress-bar"
30
+ LOAD_PROGRESS_BAR_INTERVAL_DURATION = 500 # Milliseconds
31
+ LOAD_PROGRESS_BAR_MAX_DURATION = 15 # Seconds
32
+
33
+ NEW_PAGE_INDICATOR_ID = "page-initial-load"
34
+
35
+
36
+ def team_list_dropdown(menu_id=TEAM_SELECTION_MENU_ID):
37
+ """
38
+ Build a dropdown menu from the teams associated with the current user session.
39
+
40
+ :param menu_id: menu ID for the created dropdown menu
41
+ :return: dcc.Dropdown object or a jumbotron in case of errors
42
+ """
43
+
44
+ global_params = GlobalParams()
45
+ try:
46
+ session_data = global_params.session_data
47
+ if session_data.agent_teams and len(session_data.agent_teams) > 1:
48
+ default_team = ""
49
+ else:
50
+ default_team = session_data.agent_teams[0]
51
+
52
+ except SessionDataMissing:
53
+ return no_session_id_jumbotron()
54
+
55
+ periods = session_data.declaration_periods
56
+ if periods is None:
57
+ periods = get_declaration_periods()
58
+ session_data.declaration_periods = periods
59
+ default_period = get_default_period_date(
60
+ periods, global_params.declaration_options["default_date"]
61
+ )
62
+
63
+ return dbc.Row(
64
+ [
65
+ dbc.Col(
66
+ [
67
+ dbc.Label(html.B("Equipe")),
68
+ dcc.Dropdown(
69
+ id=menu_id,
70
+ options=[
71
+ {"label": team, "value": team} for team in session_data.agent_teams
72
+ ],
73
+ loading_state={"is_loading": True},
74
+ value=default_team,
75
+ placeholder="Sélectionner une équipe",
76
+ ),
77
+ ],
78
+ width=6,
79
+ class_name="team_list_dropdown",
80
+ ),
81
+ dbc.Col(
82
+ [
83
+ dbc.Label(html.B("Période")),
84
+ dcc.Dropdown(
85
+ id=VALIDATION_PERIOD_MENU_ID,
86
+ options=[
87
+ {
88
+ "label": period.label,
89
+ "value": period.start_date,
90
+ }
91
+ for period in periods
92
+ ],
93
+ value=default_period,
94
+ placeholder="Sélectionner une période",
95
+ ),
96
+ ],
97
+ width={"size": 4, "offset": 1},
98
+ class_name="team_list_dropdown",
99
+ ),
100
+ ]
101
+ )
102
+
103
+
104
+ def build_accordion(button_number, button_content, hidden_text, tooltip=None, class_list=""):
105
+ """
106
+ Function to build an accordion associated with the component passed in button_contents.
107
+
108
+ :param button_number: the button number, must be unique for each button
109
+ :param button_content: what will be put inside the button
110
+ :param hidden_text: text to be displayed when the accordion is open
111
+ :param tooltip: text to be displayed as an optional tooltip
112
+ :param class_list: optional class list to add to the dbc.Card
113
+ :return: the accordion element
114
+
115
+ """
116
+
117
+ return html.Div(
118
+ [
119
+ dbc.Accordion(
120
+ dbc.AccordionItem(
121
+ hidden_text,
122
+ title=button_content,
123
+ class_name=class_list,
124
+ ),
125
+ id={"type": "accordion_toggle", "id": button_number},
126
+ start_collapsed=True,
127
+ ),
128
+ dbc.Tooltip(
129
+ tooltip,
130
+ target={"type": "accordion_toggle", "id": button_number},
131
+ placement="left",
132
+ key=f"acccordion_tooltip_{button_number}",
133
+ ),
134
+ ]
135
+ )
136
+
137
+
138
+ def create_progress_bar(
139
+ team: str = None,
140
+ duration: int = LOAD_PROGRESS_BAR_MAX_DURATION,
141
+ interval: float = LOAD_PROGRESS_BAR_INTERVAL_DURATION,
142
+ ):
143
+ """
144
+ Create a Div with a progress bar
145
+
146
+ :param team: currently selected team
147
+ :param duration: progress bar duration (seconds)
148
+ :param interval: interval duration (millisecondes
149
+ :return: Div
150
+ """
151
+
152
+ max_intervals = int(duration * 1000 / interval)
153
+
154
+ return html.Div(
155
+ [
156
+ (
157
+ html.Div(f"Chargement des données de l'équipe {team} en cours...")
158
+ if team
159
+ else html.Div()
160
+ ),
161
+ dcc.Interval(
162
+ id="progress-interval",
163
+ max_intervals=max_intervals,
164
+ n_intervals=0,
165
+ interval=LOAD_PROGRESS_BAR_INTERVAL_DURATION,
166
+ ),
167
+ dbc.Progress(id="progress", striped=True),
168
+ dcc.Store(id="progress-bar-max-intervals", data=max_intervals),
169
+ ],
170
+ id=LOAD_PROGRESS_BAR_ID,
171
+ )
172
+
173
+
174
+ @app.callback(
175
+ [Output("progress", "value"), Output("progress", "label")],
176
+ Input("progress-interval", "n_intervals"),
177
+ State("progress-bar-max-intervals", "data"),
178
+ prevent_initial_call=True,
179
+ )
180
+ def update_progress_bar(n, max_intervals):
181
+ """
182
+ Update the progress bar.
183
+
184
+ :param n: number of intervals since the beginning
185
+ :param max_intervals: maximum number of intervals (duration)
186
+ :return:
187
+ """
188
+
189
+ progress = int(round(n * 100 / max_intervals))
190
+ # only add text after 5% progress to ensure text isn't squashed too much
191
+ return progress, f"{progress} %" if progress >= 5 else ""
192
+
193
+
194
+ @app.callback(
195
+ Output({"type": "accordion_collapse", "id": MATCH}, "is_open"),
196
+ Input({"type": "accordion_toggle", "id": MATCH}, "n_clicks"),
197
+ State({"type": "accordion_collapse", "id": MATCH}, "is_open"),
198
+ prevent_initial_call=True,
199
+ )
200
+ def toggle_agent_accordion(n_clicks, is_open) -> bool:
201
+ """
202
+ Callback function for the agent accordion.
203
+
204
+ :param value: number of times the link was clicked
205
+ :param is_open: whether the accordion is open or closed
206
+ :return: List of n Output
207
+ """
208
+
209
+ if n_clicks:
210
+ return not is_open
211
+
212
+
213
+ @app.callback(
214
+ Output(TEAM_SELECTION_MENU_ID, "value"),
215
+ Output(TEAM_SELECTED_VALUE_ID, "data"),
216
+ Output(TEAM_SELECTION_DATE_ID, "data"),
217
+ Output(VALIDATION_PERIOD_MENU_ID, "value"),
218
+ Output(VALIDATION_PERIOD_SELECTED_ID, "data"),
219
+ Output(TEAM_SELECTION_MENU_ID, "loading_state"),
220
+ Input(TEAM_SELECTION_MENU_ID, "value"),
221
+ Input(VALIDATION_PERIOD_MENU_ID, "value"),
222
+ State(TEAM_SELECTED_VALUE_ID, "data"),
223
+ State(DATA_SELECTED_SOURCE_ID, "children"),
224
+ State(TEAM_SELECTION_DATE_ID, "data"),
225
+ State(VALIDATION_PERIOD_SELECTED_ID, "data"),
226
+ State(TEAM_SELECTION_MENU_ID, "loading_state"),
227
+ )
228
+ def save_team_and_period(
229
+ selected_team,
230
+ selected_period,
231
+ previous_team,
232
+ selected_source,
233
+ team_selection_date,
234
+ previous_period,
235
+ loading_state,
236
+ ):
237
+ """
238
+ Function to save the selected team and period in a dcc.Store. This will trigger other callbacks.
239
+ Also clear the data cache. The value of the team selection dropdown is written back: it
240
+ allows to update the initial value if one was saved into the dcc.Store.
241
+
242
+ :param selected_team: team dropdown menu value
243
+ :param selected_period: period dropdoan menu value
244
+ :param previous_team: previously selected team
245
+ :param selected_source: currently selected data source
246
+ :param team_selection_date: date of the last team selection
247
+ :param previous_period: previously selected period
248
+ :param loading_state: loading state of the team selection menu
249
+ :return: expected Output values
250
+ """
251
+
252
+ # Handle the initial display of the selection menu: if it was already displayed in another
253
+ # page and a value was previously selected (saved in TEAM_SELECTED_VALUE_ID), use it as the
254
+ # initial value (set it to this value through the callback). Do the same for the validation
255
+ # period
256
+
257
+ global_params = GlobalParams()
258
+ try:
259
+ session_data = global_params.session_data
260
+ except SessionDataMissing:
261
+ return no_session_id_jumbotron(), "", ""
262
+
263
+ if "is_loading" in loading_state and loading_state["is_loading"]:
264
+ team = previous_team
265
+ # If previous_period is empty, means that it should be initialised with the pulldown
266
+ # menu default value (in selected_period the first time the callback is executed)
267
+ if previous_period == "":
268
+ period = selected_period
269
+ else:
270
+ period = previous_period
271
+ else:
272
+ team = selected_team
273
+ period = selected_period
274
+
275
+ # Cache must be changed if the team has been changed or if the selected source doesn't match
276
+ # the cached one
277
+ if (
278
+ team != previous_team
279
+ or period != previous_period
280
+ or session_data.project_declarations_source is None
281
+ or selected_source != session_data.project_declarations_source
282
+ ):
283
+ clear_cached_data()
284
+ selection_date = f"{datetime.now()}"
285
+ else:
286
+ selection_date = team_selection_date
287
+
288
+ return team, team, selection_date, period, period, {"is_laoding": False}
289
+
290
+
291
+ # Client-side callback used to mark a table as sortable. To be marked sortable, a table must have
292
+ # an ID matching the ID type attribute TABLE_TYPE_TABLE and create a dcc.Store associated with
293
+ # an ID type attribute TABLE_TYPE_DUMMY_STORE
294
+ app.clientside_callback(
295
+ """
296
+ function make_table_sortable(dummy, table_id) {
297
+ if (!(typeof table_id === 'string' || table_id instanceof String)) {
298
+ table_id = JSON.stringify(table_id, Object.keys(table_id).sort());
299
+ };
300
+ /*alert('Mark sortable table with ID='+table_id);*/
301
+ const tableObject = document.getElementById(table_id);
302
+ sorttable.makeSortable(tableObject);
303
+ return 0;
304
+ }
305
+ """,
306
+ Output({"type": TABLE_TYPE_DUMMY_STORE, "id": MATCH}, "data"),
307
+ Input({"type": TABLE_TYPE_TABLE, "id": MATCH}, "children"),
308
+ State({"type": TABLE_TYPE_TABLE, "id": MATCH}, "id"),
309
+ prevent_initial_call=True,
310
+ )
311
+
312
+
313
+ # Emulate former Jumbotron
314
+ def ositah_jumbotron(title: str, main_text: str, details: str = None, title_class: str = None):
315
+ """
316
+ Emulate Jumbotron component available in Bootstrap v4
317
+
318
+ :param title: Jumbotron title
319
+ :param main_text: main text
320
+ :param details: optional additional text
321
+ :return: html.div()
322
+ """
323
+
324
+ return html.Div(
325
+ dbc.Container(
326
+ [
327
+ html.H1(title, className=f"display-3 {title_class if (title_class) else ''}"),
328
+ html.P(
329
+ main_text,
330
+ className="lead",
331
+ ),
332
+ html.Hr(className="my-2"),
333
+ html.P(details),
334
+ ],
335
+ fluid=True,
336
+ className="py-3",
337
+ ),
338
+ className="p-3 bg-light rounded-3",
339
+ )