======================================== 🚀 COMPREHENSIVE IMPLEMENTATION GUIDE ======================================== Major Features Update - All Code Changes ======================================== This document contains ALL code changes needed for: 1. Admin avatar upload for other users 2. Chore assignment types (any_one vs all_assigned) 3. Kiosk available chores section 4. Kiosk completion confirmation modal ======================================== STEP 1: RUN MIGRATION ======================================== Already created: backend/migrations/004_add_assignment_type.py Run it: ``` python backend/migrations/004_add_assignment_type.py ``` ======================================== STEP 2: BACKEND - Admin Avatar Upload ======================================== FILE: backend/app/api/v1/uploads.py ADD these endpoints at the end of the file: ```python @router.post("/admin/users/{user_id}/avatar", status_code=status.HTTP_200_OK) async def admin_upload_user_avatar( user_id: int, file: UploadFile = File(...), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Upload avatar for any user (admin only). """ # Check if current user is admin if not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only administrators can upload avatars for other users" ) # Get target user target_user = db.query(User).filter(User.id == user_id).first() if not target_user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) if not validate_image(file.filename): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid file type. Allowed: {', '.join(ALLOWED_EXTENSIONS)}" ) # Generate unique filename file_ext = Path(file.filename).suffix.lower() filename = f"user_{user_id}_{uuid.uuid4().hex[:8]}{file_ext}" file_path = USER_UPLOAD_DIR / filename # Delete old avatar if exists if target_user.avatar_url: old_file = USER_UPLOAD_DIR / Path(target_user.avatar_url).name if old_file.exists(): old_file.unlink() # Save new avatar save_upload_file(file, file_path) # Update user record avatar_url = f"/static/uploads/users/{filename}" target_user.avatar_url = avatar_url db.commit() db.refresh(target_user) return { "message": "Avatar uploaded successfully", "avatar_url": avatar_url } @router.delete("/admin/users/{user_id}/avatar", status_code=status.HTTP_204_NO_CONTENT) async def admin_delete_user_avatar( user_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Delete avatar for any user (admin only)""" # Check if current user is admin if not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only administrators can delete avatars for other users" ) # Get target user target_user = db.query(User).filter(User.id == user_id).first() if not target_user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) if not target_user.avatar_url: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="No avatar to delete" ) # Delete file old_file = USER_UPLOAD_DIR / Path(target_user.avatar_url).name if old_file.exists(): old_file.unlink() # Update database target_user.avatar_url = None db.commit() return None ``` ======================================== STEP 3: BACKEND - Update Public API ======================================== FILE: backend/app/api/v1/public.py UPDATE the get_public_chores endpoint to include assignment_type: In the chore_dict, add: ```python "assignment_type": chore.assignment_type, ``` ADD new endpoint for claiming chores: ```python @router.post("/chores/{chore_id}/claim") async def claim_public_chore( chore_id: int, user_id: int, db: Session = Depends(get_db) ): """ Claim an unassigned chore (add assignment). """ # Get chore chore = db.query(Chore).filter(Chore.id == chore_id).first() if not chore: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Chore not found" ) # Get user user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) # Check if already assigned existing = db.query(ChoreAssignment).filter( ChoreAssignment.chore_id == chore_id, ChoreAssignment.user_id == user_id ).first() if existing: return {"message": "Already assigned to this chore"} # Create assignment assignment = ChoreAssignment( chore_id=chore_id, user_id=user_id ) db.add(assignment) db.commit() return { "message": "Chore claimed successfully", "chore_id": chore_id, "user_id": user_id } ``` ======================================== STEP 4: FRONTEND - Update Upload Service ======================================== FILE: frontend/src/api/uploads.ts ADD these methods to uploadService: ```typescript /** * Upload avatar for another user (admin only) */ async uploadAvatarForUser(userId: number, file: File): Promise<{ message: string; avatar_url: string }> { const formData = new FormData(); formData.append('file', file); const response = await api.post(`/api/v1/uploads/admin/users/${userId}/avatar`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); return response.data; }, /** * Delete avatar for another user (admin only) */ async deleteAvatarForUser(userId: number): Promise { await api.delete(`/api/v1/uploads/admin/users/${userId}/avatar`); }, ``` ======================================== STEP 5: FRONTEND - Update Avatar Component ======================================== FILE: frontend/src/components/AvatarUpload.tsx UPDATE the interface and component: ```typescript interface AvatarUploadProps { currentAvatarUrl?: string; onUploadSuccess: (avatarUrl: string) => void; onDeleteSuccess?: () => void; userId?: number; // NEW: For admin uploading for other users } ``` In handleFileSelect, UPDATE the upload call: ```typescript try { const result = userId ? await uploadService.uploadAvatarForUser(userId, file) : await uploadService.uploadAvatar(file); onUploadSuccess(result.avatar_url); ``` In handleDelete, UPDATE the delete call: ```typescript try { if (userId) { await uploadService.deleteAvatarForUser(userId); } else { await uploadService.deleteAvatar(); } setPreviewUrl(null); ``` ======================================== STEP 6: FRONTEND - Update Settings Page ======================================== FILE: frontend/src/pages/Settings.tsx In the admin edit modal, ADD userId prop: Find the AvatarUpload component in the admin modal and update: ```typescript { setEditingUser({ ...editingUser, avatar_url: url }); }} onDeleteSuccess={() => { setEditingUser({ ...editingUser, avatar_url: undefined }); }} /> ``` ======================================== STEP 7: FRONTEND - Update Chore Interface ======================================== FILE: frontend/src/api/chores.ts ADD to Chore interface: ```typescript export interface Chore { id: number; title: string; description?: string; room: string; frequency: 'daily' | 'weekly' | 'fortnightly' | 'monthly' | 'on_trigger'; points: number; image_url?: string; assignment_type: 'any_one' | 'all_assigned'; // ADD THIS status: 'pending' | 'in_progress' | 'completed' | 'skipped'; // ... rest of fields } ``` ======================================== Due to size, I'll create separate implementation files for: - Kiosk View updates (available chores + completion modal) - Chore form updates (assignment type selector) These are complex and need their own dedicated implementation files. Would you like me to: 1. Create the kiosk updates file next? 2. Create the chore forms update file? 3. Apply what we have so far and test? ========================================