Kikulika commited on
Commit
c7e57f1
·
verified ·
1 Parent(s): 969dd8a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +325 -261
app.py CHANGED
@@ -135,6 +135,74 @@ st.markdown("""
135
  justify-content: space-between;
136
  }
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  /* Modal Dialog Styling */
139
  .modal-overlay {
140
  display: none;
@@ -223,13 +291,17 @@ st.markdown("""
223
  padding: 8px 12px;
224
  border-radius: 4px;
225
  font-weight: 500;
226
- width: 100%;
227
  }
228
 
229
  .stButton > button:hover {
230
  background-color: var(--primary-color);
231
  }
232
 
 
 
 
 
 
233
  /* Sidebar styling */
234
  .sidebar-title {
235
  font-size: 1.2rem;
@@ -251,6 +323,7 @@ st.markdown("""
251
  font-size: 10px;
252
  font-weight: 500;
253
  color: white;
 
254
  }
255
 
256
  .status-pill.todo {
@@ -281,67 +354,44 @@ st.markdown("""
281
  margin-bottom: 10px;
282
  }
283
 
284
- /* Inline status dropdown on task cards */
285
- .task-status-dropdown {
286
- position: relative;
287
- display: inline-block;
288
- }
289
-
290
- .status-dropdown-btn {
291
- background-color: transparent;
292
- border: none;
293
- cursor: pointer;
294
- font-size: 12px;
295
- color: #fff;
296
- text-align: right;
297
- padding: 0;
298
- margin: 0;
299
- width: auto;
300
- }
301
-
302
- .status-dropdown-content {
303
- display: none;
304
- position: absolute;
305
- right: 0;
306
- background-color: #f9f9f9;
307
- min-width: 120px;
308
- box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
309
- z-index: 100;
310
  border-radius: 4px;
 
311
  }
312
 
313
- .status-dropdown-content a {
314
- color: black;
315
- padding: 8px 10px;
316
- text-decoration: none;
317
- display: block;
318
- font-size: 12px;
319
- text-align: left;
320
- }
321
-
322
- .status-dropdown-content a:hover {
323
- background-color: #f1f1f1;
324
  }
325
 
326
- .task-status-dropdown:hover .status-dropdown-content {
327
- display: block;
 
328
  }
329
 
330
- /* Status colors for dropdown menu */
331
- .status-option-todo {
332
- border-left: 4px solid var(--todo-color);
333
  }
334
 
335
- .status-option-progress {
336
- border-left: 4px solid var(--progress-color);
 
337
  }
338
 
339
- .status-option-done {
340
- border-left: 4px solid var(--done-color);
 
341
  }
342
 
343
- .status-option-backlog {
344
- border-left: 4px solid var(--backlog-color);
 
 
 
345
  }
346
  </style>
347
  """, unsafe_allow_html=True)
@@ -498,7 +548,10 @@ def save_tasks():
498
  except Exception as e:
499
  print(f"Error saving tasks: {str(e)}")
500
  # Store in memory as fallback
501
- st.session_state.in_memory_tasks = tasks_dict
 
 
 
502
  st.session_state.using_memory_storage = True
503
  st.warning(f"Error saving tasks: {str(e)}. Tasks stored in memory instead.")
504
  return False
@@ -607,24 +660,12 @@ def delete_task(task_id):
607
  return True
608
  return False
609
 
610
- def open_task_details_page(task_id):
611
- """Open a new page with detailed task information"""
612
- # Store the selected task ID in session state
613
- st.session_state.selected_task_id = task_id
614
-
615
- # Use query parameters to navigate to the details page
616
- st.query_params.view = 'task_details'
617
- st.query_params.task_id = task_id
618
-
619
- # Rerun to reload the page with new query params
620
- st.rerun()
621
-
622
  # Initialize session state ONLY ONCE
623
  if 'tasks' not in st.session_state:
624
  st.session_state.tasks = load_tasks()
625
 
626
- if 'task_modal_open' not in st.session_state:
627
- st.session_state.task_modal_open = None
628
 
629
  if 'in_memory_tasks' not in st.session_state:
630
  st.session_state.in_memory_tasks = []
@@ -643,87 +684,14 @@ else:
643
  # Load assignees
644
  assignee_list = load_assignees()
645
 
646
- view_mode = st.query_params.get('view', 'main')
647
- selected_task_id = st.query_params.get('task_id', None)
648
-
649
- if view_mode == 'task_details' and selected_task_id:
650
- # Task details page
651
- task_df = st.session_state.tasks[st.session_state.tasks['ID'] == selected_task_id]
652
-
653
- if not task_df.empty:
654
- task = task_df.iloc[0]
655
- status_class = STATUS_COLORS.get(task['Status'], "backlog")
656
-
657
- # Back button
658
- if st.button("← Back to Board"):
659
- for key in list(st.query_params.keys()):
660
- del st.query_params[key]
661
-
662
- st.rerun()
663
-
664
- # Display task details in a full page
665
- st.markdown(f"<h1>{task['Title']} <span class='status-pill {status_class}'>{task['Status']}</span></h1>", unsafe_allow_html=True)
666
-
667
- # Task metadata in columns
668
- col1, col2 = st.columns(2)
669
- with col1:
670
- st.markdown(f"**Task ID:** {selected_task_id}")
671
- st.markdown(f"**Assignee:** {task['Assignee']}")
672
-
673
- with col2:
674
- date_started_str = task['Date Started'].strftime('%m/%d/%Y') if 'Date Started' in task and pd.notna(task['Date Started']) else "Not set"
675
- date_to_finish_str = task['Date to Finish'].strftime('%m/%d/%Y') if 'Date to Finish' in task and pd.notna(task['Date to Finish']) else "Not set"
676
- due_date_str = task['Due Date'].strftime('%m/%d/%Y') if 'Due Date' in task and pd.notna(task['Due Date']) else "Not set"
677
-
678
- st.markdown(f"**Date Started:** {date_started_str}")
679
- st.markdown(f"**Date to Finish:** {date_to_finish_str}")
680
- st.markdown(f"**Due Date:** {due_date_str}")
681
-
682
- # Description
683
- st.markdown("### Description")
684
- st.markdown(f"{task['Description'] if task['Description'] else 'No description provided.'}")
685
-
686
- # Task actions
687
- st.markdown("### Actions")
688
-
689
- # Status update
690
- new_status = st.selectbox(
691
- "Status",
692
- ["Backlog", "To Do", "In Progress", "Done"],
693
- index=["Backlog", "To Do", "In Progress", "Done"].index(task['Status']) if task['Status'] in ["Backlog", "To Do", "In Progress", "Done"] else 0
694
- )
695
-
696
- col1, col2 = st.columns(2)
697
- with col1:
698
- if st.button("Update Status") and new_status != task['Status']:
699
- task_idx = task_df.index[0]
700
- update_task(task_idx, 'Status', new_status)
701
- st.success(f"Updated task status to {new_status}")
702
- st.rerun()
703
-
704
- with col2:
705
- if st.button("Delete Task"):
706
- if delete_task(selected_task_id):
707
- st.success(f"Task {selected_task_id} deleted!")
708
- # Return to main board - use new API
709
- for key in list(st.query_params.keys()):
710
- del st.query_params[key]
711
- st.rerun()
712
- else:
713
- st.error(f"Failed to delete task {selected_task_id}")
714
- else:
715
- st.error(f"Task {selected_task_id} not found")
716
- # Return to main board - use new API
717
- for key in list(st.query_params.keys()):
718
- del st.query_params[key]
719
- st.rerun()
720
 
721
- else:
722
- # Display storage status notification if using memory storage
723
- if st.session_state.using_memory_storage:
724
- st.markdown('<div style="background-color: #fff3cd; color: #856404; padding: 8px; border-radius: 4px; font-size: 12px; margin-bottom: 10px;">Using in-memory storage. Tasks saved but may not persist after app restart.</div>', unsafe_allow_html=True)
725
- elif TASKS_FILE:
726
- st.markdown(f'<div style="background-color: #d4edda; color: #155724; padding: 8px; border-radius: 4px; font-size: 12px; margin-bottom: 10px;">Tasks saved to disk at: {TASKS_FILE}</div>', unsafe_allow_html=True)
727
 
728
  # Sidebar for new tasks
729
  with st.sidebar:
@@ -775,7 +743,8 @@ with st.sidebar:
775
  st.error(f"Error adding task: {str(e)}")
776
  else:
777
  st.warning("Please enter a task title")
778
- # Create hidden buttons for handling status updates from the cards
 
779
  with st.sidebar:
780
  st.markdown('<div class="sidebar-title">🔄 Update Task Status</div>', unsafe_allow_html=True)
781
 
@@ -809,126 +778,221 @@ with st.sidebar:
809
  st.success(f"Updated task {selected_id} status to {new_status}")
810
  st.rerun()
811
 
812
- # Add JavaScript for modal and status update functionality
813
- st.markdown("""
814
- <script>
815
- // Function to open task details modal
816
- function openTaskDetails(taskId) {
817
- console.log('Opening task details for: ' + taskId);
818
- // Navigate to task details page
819
- const params = new URLSearchParams(window.location.search);
820
- params.set('view', 'task_details');
821
- params.set('task_id', taskId);
822
- window.location.href = window.location.pathname + '?' + params.toString();
823
- }
824
-
825
- // Function to close task modal
826
- function closeTaskModal(taskId) {
827
- const modal = document.getElementById(`modal-${taskId}`);
828
- if (modal) {
829
- modal.style.display = 'none';
830
- }
831
- }
832
-
833
- // Close modal when clicking outside
834
- document.addEventListener('click', function(event) {
835
- if (event.target.classList.contains('modal-overlay')) {
836
- event.target.style.display = 'none';
837
- }
838
- });
839
 
840
- // Setup task cards and modals after DOM is fully loaded
841
- function setupTaskInteractions() {
842
- console.log('Setting up task interactions');
 
 
 
 
843
 
844
- // Make sure task cards are clickable
845
- document.querySelectorAll('.task-card .task-title').forEach(title => {
846
- title.addEventListener('click', function() {
847
- const taskId = this.closest('.task-card').getAttribute('data-task-id');
848
- openTaskDetails(taskId);
849
- });
850
- });
851
- }
852
-
853
- // Run setup on page load
854
- document.addEventListener('DOMContentLoaded', function() {
855
- console.log('DOM loaded, setting up task board');
856
- setTimeout(setupTaskInteractions, 500);
857
- });
858
-
859
- // Also run after Streamlit reruns
860
- const observer = new MutationObserver(function(mutations) {
861
- for (const mutation of mutations) {
862
- if (mutation.type === 'childList' && mutation.addedNodes.length) {
863
- setTimeout(setupTaskInteractions, 200);
864
- break;
865
- }
866
- }
867
- });
868
-
869
- // Start observing the document body for DOM changes
870
- document.addEventListener('DOMContentLoaded', function() {
871
- observer.observe(document.body, { childList: true, subtree: true });
872
- });
873
- </script>
874
- """, unsafe_allow_html=True)
875
- if st.session_state.tasks.empty:
876
- st.info("No tasks yet. Add your first task using the sidebar.")
877
- else:
878
- # Group tasks by status for organized display - reordering to match your screenshot
879
- statuses = ["To Do", "In Progress", "Done", "Backlog"]
880
-
881
- # Create container with reduced height
882
- st.markdown('<div style="margin-bottom: 0; padding-bottom: 0;">', unsafe_allow_html=True)
883
-
884
- # Create columns for each status
885
- cols = st.columns(len(statuses))
886
-
887
- # Display each status column
888
- for i, status_name in enumerate(statuses):
889
- with cols[i]:
890
- st.markdown(f'<h3 style="font-size: 16px; margin-bottom: 10px;">{status_name}</h3>', unsafe_allow_html=True)
891
-
892
- # Filter tasks by current status
893
- status_tasks = st.session_state.tasks[st.session_state.tasks['Status'] == status_name]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
894
 
895
- if status_tasks.empty:
896
- st.markdown("<div style='text-align:center; color:#999; padding:10px; font-size:12px;'>No tasks</div>", unsafe_allow_html=True)
897
- else:
898
- # Start the flex container for task cards
899
- st.markdown('<div class="task-container">', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
 
901
- # Render each task as a card
902
- for idx, task in status_tasks.iterrows():
903
- status_class = STATUS_COLORS.get(task['Status'], "backlog")
904
- task_id = task['ID'] if 'ID' in task and pd.notna(task['ID']) else f"#{idx+1}"
905
-
906
- # Format dates for display - shorter format
907
- date_started_str = task['Date Started'].strftime('%m/%d') if 'Date Started' in task and pd.notna(task['Date Started']) else "Not set"
908
- date_to_finish_str = task['Date to Finish'].strftime('%m/%d') if 'Date to Finish' in task and pd.notna(task['Date to Finish']) else "Not set"
909
 
910
- # Create the card HTML with inline status dropdown
911
- card_html = f"""
912
- <a href="?view=task_details&task_id={task_id}" style="text-decoration:none; color:inherit;">
913
- <div class="task-card" data-task-id="{task_id}">
914
- <div class="card-header {status_class}">
915
- <span class="task-id">{task_id}</span>
916
- </div>
917
- <div class="task-title">{task['Title']}</div>
918
- <div class="task-assignee">{task['Assignee']}</div>
919
- <div class="task-dates">
920
- <span class="date-display">Start: {date_started_str}</span>
921
- <span class="date-display">Finish: {date_to_finish_str}</span>
 
 
 
 
 
 
 
 
 
 
922
  </div>
923
- </div>
924
- </a>
925
- """
 
 
926
 
927
- # Render the card
928
- st.markdown(card_html, unsafe_allow_html=True)
929
-
930
- # Close the container
931
- st.markdown('</div>', unsafe_allow_html=True)
932
-
933
- # Close container
934
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
135
  justify-content: space-between;
136
  }
137
 
138
+ /* Task Details Container */
139
+ .task-details-container {
140
+ border: 1px solid var(--border-color);
141
+ border-radius: 8px;
142
+ padding: 20px;
143
+ margin-top: 16px;
144
+ background-color: white;
145
+ box-shadow: 0 2px 10px rgba(0,0,0,0.08);
146
+ }
147
+
148
+ .task-details-header {
149
+ display: flex;
150
+ justify-content: space-between;
151
+ align-items: center;
152
+ margin-bottom: 16px;
153
+ padding-bottom: 12px;
154
+ border-bottom: 1px solid #eee;
155
+ }
156
+
157
+ .task-details-title {
158
+ font-size: 24px;
159
+ font-weight: 600;
160
+ color: var(--primary-color);
161
+ margin: 0;
162
+ }
163
+
164
+ .task-details-section {
165
+ margin-bottom: 16px;
166
+ }
167
+
168
+ .task-details-section h3 {
169
+ font-size: 18px;
170
+ margin-bottom: 8px;
171
+ color: var(--secondary-color);
172
+ }
173
+
174
+ .task-meta-grid {
175
+ display: grid;
176
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
177
+ gap: 16px;
178
+ margin-bottom: 20px;
179
+ }
180
+
181
+ .task-meta-item {
182
+ padding: 8px 12px;
183
+ border-radius: 4px;
184
+ background-color: #f5f5f5;
185
+ }
186
+
187
+ .task-meta-label {
188
+ font-size: 12px;
189
+ color: #666;
190
+ margin-bottom: 4px;
191
+ }
192
+
193
+ .task-meta-value {
194
+ font-size: 16px;
195
+ font-weight: 500;
196
+ }
197
+
198
+ .task-description {
199
+ background-color: #f9f9f9;
200
+ padding: 16px;
201
+ border-radius: 4px;
202
+ margin-bottom: 20px;
203
+ min-height: 100px;
204
+ }
205
+
206
  /* Modal Dialog Styling */
207
  .modal-overlay {
208
  display: none;
 
291
  padding: 8px 12px;
292
  border-radius: 4px;
293
  font-weight: 500;
 
294
  }
295
 
296
  .stButton > button:hover {
297
  background-color: var(--primary-color);
298
  }
299
 
300
+ /* Full width buttons */
301
+ .full-width-button button {
302
+ width: 100%;
303
+ }
304
+
305
  /* Sidebar styling */
306
  .sidebar-title {
307
  font-size: 1.2rem;
 
323
  font-size: 10px;
324
  font-weight: 500;
325
  color: white;
326
+ margin-left: 8px;
327
  }
328
 
329
  .status-pill.todo {
 
354
  margin-bottom: 10px;
355
  }
356
 
357
+ /* Task progress bar */
358
+ .progress-container {
359
+ width: 100%;
360
+ background-color: #f1f1f1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  border-radius: 4px;
362
+ margin-top: 8px;
363
  }
364
 
365
+ .progress-bar {
366
+ height: 6px;
367
+ border-radius: 4px;
 
 
 
 
 
 
 
 
368
  }
369
 
370
+ .progress-bar.todo {
371
+ background-color: var(--todo-color);
372
+ width: 0%;
373
  }
374
 
375
+ .progress-bar.in-progress {
376
+ background-color: var(--progress-color);
377
+ width: 50%;
378
  }
379
 
380
+ .progress-bar.done {
381
+ background-color: var(--done-color);
382
+ width: 100%;
383
  }
384
 
385
+ .progress-bar.backlog {
386
+ background-color: var(--backlog-color);
387
+ width: 10%;
388
  }
389
 
390
+ /* Task details sections */
391
+ .section-divider {
392
+ height: 1px;
393
+ background-color: #eee;
394
+ margin: 15px 0;
395
  }
396
  </style>
397
  """, unsafe_allow_html=True)
 
