Emmanuel Frimpong Asante commited on
Commit
6dbfc52
·
1 Parent(s): 86ce058

update space

Browse files
.idea/workspace.xml CHANGED
@@ -5,8 +5,13 @@
5
  </component>
6
  <component name="ChangeListManager">
7
  <list default="true" id="27c9ae1a-a6fa-4472-8bcd-a7087620894b" name="Changes" comment="update space">
8
- <change beforePath="$PROJECT_DIR$/routes/health_dashboard.py" beforeDir="false" afterPath="$PROJECT_DIR$/routes/health_dashboard.py" afterDir="false" />
9
- <change beforePath="$PROJECT_DIR$/templates/dashboard.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/dashboard.html" afterDir="false" />
 
 
 
 
 
10
  </list>
11
  <option name="SHOW_DIALOG" value="false" />
12
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -119,15 +124,7 @@
119
  <workItem from="1730338434025" duration="11075000" />
120
  <workItem from="1730305361670" duration="2425000" />
121
  <workItem from="1730378091154" duration="2435000" />
122
- <workItem from="1730381153348" duration="5876000" />
123
- </task>
124
- <task id="LOCAL-00021" summary="update space">
125
- <option name="closed" value="true" />
126
- <created>1730325728071</created>
127
- <option name="number" value="00021" />
128
- <option name="presentableId" value="LOCAL-00021" />
129
- <option name="project" value="LOCAL" />
130
- <updated>1730325728071</updated>
131
  </task>
132
  <task id="LOCAL-00022" summary="update space">
133
  <option name="closed" value="true" />
@@ -513,7 +510,15 @@
513
  <option name="project" value="LOCAL" />
514
  <updated>1730386845014</updated>
515
  </task>
516
- <option name="localTasksCounter" value="70" />
 
 
 
 
 
 
 
 
517
  <servers />
518
  </component>
519
  <component name="TypeScriptGeneratedFilesManager">
 
5
  </component>
6
  <component name="ChangeListManager">
7
  <list default="true" id="27c9ae1a-a6fa-4472-8bcd-a7087620894b" name="Changes" comment="update space">
8
+ <change afterPath="$PROJECT_DIR$/models/schemas/todo_schema.py" afterDir="false" />
9
+ <change afterPath="$PROJECT_DIR$/routes/todo.py" afterDir="false" />
10
+ <change afterPath="$PROJECT_DIR$/templates/todos.html" afterDir="false" />
11
+ <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
12
+ <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
13
+ <change beforePath="$PROJECT_DIR$/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/app.py" afterDir="false" />
14
+ <change beforePath="$PROJECT_DIR$/services/auth_service.py" beforeDir="false" afterPath="$PROJECT_DIR$/services/auth_service.py" afterDir="false" />
15
  </list>
16
  <option name="SHOW_DIALOG" value="false" />
17
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
 
124
  <workItem from="1730338434025" duration="11075000" />
125
  <workItem from="1730305361670" duration="2425000" />
126
  <workItem from="1730378091154" duration="2435000" />
127
+ <workItem from="1730381153348" duration="7510000" />
 
 
 
 
 
 
 
 
128
  </task>
129
  <task id="LOCAL-00022" summary="update space">
130
  <option name="closed" value="true" />
 
510
  <option name="project" value="LOCAL" />
511
  <updated>1730386845014</updated>
512
  </task>
513
+ <task id="LOCAL-00070" summary="update space">
514
+ <option name="closed" value="true" />
515
+ <created>1730387397425</created>
516
+ <option name="number" value="00070" />
517
+ <option name="presentableId" value="LOCAL-00070" />
518
+ <option name="project" value="LOCAL" />
519
+ <updated>1730387397425</updated>
520
+ </task>
521
+ <option name="localTasksCounter" value="71" />
522
  <servers />
523
  </component>
524
  <component name="TypeScriptGeneratedFilesManager">
README.md CHANGED
@@ -64,11 +64,11 @@ This project combines a **Poultry Farming Assistance System** and a **Poultry Ma
64
  - [x] Set up image preprocessing for disease detection.
