Kikulika commited on
Commit
a51a31d
·
verified ·
1 Parent(s): 03b85d1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -145
app.py CHANGED
@@ -3,6 +3,7 @@ import pandas as pd
3
  from datetime import datetime
4
  import os
5
  import sys
 
6
 
7
  # Disable Streamlit's telemetry and usage statistics
8
  os.environ['STREAMLIT_ANALYTICS_ENABLED'] = 'false'
@@ -17,34 +18,35 @@ st.set_page_config(
17
  layout="wide"
18
  )
19
 
20
- # Apply custom CSS for a more professional broadcast look
21
  st.markdown("""
22
  <style>
23
- /* Main theme colors for broadcast industry feel */
24
  :root {
25
  --primary-color: #0A2647;
26
  --secondary-color: #144272;
27
- --accent-color: #205295;
28
- --highlight-color: #2C74B3;
29
- --text-color: #0A2647;
30
- --card-bg-todo: #f0f7ff;
31
- --card-bg-progress: #fff7e6;
32
- --card-bg-done: #efffef;
 
33
  }
34
 
35
  /* Header styling */
36
  .main-header {
37
- background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
38
  color: white;
39
  padding: 1rem 2rem;
40
  border-radius: 5px;
41
- margin-bottom: 2rem;
42
  text-align: center;
43
  }
44
 
45
  .main-header h1 {
46
  margin: 0;
47
- font-size: 2.5rem;
48
  }
49
 
50
  .company-name {
@@ -63,84 +65,70 @@ st.markdown("""
63
  }
64
 
65
  .task-card {
66
- flex: 0 0 300px;
67
- background-color: white;
68
- border-radius: 12px;
69
- padding: 15px;
70
- box-shadow: 0 4px 8px rgba(0,0,0,0.08);
71
- transition: transform 0.2s, box-shadow 0.2s;
72
- position: relative;
73
  overflow: hidden;
 
 
 
 
 
 
74
  }
75
 
76
  .task-card:hover {
77
- transform: translateY(-5px);
78
- box-shadow: 0 6px 12px rgba(0,0,0,0.12);
79
- }
80
-
81
- .task-card.todo {
82
- border-top: 5px solid #3498db;
83
- background-color: var(--card-bg-todo);
84
- }
85
-
86
- .task-card.in-progress {
87
- border-top: 5px solid #f39c12;
88
- background-color: var(--card-bg-progress);
89
- }
90
-
91
- .task-card.done {
92
- border-top: 5px solid #2ecc71;
93
- background-color: var(--card-bg-done);
94
- }
95
-
96
- .task-title {
97
- font-size: 1.2rem;
98
- font-weight: 600;
99
- color: var(--text-color);
100
- margin-bottom: 10px;
101
  }
102
 
103
- .task-meta {
 
 
 
104
  display: flex;
105
  justify-content: space-between;
106
- color: #555;
107
- font-size: 0.9rem;
108
- margin-bottom: 12px;
109
  }
110
 
111
- .task-desc {
112
- color: #444;
113
- font-size: 0.95rem;
114
- margin-bottom: 15px;
115
  }
116
 
117
- .task-footer {
118
- display: flex;
119
- justify-content: space-between;
120
- align-items: center;
121
- margin-top: 10px;
122
  }
123
 
124
- .status-pill {
125
- padding: 4px 10px;
126
- border-radius: 15px;
127
- font-size: 0.8rem;
128
- font-weight: 500;
129
  }
130
 
131
- .status-pill.todo {
132
- background-color: #e1f0ff;
133
- color: #3498db;
134
  }
135
 
136
- .status-pill.in-progress {
137
- background-color: #fff4e0;
138
- color: #f39c12;
139
  }
140
 
