Skip to content

Commit 230c7f1

Browse files
24.10.2024 ajout affichage passage dans l'interface admin
1 parent 1bb5dec commit 230c7f1

17 files changed

Lines changed: 802 additions & 165 deletions

File tree

flask_app/admin/editions/parcours/__init__.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,46 @@
88
from flask_app.admin.editions.passages.form import NewKeyForm, ChronoLoginForm, ChronoLoginForm, SetPassageForm
99
import secrets
1010
from flask_socketio import join_room, leave_room, emit
11+
from flask_app.admin.editions.passages import get_passage_data
1112

1213
parcours = Blueprint('parcours', __name__, template_folder='templates')
1314

15+
@socketio.on('connect', namespace='/edition/parcours')
16+
def parcours_connect(auth):
17+
if current_user.is_authenticated and auth.get('event_id') and auth.get('edition_id'):
18+
event:Event = Event.query.get(auth['event_id'])
19+
if not event or event.createur != current_user:
20+
return False # connection not allowed
21+
edition = event.editions.filter_by(id=auth['edition_id']).first()
22+
if not edition:
23+
return False # connection not allowed
24+
25+
session['room'] = f'edition-parcours-{event.id}-{edition.id}'
26+
join_room(session['room'], request.sid)
27+
else:
28+
return False # connection not allowed
29+
@socketio.on('disconnect', namespace='/edition/parcours')
30+
def parcours_disconnect():
31+
leave_room(session['room'], request.sid)
32+
del session['room']
33+
34+
@socketio.on('get_parcours_passage', namespace='/edition/parcours')
35+
def get_parcours_passages(parcours):
36+
parcours = Parcours.query.get(parcours)
37+
edition = Edition.query.get(session['room'].split('-')[3])
38+
inscription:list[Inscription] = Inscription.query.filter_by(edition=edition, parcours=parcours).all()
39+
data = []
40+
for coureur in inscription:
41+
passage = coureur.passages.order_by(Passage.time_stamp.desc()).first()
42+
if coureur.has_started():
43+
ic(coureur.has_started(), coureur.has_finish(), coureur.has_all_right())
44+
pass_data = get_passage_data(passage, json=True)
45+
pass_data.update({'started':True, 'finish':coureur.has_finish(), 'all_right':coureur.has_all_right()})
46+
data.append(pass_data)
47+
else:
48+
data.append({'started':False, 'dossard':coureur.dossard, 'name':coureur.inscrit.name})
49+
return data
50+
1451
@set_route(parcours, '/event/<event_name>/editions/<edition_name>/parcours')
1552
@login_required
1653
@admin_required
@@ -19,5 +56,6 @@ def view(event_name, edition_name):
1956
event = Event.query.filter_by(name=event_name).first_or_404()
2057
edition:Edition = event.editions.filter_by(name=edition_name).first_or_404()
2158
parcours = edition.parcours
59+
2260
return render_template('edition_parcours.html', parcours_data=parcours, edition_data = edition, event_data=event, user_data=user, event_modif=True, edition_sidebar=True)
2361

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{% extends 'layout.html' %}
2+
{% block import %}
3+
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
4+
{% endblock %}
25
{% block content %}
36
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
47
{% for parcours in parcours_data %}
@@ -7,11 +10,134 @@
710
</li>
811
{% endfor %}
912
</ul>
13+
<script>
14+
var socket = io('/edition/parcours', {auth: {'edition_id':{{ edition_data.id }}, 'event_id':{{ event_data.id }} } } );
15+
16+
function create_not_started(data, div, node){
17+
let id = data.dossard+'-'+data.time_stamp
18+
let time = new Date(data.time_stamp * 1000)
19+
20+
data.time_stamp = `${time.getDate()}.${time.getMonth()+1} ${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`
21+
let html =
22+
`<div class="card-header">
23+
<p class="mb-0">
24+
${data.dossard} ${ data.name }
25+
</p>
26+
<button class="
27+
</div>`
28+
29+
if (!node){
30+
let node = document.createElement('div')
31+
node.innerHTML = html
32+
33+
node.classList.add('mb-2')
34+
node.classList.add('card')
35+
node.classList.add('bg-info')
36+
37+
if (div.children.length == 0) {
38+
div.appendChild(node)
39+
} else {
40+
div.insertBefore(node, div.children[0])
41+
}
42+
}else{
43+
node.classList.remove('bg-primary')
44+
node.classList.remove('bg-danger')
45+
node.classList.remove('bg-warning')
46+
node.classList.add('bg-info')
47+
node.innerHTML = html
48+
}
49+
}
50+
51+
function create_passage(data, div, node=null) {
52+
let id = data.dossard+'-'+data.time_stamp
53+
let time = new Date(data.time_stamp * 1000)
54+
55+
data.time_stamp = `${time.getDate()}.${time.getMonth()+1} ${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`
56+
let html =
57+
`<div class="card-header">
58+
<p class="mb-0">
59+
${data.dossard} ${ data.name }
60+
<br>
61+
${data.time_stamp}
62+
</p>
63+
<button class="btn" onclick="toggleDetails('${ id }', this)">
64+
<span class="icon" id="icon-${ id }"><i class="fas fa-chevron-right"></i></span>
65+
</button>
66+
</div>
67+
<div id="detail-${ id }" class="collapse">
68+
<div class="card-body">
69+
<table>
70+
<tbody>`
71+
data.parcours.forEach(passage => {
72+
html+=
73+
`<tr>
74+
<th class="text-center pe-1"> <i class="`+(passage.current?'fa-solid fa-circle fa-l':'fa-regular fa-circle fa-xs')+`"></i></th>
75+
<td class="pe-3">${passage.stand.name}</td>
76+
<td class="pe-3">`+
77+
(passage.succes==true?passage.delta:(passage.succes==false?'<i class="fa-solid fa-xmark"></i>':''))+
78+
`</td>
79+
<td>
80+
${ passage.dist !=null?passage.dist+' km':'' }
81+
</td>
82+
</tr>`
83+
});
84+
html+= `</tbody>
85+
</table>
86+
</div>
87+
</div>`
88+
89+
let color = data.finish?data.all_right?'bg-success':'bg-warning':'bg-danger'
90+
if (!node){
91+
let node = document.createElement('div')
92+
node.innerHTML = html
93+
94+
95+
node.classList.add('mb-2')
96+
node.classList.add('card')
97+
node.classList.add(color)
98+
99+
if (div.children.length == 0) {
100+
div.appendChild(node)
101+
} else {
102+
div.insertBefore(node, div.children[0])
103+
}
104+
}else{
105+
node.classList.remove('bg-primary')
106+
node.classList.remove('bg-danger')
107+
node.classList.remove('bg-warning')
108+
node.classList.add(color)
109+
node.innerHTML = html
110+
}
111+
}
112+
113+
function toggleDetails(detailId, button) {
114+
const detailDiv = document.getElementById(`detail-${detailId}`);
115+
const icon = document.getElementById(`icon-${detailId}`);
116+
117+
detailDiv.classList.toggle('show'); // Toggle the 'show' class for smooth transition
118+
icon.innerHTML = detailDiv.classList.contains('show') ?
119+
'<i class="fas fa-chevron-down"></i>' :
120+
'<i class="fas fa-chevron-right"></i>'; // Change icon based on state
121+
}
122+
123+
</script>
10124
<div class="tab-content" id="pills-tabContent">
11125
{% for parcours in parcours_data %}
12-
<div class="tab-pane fade {% if loop.index0==0 %}show active{% endif %}" id="pills-{{ parcours.id }}" role="tabpanel" aria-labelledby="pills-{{ parcours.id }}-tab">
13-
...
126+
<div id="pills-{{ parcours.id }}" class="tab-pane fade {% if loop.index0==0 %}show active{% endif %}" id="pills-{{ parcours.id }}" role="tabpanel" aria-labelledby="pills-{{ parcours.id }}-tab">
127+
14128
</div>
129+
<script>
130+
socket.emit('get_parcours_passage', {{parcours.id}}, function(data){
131+
div = document.getElementById("pills-{{ parcours.id }}")
132+
data.forEach(function(passage){
133+
if (passage.started){
134+
create_passage(passage, div)
135+
}else{
136+
create_not_started(passage, div)
137+
}
138+
})
139+
})
140+
</script>
15141
{% endfor %}
16142
</div>
17143
{% endblock %}