65
  - [x] Build endpoints for image upload and disease analysis.
66
  - [x] Generate health-related notifications and recommendations.
67
- - [ ] Implement real-time health monitoring and alerts.
68
 
69
  ### 4. To-Do List Management
70
- - [ ] Design MongoDB schema for to-do lists.
71
- - [ ] Create routes for farmers to view and mark to-do items.
72
  - [ ] Build functionality for admins to assign tasks.
73
  - [ ] Develop an **AdminLTE 4**-based UI for managing tasks.
74
 
 
64
  - [x] Set up image preprocessing for disease detection.
65
  - [x] Build endpoints for image upload and disease analysis.
66
  - [x] Generate health-related notifications and recommendations.
67
+ - [x] Implement real-time health monitoring and alerts.
68
 
69
  ### 4. To-Do List Management
70
+ - [x] Design MongoDB schema for to-do lists.
71
+ - [x] Create routes for farmers to view and mark to-do items.
72
  - [ ] Build functionality for admins to assign tasks.
73
  - [ ] Develop an **AdminLTE 4**-based UI for managing tasks.
74
 
app.py CHANGED
@@ -13,6 +13,7 @@ from routes.disease_detection import disease_router
13
  from routes.administration import dashboard_router
14
  from services.health_monitoring_service import evaluate_health_data, get_health_alerts, send_alerts
15
  from huggingface_hub import login
 
16
 
17
  # Setup logging
18
  logging.basicConfig(level=logging.INFO)
@@ -51,7 +52,8 @@ else:
51
  app.include_router(auth_router, prefix="/auth", tags=["Authentication"])
52
  app.include_router(disease_router, prefix="/disease", tags=["Disease Detection"])
53
  app.include_router(dashboard_router, prefix="/admin", tags=["Admin Dashboard"])
54
-
 
55
  @app.get("/", response_class=HTMLResponse)
56
  async def landing_page(request: Request):
57
  """Render the landing page template."""
 
13
  from routes.administration import dashboard_router
14
  from services.health_monitoring_service import evaluate_health_data, get_health_alerts, send_alerts
15
  from huggingface_hub import login
16
+ from routes.todo import todo_router
17
 
18
  # Setup logging
19
  logging.basicConfig(level=logging.INFO)
 
52
  app.include_router(auth_router, prefix="/auth", tags=["Authentication"])
53
  app.include_router(disease_router, prefix="/disease", tags=["Disease Detection"])
54
  app.include_router(dashboard_router, prefix="/admin", tags=["Admin Dashboard"])
55
+ app.include_router(todo_router, prefix="/admin/todos", tags=["Admin To-Do List"])
56
+ app.include_router(todo_router, prefix="/todos", tags=["Farmer To-Do List"])
57
  @app.get("/", response_class=HTMLResponse)
58
  async def landing_page(request: Request):
59
  """Render the landing page template."""
