nmtalhp commited on
Commit
b5e8db1
·
verified ·
1 Parent(s): 7026d8f

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. app.py +855 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,855 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Schedule & Note Management Web App
3
+ A lightweight Gradio 6 application for managing tasks, events, reminders, and fees.
4
+ Based on Product Requirements Document v1.0
5
+ """
6
+
7
+ import gradio as gr
8
+ import json
9
+ from datetime import datetime, timedelta
10
+ from typing import Optional
11
+ from enum import Enum
12
+
13
+ # ============================================================================
14
+ # DATA MODELS AND STATE MANAGEMENT
15
+ # ============================================================================
16
+
17
+ class TaskPriority(str, Enum):
18
+ LOW = "Low"
19
+ MEDIUM = "Medium"
20
+ HIGH = "High"
21
+
22
+ class TaskStatus(str, Enum):
23
+ PENDING = "Pending"
24
+ COMPLETED = "Completed"
25
+
26
+ class ReminderType(str, Enum):
27
+ NONE = "None"
28
+ BEFORE_15_MIN = "15 minutes before"
29
+ BEFORE_30_MIN = "30 minutes before"
30
+ BEFORE_1_HOUR = "1 hour before"
31
+ BEFORE_1_DAY = "1 day before"
32
+
33
+ # Sample data for demonstration
34
+ SAMPLE_TASKS = [
35
+ {
36
+ "id": "1",
37
+ "title": "Team Meeting",
38
+ "description": "Weekly sync with the development team",
39
+ "date": datetime.now().strftime("%Y-%m-%d"),
40
+ "time": "09:00",
41
+ "priority": TaskPriority.HIGH.value,
42
+ "status": TaskStatus.PENDING.value,
43
+ "fee": 0.0,
44
+ "reminder": ReminderType.BEFORE_15_MIN.value,
45
+ "notes": "Prepare agenda beforehand",
46
+ "created_at": datetime.now().isoformat()
47
+ },
48
+ {
49
+ "id": "2",
50
+ "title": "Client Presentation",
51
+ "description": "Present the new product roadmap",
52
+ "date": datetime.now().strftime("%Y-%m-%d"),
53
+ "time": "14:00",
54
+ "priority": TaskPriority.HIGH.value,
55
+ "status": TaskStatus.PENDING.value,
56
+ "fee": 150.0,
57
+ "reminder": ReminderType.BEFORE_1_HOUR.value,
58
+ "notes": "Review slides and prepare demo",
59
+ "created_at": datetime.now().isoformat()
60
+ },
61
+ {
62
+ "id": "3",
63
+ "title": "Code Review",
64
+ "description": "Review PRs for the new feature branch",
65
+ "date": (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d"),
66
+ "time": "11:00",
67
+ "priority": TaskPriority.MEDIUM.value,
68
+ "status": TaskStatus.PENDING.value,
69
+ "fee": 0.0,
70
+ "reminder": ReminderType.BEFORE_30_MIN.value,
71
+ "notes": "Check for security vulnerabilities",
72
+ "created_at": datetime.now().isoformat()
73
+ },
74
+ {
75
+ "id": "4",
76
+ "title": "Gym Session",
77
+ "description": "Weekly workout routine",
78
+ "date": datetime.now().strftime("%Y-%m-%d"),
79
+ "time": "18:00",
80
+ "priority": TaskPriority.LOW.value,
81
+ "status": TaskStatus.COMPLETED.value,
82
+ "fee": 50.0,
83
+ "reminder": ReminderType.NONE.value,
84
+ "notes": "Focus on cardio today",
85
+ "created_at": datetime.now().isoformat()
86
+ }
87
+ ]
88
+
89
+
90
+ def get_next_id(tasks: list) -> str:
91
+ """Generate a new unique ID for tasks."""
92
+ if not tasks:
93
+ return "1"
94
+ max_id = max(int(task["id"]) for task in tasks)
95
+ return str(max_id + 1)
96
+
97
+
98
+ def calculate_weekly_stats(tasks: list) -> dict:
99
+ """Calculate statistics for the current week."""
100
+ now = datetime.now()
101
+ week_start = now - timedelta(days=now.weekday())
102
+ week_end = week_start + timedelta(days=6)
103
+
104
+ weekly_tasks = [
105
+ t for t in tasks
106
+ if week_start.strftime("%Y-%m-%d") <= t["date"] <= week_end.strftime("%Y-%m-%d")
107
+ ]
108
+
109
+ completed = sum(1 for t in weekly_tasks if t["status"] == TaskStatus.COMPLETED.value)
110
+ total_fee = sum(float(t.get("fee", 0) or 0) for t in weekly_tasks)
111
+
112
+ return {
113
+ "total_tasks": len(weekly_tasks),
114
+ "completed": completed,
115
+ "pending": len(weekly_tasks) - completed,
116
+ "total_fee": total_fee,
117
+ "completion_rate": round((completed / len(weekly_tasks) * 100), 1) if weekly_tasks else 0
118
+ }
119
+
120
+
121
+ def get_weekly_schedule(tasks: list) -> str:
122
+ """Generate a weekly schedule display."""
123
+ now = datetime.now()
124
+ days = []
125
+
126
+ for i in range(7):
127
+ day = now + timedelta(days=i)
128
+ day_str = day.strftime("%Y-%m-%d")
129
+ day_name = day.strftime("%A")
130
+ is_today = i == 0
131
+
132
+ day_tasks = [
133
+ f" • **{t['time']}** - {t['title']}"
134
+ for t in sorted(tasks, key=lambda x: x.get("time", ""))
135
+ if t["date"] == day_str
136
+ ]
137
+
138
+ day_header = f"**{day_name} {day.strftime('%b %d')}" + (' (Today)' if is_today else '') + "**"
139
+ day_content = "\n".join(day_tasks) if day_tasks else " No tasks scheduled"
140
+ days.append(f"{day_header}\n{day_content}")
141
+
142
+ return "\n\n".join(days)
143
+
144
+
145
+ # ============================================================================
146
+ # APPLICATION STATE
147
+ # ============================================================================
148
+
149
+ class AppState:
150
+ """Application state manager for Gradio 6."""
151
+
152
+ def __init__(self):
153
+ self.tasks = SAMPLE_TASKS.copy()
154
+
155
+ def get_tasks(self) -> list:
156
+ """Get all tasks."""
157
+ return self.tasks
158
+
159
+ def add_task(self, task: dict) -> None:
160
+ """Add a new task."""
161
+ task["id"] = get_next_id(self.tasks)
162
+ task["created_at"] = datetime.now().isoformat()
163
+ self.tasks.append(task)
164
+
165
+ def update_task(self, task_id: str, updates: dict) -> None:
166
+ """Update an existing task."""
167
+ for task in self.tasks:
168
+ if task["id"] == task_id:
169
+ task.update(updates)
170
+ break
171
+
172
+ def delete_task(self, task_id: str) -> None:
173
+ """Delete a task by ID."""
174
+ self.tasks = [t for t in self.tasks if t["id"] != task_id]
175
+
176
+ def get_task_by_id(self, task_id: str) -> Optional[dict]:
177
+ """Get a task by its ID."""
178
+ for task in self.tasks:
179
+ if task["id"] == task_id:
180
+ return task
181
+ return None
182
+
183
+
184
+ # Initialize global state
185
+ app_state = AppState()
186
+
187
+
188
+ # ============================================================================
189
+ # HELPER FUNCTIONS
190
+ # ============================================================================
191
+
192
+ def refresh_task_list() -> dict:
193
+ """Refresh the task list and return current state."""
194
+ tasks = app_state.get_tasks()
195
+ return {
196
+ task_list: create_task_cards(tasks),
197
+ task_count: f"Total Tasks: {len(tasks)}",
198
+ weekly_stats: create_weekly_stats_display(),
199
+ weekly_schedule: get_weekly_schedule(tasks)
200
+ }
201
+
202
+
203
+ def create_task_cards(tasks: list) -> str:
204
+ """Create HTML cards for task display."""
205
+ if not tasks:
206
+ return """
207
+ <div style="text-align: center; padding: 40px; color: #666;">
208
+ <h3>📋 No tasks yet</h3>
209
+ <p>Create your first task to get started!</p>
210
+ </div>
211
+ """
212
+
213
+ cards = []
214
+ sorted_tasks = sorted(tasks, key=lambda x: (x.get("date", ""), x.get("time", "")))
215
+
216
+ for task in sorted_tasks:
217
+ priority_colors = {
218
+ "Low": "#28a745",
219
+ "Medium": "#ffc107",
220
+ "High": "#dc3545"
221
+ }
222
+ status_colors = {
223
+ "Pending": "#6c757d",
224
+ "Completed": "#28a745"
225
+ }
226
+ priority_color = priority_colors.get(task.get("priority", "Medium"), "#6c757d")
227
+ status_color = status_colors.get(task.get("status", "Pending"), "#6c757d")
228
+
229
+ card = f"""
230
+ <div style="
231
+ background: white;
232
+ border-radius: 12px;
233
+ padding: 16px;
234
+ margin-bottom: 12px;
235
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
236
+ border-left: 4px solid {priority_color};
237
+ ">
238
+ <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px;">
239
+ <div>
240
+ <h4 style="margin: 0; color: #333;">{task['title']}</h4>
241
+ <span style="
242
+ display: inline-block;
243
+ background: {priority_color}20;
244
+ color: {priority_color};
245
+ padding: 2px 8px;
246
+ border-radius: 4px;
247
+ font-size: 12px;
248
+ margin-top: 4px;
249
+ ">{task.get('priority', 'Medium')}</span>
250
+ <span style="
251
+ display: inline-block;
252
+ background: {status_color}20;
253
+ color: {status_color};
254
+ padding: 2px 8px;
255
+ border-radius: 4px;
256
+ font-size: 12px;
257
+ margin-left: 8px;
258
+ ">{task.get('status', 'Pending')}</span>
259
+ </div>
260
+ <div style="text-align: right; color: #666; font-size: 14px;">
261
+ <div>📅 {task.get('date', 'No date')}</div>
262
+ <div>🕐 {task.get('time', 'No time')}</div>
263
+ </div>
264
+ </div>
265
+ <p style="color: #666; font-size: 14px; margin: 8px 0;">
266
+ {task.get('description', 'No description')}
267
+ </p>
268
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee;">
269
+ <div style="font-size: 14px;">
270
+ {'💰 Fee: $' + str(task.get('fee', 0)) if float(task.get('fee', 0)) > 0 else ''}
271
+ {' | ⏰ Reminder: ' + task.get('reminder', 'None') if task.get('reminder') != 'None' else ''}
272
+ </div>
273
+ <div style="display: flex; gap: 8px;">
274
+ <span style="font-size: 12px; color: #666;">ID: {task['id']}</span>
275
+ </div>
276
+ </div>
277
+ </div>
278
+ """
279
+ cards.append(card)
280
+
281
+ return "\n".join(cards)
282
+
283
+
284
+ def create_weekly_stats_display() -> str:
285
+ """Create weekly statistics display."""
286
+ stats = calculate_weekly_stats(app_state.get_tasks())
287
+
288
+ return f"""
289
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px;">
290
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; text-align: center;">
291
+ <div style="font-size: 32px; font-weight: bold;">{stats['total_tasks']}</div>
292
+ <div style="font-size: 14px; opacity: 0.9;">This Week's Tasks</div>
293
+ </div>
294
+ <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 20px; border-radius: 12px; text-align: center;">
295
+ <div style="font-size: 32px; font-weight: bold;">{stats['completed']}</div>
296
+ <div style="font-size: 14px; opacity: 0.9;">Completed</div>
297
+ </div>
298
+ <div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 20px; border-radius: 12px; text-align: center;">
299
+ <div style="font-size: 32px; font-weight: bold;">{stats['pending']}</div>
300
+ <div style="font-size: 14px; opacity: 0.9;">Pending</div>
301
+ </div>
302
+ <div style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); color: white; padding: 20px; border-radius: 12px; text-align: center;">
303
+ <div style="font-size: 32px; font-weight: bold;">${stats['total_fee']:.2f}</div>
304
+ <div style="font-size: 14px; opacity: 0.9;">Total Fees</div>
305
+ </div>
306
+ </div>
307
+ """
308
+
309
+
310
+ # ============================================================================
311
+ # TASK OPERATIONS
312
+ # ============================================================================
313
+
314
+ def add_new_task(
315
+ title: str,
316
+ description: str,
317
+ task_date: str,
318
+ task_time: str,
319
+ priority: str,
320
+ fee: float,
321
+ reminder: str,
322
+ notes: str
323
+ ) -> dict:
324
+ """Add a new task to the system."""
325
+ if not title or not task_date or not task_time:
326
+ return {
327
+ task_list: create_task_cards(app_state.get_tasks()),
328
+ status_msg: gr.update(value="❌ Please fill in all required fields!", visible=True),
329
+ form_message: gr.update(value="❌ Error: Title, date, and time are required", visible=True)
330
+ }
331
+
332
+ new_task = {
333
+ "title": title,
334
+ "description": description,
335
+ "date": task_date,
336
+ "time": task_time,
337
+ "priority": priority,
338
+ "status": TaskStatus.PENDING.value,
339
+ "fee": fee,
340
+ "reminder": reminder,
341
+ "notes": notes
342
+ }
343
+
344
+ app_state.add_task(new_task)
345
+
346
+ return {
347
+ task_list: create_task_cards(app_state.get_tasks()),
348
+ status_msg: gr.update(value="✅ Task created successfully!", visible=True),
349
+ form_message: gr.update(value="✅ Task added successfully!", visible=True),
350
+ # Clear form
351
+ title_input: gr.update(value=""),
352
+ description_input: gr.update(value=""),
353
+ fee_input: gr.update(value=0.0),
354
+ notes_input: gr.update(value="")
355
+ }
356
+
357
+
358
+ def update_task_status(task_id: str, new_status: str) -> dict:
359
+ """Update a task's status."""
360
+ if task_id and new_status:
361
+ app_state.update_task(task_id, {"status": new_status})
362
+
363
+ return {
364
+ task_list: create_task_cards(app_state.get_tasks()),
365
+ status_msg: gr.update(value=f"✅ Task {task_id} marked as {new_status}", visible=True)
366
+ }
367
+
368
+
369
+ def delete_task_action(task_id: str) -> dict:
370
+ """Delete a task."""
371
+ if task_id:
372
+ app_state.delete_task(task_id)
373
+
374
+ return {
375
+ task_list: create_task_cards(app_state.get_tasks()),
376
+ status_msg: gr.update(value=f"✅ Task deleted successfully", visible=True)
377
+ }
378
+
379
+
380
+ def load_task_for_edit(task_id: str) -> dict:
381
+ """Load task data into the edit form."""
382
+ task = app_state.get_task_by_id(task_id)
383
+
384
+ if task:
385
+ return {
386
+ edit_task_id: task_id,
387
+ edit_title: gr.update(value=task["title"]),
388
+ edit_description: gr.update(value=task.get("description", "")),
389
+ edit_date: gr.update(value=task.get("date", "")),
390
+ edit_time: gr.update(value=task.get("time", "")),
391
+ edit_priority: gr.update(value=task.get("priority", "Medium")),
392
+ edit_fee: gr.update(value=float(task.get("fee", 0) or 0)),
393
+ edit_reminder: gr.update(value=task.get("reminder", "None")),
394
+ edit_notes: gr.update(value=task.get("notes", "")),
395
+ show_edit_form: gr.update(visible=True)
396
+ }
397
+ return {show_edit_form: gr.update(visible=False)}
398
+
399
+
400
+ def save_edited_task(
401
+ task_id: str,
402
+ title: str,
403
+ description: str,
404
+ task_date: str,
405
+ task_time: str,
406
+ priority: str,
407
+ fee: float,
408
+ reminder: str,
409
+ notes: str
410
+ ) -> dict:
411
+ """Save edited task."""
412
+ if task_id and title:
413
+ updates = {
414
+ "title": title,
415
+ "description": description,
416
+ "date": task_date,
417
+ "time": task_time,
418
+ "priority": priority,
419
+ "fee": fee,
420
+ "reminder": reminder,
421
+ "notes": notes
422
+ }
423
+ app_state.update_task(task_id, updates)
424
+
425
+ return {
426
+ task_list: create_task_cards(app_state.get_tasks()),
427
+ status_msg: gr.update(value="✅ Task updated successfully!", visible=True),
428
+ show_edit_form: gr.update(visible=False)
429
+ }
430
+
431
+
432
+ def filter_tasks(status_filter: str, priority_filter: str) -> dict:
433
+ """Filter tasks by status and priority."""
434
+ tasks = app_state.get_tasks()
435
+
436
+ if status_filter != "All":
437
+ tasks = [t for t in tasks if t.get("status") == status_filter]
438
+
439
+ if priority_filter != "All":
440
+ tasks = [t for t in tasks if t.get("priority") == priority_filter]
441
+
442
+ return {
443
+ task_list: create_task_cards(tasks)
444
+ }
445
+
446
+
447
+ def search_tasks(search_query: str) -> dict:
448
+ """Search tasks by title or description."""
449
+ if not search_query.strip():
450
+ return {task_list: create_task_cards(app_state.get_tasks())}
451
+
452
+ query = search_query.lower()
453
+ tasks = [
454
+ t for t in app_state.get_tasks()
455
+ if query in t.get("title", "").lower() or query in t.get("description", "").lower()
456
+ ]
457
+
458
+ return {
459
+ task_list: create_task_cards(tasks)
460
+ }
461
+
462
+
463
+ # ============================================================================
464
+ # GRADIO 6 APPLICATION
465
+ # ============================================================================
466
+
467
+ # Custom theme for the application
468
+ custom_theme = gr.themes.Soft(
469
+ primary_hue="indigo",
470
+ secondary_hue="purple",
471
+ neutral_hue="slate",
472
+ font=gr.themes.GoogleFont("Inter"),
473
+ text_size="lg",
474
+ spacing_size="lg",
475
+ radius_size="md"
476
+ ).set(
477
+ button_primary_background_fill="*primary_600",
478
+ button_primary_background_fill_hover="*primary_700",
479
+ button_secondary_background_fill="*secondary_100",
480
+ button_secondary_background_fill_hover="*secondary_200",
481
+ block_title_text_weight="600",
482
+ block_background_fill="white",
483
+ block_radius="12px",
484
+ )
485
+
486
+ with gr.Blocks(theme=custom_theme) as demo:
487
+ # Header
488
+ gr.HTML("""
489
+ <div style="text-align: center; padding: 20px 0; margin-bottom: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white;">
490
+ <h1 style="margin: 0; font-size: 28px;">📅 Schedule & Note Management</h1>
491
+ <p style="margin: 8px 0 0 0; opacity: 0.9;">Plan tasks, manage events, and track fees efficiently</p>
492
+ </div>
493
+ """)
494
+
495
+ gr.HTML("""
496
+ <div style="text-align: right; margin-bottom: 10px;">
497
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #667eea; text-decoration: none; font-size: 14px;">
498
+ ✨ Built with anycoder
499
+ </a>
500
+ </div>
501
+ """)
502
+
503
+ # Main content with tabs
504
+ with gr.Tabs() as tabs:
505
+ # Tab 1: Dashboard
506
+ with gr.TabItem("📊 Dashboard", id="dashboard"):
507
+ gr.Markdown("### 📊 Weekly Overview")
508
+
509
+ with gr.Row():
510
+ with gr.Column(scale=2):
511
+ weekly_stats_html = gr.HTML(
512
+ value=create_weekly_stats_display(),
513
+ elem_id="weekly-stats"
514
+ )
515
+ with gr.Column(scale=1):
516
+ gr.Markdown("### 📅 Quick Stats")
517
+ task_count = gr.Markdown(f"**Total Tasks: {len(app_state.get_tasks())}**")
518
+
519
+ gr.Markdown("### 📆 This Week's Schedule")
520
+ weekly_schedule = gr.Markdown(
521
+ value=get_weekly_schedule(app_state.get_tasks()),
522
+ elem_id="weekly-schedule"
523
+ )
524
+
525
+ status_msg = gr.Markdown(visible=False, value="")
526
+
527
+ # Tab 2: Task Management
528
+ with gr.TabItem("✅ Tasks", id="tasks"):
529
+ gr.Markdown("### ✅ Task Management")
530
+
531
+ # Search and filter row
532
+ with gr.Row():
533
+ with gr.Column(scale=3):
534
+ search_input = gr.Textbox(
535
+ label="🔍 Search Tasks",
536
+ placeholder="Search by title or description...",
537
+ elem_id="search-input"
538
+ )
539
+ with gr.Column(scale=1):
540
+ status_filter = gr.Dropdown(
541
+ choices=["All", "Pending", "Completed"],
542
+ value="All",
543
+ label="Status"
544
+ )
545
+ with gr.Column(scale=1):
546
+ priority_filter = gr.Dropdown(
547
+ choices=["All", "Low", "Medium", "High"],
548
+ value="All",
549
+ label="Priority"
550
+ )
551
+
552
+ # Task list display
553
+ task_list = gr.HTML(
554
+ value=create_task_cards(app_state.get_tasks()),
555
+ elem_id="task-list"
556
+ )
557
+
558
+ status_msg = gr.Markdown(visible=False, value="")
559
+
560
+ # Tab 3: Add New Task
561
+ with gr.TabItem("➕ Add Task", id="add-task"):
562
+ gr.Markdown("### ➕ Create New Task")
563
+
564
+ with gr.Row():
565
+ with gr.Column(scale=2):
566
+ title_input = gr.Textbox(
567
+ label="Task Title *",
568
+ placeholder="Enter task title",
569
+ elem_id="title-input"
570
+ )
571
+ description_input = gr.Textbox(
572
+ label="Description",
573
+ placeholder="Enter task description",
574
+ lines=3,
575
+ elem_id="description-input"
576
+ )
577
+ with gr.Column(scale=1):
578
+ task_date = gr.DateTime(
579
+ label="Date & Time *",
580
+ type="datetime",
581
+ elem_id="task-date"
582
+ )
583
+ task_time = gr.Textbox(
584
+ label="Time (HH:MM) *",
585
+ placeholder="09:00",
586
+ value="09:00",
587
+ elem_id="task-time"
588
+ )
589
+
590
+ with gr.Row():
591
+ with gr.Column():
592
+ priority = gr.Radio(
593
+ choices=["Low", "Medium", "High"],
594
+ value="Medium",
595
+ label="Priority"
596
+ )
597
+ with gr.Column():
598
+ fee_input = gr.Number(
599
+ label="Fee/Cost ($)",
600
+ value=0.0,
601
+ minimum=0.0,
602
+ elem_id="fee-input"
603
+ )
604
+ with gr.Column():
605
+ reminder = gr.Dropdown(
606
+ choices=["None", "15 minutes before", "30 minutes before",
607
+ "1 hour before", "1 day before"],
608
+ value="None",
609
+ label="Reminder"
610
+ )
611
+
612
+ notes_input = gr.Textbox(
613
+ label="Notes",
614
+ placeholder="Additional notes or comments...",
615
+ lines=2,
616
+ elem_id="notes-input"
617
+ )
618
+
619
+ form_message = gr.Markdown(visible=False, value="")
620
+
621
+ with gr.Row():
622
+ add_btn = gr.Button("Create Task", variant="primary", size="lg")
623
+ clear_btn = gr.Button("Clear Form", variant="secondary")
624
+
625
+ # Tab 4: Edit Task
626
+ with gr.TabItem("✏️ Edit Task", id="edit-task"):
627
+ gr.Markdown("### ✏️ Edit Task")
628
+
629
+ # Task selection dropdown
630
+ task_selector = gr.Dropdown(
631
+ choices=[(f"{t['title']} ({t.get('date', 'No date')}) - ID:{t['id']}", t['id'])
632
+ for t in app_state.get_tasks()],
633
+ label="Select Task to Edit",
634
+ allow_custom_value=True,
635
+ elem_id="task-selector"
636
+ )
637
+
638
+ show_edit_form = gr.Column(visible=False, elem_id="edit-form-container")
639
+ with show_edit_form:
640
+ edit_task_id = gr.Textbox(visible=False, elem_id="edit-task-id")
641
+
642
+ with gr.Row():
643
+ with gr.Column(scale=2):
644
+ edit_title = gr.Textbox(
645
+ label="Task Title *",
646
+ placeholder="Enter task title",
647
+ elem_id="edit-title"
648
+ )
649
+ edit_description = gr.Textbox(
650
+ label="Description",
651
+ placeholder="Enter task description",
652
+ lines=3,
653
+ elem_id="edit-description"
654
+ )
655
+ with gr.Column(scale=1):
656
+ edit_date = gr.Textbox(
657
+ label="Date (YYYY-MM-DD) *",
658
+ placeholder="2024-01-15",
659
+ elem_id="edit-date"
660
+ )
661
+ edit_time = gr.Textbox(
662
+ label="Time (HH:MM) *",
663
+ placeholder="09:00",
664
+ elem_id="edit-time"
665
+ )
666
+
667
+ with gr.Row():
668
+ with gr.Column():
669
+ edit_priority = gr.Radio(
670
+ choices=["Low", "Medium", "High"],
671
+ value="Medium",
672
+ label="Priority"
673
+ )
674
+ with gr.Column():
675
+ edit_fee = gr.Number(
676
+ label="Fee/Cost ($)",
677
+ value=0.0,
678
+ minimum=0.0,
679
+ elem_id="edit-fee"
680
+ )
681
+ with gr.Column():
682
+ edit_reminder = gr.Dropdown(
683
+ choices=["None", "15 minutes before", "30 minutes before",
684
+ "1 hour before", "1 day before"],
685
+ value="None",
686
+ label="Reminder"
687
+ )
688
+
689
+ edit_notes = gr.Textbox(
690
+ label="Notes",
691
+ placeholder="Additional notes or comments...",
692
+ lines=2,
693
+ elem_id="edit-notes"
694
+ )
695
+
696
+ with gr.Row():
697
+ save_btn = gr.Button("Save Changes", variant="primary", size="lg")
698
+ cancel_btn = gr.Button("Cancel", variant="secondary")
699
+
700
+ # Tab 5: Notes Manager
701
+ with gr.TabItem("📝 Notes", id="notes"):
702
+ gr.Markdown("### 📝 Quick Notes")
703
+ gr.Markdown("*Notes are attached to individual tasks. View task notes in the Tasks tab.*")
704
+
705
+ with gr.Row():
706
+ quick_note = gr.Textbox(
707
+ label="Quick Note",
708
+ placeholder="Write a quick note...",
709
+ lines=4
710
+ )
711
+ note_display = gr.Markdown(
712
+ value="**Recent Notes**\n\n• Review meeting notes from Monday\n• Update project timeline\n• Prepare budget report",
713
+ elem_id="note-display"
714
+ )
715
+
716
+ with gr.Row():
717
+ save_note_btn = gr.Button("Save Note", variant="primary")
718
+ clear_note_btn = gr.Button("Clear", variant="secondary")
719
+
720
+ # Tab 6: Fees Summary
721
+ with gr.TabItem("💰 Fees", id="fees"):
722
+ gr.Markdown("### 💰 Fee Tracking Summary")
723
+
724
+ # Fee statistics
725
+ tasks = app_state.get_tasks()
726
+ total_fees = sum(float(t.get("fee", 0) or 0) for t in tasks)
727
+ task_with_fees = sum(1 for t in tasks if float(t.get("fee", 0) or 0) > 0)
728
+ avg_fee = total_fees / task_with_fees if task_with_fees > 0 else 0
729
+
730
+ gr.HTML(f"""
731
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px;">
732
+ <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 24px; border-radius: 12px; text-align: center;">
733
+ <div style="font-size: 36px; font-weight: bold;">${total_fees:.2f}</div>
734
+ <div style="font-size: 16px; opacity: 0.9;">Total Fees</div>
735
+ </div>
736
+ <div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 24px; border-radius: 12px; text-align: center;">
737
+ <div style="font-size: 36px; font-weight: bold;">{task_with_fees}</div>
738
+ <div style="font-size: 16px; opacity: 0.9;">Tasks with Fees</div>
739
+ </div>
740
+ <div style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); color: white; padding: 24px; border-radius: 12px; text-align: center;">
741
+ <div style="font-size: 36px; font-weight: bold;">${avg_fee:.2f}</div>
742
+ <div style="font-size: 16px; opacity: 0.9;">Average Fee</div>
743
+ </div>
744
+ </div>
745
+ """)
746
+
747
+ gr.Markdown("### 💵 Tasks with Fees")
748
+
749
+ fee_task_list = gr.HTML(
750
+ value=create_task_cards([t for t in tasks if float(t.get("fee", 0) or 0) > 0]),
751
+ elem_id="fee-task-list"
752
+ )
753
+
754
+ # ============================================================================
755
+ # EVENT HANDLERS
756
+ # ============================================================================
757
+
758
+ # Search and filter handlers
759
+ search_input.submit(search_tasks, inputs=search_input, outputs=[task_list])
760
+ status_filter.change(filter_tasks, inputs=[status_filter, priority_filter], outputs=[task_list])
761
+ priority_filter.change(filter_tasks, inputs=[status_filter, priority_filter], outputs=[task_list])
762
+
763
+ # Add task handlers
764
+ add_btn.click(
765
+ add_new_task,
766
+ inputs=[title_input, description_input, task_date, task_time,
767
+ priority, fee_input, reminder, notes_input],
768
+ outputs=[task_list, status_msg, form_message, title_input,
769
+ description_input, fee_input, notes_input]
770
+ )
771
+
772
+ clear_btn.click(
773
+ lambda: {
774
+ title_input: gr.update(value=""),
775
+ description_input: gr.update(value=""),
776
+ fee_input: gr.update(value=0.0),
777
+ notes_input: gr.update(value=""),
778
+ form_message: gr.update(visible=False)
779
+ },
780
+ outputs=[title_input, description_input, fee_input, notes_input, form_message]
781
+ )
782
+
783
+ # Task selector for editing
784
+ task_selector.select(
785
+ load_task_for_edit,
786
+ inputs=task_selector,
787
+ outputs=[edit_task_id, edit_title, edit_description, edit_date, edit_time,
788
+ edit_priority, edit_fee, edit_reminder, edit_notes, show_edit_form]
789
+ )
790
+
791
+ # Save edited task
792
+ save_btn.click(
793
+ save_edited_task,
794
+ inputs=[edit_task_id, edit_title, edit_description, edit_date, edit_time,
795
+ edit_priority, edit_fee, edit_reminder, edit_notes],
796
+ outputs=[task_list, status_msg, show_edit_form]
797
+ )
798
+
799
+ # Cancel edit
800
+ cancel_btn.click(
801
+ lambda: {show_edit_form: gr.update(visible=False)},
802
+ outputs=[show_edit_form]
803
+ )
804
+
805
+ # Notes handlers
806
+ save_note_btn.click(
807
+ lambda note: {
808
+ note_display: note_display.value + f"\n• {note}" if note else note_display.value
809
+ },
810
+ inputs=quick_note,
811
+ outputs=[note_display]
812
+ )
813
+
814
+ clear_note_btn.click(
815
+ lambda: {quick_note: gr.update(value="")},
816
+ outputs=[quick_note]
817
+ )
818
+
819
+ # Refresh button (hidden, used for updates)
820
+ refresh_btn = gr.Button("Refresh", visible=False)
821
+ refresh_btn.click(refresh_task_list, outputs=[task_list, task_count, weekly_stats_html, weekly_schedule])
822
+
823
+ # ============================================================================
824
+ # LAUNCH APPLICATION
825
+ # ============================================================================
826
+
827
+ demo.launch(
828
+ theme=gr.themes.Soft(
829
+ primary_hue="indigo",
830
+ secondary_hue="purple",
831
+ neutral_hue="slate",
832
+ font=gr.themes.GoogleFont("Inter"),
833
+ text_size="lg",
834
+ spacing_size="lg",
835
+ radius_size="md"
836
+ ),
837
+ title="Schedule & Note Management App",
838
+ description="A lightweight application for managing tasks, events, reminders, and fees. Based on PRD v1.0",
839
+ css="""
840
+ .gradio-container {
841
+ max-width: 1200px !important;
842
+ }
843
+ #weekly-stats, #fee-task-list {
844
+ margin-bottom: 20px;
845
+ }
846
+ #search-input {
847
+ border-radius: 8px;
848
+ }
849
+ """,
850
+ footer_links=[
851
+ {"label": "Documentation", "url": "#"},
852
+ {"label": "Support", "url": "#"},
853
+ {"api": "api"}
854
+ ]
855
+ )
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=6.0
2
+ requests
3
+ Pillow