Kikulika commited on
Commit
21546e4
·
verified ·
1 Parent(s): 4048a82

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +339 -92
app.py CHANGED
@@ -1,9 +1,10 @@
1
  import streamlit as st
2
  import pandas as pd
3
- from datetime import datetime
4
  import os
5
  import sys
6
  import random
 
7
 
8
  # Disable Streamlit's telemetry and usage statistics
9
  os.environ['STREAMLIT_ANALYTICS_ENABLED'] = 'false'
@@ -13,12 +14,12 @@ os.environ['STREAMLIT_SERVER_HEADLESS'] = 'true'
13
 
14
  # Set page configuration
15
  st.set_page_config(
16
- page_title="Broadcast Solutions Task Board",
17
- page_icon="📺",
18
  layout="wide"
19
  )
20
 
21
- # Apply custom CSS for a horizontal board layout
22
  st.markdown("""
23
  <style>
24
  /* Main theme colors */
@@ -36,21 +37,6 @@ st.markdown("""
36
  --backlog-color: #8BC34A;
37
  }
38
 
39
- /* Header styling */
40
- .main-header {
41
- background-color: var(--header-bg);
42
- color: white;
43
- padding: 1rem;
44
- text-align: center;
45
- border-radius: 3px;
46
- margin-bottom: 20px;
47
- }
48
-
49
- .main-header h1 {
50
- margin: 0;
51
- font-size: 1.8rem;
52
- }
53
-
54
  /* Task Grid Layout */