flask_app/admin/editions/passages/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def dashboard_connect(auth):
178178
ic('dashboard connected')
179179

180180
else:
181-
False # connection not allowed
181+
return False # connection not allowed
182182

183183
@socketio.on('disconnect', namespace='/dashboard')
184184
def dashboard_disconnect():

flask_app/admin/editions/templates/edition-sidebar.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,21 @@
2727
<a
2828
class="nav-link "
2929
href="{{ url_for('admin.editions.passages.dashboard', event_name=event_data.name, edition_name=edition_data.name) }}"
30-
>gerer les passages</a
30+
>{{ _('admin.editions.gererpassage') }}</a
3131
>
3232
</li>
3333
<li class="nav-item bg-info btn mb-2">
3434
<a
3535
class="nav-link "
3636
href="{{ url_for('admin.editions.dossard.generate_dossard', event_name=event_data.name, edition_name=edition_data.name) }}"
37-
>gerer les dossard</a
37+
>{{ _('admin.editions.gererdossard') }}</a
3838
>
3939
</li>
4040
<li class="nav-item bg-info btn mb-2">
4141
<a
4242
class="nav-link "
4343
href="{{ url_for('admin.editions.parcours.view', event_name=event_data.name, edition_name=edition_data.name) }}"
44-
>gerer les dossard</a
44+
>{{ _('admin.editions.gererparcours') }}</a
4545
>
4646
</li>
4747
</ul>

