Files
family-hub/IMPLEMENTATION_GUIDE_PART1.txt

317 lines
8.6 KiB
Plaintext

========================================
🚀 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<void> {
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
<AvatarUpload
userId={editingUser.id} // ADD THIS
currentAvatarUrl={editingUser.avatar_url}
onUploadSuccess={(url) => {
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?
========================================