ositah 24.4.dev1__py3-none-any.whl → 24.7.dev1__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.
Files changed (47) hide show
  1. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/METADATA +1 -1
  2. ositah-24.7.dev1.dist-info/RECORD +6 -0
  3. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/WHEEL +1 -1
  4. ositah/__init__.py +0 -0
  5. ositah/app.py +0 -17
  6. ositah/apps/__init__.py +0 -0
  7. ositah/apps/analysis.py +0 -774
  8. ositah/apps/configuration/__init__.py +0 -0
  9. ositah/apps/configuration/callbacks.py +0 -917
  10. ositah/apps/configuration/main.py +0 -542
  11. ositah/apps/configuration/parameters.py +0 -74
  12. ositah/apps/configuration/tools.py +0 -112
  13. ositah/apps/export.py +0 -1172
  14. ositah/apps/validation/__init__.py +0 -0
  15. ositah/apps/validation/callbacks.py +0 -240
  16. ositah/apps/validation/main.py +0 -89
  17. ositah/apps/validation/parameters.py +0 -25
  18. ositah/apps/validation/tables.py +0 -654
  19. ositah/apps/validation/tools.py +0 -533
  20. ositah/assets/arrow_down_up.svg +0 -4
  21. ositah/assets/ositah.css +0 -54
  22. ositah/assets/sort_ascending.svg +0 -5
  23. ositah/assets/sort_descending.svg +0 -6
  24. ositah/assets/sorttable.js +0 -499
  25. ositah/main.py +0 -449
  26. ositah/ositah.example.cfg +0 -215
  27. ositah/static/style.css +0 -54
  28. ositah/templates/base.html +0 -22
  29. ositah/templates/bootstrap_login.html +0 -38
  30. ositah/templates/login_form.html +0 -27
  31. ositah/utils/__init__.py +0 -0
  32. ositah/utils/agents.py +0 -117
  33. ositah/utils/authentication.py +0 -287
  34. ositah/utils/cache.py +0 -19
  35. ositah/utils/core.py +0 -13
  36. ositah/utils/exceptions.py +0 -64
  37. ositah/utils/hito_db.py +0 -51
  38. ositah/utils/hito_db_model.py +0 -245
  39. ositah/utils/menus.py +0 -339
  40. ositah/utils/period.py +0 -135
  41. ositah/utils/projects.py +0 -1175
  42. ositah/utils/teams.py +0 -42
  43. ositah/utils/utils.py +0 -459
  44. ositah-24.4.dev1.dist-info/RECORD +0 -46
  45. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/LICENSE +0 -0
  46. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/entry_points.txt +0 -0
  47. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/top_level.txt +0 -0