548
  except Exception as e:
549
  print(f"Error saving tasks: {str(e)}")
550
  # Store in memory as fallback
551
+ try:
552
+ st.session_state.in_memory_tasks = tasks_dict
553
+ except:
554
+ pass
555
  st.session_state.using_memory_storage = True
556
  st.warning(f"Error saving tasks: {str(e)}. Tasks stored in memory instead.")
557
  return False
 
660
  return True
661
  return False
662
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  # Initialize session state ONLY ONCE
664
  if 'tasks' not in st.session_state:
665
  st.session_state.tasks = load_tasks()
666
 
667
+ if 'selected_task_id' not in st.session_state:
668
+ st.session_state.selected_task_id = None
669
 
670
  if 'in_memory_tasks' not in st.session_state:
671
  st.session_state.in_memory_tasks = []
 
684
  # Load assignees
685
  assignee_list = load_assignees()
686
 
687
+ # Application Header
688
+ st.markdown("<h1 style='font-size: 24px; color: var(--primary-color);'>📋 Task Board</h1>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
689
 
690
+ # Display storage status notification if using memory storage
691
+ if st.session_state.using_memory_storage:
692
+ st.markdown('<div style="background-color: #fff3cd; color: #856404; padding: 8px; border-radius: 4px; font-size: 12px; margin-bottom: 10px;">Using in-memory storage. Tasks saved but may not persist after app restart.</div>', unsafe_allow_html=True)
693
+ elif TASKS_FILE:
694
+ st.markdown(f'<div style="background-color: #d4edda; color: #155724; padding: 8px; border-radius: 4px; font-size: 12px; margin-bottom: 10px;">Tasks saved to disk at: {TASKS_FILE}</div>', unsafe_allow_html=True)
 
695
 
696
  # Sidebar for new tasks
697
  with st.sidebar:
 
743
  st.error(f"Error adding task: {str(e)}")
744
  else:
745
  st.warning("Please enter a task title")
746
+
747
+ # Sidebar additional options for task status updates
748
  with st.sidebar:
749
  st.markdown('<div class="sidebar-title">🔄 Update Task Status</div>', unsafe_allow_html=True)
750
 
 
778
  st.success(f"Updated task {selected_id} status to {new_status}")
779
  st.rerun()
780
 
781
+ # MAIN CONTENT AREA
782
+ # Check for selected task ID from URL parameters
783
+ selected_task_id = st.query_params.get('task_id', None)
784
+
785
+ # If a task is selected, show task details
786
+ if selected_task_id:
787
+ # Get task details
788
+ task_df = st.session_state.tasks[st.session_state.tasks['ID'] == selected_task_id]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
789
 
790
+ if not task_df.empty:
791
+ # Back to board button
792
+ if st.button("← Back to Board", key="back_btn"):
793
+ # Clear the task_id parameter and rerun
794
+ for key in list(st.query_params.keys()):
795
+ del st.query_params[key]
796
+ st.rerun()
797
 
798
+ # Get task data
799
+ task = task_df.iloc[0]
800
+ status_class = STATUS_COLORS.get(task['Status'], "backlog")
801
+
802
+ # Create the task details container
803
+ st.markdown('<div class="task-details-container">', unsafe_allow_html=True)
804
+
805
+ # Task header with title and status
806
+ st.markdown(f"""
807
+ <div class="task-details-header">
808
+ <h2 class="task-details-title">{task['Title']}
809
+ <span class="status-pill {status_class}">{task['Status']}</span>
810
+ </h2>
811
+ </div>
812
+ """, unsafe_allow_html=True)
813
+
814
+ # Task metadata in grid layout
815
+ st.markdown('<div class="task-meta-grid">', unsafe_allow_html=True)
816
+
817
+ # Task ID
818
+ st.markdown(f"""
819
+ <div class="task-meta-item">
820
+ <div class="task-meta-label">Task ID</div>
821
+ <div class="task-meta-value">{selected_task_id}</div>
822
+ </div>
823
+ """, unsafe_allow_html=True)
824
+
825
+ # Assignee
826
+ st.markdown(f"""
827
+ <div class="task-meta-item">
828
+ <div class="task-meta-label">Assignee</div>
829
+ <div class="task-meta-value">{task['Assignee']}</div>
830
+ </div>
831
+ """, unsafe_allow_html=True)
832
+
833
+ # Format dates for display
834
+ date_started_str = task['Date Started'].strftime('%m/%d/%Y') if 'Date Started' in task and pd.notna(task['Date Started']) else "Not set"
835
+ date_to_finish_str = task['Date to Finish'].strftime('%m/%d/%Y') if 'Date to Finish' in task and pd.notna(task['Date to Finish']) else "Not set"
836
+ due_date_str = task['Due Date'].strftime('%m/%d/%Y') if 'Due Date' in task and pd.notna(task['Due Date']) else "Not set"
837
+
838
+ # Date Started
839
+ st.markdown(f"""
840
+ <div class="task-meta-item">
841
+ <div class="task-meta-label">Date Started</div>
842
+ <div class="task-meta-value">{date_started_str}</div>
843
+ </div>
844
+ """, unsafe_allow_html=True)
845
+
846
+ # Date to Finish
847
+ st.markdown(f"""
848
+ <div class="task-meta-item">
849
+ <div class="task-meta-label">Date to Finish</div>
850
+ <div class="task-meta-value">{date_to_finish_str}</div>
851
+ </div>
852
+ """, unsafe_allow_html=True)
853
+
854
+ # Due Date
855
+ st.markdown(f"""
856
+ <div class="task-meta-item">
857
+ <div class="task-meta-label">Due Date</div>
858
+ <div class="task-meta-value">{due_date_str}</div>
859
+ </div>
860
+ """, unsafe_allow_html=True)
861
+
862
+ st.markdown('</div>', unsafe_allow_html=True)
863
+
864
+ # Task progress visualization
865
+ st.markdown(f"""
866
+ <div class="progress-container">
867
+ <div class="progress-bar {status_class}"></div>
868
+ </div>
869
+ """, unsafe_allow_html=True)
870
+
871
+ # Description section
872
+ st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
873
+ st.markdown('<div class="task-details-section">', unsafe_allow_html=True)
874
+ st.markdown('<h3>Description</h3>', unsafe_allow_html=True)
875
+ st.markdown('<div class="task-description">', unsafe_allow_html=True)
876
+ st.markdown(f"{task['Description'] if task['Description'] else 'No description provided.'}", unsafe_allow_html=True)
877
+ st.markdown('</div>', unsafe_allow_html=True)
878
+ st.markdown('</div>', unsafe_allow_html=True)
879
+
880
+ # Actions section
881
+ st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
882
+ st.markdown('<div class="task-details-section">', unsafe_allow_html=True)
883
+ st.markdown('<h3>Actions</h3>', unsafe_allow_html=True)
884
+
885
+ # Create columns for actions
886
+ col1, col2 = st.columns(2)
887
+
888
+ with col1:
889
+ # Status update form
890
+ new_status = st.selectbox(
891
+ "Update Status",
892
+ ["Backlog", "To Do", "In Progress", "Done"],
893
+ index=["Backlog", "To Do", "In Progress", "Done"].index(task['Status']) if task['Status'] in ["Backlog", "To Do", "In Progress", "Done"] else 0
894
+ )
895
 
896
+ if st.button("Save Status Change", use_container_width=True):
897
+ if new_status != task['Status']:
898
+ task_idx = task_df.index[0]
899
+ update_task(task_idx, 'Status', new_status)
900
+ st.success(f"Updated task status to {new_status}")
901
+ st.rerun()
902
+
903
+ with col2:
904
+ if st.button("Delete Task", use_container_width=True):
905
+ if delete_task(selected_task_id):
906
+ st.success(f"Task {selected_task_id} deleted!")
907
+ # Return to main board
908
+ for key in list(st.query_params.keys()):
909
+ del st.query_params[key]
910
+ st.rerun()
911
+ else:
912
+ st.error(f"Failed to delete task {selected_task_id}")
913
+
914
+ st.markdown('</div>', unsafe_allow_html=True)
915
+
916
+ # Close the task details container
917
+ st.markdown('</div>', unsafe_allow_html=True)
918
+ else:
919
+ st.error(f"Task {selected_task_id} not found")
920
+ # Return to main board
921
+ if st.button("Back to Board"):
922
+ for key in list(st.query_params.keys()):
923
+ del st.query_params[key]
924
+ st.rerun()
925
+
926
+ else:
927
+ # Main board view showing all tasks by status
928
+ if st.session_state.tasks.empty:
929
+ st.info("No tasks yet. Add your first task using the sidebar.")
930
+ else:
931
+ # Group tasks by status for organized display - reordering to match desired layout
932
+ statuses = ["To Do", "In Progress", "Done", "Backlog"]
933
+
934
+ # Create container with reduced height
935
+ st.markdown('<div style="margin-bottom: 0; padding-bottom: 0;">', unsafe_allow_html=True)
936
+
937
+ # Create columns for each status
938
+ cols = st.columns(len(statuses))
939
+
940
+ # Display each status column
941
+ for i, status_name in enumerate(statuses):
942
+ with cols[i]:
943
+ st.markdown(f'<h3 style="font-size: 16px; margin-bottom: 10px;">{status_name}</h3>', unsafe_allow_html=True)
944
 
945
+ # Filter tasks by current status
946
+ status_tasks = st.session_state.tasks[st.session_state.tasks['Status'] == status_name]
947
+
948
+ if status_tasks.empty:
949
+ st.markdown("<div style='text-align:center; color:#999; padding:10px; font-size:12px;'>No tasks</div>", unsafe_allow_html=True)
950
+ else:
951
+ # Start the flex container for task cards
952
+ st.markdown('<div class="task-container">', unsafe_allow_html=True)
953
 
954
+ # Render each task as a card
955
+ for idx, task in status_tasks.iterrows():
956
+ status_class = STATUS_COLORS.get(task['Status'], "backlog")
957
+ task_id = task['ID'] if 'ID' in task and pd.notna(task['ID']) else f"#{idx+1}"
958
+
959
+ # Format dates for display - shorter format
960
+ date_started_str = task['Date Started'].strftime('%m/%d') if 'Date Started' in task and pd.notna(task['Date Started']) else "Not set"
961
+ date_to_finish_str = task['Date to Finish'].strftime('%m/%d') if 'Date to Finish' in task and pd.notna(task['Date to Finish']) else "Not set"
962
+
963
+ # Create the card HTML with direct query parameter link
964
+ card_html = f"""
965
+ <a href="?task_id={task_id}" style="text-decoration:none; color:inherit;">
966
+ <div class="task-card" data-task-id="{task_id}">
967
+ <div class="card-header {status_class}">
968
+ <span class="task-id">{task_id}</span>
969
+ </div>
970
+ <div class="task-title">{task['Title']}</div>
971
+ <div class="task-assignee">{task['Assignee']}</div>
972
+ <div class="task-dates">
973
+ <span class="date-display">Start: {date_started_str}</span>
974
+ <span class="date-display">Finish: {date_to_finish_str}</span>
975
+ </div>
976
  </div>
977
+ </a>
978
+ """
979
+
980
+ # Render the card
981
+ st.markdown(card_html, unsafe_allow_html=True)
982
 
983
+ # Close the container
984
+ st.markdown('</div>', unsafe_allow_html=True)
985
+
986
+ # Close container
987
+ st.markdown('</div>', unsafe_allow_html=True)
988
+
989
+ # Add JavaScript for better interactivity
990
+ st.markdown("""
991
+ <script>
992
+ // Make task cards clickable - will navigate to the task details view
993
+ document.addEventListener('DOMContentLoaded', function() {
994
+ console.log('Setting up task board interactions');
995
+ // Add any additional JavaScript for enhancing the task board
996
+ });
997
+ </script>
998
+ """, unsafe_allow_html=True)