141
- .status-pill.done {
142
- background-color: #e0ffe0;
143
- color: #2ecc71;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  }
145
 
146
  /* Form styling */
@@ -156,7 +144,7 @@ st.markdown("""
156
  /* Button styling */
157
  .stButton > button {
158
  width: 100%;
159
- background-color: var(--accent-color);
160
  color: white;
161
  border: none;
162
  padding: 10px 15px;
@@ -165,7 +153,7 @@ st.markdown("""
165
  }
166
 
167
  .stButton > button:hover {
168
- background-color: var(--highlight-color);
169
  }
170
 
171
  /* Hide storage path message */
@@ -184,37 +172,22 @@ st.markdown("""
184
  </style>
185
  """, unsafe_allow_html=True)
186
 
187
- # Color mapping
188
  STATUS_COLORS = {
189
  "To Do": "todo",
190
  "In Progress": "in-progress",
191
- "Done": "done"
 
192
  }
193
 
194
  # Set up paths
195
  TASKS_FILE = "tasks.csv" # Simplified path for tasks
196
-
197
- # Check all possible locations for Assignee.txt
198
- def find_assignee_file():
199
- possible_locations = [
200
- "Assignee.txt", # Root directory
201
- "./Assignee.txt", # Explicit current directory
202
- "/app/Assignee.txt", # Docker app directory
203
- os.path.join(os.getcwd(), "Assignee.txt"), # Full path to current directory
204
- "/data/Assignee.txt", # Data directory
205
- "../Assignee.txt", # Parent directory
206
- ]
207
-
208
- for location in possible_locations:
209
- if os.path.exists(location):
210
- return location
211
-
212
- return None
213
 
214
  def create_empty_df():
215
  """Create an empty DataFrame with the required columns"""
216
  return pd.DataFrame(columns=[
217
- 'Title', 'Description', 'Assignee', 'Status', 'Due Date'
218
  ])
219
 
220
  def load_tasks():
@@ -224,6 +197,9 @@ def load_tasks():
224
  df = pd.read_csv(TASKS_FILE, parse_dates=['Due Date'])
225
  if df.empty:
226
  return create_empty_df()
 
 
 
227
  return df
228
  else:
229
  return create_empty_df()
@@ -240,51 +216,108 @@ def save_tasks():
240
  return False
241
 
242
  def load_assignees():
243
- """Load assignees from text file with extensive debugging and fallbacks"""
244
- default_assignees = ["John Doe", "Jane Smith", "Alex Johnson", "Maria Garcia"]
 
245
 
246
- # Find the Assignee.txt file
247
- assignee_file_path = find_assignee_file()
 
248
 
249
- if not assignee_file_path:
250
- # Debugging: log available files in the current directory
251
- files_in_cwd = os.listdir(os.getcwd())
252
- print(f"Files in current directory: {files_in_cwd}")
253
- return default_assignees
 
 
 
254
 
 
 
 
 
 
 
 
 
 
 
255
  try:
256
- assignees = []
257
- with open(assignee_file_path, "r") as f:
258
  content = f.read()
259
- # Debug: Print the content of the file
260
- print(f"Content of {assignee_file_path}: {content}")
261
 
 
 
262
  lines = [line.strip() for line in content.split('\n') if line.strip()]
 
263
  for line in lines:
264
  if '-' in line:
 
265
  name_part = line.split('-')[0].strip()
266
- if ' ' in name_part:
267
  assignees.append(name_part)
268
- else:
269
- # If there's no dash, just use the whole line if it has a space
270
- if ' ' in line:
271
- assignees.append(line)
272
-
273
- # If we successfully parsed assignees, return them
274
- if assignees:
 
 
 
 
275
  return assignees
 
 
 
 
 
 
 
 
 
 
 
276
 
277
- # If no assignees were extracted, return defaults
278
- return default_assignees
 
 
 
 
 
 
 
 
 
279
  except Exception as e:
280
- print(f"Error loading assignees: {str(e)}")
281
- return default_assignees
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
 
283
  # Initialize session state ONLY ONCE
284
  if 'tasks' not in st.session_state:
285
  st.session_state.tasks = load_tasks()
286
 
287
- # Load assignees once and reuse
288
  assignee_list = load_assignees()
289
 
290
  # Custom header
@@ -301,12 +334,14 @@ with st.sidebar:
301
  title = st.text_input("Task Title")
302
  description = st.text_area("Description")
303
  assignee = st.selectbox("Assignee", assignee_list)
304
- status = st.selectbox("Status", ["To Do", "In Progress", "Done"])
305
  due_date = st.date_input("Due Date")
306
 
307
  if st.button("Add Task"):
308
  if title: # Basic validation
 
309
  new_task = {
 
310
  'Title': title,
311
  'Description': description,
312
  'Assignee': assignee,
@@ -323,52 +358,48 @@ with st.sidebar:
323
  else:
324
  st.warning("Please enter a task title")
325
 
326
- # Display tasks in a horizontal grid
327
  if st.session_state.tasks.empty:
328
  st.info("No tasks yet. Add your first task using the sidebar.")
329
  else:
330
  # Start the grid container
331
  st.markdown('<div class="task-grid">', unsafe_allow_html=True)
332
 
333
- # Render each task as a card in the grid
334
  for idx, task in st.session_state.tasks.iterrows():
335
- status_class = STATUS_COLORS.get(task['Status'], "todo")
 
336
 
337
- # Format the date
338
- due_date = task['Due Date']
339
- if isinstance(due_date, datetime):
340
- formatted_date = due_date.strftime('%Y-%m-%d')
341
- else:
342
- formatted_date = due_date
343
-
344
  # Create the card HTML
345
  card_html = f"""
346
- <div class="task-card {status_class}">
347
- <div class="task-title">{task['Title']}</div>
348
- <div class="task-meta">
349
- <span><strong>Assignee:</strong> {task['Assignee']}</span>
350
- <span><strong>Due:</strong> {formatted_date}</span>
351
- </div>
352
- <div class="task-desc">{task['Description']}</div>
353
- <div class="task-footer">
354
- <span class="status-pill {status_class}">{task['Status']}</span>
355
  </div>
 
 
356
  </div>
357
  """
358
 
359
  # Render the card
360
  st.markdown(card_html, unsafe_allow_html=True)
361
 
362
- # Hidden status update below each card
363
- # We'll use columns to make this less prominent
364
- cols = st.columns([3, 1])
365
- with cols[1]:
 
 
 
 
 
366
  new_status = st.selectbox(
367
- "Update Status", # Provide a label but hide it
368
- ["To Do", "In Progress", "Done"],
369
- index=["To Do", "In Progress", "Done"].index(task['Status']),
370
  key=f"status_{idx}",
371
- label_visibility="hidden" # Use hidden instead of collapsed
372
  )
373
 
374
  if new_status != task['Status']:
 
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'
 
18
  layout="wide"
19
  )
20
 
21
+ # Apply custom CSS for a backlog-style board
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: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
40
  color: white;
41
  padding: 1rem 2rem;
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: 2.2rem;
50
  }