ositah/utils/hito_db.py DELETED
@@ -1,51 +0,0 @@
1
- """
2
- Module defining a few helper functions to access the DB through SQLAlchemy
3
- """
4
-
5
- import re
6
- from uuid import uuid4
7
-
8
- from flask import g
9
- from sqlalchemy import event
10
- from sqlalchemy.engine import Engine
11
- from sqlalchemy.orm import scoped_session, sessionmaker
12
-
13
- from ositah.app import app
14
- from ositah.utils.utils import GlobalParams
15
-
16
-
17
- def new_uuid():
18
- """
19
- Wrapper over uuid4() to return the UUID as a string. Allow using a callable in the column
20
- default attribute (else it is interpreted as constant and the UUID is the same for every row).
21
-
22
- :return: UUID string
23
- """
24
- return str(uuid4())
25
-
26
-
27
- @event.listens_for(Engine, "connect")
28
- def set_sqlite_pragma(dbapi_connection, connection_record):
29
- if re.match("sqlite:", app.server.config["SQLALCHEMY_DATABASE_URI"]):
30
- cursor = dbapi_connection.cursor()
31
- cursor.execute("PRAGMA foreign_keys=ON")
32
- cursor.close()
33
-
34
-
35
- def get_db(init_session: bool = True) -> None:
36
- """
37
- Return the DB handler if already initialized, else create it and initialize a DB session
38
- in the application context.
39
-
40
- :param init_session: if True initialize a session if necessary
41
- :return: DB handler
42
- """
43
- global_params = GlobalParams()
44
-
45
- with app.server.app_context():
46
- if "db" not in g:
47
- g.db = global_params.hito_db
48
- if init_session and not g.db.session.bind:
49
- g.db.session = scoped_session(sessionmaker(g.db.engine))
50
-
51
- return g.db
@@ -1,245 +0,0 @@
1
- """
2
- Module defining the DB SQLAlchemy model.
3
- This is done by using the class provided by Flask-sqlalchemy that requires the DB connection
4
- to be initialized before. Thus this module must not be imported before it is done.
5
- """
6
-
7
- from hito_tools.utils import sql_longtext_to_list
8
-
9
- from ositah.utils.hito_db import new_uuid
10
- from ositah.utils.utils import GlobalParams
11
-
12
- global_params = GlobalParams()
13
- db = global_params.hito_db
14
-
15
- team_mgrs = db.Table(
16
- "team_agent",
17
- db.Column("team_id", db.String, db.ForeignKey("team.id")),
18
- db.Column("agent_id", db.String, db.ForeignKey("agent.id")),
19
- )
20
-
21
- children_team_mgrs = db.Table(
22
- "agent_team_parent_responsable",
23
- db.Column("team_id", db.String, db.ForeignKey("team.id")),
24
- db.Column("agent_id", db.String, db.ForeignKey("agent.id")),
25
- )
26
-
27
- activity_teams = db.Table(
28
- "activite_team",
29
- db.Column("activite_id", db.String, db.ForeignKey("activite.id")),
30
- db.Column("team_id", db.String, db.ForeignKey("team.id")),
31
- )
32
-
33
- project_teams = db.Table(
34
- "projet_team",
35
- db.Column("projet_id", db.String, db.ForeignKey("projet.id")),
36
- db.Column("team_id", db.String, db.ForeignKey("team.id")),
37
- )
38
-
39
-
40
- class Agent(db.Model):
41
- id = db.Column(db.String, primary_key=True)
42
- nom = db.Column(db.String)
43
- prenom = db.Column(db.String)
44
- email = db.Column(db.String)
45
- email_auth = db.Column(db.String)
46
- roles = db.Column(db.Text)
47
- statut = db.Column(db.String)
48
- team_id = db.Column(db.String)
49
-
50
- # Teams an agent is directly managing
51
- teams = db.relationship("Team", secondary=team_mgrs, backref=db.backref("managers"))
52
-
53
- # Children teams of the teams an agent is managing
54
- children_teams = db.relationship(
55
- "Team", secondary=children_team_mgrs, backref=db.backref("children_managers")
56
- )
57
-
58
- def __repr__(self):
59
- if self.email_auth is None or len(self.email_auth) == 0:
60
- connexion_email = self.email
61
- else:
62
- connexion_email = self.email_auth
63
- return (
64
- f"<Agent(nom={self.nom}, email={self.email}, roles={sql_longtext_to_list(self.roles)}"
65
- f" (email connexion={connexion_email}))>"
66
- )
67
-
68
-
69
- class Carriere(db.Model):
70
- id = db.Column(db.String, primary_key=True)
71
- date_debut = db.Column(db.DateTime)
72
- date_fin = db.Column(db.DateTime)
73
- type = db.Column(db.String)
74
- agent_id = db.Column(db.String(36), db.ForeignKey("agent.id"), nullable=False)
75
-
76
- def __repr__(self):
77
- date = ""
78
- if self.date_debut:
79
- date += f" date_debut={self.date_debut}"
80
- if self.date_fin:
81
- date += f" date_debut={self.date_fin}"
82
- return f"<Carriere(id={self.id}, type={self.type}{date})>"
83
-
84
-
85
- class Team(db.Model):
86
- id = db.Column(db.String, primary_key=True)
87
- nom = db.Column(db.String)
88
- description = db.Column(db.String)
89
- parent_team_id = db.Column(db.String)
90
-
91
- def __repr__(self):
92
- if not self.description or len(self.description) == 0:
93
- description = self.nom
94
- else:
95
- description = self.description
96
- return f"<Team(nom={self.nom}, description={description})>"
97
-
98
-
99
- class Referentiel(db.Model):
100
- id = db.Column(db.Integer, primary_key=True)
101
- libelle = db.Column(db.String)
102
- ordre = db.Column(db.Integer)
103
- object_class = db.Column("class", db.String)
104
-
105
- def __repr__(self):
106
- return f"Referentiel<id={self.id}, libelle={self.libelle}, class={self.object_class}>"
107
-
108
-
109
- class Activite(db.Model):
110
- id = db.Column(db.String, primary_key=True, default=new_uuid)
111
- libelle = db.Column(db.String)
112
- description = db.Column(db.String)
113
- ordre = db.Column(db.Integer)
114
-
115
- activite_nsip_referentiel_id = db.Column(db.String, db.ForeignKey("referentiel.id"))
116
- projet_nsip_referentiel_id = db.Column(db.String, db.ForeignKey("referentiel.id"))
117
-
118
- referentiel_nsip_project = db.relationship(
119
- "Referentiel",
120
- backref=db.backref("activite_project", lazy=True),
121
- foreign_keys=[projet_nsip_referentiel_id],
122
- )
123
- referentiel_nsip_activity = db.relationship(
124
- "Referentiel",
125
- backref=db.backref("activite_activity", lazy=True),
126
- foreign_keys=[activite_nsip_referentiel_id],
127
- )
128
-
129
- # Teams associated with an activity
130
- teams = db.relationship(
131
- "Team",
132
- secondary=activity_teams,
133
- lazy="subquery",
134
- backref=db.backref("activity_teams", lazy=True),
135
- )
136
-
137
- def __repr__(self):
138
- return f"<Activite(libelle={self.libelle}, id={id})>"
139
-
140
-
141
- class Projet(db.Model):
142
- id = db.Column(db.String, primary_key=True, default=new_uuid)
143
- libelle = db.Column(db.String)
144
- description = db.Column(db.String)
145
- ordre = db.Column(db.Integer)
146
-
147
- activite_nsip_referentiel_id = db.Column(db.String, db.ForeignKey("referentiel.id"))
148
- projet_nsip_referentiel_id = db.Column(db.String, db.ForeignKey("referentiel.id"))
149
-
150
- referentiel_nsip_project = db.relationship(
151
- "Referentiel",
152
- backref=db.backref("projet_project", lazy=True),
153
- foreign_keys=[projet_nsip_referentiel_id],
154
- )
155
- referentiel_nsip_activity = db.relationship(
156
- "Referentiel",
157
- backref=db.backref("projet_activity", lazy=True),
158
- foreign_keys=[activite_nsip_referentiel_id],
159
- )
160
-
161
- # Teams associated with a project
162
- teams = db.relationship(
163
- "Team",
164
- secondary=project_teams,
165
- lazy="subquery",
166
- backref=db.backref("project_teams", lazy=True),
167
- )
168
-
169
- def __repr__(self):
170
- return f"<Projet(libelle={self.libelle}, id={id})>"
171
-
172
-
173
- class ActiviteDetail(db.Model):
174
- id = db.Column(db.Integer, primary_key=True)
175
- activite_id = db.Column(db.String, db.ForeignKey("activite.id"))
176
- projet_id = db.Column(db.String, db.ForeignKey("projet.id"))
177
- agent_id = db.Column(db.String, db.ForeignKey("agent.id"))
178
- team_id = db.Column(db.String, db.ForeignKey("team.id"))
179
- type = db.Column(db.Integer)
180
- date = db.Column(db.DateTime)
181
- pourcent = db.Column(db.Float)
182
- nbHeures = db.Column(db.Float)
183
-
184
- project = db.relationship("Projet", backref=db.backref("activity_detail", lazy=True))
185
- agent = db.relationship(
186
- "Agent",
187
- backref=db.backref("activity_detail", lazy=True),
188
- foreign_keys=[agent_id],
189
- )
190
- team = db.relationship(
191
- "Team", backref=db.backref("activity_detail", lazy=True), foreign_keys=[team_id]
192
- )
193
-
194
- def __repr__(self):
195
- return (
196
- f"<ActiviteDetail(id={self.id}, date={self.date}, pourcent={self.pourcent},"
197
- f" nbHeures={self.nbHeures})>"
198
- )
199
-
200
-
201
- class OSITAHSession(db.Model):
202
- id = db.Column(db.String(36), primary_key=True)
203
- email = db.Column(db.String(255), nullable=False)
204
- last_use = db.Column(db.DateTime, nullable=False)
205
-
206
-
207
- class OSITAHValidation(db.Model):
208
- id = db.Column(db.String(36), primary_key=True, default=new_uuid)
209
- validated = db.Column(db.Boolean, nullable=False)
210
- timestamp = db.Column(db.DateTime, nullable=False)
211
- initial_timestamp = db.Column(db.DateTime, nullable=True)
212
- agent_id = db.Column(db.String(36), db.ForeignKey("agent.id"), nullable=False)
213
- period_id = db.Column(
214
- db.String(36), db.ForeignKey("ositah_validation_period.id"), nullable=False
215
- )
216
-
217
- agent = db.relationship("Agent", backref=db.backref("validation", lazy=True))
218
- period = db.relationship(
219
- "OSITAHValidationPeriod", backref=db.backref("validation_data", lazy=True)
220
- )
221
-
222
-
223
- class OSITAHValidationPeriod(db.Model):
224
- id = db.Column(db.String(36), primary_key=True, default=new_uuid)
225
- name = db.Column(db.String(255), unique=True, nullable=False)
226
- start_date = db.Column(db.DateTime, nullable=False)
227
- end_date = db.Column(db.DateTime, nullable=False)
228
- validation_date = db.Column(db.DateTime, nullable=False)
229
-
230
-
231
- class OSITAHProjectDeclaration(db.Model):
232
- id = db.Column(db.String(36), primary_key=True, default=new_uuid)
233
- projet = db.Column(db.String(255), nullable=False)
234
- masterprojet = db.Column(db.String(255), nullable=False)
235
- category = db.Column(db.String(255), nullable=False)
236
- hours = db.Column(db.Float, nullable=False)
237
- # quotite is the fraction of FTE declared by the agent (1=full time)
238
- quotite = db.Column(db.Float, nullable=False)
239
- validation_id = db.Column(db.String(36), db.ForeignKey("ositah_validation.id"), nullable=False)
240
- # Allow foreign key to Hito projet table to be null so that a Hito project can be delete
241
- # without deleting the declaration
242
- hito_project_id = db.Column(db.String(36), db.ForeignKey("projet.id"), nullable=True)
243
-
244
- validation = db.relationship("OSITAHValidation", backref=db.backref("project", lazy=True))
245
- project = db.relationship("Projet", backref=db.backref("validation_project", lazy=True))
ositah/utils/menus.py DELETED
@@ -1,339 +0,0 @@
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
- )