models/schemas/todo_schema.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+ from datetime import datetime
4
+ from bson import ObjectId
5
+
6
+ class PyObjectId(ObjectId):
7
+ """Custom ObjectId class for Pydantic validation."""
8
+ @classmethod
9
+ def __get_validators__(cls):
10
+ yield cls.validate
11
+
12
+ @classmethod
13
+ def validate(cls, v):
14
+ if not ObjectId.is_valid(v):
15
+ raise ValueError("Invalid ObjectId")
16
+ return ObjectId(v)
17
+
18
+ class ToDoItem(BaseModel):
19
+ id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
20
+ title: str
21
+ description: Optional[str] = None
22
+ due_date: datetime
23
+ is_completed: bool = False
24
+ assigned_to: Optional[str] = None # Username or User ID of the farmer
25
+ created_at: datetime = Field(default_factory=datetime.utcnow)
26
+ updated_at: Optional[datetime] = None # Track the latest update timestamp
27
+
28
+ class Config:
29
+ json_encoders = {ObjectId: str}
30
+ schema_extra = {
31
+ "example": {
32
+ "title": "Feed the chickens",
33
+ "description": "Provide feed and check water levels for all chicken pens",
34
+ "due_date": "2024-11-05T14:00:00Z",
35
+ "is_completed": False,
36
+ "assigned_to": "farmer_username",
37
+ "created_at": "2024-10-31T10:00:00Z",
38
+ "updated_at": "2024-10-31T12:00:00Z"
39
+ }
40
+ }
41
+
42
+ class ToDoCreate(BaseModel):
43
+ """Schema for creating a new to-do item."""
44
+ title: str
45
+ description: Optional[str] = None
46
+ due_date: datetime
47
+ assigned_to: Optional[str] = None # Optional assignment at creation
48
+
49
+ class Config:
50
+ schema_extra = {
51
+ "example": {
52
+ "title": "Inspect chicken feed storage",
53
+ "description": "Check for any signs of spoilage or infestation",
54
+ "due_date": "2024-11-02T10:00:00Z",
55
+ "assigned_to": "farmer_username"
56
+ }
57
+ }
58
+
59
+ class ToDoUpdate(BaseModel):
60
+ """Schema for updating an existing to-do item."""
61
+ title: Optional[str] = None
62
+ description: Optional[str] = None
63
+ due_date: Optional[datetime] = None
64
+ is_completed: Optional[bool] = None # Allow marking as completed
65
+ assigned_to: Optional[str] = None # Allow reassignment if needed
66
+
67
+ class Config:
68
+ schema_extra = {
69
+ "example": {
70
+ "title": "Check chicken feed storage",
71
+ "description": "Ensure feed is properly stored to avoid spoilage",
72
+ "due_date": "2024-11-03T08:00:00Z",
73
+ "is_completed": True,
74
+ "assigned_to": "another_farmer_username"
75
+ }
76
+ }
routes/administration.py CHANGED
@@ -1,9 +1,14 @@
1
  # routes/administration.py
2
 
3
- from fastapi import APIRouter, Request
4
  from fastapi.templating import Jinja2Templates
5
  from datetime import datetime
 
 
6
  from starlette.responses import HTMLResponse
 
 
 
7
  from services.health_monitoring_service import evaluate_health_data
8
  from services.utils import db # MongoDB instance from utils
9
 
@@ -44,3 +49,25 @@ async def health_dashboard(request: Request):
44
 
45
  # Render the dashboard template with real-time and historical alert data
46
  return templates.TemplateResponse("dashboard.html", context)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # routes/administration.py
2
 
3
+ from fastapi import APIRouter, Request, HTTPException, Depends
4
  from fastapi.templating import Jinja2Templates
5
  from datetime import datetime
6
+
7
+ from starlette import status
8
  from starlette.responses import HTMLResponse
9
+
10
+ from routes.todo import todo_collection
11
+ from services.auth_service import is_admin_user, get_current_user
12
  from services.health_monitoring_service import evaluate_health_data
13
  from services.utils import db # MongoDB instance from utils
14
 
 
49
 
50
  # Render the dashboard template with real-time and historical alert data
51
  return templates.TemplateResponse("dashboard.html", context)
