github-actions[bot] commited on
Commit
c08ead7
·
1 Parent(s): 9408693

🚀 Auto-deploy from GitHub Actions - 2026-02-28 23:34:45

Browse files
database/db_connection.py CHANGED
@@ -45,4 +45,15 @@ def init_database():
45
  );
46
  """))
47
 
 
 
 
 
 
 
 
 
 
 
 
48
  conn.commit()
 
45
  );
46
  """))
47
 
48
+ # Table notes
49
+ conn.execute(text("""
50
+ CREATE TABLE IF NOT EXISTS notes (
51
+ note_id SERIAL PRIMARY KEY,
52
+ app_id INT REFERENCES applications(app_id) ON DELETE CASCADE,
53
+ note_text TEXT NOT NULL,
54
+ created_at TIMESTAMP DEFAULT NOW(),
55
+ updated_at TIMESTAMP DEFAULT NOW()
56
+ );
57
+ """))
58
+
59
  conn.commit()
database/queries.py CHANGED
@@ -117,5 +117,76 @@ def delete_application(app_id):
117
  except Exception as e:
118
  session.rollback()
119
  raise e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  finally:
121
  session.close()
 
117
  except Exception as e:
118
  session.rollback()
119
  raise e
120
+ finally:
121
+ session.close()
122
+
123
+ # ============ NOTES QUERIES ============
124
+
125
+ def create_note(app_id, note_text):
126
+ """Crée une nouvelle note pour une candidature"""
127
+ session = get_db_session()
128
+ try:
129
+ result = session.execute(text("""
130
+ INSERT INTO notes (app_id, note_text)
131
+ VALUES (:app_id, :note_text)
132
+ RETURNING note_id
133
+ """), {
134
+ "app_id": app_id,
135
+ "note_text": note_text
136
+ })
137
+ note_id = result.fetchone()[0]
138
+ session.commit()
139
+ return note_id
140
+ except Exception as e:
141
+ session.rollback()
142
+ raise e
143
+ finally:
144
+ session.close()
145
+
146
+ def get_notes_by_application(app_id):
147
+ """Récupère toutes les notes d'une candidature"""
148
+ session = get_db_session()
149
+ try:
150
+ result = session.execute(text("""
151
+ SELECT * FROM notes
152
+ WHERE app_id = :app_id
153
+ ORDER BY created_at DESC
154
+ """), {"app_id": app_id})
155
+ notes = [dict(row._mapping) for row in result.fetchall()]
156
+ return notes
157
+ finally:
158
+ session.close()
159
+
160
+ def update_note(note_id, note_text):
161
+ """Met à jour une note"""
162
+ session = get_db_session()
163
+ try:
164
+ session.execute(text("""
165
+ UPDATE notes
166
+ SET note_text = :note_text, updated_at = NOW()
167
+ WHERE note_id = :note_id
168
+ """), {
169
+ "note_text": note_text,
170
+ "note_id": note_id
171
+ })
172
+ session.commit()
173
+ except Exception as e:
174
+ session.rollback()
175
+ raise e
176
+ finally:
177
+ session.close()
178
+
179
+ def delete_note(note_id):
180
+ """Supprime une note"""
181
+ session = get_db_session()
182
+ try:
183
+ session.execute(
184
+ text("DELETE FROM notes WHERE note_id = :note_id"),
185
+ {"note_id": note_id}
186
+ )
187
+ session.commit()
188
+ except Exception as e:
189
+ session.rollback()
190
+ raise e
191
  finally:
192
  session.close()
pages/2_📊_Mes_candidatures.py CHANGED
@@ -2,7 +2,15 @@ import streamlit as st
2
  import pandas as pd
3
  from datetime import date, timedelta
4
  from utils.auth import is_logged_in
5
- from database.queries import get_user_applications, update_application, delete_application
 
 
 
 
 
 
 
 
6
 
7
  # Configuration de la page
8
  st.set_page_config(
@@ -115,64 +123,165 @@ else:
115
  }
116
  status_badge = status_colors.get(app['status'], "⚪")
117
 
118
- with st.expander(f"{status_badge} **{app['company']}** - {app['application_date'].strftime('%d/%m/%Y')}", expanded=False):
119
- col1, col2 = st.columns([2, 1])
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
- with col1:
122
- st.markdown(f"**Description :**")
123
- st.text(app['description'] or "Aucune description")
124
-
125
- st.markdown(f"**📅 Date de candidature :** {app['application_date'].strftime('%d/%m/%Y')} ({days_since_application} jours)")
126
 