flask_app/admin/editions/templates/editions.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends 'layout.html' %} {% block content %}
2-
<h3>editions</h3>
2+
<h3>{{ _('admin.editions.editions') }}</h3>
33
<div class="d-flex justify-content-center flex-wrap">
44
{% for edition in event_data.editions %}
55
<div class="d-inline-flex card m-1">
@@ -35,7 +35,7 @@ <h5 class="px-2">{{edition.name}}</h5>
3535
class="text-decoration-none link-dark"
3636
href="{{ url_for('admin.parcours.parcours_page', event=event_data.name) }}"
3737
>
38-
<p>commencer par creer un parcours</p>
38+
<p>{{ _('admin.editions.startcreateparcours') }}</p>
3939
</a>
4040
{% endif %}
4141
</div>
@@ -52,7 +52,7 @@ <h5 class="px-2">{{edition.name}}</h5>
5252
<div class="modal-dialog">
5353
<div class="modal-content">
5454
<div class="modal-header">
55-
<h5 class="modal-title" id="new_edition_label">New edition</h5>
55+
<h5 class="modal-title" id="new_edition_label">{{ _('admin.editions.newedition') }}</h5>
5656
<button
5757
type="button"
5858
class="btn-close"

flask_app/babel.cfg

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
[python: flask_app/admin/editions/forms.py]
1111
[python: flask_app/admin/editions/__init__.py]
1212
[python: flask_app/admin/editions/dossard/__init__.py]
13+
[python: flask_app/admin/editions/parcours/__init__.py]
1314
[python: flask_app/admin/editions/passages/form.py]
1415
[python: flask_app/admin/editions/passages/__init__.py]
1516
[python: flask_app/admin/parcours/forms.py]
@@ -22,12 +23,13 @@
2223
[jinja2: flask_app/admin/coureurs/templates/coureurs.html]
2324
[jinja2: flask_app/admin/coureurs/templates/view_coureur.html]
2425
[jinja2: flask_app/admin/editions/dossard/templates/dossard.html]
26+
[jinja2: flask_app/admin/editions/dossard/templates/generate_dossard.html]
27+
[jinja2: flask_app/admin/editions/parcours/templates/edition_parcours.html]
2528
[jinja2: flask_app/admin/editions/passages/templates/chrono.html]
2629
[jinja2: flask_app/admin/editions/passages/templates/chrono_home.html]
2730
[jinja2: flask_app/admin/editions/passages/templates/dashboard.html]
2831
[jinja2: flask_app/admin/editions/templates/edition-sidebar.html]
2932
[jinja2: flask_app/admin/editions/templates/editions.html]
30-
[jinja2: flask_app/admin/editions/templates/generate_dossard.html]
3133
[jinja2: flask_app/admin/editions/templates/modify_edition.html]
3234
[jinja2: flask_app/admin/parcours/templates/map_iframe.html]
3335
[jinja2: flask_app/admin/parcours/templates/modify_parcours.html]
@@ -43,6 +45,8 @@
4345
[jinja2: flask_app/templates/sidebar.html]
4446
[jinja2: flask_app/users/templates/inscription.html]
4547
[jinja2: flask_app/users/templates/login.html]
48+
[jinja2: flask_app/users/templates/modify_profil.html]
49+
[jinja2: flask_app/users/templates/profil.html]
4650
[jinja2: flask_app/users/templates/signup.html]
4751
[jinja2: flask_app/view/templates/view_edition.html]
4852
[jinja2: flask_app/view/templates/view_event.html]