52
+
53
+
54
+ @dashboard_router.get("/tasks", response_class=HTMLResponse)
55
+ async def manage_tasks(request: Request, current_user: str = Depends(get_current_user)):
56
+ """
57
+ Render the task management page for admin users.
58
+ """
59
+ # Check if current user is an admin
60
+ if not is_admin_user(current_user):
61
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
62
+
63
+ # Fetch all tasks and farmers for the dropdown
64
+ tasks = list(todo_collection.find({}))
65
+ farmers = list(db.get_collection("users").find({"role": "farmer"}))
66
+
67
+ context = {
68
+ "request": request,
69
+ "tasks": tasks,
70
+ "farmers": farmers,
71
+ }
72
+ return templates.TemplateResponse("admin_tasks.html", context)
73
+
routes/todo.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, Request, HTTPException, status
2
+ from fastapi.responses import JSONResponse
3
+ from pydantic import BaseModel, Field
4
+ from typing import Optional, List
5
+ from services.auth_service import get_current_user, is_admin_user
6
+ from services.utils import db
7
+ from models.schemas.todo_schema import ToDoItem, ToDoCreate, ToDoUpdate
8
+ from datetime import datetime
9
+ from bson import ObjectId
10
+
11
+ # Initialize the router
12
+ todo_router = APIRouter()
13
+
14
+ # MongoDB Collection for To-Do items
15
+ todo_collection = db.get_collection("todo_items")
16
+
17
+
18
+
19
+
20
+ # Admin Farmer Routes
21
+ @todo_router.post("/admin/assign_task", response_model=dict)
22
+ async def assign_task(
23
+ todo_data: ToDoCreate,
24
+ assigned_to: str,
25
+ current_user: str = Depends(get_current_user)
26
+ ):
27
+ """
28
+ Allows admin to assign a task to a farmer.
29
+ """
30
+ if not is_admin_user(current_user):
31
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
32
+
33
+ task = todo_data.dict()
34
+ task.update({
35
+ "assigned_to": assigned_to,
36
+ "created_by": current_user,
37
+ "is_completed": False,
38
+ "created_at": datetime.utcnow()
39
+ })
40
+
41
+ result = todo_collection.insert_one(task)
42
+ if result.inserted_id:
43
+ return {"message": "Task assigned successfully."}
44
+ else:
45
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Task assignment failed.")
46
+
47
+ @todo_router.post("/admin/todos", response_model=ToDoItem)
48
+ async def create_todo_item(
49
+ todo_data: ToDoCreate,
50
+ current_user: str = Depends(get_current_user),
51
+ ):
52
+ """
53
+ Allows an admin farmer to create a new to-do item.
54
+
55
+ Parameters:
56
+ todo_data (ToDoCreate): Data for creating the to-do item.
57
+ current_user (str): Current admin user creating the task.
58
+
59
+ Returns:
60
+ ToDoItem: The created to-do item document.
61
+ """
62
+ if not is_admin_user(current_user):
63
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
64
+
65
+ todo_item = todo_data.dict()
66
+ todo_item.update({
67
+ "is_completed": False,
68
+ "created_by": current_user,
69
+ "created_at": datetime.utcnow(),
70
+ })
71
+ result = todo_collection.insert_one(todo_item)
72
+
73
+ if result.inserted_id:
74
+ return JSONResponse(status_code=status.HTTP_201_CREATED,
75
+ content={"message": "To-do item created successfully."})
76
+ else:
77
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create to-do item.")
78
+
79
+
80
+ @todo_router.get("/admin/todos", response_model=List[ToDoItem])
81
+ async def get_all_todo_items(current_user: str = Depends(get_current_user)):
82
+ """
83
+ Allows an admin farmer to view all to-do items.
84
+
85
+ Parameters:
86
+ current_user (str): Current admin user.
87
+
88
+ Returns:
89
+ List[ToDoItem]: List of all to-do items.
90
+ """
91
+ if not is_admin_user(current_user):
92
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
93
+
94
+ todos = list(todo_collection.find({}))
95
+ return todos
96
+
97
+
98
+ @todo_router.put("/admin/todos/{todo_id}", response_model=dict)
99
+ async def update_todo_item(
100
+ todo_id: str,
101
+ todo_data: ToDoUpdate,
102
+ current_user: str = Depends(get_current_user),
103
+ ):
104
+ """
105
+ Allows an admin farmer to update any to-do item details.
106
+
107
+ Parameters:
108
+ todo_id (str): ID of the to-do item.
109
+ todo_data (ToDoUpdate): Data to update on the to-do item.
110
+
111
+ Returns:
112
+ dict: Update confirmation.
113
+ """
114
+ if not is_admin_user(current_user):
115
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
116
+
117
+ update_data = {k: v for k, v in todo_data.dict().items() if v is not None}
118
+ result = todo_collection.update_one({"_id": ObjectId(todo_id)}, {"$set": update_data})
119
+
120
+ if result.modified_count:
121
+ return {"message": "To-do item updated successfully."}
122
+ else:
123
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="To-do item not found or not modified.")
124
+
125
+
126
+ @todo_router.delete("/admin/todos/{todo_id}", response_model=dict)
127
+ async def delete_todo_item(todo_id: str, current_user: str = Depends(get_current_user)):
128
+ """
129
+ Allows an admin farmer to delete any to-do item.
130
+
131
+ Parameters:
132
+ todo_id (str): ID of the to-do item to delete.
133
+
134
+ Returns:
135
+ dict: Deletion confirmation.
136
+ """
137
+ if not is_admin_user(current_user):
138
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
139
+
140
+ result = todo_collection.delete_one({"_id": ObjectId(todo_id)})
141
+ if result.deleted_count:
142
+ return {"message": "To-do item deleted successfully."}
143
+ else:
144
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="To-do item not found.")
145
+
146
+
147
+ # Normal Farmer Routes
148
+
149
+ @todo_router.get("/todos", response_model=List[ToDoItem])
150
+ async def get_assigned_todo_items(current_user: str = Depends(get_current_user)):
151
+ """
152
+ Allows a normal farmer to retrieve all tasks assigned to them.
153
+
154
+ Parameters:
155
+ current_user (str): Current authenticated farmer.
156
+
157
+ Returns:
158
+ List[ToDoItem]: List of to-do items assigned to the user.
159
+ """
160
+ todos = list(todo_collection.find({"assigned_to": current_user}))
161
+ return todos
162
+
163
+
164
+ @todo_router.put("/todos/{todo_id}/complete", response_model=dict)
165
+ async def complete_assigned_todo_item(todo_id: str, current_user: str = Depends(get_current_user)):
166
+ """
167
+ Allows a normal farmer to mark an assigned to-do item as completed.
168
+
169
+ Parameters:
170
+ todo_id (str): ID of the to-do item to mark as completed.
171
+
172
+ Returns:
173
+ dict: Completion confirmation message.
174
+ """
175
+ # Verify if the to-do item is assigned to the current user
176
+ todo = todo_collection.find_one({"_id": ObjectId(todo_id), "assigned_to": current_user})
177
+ if not todo:
178
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
179
+ detail="To-do item not found or not assigned to you.")
180
+
181
+ todo_collection.update_one({"_id": ObjectId(todo_id)},
182
+ {"$set": {"is_completed": True, "completed_at": datetime.utcnow()}})
183
+ return {"message": f"To-do item '{todo.get('title')}' marked as completed."}
services/auth_service.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import os
2
  import logging