51
 
52
  .company-name {
 
65
  }
66
 
67
  .task-card {
68
+ flex: 0 0 180px;
69
+ border: 1px solid var(--border-color);
70
+ border-radius: 4px;
 
 
 
 
71
  overflow: hidden;
72
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
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 {
86
+ padding: 8px;
87
+ color: white;
88
+ font-weight: bold;
89
  display: flex;
90
  justify-content: space-between;
91
+ align-items: center;
 
 
92
  }
93
 
94
+ .card-header.todo {
95
+ background-color: var(--todo-color);
 
 
96
  }
97
 
98
+ .card-header.in-progress {
99
+ background-color: var(--progress-color);
 
 
 
100
  }
101
 
102
+ .card-header.done {
103
+ background-color: var(--done-color);
 
 
 
104
  }
105
 
106
+ .card-header.backlog {
107
+ background-color: var(--backlog-color);
 
108
  }
109
 
110
+ .task-id {
111
+ font-size: 14px;
112
+ font-weight: bold;
113
  }
114
 
115
+ .task-title {
116
+ padding: 10px;
117
+ font-weight: bold;
118
+ color: var(--text-color);
119
+ font-size: 14px;
120
+ height: 40px;
121
+ overflow: hidden;
122
+ text-overflow: ellipsis;
123
+ }
124
+
125
+ .task-assignee {
126
+ padding: 0 10px;
127
+ color: #666;
128
+ font-size: 12px;
129
+ font-style: italic;
130
+ margin-top: auto;
131
+ margin-bottom: 10px;
132
  }
133
 
134
  /* Form styling */
 
144
  /* Button styling */
145
  .stButton > button {
146
  width: 100%;
147
+ background-color: var(--secondary-color);
148
  color: white;
149
  border: none;
150
  padding: 10px 15px;
 
153
  }
154
 
155
  .stButton > button:hover {
156
+ background-color: var(--primary-color);
157
  }
158
 
159
  /* Hide storage path message */
 
172
  </style>
173
  """, unsafe_allow_html=True)
174
 
175
+ # Status color mapping
176
  STATUS_COLORS = {
177
  "To Do": "todo",
178
  "In Progress": "in-progress",
179
+ "Done": "done",
180
+ "Backlog": "backlog"
181
  }
182
 
183
  # Set up paths
184
  TASKS_FILE = "tasks.csv" # Simplified path for tasks
185
+ ASSIGNEE_FILE = "Assignee.txt" # Direct path to Assignee.txt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
  def create_empty_df():
188
  """Create an empty DataFrame with the required columns"""
189
  return pd.DataFrame(columns=[
190
+ 'ID', 'Title', 'Description', 'Assignee', 'Status', 'Due Date'
191
  ])
192
 
193
  def load_tasks():
 
197
  df = pd.read_csv(TASKS_FILE, parse_dates=['Due Date'])
198
  if df.empty:
199
  return create_empty_df()
200
+ # Ensure ID column exists
201
+ if 'ID' not in df.columns:
202
+ df['ID'] = [f"#{i}" for i in range(1, len(df) + 1)]
203
  return df
204
  else:
205
  return create_empty_df()
 
216
  return False
217
 
218
  def load_assignees():
219
+ """Load assignees from Assignee.txt with strict requirement"""
220
+ # Always log file contents for debugging
221
+ log_files_in_directory()
222
 
223
+ # Check direct path first
224
+ if os.path.exists(ASSIGNEE_FILE):
225
+ return parse_assignee_file(ASSIGNEE_FILE)
226
 
227
+ # If not found, check other common locations
228
+ possible_locations = [
229
+ "./Assignee.txt",
230
+ "/app/Assignee.txt",
231
+ os.path.join(os.getcwd(), "Assignee.txt"),
232
+ "/data/Assignee.txt",
233
+ "../Assignee.txt",
234
+ ]
235
 
236
+ for location in possible_locations:
237
+ if os.path.exists(location):
238
+ return parse_assignee_file(location)
239
+
240
+ # If we reach here, the file wasn't found anywhere
241
+ st.error("Assignee.txt file not found. Please create this file in the app directory.")
242
+ return ["ERROR: Please create Assignee.txt file"]
243
+
244
+ def parse_assignee_file(file_path):
245
+ """Parse the assignee file and extract names"""
246
  try:
247
+ with open(file_path, "r") as f:
 
248
  content = f.read()
249
+ # Debug: Print content to logs
250
+ print(f"Content of {file_path}:\n{content}")
251
 
252
+ # Parse lines and extract names
253
+ assignees = []
254
  lines = [line.strip() for line in content.split('\n') if line.strip()]
255
+
256
  for line in lines:
257
  if '-' in line:
258
+ # Format: "Name - Role"
259
  name_part = line.split('-')[0].strip()
260
+ if name_part:
261
  assignees.append(name_part)
262
+ elif line:
263
+ # Just use the line as is if no dash
264
+ assignees.append(line)
265
+
266
+ # Log what we found
267
+ print(f"Found {len(assignees)} assignees: {assignees}")
268
+
269
+ if not assignees:
270
+ st.warning(f"No valid assignee names found in {file_path}. File exists but may be empty or improperly formatted.")
271
+ return ["ERROR: Empty or invalid Assignee.txt"]
272
+
273
  return assignees
274
+ except Exception as e:
275
+ st.error(f"Error reading assignee file: {str(e)}")
276
+ print(f"Error reading {file_path}: {str(e)}")
277
+ return ["ERROR: Cannot read Assignee.txt"]
278
+
279
+ def log_files_in_directory():
280
+ """Log files in current directory for debugging"""
281
+ try:
282
+ cwd = os.getcwd()
283
+ files = os.listdir(cwd)
284
+ print(f"Files in {cwd}: {files}")
285
 
286
+ # Also check parent directory
287
+ parent = os.path.dirname(cwd)
288
+ if os.path.exists(parent):
289
+ parent_files = os.listdir(parent)
290
+ print(f"Files in parent directory {parent}: {parent_files}")
291
+
292
+ # Check app directory if different
293
+ app_dir = "/app"
294
+ if os.path.exists(app_dir) and app_dir != cwd:
295
+ app_files = os.listdir(app_dir)
296
+ print(f"Files in {app_dir}: {app_files}")
297
  except Exception as e:
298
+ print(f"Error listing directory contents: {str(e)}")
299
+
300
+ # Generate a new task ID
301
+ def generate_task_id():
302
+ if len(st.session_state.tasks) == 0:
303
+ return "#1"
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:
311
+ return "#1"
312
+ except:
313
+ # If any error occurs, generate a random ID
314
+ return f"#{random.randint(1, 100)}"
315
 
316
  # Initialize session state ONLY ONCE
317
  if 'tasks' not in st.session_state:
318
  st.session_state.tasks = load_tasks()
319
 
320
+ # Load assignees directly from the file
321
  assignee_list = load_assignees()
322
 
323
  # Custom header
 
334
  title = st.text_input("Task Title")
335
  description = st.text_area("Description")
336
  assignee = st.selectbox("Assignee", assignee_list)
337
+ status = st.selectbox("Status", ["Backlog", "To Do", "In Progress", "Done"])
338
  due_date = st.date_input("Due Date")
339
 
340
  if st.button("Add Task"):
341
  if title: # Basic validation
342
+ task_id = generate_task_id()
343
  new_task = {
344
+ 'ID': task_id,
345
  'Title': title,
346
  'Description': description,
347
  'Assignee': assignee,
 
358
  else:
359
  st.warning("Please enter a task title")
360
 
361
+ # Display tasks in a backlog-style grid
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 grid container
366
  st.markdown('<div class="task-grid">', unsafe_allow_html=True)
367
 
368
+ # Render each task as a backlog item card
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}"
372
 
 
 
 
 
 
 
 
373
  # Create the card HTML
374
  card_html = f"""
375
+ <div class="task-card">
376
+ <div class="card-header {status_class}">
377
+ <span class="task-id">{task_id}</span>
378
+ <span>{task['Status']}</span>
 
 
 
 
 
379
  </div>
380
+ <div class="task-title">{task['Title']}</div>
381
+ <div class="task-assignee">{task['Assignee']}</div>
382
  </div>
383
  """
384
 
385
  # Render the card
386
  st.markdown(card_html, unsafe_allow_html=True)
387
 
388
+ # Add a hidden status update below each card
389
+ # Create an invisible element for status updates
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"Update {task_id}",
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="hidden"
403
  )
404
 
405
  if new_status != task['Status']: