Update app.py
Browse files
app.py
CHANGED
|
@@ -18,54 +18,50 @@ st.set_page_config(
|
|
| 18 |
layout="wide"
|
| 19 |
)
|
| 20 |
|
| 21 |
-
# Apply custom CSS for a
|
| 22 |
st.markdown("""
|
| 23 |
<style>
|
| 24 |
/* Main theme colors */
|
| 25 |
:root {
|
| 26 |
--primary-color: #0A2647;
|
| 27 |
--secondary-color: #144272;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
--text-color: #333;
|
| 29 |
--todo-color: #03A9F4;
|
| 30 |
--progress-color: #FF5722;
|
| 31 |
--done-color: #4CAF50;
|
| 32 |
-
--border-color: #e0e0e0;
|
| 33 |
--backlog-color: #8BC34A;
|
| 34 |
-
--card-bg: #ffffff;
|
| 35 |
}
|
| 36 |
|
| 37 |
/* Header styling */
|
| 38 |
.main-header {
|
| 39 |
-
background
|
| 40 |
color: white;
|
| 41 |
-
padding: 1rem
|
| 42 |
-
border-radius: 5px;
|
| 43 |
-
margin-bottom: 1.5rem;
|
| 44 |
text-align: center;
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
|
| 47 |
.main-header h1 {
|
| 48 |
margin: 0;
|
| 49 |
-
font-size:
|
| 50 |
}
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
font-size: 1.2rem;
|
| 55 |
-
opacity: 0.9;
|
| 56 |
-
letter-spacing: 1px;
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
/* Task Cards */
|
| 60 |
-
.task-grid {
|
| 61 |
display: flex;
|
| 62 |
flex-wrap: wrap;
|
| 63 |
-
gap:
|
| 64 |
-
|
| 65 |
}
|
| 66 |
|
| 67 |
.task-card {
|
| 68 |
-
|
|
|
|
| 69 |
border: 1px solid var(--border-color);
|
| 70 |
border-radius: 4px;
|
| 71 |
overflow: hidden;
|
|
@@ -73,13 +69,6 @@ st.markdown("""
|
|
| 73 |
display: flex;
|
| 74 |
flex-direction: column;
|
| 75 |
background-color: var(--card-bg);
|
| 76 |
-
height: 150px;
|
| 77 |
-
transition: transform 0.2s, box-shadow 0.2s;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
.task-card:hover {
|
| 81 |
-
transform: translateY(-3px);
|
| 82 |
-
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
| 83 |
}
|
| 84 |
|
| 85 |
.card-header {
|
|
@@ -131,6 +120,28 @@ st.markdown("""
|
|
| 131 |
margin-bottom: 10px;
|
| 132 |
}
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
/* Form styling */
|
| 135 |
.sidebar .block-container {
|
| 136 |
padding-top: 2rem;
|
|
@@ -156,18 +167,17 @@ st.markdown("""
|
|
| 156 |
background-color: var(--primary-color);
|
| 157 |
}
|
| 158 |
|
| 159 |
-
/*
|
| 160 |
-
.
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
| 162 |
}
|
| 163 |
|
| 164 |
-
/*
|
| 165 |
-
.status-
|
| 166 |
-
|
| 167 |
-
padding: 5px;
|
| 168 |
-
border-radius: 5px;
|
| 169 |
-
border: 1px solid #ddd;
|
| 170 |
-
margin-top: 5px;
|
| 171 |
}
|
| 172 |
</style>
|
| 173 |
""", unsafe_allow_html=True)
|
|
@@ -304,7 +314,7 @@ def generate_task_id():
|
|
| 304 |
|
| 305 |
# Extract existing IDs and find the maximum
|
| 306 |
try:
|
| 307 |
-
existing_ids = [int(task_id.replace('#', '')) for task_id in st.session_state.tasks['ID'].tolist() if task_id.startswith('#')]
|
| 308 |
if existing_ids:
|
| 309 |
return f"#{max(existing_ids) + 1}"
|
| 310 |
else:
|
|
@@ -320,17 +330,12 @@ if 'tasks' not in st.session_state:
|
|
| 320 |
# Load assignees directly from the file
|
| 321 |
assignee_list = load_assignees()
|
| 322 |
|
| 323 |
-
#
|
| 324 |
-
st.markdown(""
|
| 325 |
-
<div class="main-header">
|
| 326 |
-
<span class="company-name">BROADCAST SOLUTIONS</span>
|
| 327 |
-
<h1>Task Board</h1>
|
| 328 |
-
</div>
|
| 329 |
-
""", unsafe_allow_html=True)
|
| 330 |
|
| 331 |
# Sidebar for new tasks
|
| 332 |
with st.sidebar:
|
| 333 |
-
st.
|
| 334 |
title = st.text_input("Task Title")
|
| 335 |
description = st.text_area("Description")
|
| 336 |
assignee = st.selectbox("Assignee", assignee_list)
|
|
@@ -358,14 +363,14 @@ with st.sidebar:
|
|
| 358 |
else:
|
| 359 |
st.warning("Please enter a task title")
|
| 360 |
|
| 361 |
-
# Display tasks in a
|
| 362 |
if st.session_state.tasks.empty:
|
| 363 |
st.info("No tasks yet. Add your first task using the sidebar.")
|
| 364 |
else:
|
| 365 |
-
# Start the
|
| 366 |
-
st.markdown('<div class="task-
|
| 367 |
|
| 368 |
-
# Render each task as a
|
| 369 |
for idx, task in st.session_state.tasks.iterrows():
|
| 370 |
status_class = STATUS_COLORS.get(task['Status'], "backlog")
|
| 371 |
task_id = task['ID'] if 'ID' in task and pd.notna(task['ID']) else f"#{idx+1}"
|
|
@@ -384,28 +389,35 @@ else:
|
|
| 384 |
|
| 385 |
# Render the card
|
| 386 |
st.markdown(card_html, unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
with st.container():
|
| 391 |
-
st.markdown(f"""
|
| 392 |
-
<div style="display: none;">
|
| 393 |
-
<p>Update {task_id}</p>
|
| 394 |
-
</div>
|
| 395 |
-
""", unsafe_allow_html=True)
|
| 396 |
-
|
| 397 |
new_status = st.selectbox(
|
| 398 |
-
f"
|
| 399 |
["Backlog", "To Do", "In Progress", "Done"],
|
| 400 |
index=["Backlog", "To Do", "In Progress", "Done"].index(task['Status']) if task['Status'] in ["Backlog", "To Do", "In Progress", "Done"] else 0,
|
| 401 |
key=f"status_{idx}",
|
| 402 |
-
label_visibility="
|
| 403 |
)
|
| 404 |
|
| 405 |
if new_status != task['Status']:
|
| 406 |
st.session_state.tasks.at[idx, 'Status'] = new_status
|
| 407 |
save_tasks() # Save after updating
|
| 408 |
st.rerun()
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
|
|
|
| 18 |
layout="wide"
|
| 19 |
)
|
| 20 |
|
| 21 |
+
# Apply custom CSS for a horizontal board layout
|
| 22 |
st.markdown("""
|
| 23 |
<style>
|
| 24 |
/* Main theme colors */
|
| 25 |
:root {
|
| 26 |
--primary-color: #0A2647;
|
| 27 |
--secondary-color: #144272;
|
| 28 |
+
--highlight-color: #2C74B3;
|
| 29 |
+
--header-bg: #0f2942;
|
| 30 |
+
--card-bg: #ffffff;
|
| 31 |
+
--border-color: #e0e0e0;
|
| 32 |
--text-color: #333;
|
| 33 |
--todo-color: #03A9F4;
|
| 34 |
--progress-color: #FF5722;
|
| 35 |
--done-color: #4CAF50;
|
|
|
|
| 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;
|
| 57 |
flex-wrap: wrap;
|
| 58 |
+
gap: 16px;
|
| 59 |
+
align-items: flex-start;
|
| 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 |
display: flex;
|
| 70 |
flex-direction: column;
|
| 71 |
background-color: var(--card-bg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
}
|
| 73 |
|
| 74 |
.card-header {
|
|
|
|
| 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 */
|
| 146 |
.sidebar .block-container {
|
| 147 |
padding-top: 2rem;
|
|
|
|
| 167 |
background-color: var(--primary-color);
|
| 168 |
}
|
| 169 |
|
| 170 |
+
/* Sidebar styling */
|
| 171 |
+
.sidebar-title {
|
| 172 |
+
font-size: 1.2rem;
|
| 173 |
+
font-weight: 600;
|
| 174 |
+
margin-bottom: 1rem;
|
| 175 |
+
color: var(--primary-color);
|
| 176 |
}
|
| 177 |
|
| 178 |
+
/* Hide duplicated elements */
|
| 179 |
+
.status-hidden {
|
| 180 |
+
display: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
}
|
| 182 |
</style>
|
| 183 |
""", unsafe_allow_html=True)
|
|
|
|
| 314 |
|
| 315 |
# Extract existing IDs and find the maximum
|
| 316 |
try:
|
| 317 |
+
existing_ids = [int(task_id.replace('#', '')) for task_id in st.session_state.tasks['ID'].tolist() if isinstance(task_id, str) and task_id.startswith('#')]
|
| 318 |
if existing_ids:
|
| 319 |
return f"#{max(existing_ids) + 1}"
|
| 320 |
else:
|
|
|
|
| 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)
|
| 339 |
title = st.text_input("Task Title")
|
| 340 |
description = st.text_area("Description")
|
| 341 |
assignee = st.selectbox("Assignee", assignee_list)
|
|
|
|
| 363 |
else:
|
| 364 |
st.warning("Please enter a task title")
|
| 365 |
|
| 366 |
+
# Display tasks in a horizontal grid layout
|
| 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}"
|
|
|
|
| 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
|