3
  from datetime import datetime, timedelta
@@ -9,7 +11,7 @@ from models.schemas.user_schema import UserCreate
9
  from fastapi import Depends, HTTPException, status
10
  from pydantic import BaseModel
11
  from jose import JWTError, jwt
12
- from services.utils import mongo_instance, log_to_db # MongoDB and logging utilities
13
 
14
  # Set up logging with a detailed format
15
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -135,6 +137,13 @@ def get_current_user(token: str = Depends(oauth2_scheme)) -> Union[str, None]:
135
  logger.info(f"Access granted to user: {username}")
136
  return username
137
 
 
 
 
 
 
 
 
138
  def authenticate_user(username: str, password: str) -> Optional[dict]:
139
  """
140
  Authenticates a user by checking username and password.
 
1
+ # services/auth_service.py
2
+
3
  import os
4
  import logging
5
  from datetime import datetime, timedelta
 
11
  from fastapi import Depends, HTTPException, status
12
  from pydantic import BaseModel
13
  from jose import JWTError, jwt
14
+ from services.utils import mongo_instance, log_to_db, db # MongoDB and logging utilities
15
 
16
  # Set up logging with a detailed format
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
137
  logger.info(f"Access granted to user: {username}")
138
  return username
139
 
140
+ def is_admin_user(current_user: str):
141
+ # This is a placeholder for role-based validation. Replace with actual role check logic.
142
+ user = db.get_collection("users").find_one({"username": current_user})
143
+ if user and user.get("role") == "admin":
144
+ return True
145
+ return False
146
+
147
  def authenticate_user(username: str, password: str) -> Optional[dict]:
148
  """
149
  Authenticates a user by checking username and password.