flask_app/lib.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@ def get_points_elevation(points:list[tuple[float]]):
1919
def calc_points_dist(lat1, lng1, lat2, lng2):
2020
'return the spherical dist of the two points in km'
2121
return acos((sin(radians(lat1)) * sin(radians(lat2))) + (cos(radians(lat1)) * cos(radians(lat2))) * (cos(radians(lng2) - radians(lng1)))) * 6371
22+
23+
24+
def deg_to_dms(deg):
25+
"""Convert from decimal degrees to degrees, minutes, seconds."""
26+
m, s = divmod(abs(deg)*3600, 60)
27+
d, m = divmod(m, 60)
28+
if deg < 0:
29+
d = -d
30+
d, m = int(d), int(m)
31+
return d, m, s

flask_app/models.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,38 @@ class Inscription(db.Model):
223223

224224
def __repr__(self) -> str:
225225
return f'<Inscription id:{self.id} dossard:{self.dossard} >'
226+
227+
def has_started(self)->bool:
228+
return bool(self.passages.count())
229+
230+
def get_last_passage(self)->Passage:
231+
return self.passages.order_by(Passage.time_stamp.desc()).first()
232+
233+
def get_run(self):
234+
user_passages:list[Passage] = self.passages.filter(Passage.time_stamp<=self.get_last_passage().time_stamp).all()
235+
run=[]
236+
if len(user_passages)>0:
237+
for stand in self.parcours.iter_chrono_list():
238+
if len(user_passages)>0 and stand == user_passages[0].get_stand():
239+
user_passages.pop(0)
240+
run.append(True)
241+
elif len(user_passages)>0:
242+
run.append(False)
243+
else:
244+
run.append(None)
245+
return run
246+
247+
def has_all_right(self)->bool:
248+
if not self.has_started():
249+
return False
250+
run = self.get_run()
251+
return all(run)
252+
253+
def has_finish(self)->bool:
254+
if not self.has_started():
255+
return False
256+
run = self.get_run()
257+
return run[-1]!=None
226258

227259
class PassageKey(db.Model):
228260
__allow_unmapped__ = True

0 commit comments

Comments
 (0)