feat: implement per-member reading progress tracking for shared books (#30)

Backend (apps/groups):
- ReadingGroup model: named groups linked to EBooks with creator tracking
- GroupMembership model: user-group association with member/admin roles
- MemberProgress model: per-member progress with section tracking,
  percentage, time_spent, privacy toggles, and device-position preservation
- ReadingGroupViewSet: CRUD, join/leave, members list, progress endpoints
- GET /api/groups/{id}/members/progress/ — all members' progress
  (public: section label only; own: full detail; private: stubbed)
- PATCH /api/groups/{id}/progress/ — update own progress with auto-
  percentage calculation from section boundaries
- GET /api/groups/{id}/progress/summary/ — admin dashboard with
  averages, started/finished counts, and per-member details
- Permissions: IsGroupMember, IsGroupAdmin

Frontend:
- Groups list page (/groups) with create modal and book selector
- Group detail page (/groups/:id) with progress and admin summary tabs
- Progress bar visualization per member with privacy-aware display
- Admin stat cards (total members, started, finished, avg progress, avg time)
- Navigation link from Library header

Shared:
- ReadingGroupSummary and MemberProgressPublic types
- API endpoint constants for groups routes

Closes #30
This commit is contained in:
Marko
2026-06-20 19:31:21 +00:00
parent 26c5f6f06b
commit 8a86cf8388
20 changed files with 1571 additions and 0 deletions
+32
View File
@@ -215,4 +215,36 @@ export interface PaginatedResponse<T> {
export interface ApiError {
detail?: string;
[key: string]: unknown;
}
// ---- Reading Groups ----
export interface ReadingGroupSummary {
id: number;
name: string;
ebook: number;
ebook_title: string;
ebook_author: string;
created_by_email: string;
description: string;
member_count: number;
my_progress: {
current_section: number;
percentage: number;
time_spent_seconds: number;
section_label: string;
} | null;
created_at: string;
updated_at: string;
}
export interface MemberProgressPublic {
user_id: number;
user_email: string;
current_section: number;
total_sections: number;
section_label: string;
percentage: number;
time_spent_seconds: number;
updated_at: string;
}
+9
View File
@@ -114,4 +114,13 @@ export const API_ENDPOINTS = {
bookmarkDetail: (id: string) => `/api/annotations/bookmarks/${id}/`,
noteDetail: (id: string) => `/api/annotations/notes/${id}/`,
},
groups: {
list: "/api/groups/",
detail: (id: number) => `/api/groups/${id}/`,
membersProgress: (id: number) => `/api/groups/${id}/members/progress/`,
myProgress: (id: number) => `/api/groups/${id}/progress/`,
progressSummary: (id: number) => `/api/groups/${id}/progress/summary/`,
join: (id: number) => `/api/groups/${id}/join/`,
leave: (id: number) => `/api/groups/${id}/leave/`,
},
} as const;