templates/admin_tasks.html ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="content-header">
5
+ <div class="container-fluid">
6
+ <div class="row mb-2">
7
+ <div class="col-sm-6">
8
+ <h1 class="m-0">Task Management</h1>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="content">
15
+ <div class="container-fluid">
16
+ <!-- Task Assignment Form -->
17
+ <div class="card card-primary">
18
+ <div class="card-header">
19
+ <h3 class="card-title">Assign New Task</h3>
20
+ </div>
21
+ <form id="taskForm" method="POST" action="/admin/assign_task">
22
+ <div class="card-body">
23
+ <div class="form-group">
24
+ <label for="taskTitle">Task Title</label>
25
+ <input type="text" class="form-control" id="taskTitle" name="title" placeholder="Enter task title" required>
26
+ </div>
27
+ <div class="form-group">
28
+ <label for="taskDescription">Description</label>
29
+ <textarea class="form-control" id="taskDescription" name="description" placeholder="Task description"></textarea>
30
+ </div>
31
+ <div class="form-group">
32
+ <label for="dueDate">Due Date</label>
33
+ <input type="datetime-local" class="form-control" id="dueDate" name="due_date" required>
34
+ </div>
35
+ <div class="form-group">
36
+ <label for="assignedTo">Assign To</label>
37
+ <select class="form-control" id="assignedTo" name="assigned_to">
38
+ <!-- Populate with list of farmers from backend -->
39
+ {% for farmer in farmers %}
40
+ <option value="{{ farmer.username }}">{{ farmer.username }}</option>
41
+ {% endfor %}
42
+ </select>
43
+ </div>
44
+ </div>
45
+ <div class="card-footer">
46
+ <button type="submit" class="btn btn-primary">Assign Task</button>
47
+ </div>
48
+ </form>
49
+ </div>
50
+
51
+ <!-- Task List -->
52
+ <div class="card mt-4">
53
+ <div class="card-header">
54
+ <h3 class="card-title">Assigned Tasks</h3>
55
+ </div>
56
+ <div class="card-body">
57
+ <table class="table table-bordered">
58
+ <thead>
59
+ <tr>
60
+ <th>Task Title</th>
61
+ <th>Description</th>
62
+ <th>Due Date</th>
63
+ <th>Assigned To</th>
64
+ <th>Status</th>
65
+ <th>Actions</th>
66
+ </tr>
67
+ </thead>
68
+ <tbody>
69
+ {% for task in tasks %}
70
+ <tr>
71
+ <td>{{ task.title }}</td>
72
+ <td>{{ task.description }}</td>
73
+ <td>{{ task.due_date }}</td>
74
+ <td>{{ task.assigned_to }}</td>
75
+ <td>{{ 'Completed' if task.is_completed else 'Pending' }}</td>
76
+ <td>
77
+ <a href="/admin/todos/{{ task._id }}/edit" class="btn btn-warning btn-sm">Edit</a>
78
+ <a href="/admin/todos/{{ task._id }}/delete" class="btn btn-danger btn-sm">Delete</a>
79
+ </td>
80
+ </tr>
81
+ {% endfor %}
82
+ </tbody>
83
+ </table>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ {% endblock %}
templates/todos.html ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block title %}To-Do List{% endblock %}
3
+ {% block content %}
4
+
5
+ <h2>To-Do List</h2>
6
+
7
+ <ul>
8
+ {% for todo in todos %}
9
+ <li>
10
+ <input type="checkbox" onclick="completeTodo('{{ todo['_id'] }}')" {% if todo.is_completed %}checked{% endif %}>
11
+ <span>{{ todo.title }}</span> - <span>{{ todo.due_date.strftime('%Y-%m-%d') }}</span>
12
+ </li>
13
+ {% endfor %}
14
+ </ul>
15
+
16
+ <script>
17
+ async function completeTodo(todoId) {
18
+ const response = await fetch(`/todo/todos/${todoId}/complete`, { method: "PUT" });
19
+ if (response.ok) {
20
+ alert("To-do item marked as completed!");
21
+ window.location.reload();
22
+ } else {
23
+ alert("Failed to mark to-do as completed.");
24
+ }
25
+ }
26
+ </script>
27
+
28
+ {% endblock %}