127
- if app['end_date']:
128
- st.markdown(f"**🏁 Date de fin :** {app['end_date'].strftime('%d/%m/%Y')}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
- st.markdown(f"**⏰ Délai attendu :** {app['expected_delay_days']} jours")
131
- st.markdown(f"**📆 Relance suggérée :** {relance_date.strftime('%d/%m/%Y')}")
132
-
133
- # Alerte si délai dépassé
134
- if app['status'] == "En attente" and date.today() > relance_date:
135
- days_overdue = (date.today() - relance_date).days
136
- st.warning(f"⚠️ Délai dépassé de {days_overdue} jour(s) - Pensez à relancer !")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- with col2:
139
- st.markdown("**Actions**")
140
-
141
- # Modification du statut
142
- new_status = st.selectbox(
143
- "Statut",
144
- options=["En attente", "Relancé", "Accepté", "Refusé"],
145
- index=["En attente", "Relancé", "Accepté", "Refusé"].index(app['status']),
146
- key=f"status_{app['app_id']}"
147
- )
148
-
149
- col_btn1, col_btn2 = st.columns(2)
150
 
151
- with col_btn1:
152
- if st.button("💾 Sauvegarder", key=f"save_{app['app_id']}", type="primary", use_container_width=True):
 
 
 
 
 
 
 
 
 
 
153
  try:
154
- update_application(
155
- app_id=app['app_id'],
156
- company=app['company'],
157
- description=app['description'],
158
- application_date=app['application_date'],
159
- end_date=app['end_date'],
160
- expected_delay_days=app['expected_delay_days'],
161
- status=new_status
162
- )
163
- st.success("✅ Mis à jour !")
164
  st.rerun()
165
  except Exception as e:
166
  st.error(f"Erreur : {e}")
167
 
168
- with col_btn2:
169
- if st.button("🗑️ Supprimer", key=f"delete_{app['app_id']}", type="secondary", use_container_width=True):
170
- try:
171
- delete_application(app['app_id'])
172
- st.success("🗑️ Supprimée !")
173
- st.rerun()
174
- except Exception as e:
175
- st.error(f"Erreur : {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  # ============ EXPORT ============
178
 
 
2
  import pandas as pd
3
  from datetime import date, timedelta
4
  from utils.auth import is_logged_in
5
+ from database.queries import (
6
+ get_user_applications,
7
+ update_application,
8
+ delete_application,
9
+ get_notes_by_application,
10
+ create_note,
11
+ update_note,
12
+ delete_note
13
+ )
14
 
15
  # Configuration de la page
16
  st.set_page_config(
 
123
  }
124
  status_badge = status_colors.get(app['status'], "⚪")
125
 
126
+ # Récupération des notes
127
+ try:
128
+ notes = get_notes_by_application(app['app_id'])
129
+ note_count = len(notes)
130
+ except:
131
+ notes = []
132
+ note_count = 0
133
+
134
+ with st.expander(
135
+ f"{status_badge} **{app['company']}** - {app['application_date'].strftime('%d/%m/%Y')} {'📝 ' + str(note_count) if note_count > 0 else ''}",
136
+ expanded=False
137
+ ):
138
+ # ============ TABS : Infos | Notes ============
139
+ tab1, tab2 = st.tabs(["📋 Informations", f"📝 Notes ({note_count})"])
140
 
141
+ # ============ TAB 1 : INFORMATIONS ============
142
+ with tab1:
143
+ col1, col2 = st.columns([2, 1])
 
 
144
 
145
+ with col1:
146
+ st.markdown(f"**Description :**")
147
+ st.text(app['description'] or "Aucune description")
148
+
149
+ st.markdown(f"**📅 Date de candidature :** {app['application_date'].strftime('%d/%m/%Y')} ({days_since_application} jours)")
150
+
151
+ if app['end_date']:
152
+ st.markdown(f"**🏁 Date de fin :** {app['end_date'].strftime('%d/%m/%Y')}")
153
+
154
+ st.markdown(f"**⏰ Délai attendu :** {app['expected_delay_days']} jours")
155
+ st.markdown(f"**📆 Relance suggérée :** {relance_date.strftime('%d/%m/%Y')}")
156
+
157
+ # Alerte si délai dépassé
158
+ if app['status'] == "En attente" and date.today() > relance_date:
159
+ days_overdue = (date.today() - relance_date).days
160
+ st.warning(f"⚠️ Délai dépassé de {days_overdue} jour(s) - Pensez à relancer !")
161
 
162
+ with col2:
163
+ st.markdown("**Actions**")
164
+
165
+ # Modification du statut
166
+ new_status = st.selectbox(
167
+ "Statut",
168
+ options=["En attente", "Relancé", "Accepté", "Refusé"],
169
+ index=["En attente", "Relancé", "Accepté", "Refusé"].index(app['status']),
170
+ key=f"status_{app['app_id']}"
171
+ )
172
+
173
+ col_btn1, col_btn2 = st.columns(2)
174
+
175
+ with col_btn1:
176
+ if st.button("💾 Sauvegarder", key=f"save_{app['app_id']}", type="primary", use_container_width=True):
177
+ try:
178
+ update_application(
179
+ app_id=app['app_id'],
180
+ company=app['company'],
181
+ description=app['description'],
182
+ application_date=app['application_date'],
183
+ end_date=app['end_date'],
184
+ expected_delay_days=app['expected_delay_days'],
185
+ status=new_status
186
+ )
187
+ st.success("✅ Mis à jour !")
188
+ st.rerun()
189
+ except Exception as e:
190
+ st.error(f"Erreur : {e}")
191
+
192
+ with col_btn2:
193
+ if st.button("🗑️ Supprimer", key=f"delete_{app['app_id']}", type="secondary", use_container_width=True):
194
+ try:
195
+ delete_application(app['app_id'])
196
+ st.success("🗑️ Supprimée !")
197
+ st.rerun()
198
+ except Exception as e:
199
+ st.error(f"Erreur : {e}")
200
 
201
+ # ============ TAB 2 : NOTES ============
202
+ with tab2:
203
+ st.markdown("### 📝 Ajouter une note")
 
 
 
 
 
 
 
 
 
204
 
205
+ # Formulaire d'ajout de note
206
+ with st.form(f"add_note_form_{app['app_id']}", clear_on_submit=True):
207
+ new_note_text = st.text_area(
208
+ "Nouvelle note",
209
+ placeholder="Ex: Entretien prévu le 25/02, Relancé par email, Feedback positif...",
210
+ height=100,
211
+ key=f"new_note_{app['app_id']}"
212
+ )
213
+
214
+ submitted = st.form_submit_button("➕ Ajouter la note", use_container_width=True)
215
+
216
+ if submitted and new_note_text.strip():
217
  try:
218
+ create_note(app['app_id'], new_note_text.strip())
219
+ st.success("✅ Note ajoutée !")
 
 
 
 
 
 
 
 
220
  st.rerun()
221
  except Exception as e:
222
  st.error(f"Erreur : {e}")
223
 
224
+ st.markdown("---")
225
+
226
+ # Affichage des notes existantes
227
+ if notes:
228
+ st.markdown(f"### 📋 Notes existantes ({len(notes)})")
229
+
230
+ for note in notes:
231
+ note_id = note['note_id']
232
+
233
+ with st.container():
234
+ col_note1, col_note2 = st.columns([4, 1])
235
+
236
+ with col_note1:
237
+ # Mode édition ou affichage
238
+ if f"edit_note_{note_id}" in st.session_state and st.session_state[f"edit_note_{note_id}"]:
239
+ edited_text = st.text_area(
240
+ "Modifier la note",
241
+ value=note['note_text'],
242
+ height=100,
243
+ key=f"edit_text_{note_id}"
244
+ )
245
+ else:
246
+ st.markdown(f"**{note['created_at'].strftime('%d/%m/%Y à %H:%M')}**")
247
+ st.text(note['note_text'])
248
+
249
+ if note['updated_at'] != note['created_at']:
250
+ st.caption(f"_Modifié le {note['updated_at'].strftime('%d/%m/%Y à %H:%M')}_")
251
+
252
+ with col_note2:
253
+ # Boutons d'action
254
+ if f"edit_note_{note_id}" in st.session_state and st.session_state[f"edit_note_{note_id}"]:
255
+ # Mode édition
256
+ if st.button("💾 Sauver", key=f"save_note_{note_id}", use_container_width=True):
257
+ try:
258
+ update_note(note_id, edited_text)
259
+ st.session_state[f"edit_note_{note_id}"] = False
260
+ st.success("✅ Note modifiée !")
261
+ st.rerun()
262
+ except Exception as e:
263
+ st.error(f"Erreur : {e}")
264
+
265
+ if st.button("❌ Annuler", key=f"cancel_note_{note_id}", use_container_width=True):
266
+ st.session_state[f"edit_note_{note_id}"] = False
267
+ st.rerun()
268
+ else:
269
+ # Mode affichage
270
+ if st.button("✏️ Modifier", key=f"edit_btn_{note_id}", use_container_width=True):
271
+ st.session_state[f"edit_note_{note_id}"] = True
272
+ st.rerun()
273
+
274
+ if st.button("🗑️ Supprimer", key=f"delete_note_{note_id}", use_container_width=True):
275
+ try:
276
+ delete_note(note_id)
277
+ st.success("🗑️ Note supprimée !")
278
+ st.rerun()
279
+ except Exception as e:
280
+ st.error(f"Erreur : {e}")
281
+
282
+ st.markdown("---")
283
+ else:
284
+ st.info("💬 Aucune note pour cette candidature")
285
 
286
  # ============ EXPORT ============
287