55
  .task-container {
56
  display: flex;
@@ -60,8 +46,8 @@ st.markdown("""
60
  }
61
 
62
  .task-card {
63
- width: 180px;
64
- height: 150px;
65
  border: 1px solid var(--border-color);
66
  border-radius: 4px;
67
  overflow: hidden;
@@ -69,6 +55,13 @@ st.markdown("""
69
  display: flex;
70
  flex-direction: column;
71
  background-color: var(--card-bg);
 
 
 
 
 
 
 
72
  }
73
 
74
  .card-header {
@@ -117,29 +110,65 @@ st.markdown("""
117
  font-size: 12px;
118
  font-style: italic;
119
  margin-top: auto;
120
- margin-bottom: 10px;
121
  }
122
 
123
- /* Status update section */
124
- .status-update-section {
125
- margin-top: 30px;
126
- border-top: 1px solid #eee;
127
- padding-top: 20px;
 
128
  }
129
 
130
- .status-update-row {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  display: flex;
 
132
  align-items: center;
133
- margin-bottom: 10px;
 
 
134
  }
135
 
136
- .status-label {
137
- width: 200px;
138
- font-weight: 500;
 
139
  }
140
 
141
- .status-select {
142
- flex-grow: 1;
 
 
143
  }
144
 
145
  /* Form styling */
@@ -150,11 +179,10 @@ st.markdown("""
150
  /* Remove default Streamlit formatting */
151
  #MainMenu {visibility: hidden;}
152
  footer {visibility: hidden;}
153
- .block-container {padding-top: 0rem; padding-bottom: 0rem;}
154
 
155
  /* Button styling */
156
  .stButton > button {
157
- width: 100%;
158
  background-color: var(--secondary-color);
159
  color: white;
160
  border: none;
@@ -179,7 +207,70 @@ st.markdown("""
179
  .status-hidden {
180
  display: none;
181
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  """, unsafe_allow_html=True)
184
 
185
  # Status color mapping
@@ -191,38 +282,63 @@ STATUS_COLORS = {
191
  }
192
 
193
  # Set up paths
194
- TASKS_FILE = "tasks.csv" # Simplified path for tasks
195
  ASSIGNEE_FILE = "Assignee.txt" # Direct path to Assignee.txt
196
 
197
  def create_empty_df():
198
  """Create an empty DataFrame with the required columns"""
199
  return pd.DataFrame(columns=[
200
- 'ID', 'Title', 'Description', 'Assignee', 'Status', 'Due Date'
 
201
  ])
202
 
203
  def load_tasks():
204
- """Load tasks from CSV file with better error handling"""
205
  try:
206
  if os.path.exists(TASKS_FILE):
207
- df = pd.read_csv(TASKS_FILE, parse_dates=['Due Date'])
208
- if df.empty:
209
- return create_empty_df()
210
- # Ensure ID column exists
211
- if 'ID' not in df.columns:
212
- df['ID'] = [f"#{i}" for i in range(1, len(df) + 1)]
213
- return df
 
 
 
 
 
 
 
 
 
 
 
214
  else:
215
  return create_empty_df()
216
- except Exception:
 
217
  return create_empty_df()
218
 
219
  def save_tasks():
220
- """Save tasks to CSV file with better error handling"""
221
  try:
222
- st.session_state.tasks.to_csv(TASKS_FILE, index=False)
 
 
 
 
 
 
 
 
 
 
 
223
  return True
224
- except Exception:
225
- # Silently continue with in-memory storage
 
226
  return False
227
 
228
  def load_assignees():
@@ -323,16 +439,30 @@ def generate_task_id():
323
  # If any error occurs, generate a random ID
324
  return f"#{random.randint(1, 100)}"
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  # Initialize session state ONLY ONCE
327
  if 'tasks' not in st.session_state:
328
  st.session_state.tasks = load_tasks()
 
 
 
329
 
330
  # Load assignees directly from the file
331
  assignee_list = load_assignees()
332
 
333
- # Main header
334
- st.markdown('<div class="main-header"><h1>Task Board</h1></div>', unsafe_allow_html=True)
335
-
336
  # Sidebar for new tasks
337
  with st.sidebar:
338
  st.markdown('<div class="sidebar-title">➕ Add New Task</div>', unsafe_allow_html=True)
@@ -340,6 +470,14 @@ with st.sidebar:
340
  description = st.text_area("Description")
341
  assignee = st.selectbox("Assignee", assignee_list)
342
  status = st.selectbox("Status", ["Backlog", "To Do", "In Progress", "Done"])
 
 
 
 
 
 
 
 
343
  due_date = st.date_input("Due Date")
344
 
345
  if st.button("Add Task"):
@@ -351,6 +489,8 @@ with st.sidebar:
351
  'Description': description,
352
  'Assignee': assignee,
353
  'Status': status,
 
 
354
  'Due Date': due_date
355
  }
356
  st.session_state.tasks = pd.concat([
@@ -367,57 +507,164 @@ with st.sidebar:
367
  if st.session_state.tasks.empty:
368
  st.info("No tasks yet. Add your first task using the sidebar.")
369
  else:
370
- # Start the flex container for task cards
371
- st.markdown('<div class="task-container">', unsafe_allow_html=True)
 
 
 
372
 
373
- # Render each task as a card
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  for idx, task in st.session_state.tasks.iterrows():
375
- status_class = STATUS_COLORS.get(task['Status'], "backlog")
376
  task_id = task['ID'] if 'ID' in task and pd.notna(task['ID']) else f"#{idx+1}"
 
 
 
 
 
 
377
 
378
- # Create the card HTML
379
- card_html = f"""
380
- <div class="task-card">
381
- <div class="card-header {status_class}">
382
- <span class="task-id">{task_id}</span>
383
- <span>{task['Status']}</span>
 
 
 
 
 
 
 
 
 
 
 
384
  </div>
385
- <div class="task-title">{task['Title']}</div>
386
- <div class="task-assignee">{task['Assignee']}</div>
387
  </div>
388
  """
389
 
390
- # Render the card
391
- st.markdown(card_html, unsafe_allow_html=True)
392
-
393
- # Close the container
394
- st.markdown('</div>', unsafe_allow_html=True)
395
-
396
- # Add status update section
397
- st.markdown('<div class="status-update-section"><h3>Update Task Status</h3></div>', unsafe_allow_html=True)
398
-
399
- # Create two columns for the status update controls
400
- status_cols = st.columns(2)
401
-
402
- # Display status update controls in two columns
403
- col_index = 0
404
- for idx, task in st.session_state.tasks.iterrows():
405
- task_id = task['ID'] if 'ID' in task and pd.notna(task['ID']) else f"#{idx+1}"
406
 
407
- with status_cols[col_index % 2]:
408
- st.markdown(f"<div class='status-label'>{task_id}: {task['Title'][:20]}...</div>", unsafe_allow_html=True)
 
 
 
 
409
  new_status = st.selectbox(
410
- f"Status for {task_id}",
411
  ["Backlog", "To Do", "In Progress", "Done"],
412
  index=["Backlog", "To Do", "In Progress", "Done"].index(task['Status']) if task['Status'] in ["Backlog", "To Do", "In Progress", "Done"] else 0,
413
- key=f"status_{idx}",
414
- label_visibility="collapsed"
415
  )
416
 
417
- if new_status != task['Status']:
418
- st.session_state.tasks.at[idx, 'Status'] = new_status
419
- save_tasks() # Save after updating
420
  st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
- # Switch to next column
423
- col_index += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import pandas as pd
3
+ from datetime import datetime, date
4
  import os
5
  import sys
6
  import random
7
+ import json
8
 
9
  # Disable Streamlit's telemetry and usage statistics
10
  os.environ['STREAMLIT_ANALYTICS_ENABLED'] = 'false'
 
14
 
15
  # Set page configuration
16
  st.set_page_config(
17
+ page_title="Task Board",
18
+ page_icon="📋",
19
  layout="wide"
20
  )
21
 
22
+ # Apply custom CSS for a horizontal board layout with expandable tasks
23
  st.markdown("""
24
  <style>
25
  /* Main theme colors */
 
37
  --backlog-color: #8BC34A;
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  /* Task Grid Layout */
41
  .task-container {
42
  display: flex;
 
46
  }
47
 
48
  .task-card {
49
+ width: 220px;
50
+ min-height: 150px;
51
  border: 1px solid var(--border-color);
52
  border-radius: 4px;
53
  overflow: hidden;
 
55
  display: flex;
56
  flex-direction: column;
57
  background-color: var(--card-bg);
58
+ transition: transform 0.2s, box-shadow 0.2s;
59
+ cursor: pointer;
60
+ }
61
+
62
+ .task-card:hover {
63
+ transform: translateY(-3px);
64
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
65
  }
66
 
67
  .card-header {
 
110
  font-size: 12px;
111
  font-style: italic;
112
  margin-top: auto;
113
+ margin-bottom: 5px;
114
  }
115
 
116
+ .task-dates {
117
+ padding: 0 10px 10px;
118
+ color: #666;
119
+ font-size: 11px;
120
+ display: flex;
121
+ justify-content: space-between;
122
  }
123
 
124
+ /* Modal Dialog Styling */
125
+ .modal-overlay {
126
+ display: none;
127
+ position: fixed;
128
+ top: 0;
129
+ left: 0;
130
+ right: 0;
131
+ bottom: 0;
132
+ background-color: rgba(0, 0, 0, 0.5);
133
+ z-index: 1000;
134
+ backdrop-filter: blur(2px);
135
+ }
136
+
137
+ .modal-content {
138
+ position: fixed;
139
+ top: 50%;
140
+ left: 50%;
141
+ transform: translate(-50%, -50%);
142
+ background-color: white;
143
+ padding: 20px;
144
+ border-radius: 5px;
145
+ width: 80%;
146
+ max-width: 600px;
147
+ max-height: 80vh;
148
+ overflow-y: auto;
149
+ z-index: 1001;
150
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
151
+ }
152
+
153
+ .modal-header {
154
  display: flex;
155
+ justify-content: space-between;
156
  align-items: center;
157
+ margin-bottom: 15px;
158
+ padding-bottom: 10px;
159
+ border-bottom: 1px solid #eee;
160
  }
161
 
162
+ .modal-close {
163
+ cursor: pointer;
164
+ font-size: 20px;
165
+ color: #999;
166
  }
167
 
168
+ .modal-title {
169
+ font-size: 18px;
170
+ font-weight: 600;
171
+ color: var(--primary-color);
172
  }
173
 
174
  /* Form styling */
 
179
  /* Remove default Streamlit formatting */
180
  #MainMenu {visibility: hidden;}
181
  footer {visibility: hidden;}
182
+ .block-container {padding-top: 1rem; padding-bottom: 0rem;}
183
 
184
  /* Button styling */
185
  .stButton > button {
 
186
  background-color: var(--secondary-color);
187
  color: white;
188
  border: none;
 
207
  .status-hidden {
208
  display: none;
209
  }
210
+
211
+ /* Status Pills */
212
+ .status-pill {
213
+ display: inline-block;
214
+ padding: 3px 8px;
215
+ border-radius: 12px;
216
+ font-size: 11px;
217
+ font-weight: 500;
218
+ color: white;
219
+ }
220
+
221
+ .status-pill.todo {
222
+ background-color: var(--todo-color);
223
+ }
224
+
225
+ .status-pill.in-progress {
226
+ background-color: var(--progress-color);
227
+ }
228
+
229
+ .status-pill.done {
230
+ background-color: var(--done-color);
231
+ }
232
+
233
+ .status-pill.backlog {
234
+ background-color: var(--backlog-color);
235
+ }
236
+
237
+ /* Date display in task card */
238
+ .date-display {
239
+ font-size: 10px;
240
+ color: #777;
241
+ }
242
  </style>
243
+
244
+ <script>
245
+ document.addEventListener('DOMContentLoaded', function() {
246
+ // Task card click handler setup
247
+ document.querySelectorAll('.task-card').forEach(card => {
248
+ card.addEventListener('click', function() {
249
+ const taskId = this.getAttribute('data-task-id');
250
+ openTaskModal(taskId);
251
+ });
252
+ });
253
+
254
+ // Close modal when clicking the X or outside the modal
255
+ document.querySelectorAll('.modal-close, .modal-overlay').forEach(el => {
256
+ el.addEventListener('click', function(e) {
257
+ if (e.target === this) {
258
+ closeAllModals();
259
+ }
260
+ });
261
+ });
262
+
263
+ function openTaskModal(taskId) {
264
+ document.getElementById(`modal-${taskId}`).style.display = 'block';
265
+ }
266
+
267
+ function closeAllModals() {
268
+ document.querySelectorAll('.modal-overlay').forEach(modal => {
269
+ modal.style.display = 'none';
270
+ });
271
+ }
272
+ });
273
+ </script>
274
  """, unsafe_allow_html=True)
275
 
276
  # Status color mapping
 
282
  }
283
 
284
  # Set up paths
285
+ TASKS_FILE = "tasks.json" # Using JSON for better persistence
286
  ASSIGNEE_FILE = "Assignee.txt" # Direct path to Assignee.txt
287
 
288
  def create_empty_df():
289
  """Create an empty DataFrame with the required columns"""
290
  return pd.DataFrame(columns=[
291
+ 'ID', 'Title', 'Description', 'Assignee', 'Status',
292
+ 'Date Started', 'Date to Finish', 'Due Date' # Added new date fields
293
  ])
294
 
295
  def load_tasks():
296
+ """Load tasks from JSON file with better error handling"""
297
  try:
298
  if os.path.exists(TASKS_FILE):
299
+ with open(TASKS_FILE, 'r') as f:
300
+ tasks_data = json.load(f)
301
+ df = pd.DataFrame(tasks_data)
302
+
303
+ # Convert date strings back to datetime objects
304
+ if not df.empty:
305
+ date_cols = ['Date Started', 'Date to Finish', 'Due Date']
306
+ for col in date_cols:
307
+ if col in df.columns:
308
+ df[col] = pd.to_datetime(df[col]).dt.date
309
+
310
+ if df.empty:
311
+ return create_empty_df()
312
+
313
+ # Ensure ID column exists
314
+ if 'ID' not in df.columns:
315
+ df['ID'] = [f"#{i}" for i in range(1, len(df) + 1)]
316
+ return df
317
  else:
318
  return create_empty_df()
319
+ except Exception as e:
320
+ print(f"Error loading tasks: {str(e)}")
321
  return create_empty_df()
322
 
323
  def save_tasks():
324
+ """Save tasks to JSON file with better error handling"""
325
  try:
326
+ # Convert DataFrame to a list of dictionaries
327
+ tasks_dict = st.session_state.tasks.to_dict(orient='records')
328
+
329
+ # Convert date objects to strings for JSON serialization
330
+ for task in tasks_dict:
331
+ for key, value in task.items():
332
+ if isinstance(value, (datetime, date)):
333
+ task[key] = value.isoformat()
334
+
335
+ # Save to JSON file
336
+ with open(TASKS_FILE, 'w') as f:
337
+ json.dump(tasks_dict, f, indent=2)
338
  return True
339
+ except Exception as e:
340
+ print(f"Error saving tasks: {str(e)}")
341
+ st.error(f"Error saving tasks: {str(e)}")
342
  return False
343
 
344
  def load_assignees():
 
439
  # If any error occurs, generate a random ID
440
  return f"#{random.randint(1, 100)}"
441
 
442
+ def update_task(task_idx, field, value):
443
+ """Update a task field and save changes"""
444
+ st.session_state.tasks.at[task_idx, field] = value
445
+ save_tasks()
446
+
447
+ def delete_task(task_id):
448
+ """Delete a task by ID"""
449
+ task_idx = st.session_state.tasks[st.session_state.tasks['ID'] == task_id].index
450
+ if not task_idx.empty:
451
+ st.session_state.tasks = st.session_state.tasks.drop(task_idx).reset_index(drop=True)
452
+ save_tasks()
453
+ return True
454
+ return False
455
+
456
  # Initialize session state ONLY ONCE
457
  if 'tasks' not in st.session_state:
458
  st.session_state.tasks = load_tasks()
459
+
460
+ if 'task_modal_open' not in st.session_state:
461
+ st.session_state.task_modal_open = None
462
 
463
  # Load assignees directly from the file
464
  assignee_list = load_assignees()
465
 
 
 
 
466
  # Sidebar for new tasks
467
  with st.sidebar:
468
  st.markdown('<div class="sidebar-title">➕ Add New Task</div>', unsafe_allow_html=True)
 
470
  description = st.text_area("Description")
471
  assignee = st.selectbox("Assignee", assignee_list)
472
  status = st.selectbox("Status", ["Backlog", "To Do", "In Progress", "Done"])
473
+
474
+ # Added date fields
475
+ col1, col2 = st.columns(2)
476
+ with col1:
477
+ date_started = st.date_input("Date Started", value=datetime.now().date())
478
+ with col2:
479
+ date_to_finish = st.date_input("Date to Finish", value=datetime.now().date())
480
+
481
  due_date = st.date_input("Due Date")
482
 
483
  if st.button("Add Task"):
 
489
  'Description': description,
490
  'Assignee': assignee,
491
  'Status': status,
492
+ 'Date Started': date_started,
493
+ 'Date to Finish': date_to_finish,
494
  'Due Date': due_date
495
  }
496
  st.session_state.tasks = pd.concat([
 
507
  if st.session_state.tasks.empty:
508
  st.info("No tasks yet. Add your first task using the sidebar.")
509
  else:
510
+ # Group tasks by status for organized display
511
+ statuses = ["To Do", "In Progress", "Done", "Backlog"]
512
+
513
+ # Create columns for each status
514
+ cols = st.columns(len(statuses))
515
 
516
+ # Display each status column
517
+ for i, status_name in enumerate(statuses):
518
+ with cols[i]:
519
+ st.markdown(f"<h3>{status_name}</h3>", unsafe_allow_html=True)
520
+
521
+ # Filter tasks by current status
522
+ status_tasks = st.session_state.tasks[st.session_state.tasks['Status'] == status_name]
523
+
524
+ if status_tasks.empty:
525
+ st.markdown("<div style='text-align:center; color:#999; padding:20px;'>No tasks</div>", unsafe_allow_html=True)
526
+ else:
527
+ # Start the flex container for task cards
528
+ st.markdown('<div class="task-container">', unsafe_allow_html=True)
529
+
530
+ # Render each task as a card
531
+ for idx, task in status_tasks.iterrows():
532
+ status_class = STATUS_COLORS.get(task['Status'], "backlog")
533
+ task_id = task['ID'] if 'ID' in task and pd.notna(task['ID']) else f"#{idx+1}"
534
+
535
+ # Format dates for display
536
+ date_started_str = task['Date Started'].strftime('%Y-%m-%d') if 'Date Started' in task and pd.notna(task['Date Started']) else "Not set"
537
+ date_to_finish_str = task['Date to Finish'].strftime('%Y-%m-%d') if 'Date to Finish' in task and pd.notna(task['Date to Finish']) else "Not set"
538
+
539
+ # Create the card HTML with dates and make it clickable
540
+ card_html = f"""
541
+ <div class="task-card" data-task-id="{task_id}" onclick="document.getElementById('modal-{task_id}').style.display='block';">
542
+ <div class="card-header {status_class}">
543
+ <span class="task-id">{task_id}</span>
544
+ <span>{task['Status']}</span>
545
+ </div>
546
+ <div class="task-title">{task['Title']}</div>
547
+ <div class="task-assignee">{task['Assignee']}</div>
548
+ <div class="task-dates">
549
+ <span class="date-display">Start: {date_started_str}</span>
550
+ <span class="date-display">Finish: {date_to_finish_str}</span>
551
+ </div>
552
+ </div>
553
+ """
554
+
555
+ # Render the card
556
+ st.markdown(card_html, unsafe_allow_html=True)
557
+
558
+ # Close the container
559
+ st.markdown('</div>', unsafe_allow_html=True)
560
+
561
+ # Create task modals for each task
562
  for idx, task in st.session_state.tasks.iterrows():
 
563
  task_id = task['ID'] if 'ID' in task and pd.notna(task['ID']) else f"#{idx+1}"
564
+ status_class = STATUS_COLORS.get(task['Status'], "backlog")
565
+
566
+ # Format dates for display in modal
567
+ date_started_str = task['Date Started'].strftime('%Y-%m-%d') if 'Date Started' in task and pd.notna(task['Date Started']) else "Not set"
568
+ date_to_finish_str = task['Date to Finish'].strftime('%Y-%m-%d') if 'Date to Finish' in task and pd.notna(task['Date to Finish']) else "Not set"
569
+ due_date_str = task['Due Date'].strftime('%Y-%m-%d') if 'Due Date' in task and pd.notna(task['Due Date']) else "Not set"
570
 
571
+ # Create modal HTML
572
+ modal_html = f"""
573
+ <div id="modal-{task_id}" class="modal-overlay">
574
+ <div class="modal-content">
575
+ <div class="modal-header">
576
+ <div class="modal-title">{task['Title']} <span class="status-pill {status_class}">{task['Status']}</span></div>
577
+ <span class="modal-close" onclick="document.getElementById('modal-{task_id}').style.display='none';">&times;</span>
578
+ </div>
579
+ <div class="modal-body">
580
+ <p><strong>Task ID:</strong> {task_id}</p>
581
+ <p><strong>Assignee:</strong> {task['Assignee']}</p>
582
+ <p><strong>Description:</strong><br>{task['Description']}</p>
583
+ <p><strong>Date Started:</strong> {date_started_str}</p>
584
+ <p><strong>Date to Finish:</strong> {date_to_finish_str}</p>
585
+ <p><strong>Due Date:</strong> {due_date_str}</p>
586
+ <div id="task-status-{task_id}"></div>
587
+ </div>
588
  </div>
 
 
589
  </div>
590
  """
591
 
592
+ # Render the modal
593
+ st.markdown(modal_html, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
 
595
+ # Add task status update widget inside the task modal
596
+ # This is hacky but gets the job done - using a placeholder to inject the input into the modal
597
+ status_placeholder = st.empty()
598
+ with status_placeholder:
599
+ # Hide this with CSS and use JavaScript to move it into the modal
600
+ st.markdown(f"<div id='status-update-{task_id}' style='display:none;'>", unsafe_allow_html=True)
601
  new_status = st.selectbox(
602
+ f"Update Status for {task_id}",
603
  ["Backlog", "To Do", "In Progress", "Done"],
604
  index=["Backlog", "To Do", "In Progress", "Done"].index(task['Status']) if task['Status'] in ["Backlog", "To Do", "In Progress", "Done"] else 0,
605
+ key=f"modal_status_{idx}"
 
606
  )
607
 
608
+ if st.button("Update Status", key=f"update_btn_{idx}"):
609
+ update_task(idx, 'Status', new_status)
 
610
  st.rerun()
611
+
612
+ if st.button("Delete Task", key=f"delete_btn_{idx}"):
613
+ if delete_task(task_id):
614
+ st.success(f"Task {task_id} deleted!")
615
+ st.rerun()
616
+ else:
617
+ st.error(f"Failed to delete task {task_id}")
618
+
619
+ st.markdown("</div>", unsafe_allow_html=True)
620
+
621
+ # JavaScript to move the status update widget into the modal
622
+ st.markdown(f"""
623
+ <script>
624
+ // Move the status widget into the modal
625
+ document.addEventListener('DOMContentLoaded', function() {{
626
+ var statusEl = document.getElementById('status-update-{task_id}');
627
+ var targetEl = document.getElementById('task-status-{task_id}');
628
+ if (statusEl && targetEl) {{
629
+ statusEl.style.display = 'block';
630
+ targetEl.appendChild(statusEl);
631
+ }}
632
+ }});
633
+ </script>
634
+ """, unsafe_allow_html=True)
635
+
636
+ # JavaScript to make task modals work properly in Streamlit
637
+ st.markdown("""
638
+ <script>
639
+ // Ensure modals work properly with Streamlit's update system
640
+ function setupTaskModals() {
641
+ document.querySelectorAll('.task-card').forEach(card => {
642
+ card.addEventListener('click', function(e) {
643
+ e.preventDefault();
644
+ const taskId = this.getAttribute('data-task-id');
645
+ document.getElementById(`modal-${taskId}`).style.display = 'block';
646
+ });
647
+ });
648
 
649
+ document.querySelectorAll('.modal-close, .modal-overlay').forEach(el => {
650
+ el.addEventListener('click', function(e) {
651
+ if (e.target === this) {
652
+ document.querySelectorAll('.modal-overlay').forEach(modal => {
653
+ modal.style.display = 'none';
654
+ });
655
+ }
656
+ });
657
+ });
658
+ }
659
+
660
+ // Run setup on initial load and after Streamlit updates
661
+ document.addEventListener('DOMContentLoaded', setupTaskModals);
662
+
663
+ // For Streamlit's reactive updates
664
+ const observer = new MutationObserver(function(mutations) {
665
+ setupTaskModals();
666
+ });
667
+
668
+ observer.observe(document.body, { childList: true, subtree: true });
669
+ </script>
670
+ """, unsafe_allow_html=True)