Roadmap P0
- Ship /logo.svg fallback and rewire asset references (superseded later by
the project-manager PNG in a separate commit).
Roadmap P1
- Response envelope alignment ({items,total,page,size}) across services
and query hooks.
- Approval/ticket UX updates tied to the new backend rollback semantics.
Roadmap P2
- React.lazy + Vite manualChunks to split vendor-radix/charts/icons/forms
from the main bundle and cut initial JS.
- Fix the 181 tsc errors (PaginationParams class + per-service generics).
- Auto-refresh flow for JWT refresh tokens wired into api-client.ts.
Roadmap P3
- i18n: i18next + language detector, en/ar locales, RTL auto-switch,
LanguageToggle component in RoleLayout and AdminLmsLayout.
- GDPR: PrivacyCenter page for data export and right-to-erasure, backed
by gdpr.service.ts; logout via AuthContext after erasure.
- Human-in-the-loop exam review UI: admin ExamReviewQueue + ExamReviewDetail
pages, useExamReview hooks, exam-review.service.ts.
- AI quality loop: AIPromptEditor (versioning + render preview),
AIFeedbackButtons for students, AIFeedbackTriage admin page, dedicated
services, hooks, and types.
- Dark mode: ThemeProvider + ThemeToggle + chart color tokens.
- Accessibility: DialogDescription on every DialogContent; alert-dialog
and sheet updates.
- Retire dead pages: ExamPage marketing, Index, ProfilePage import.
- Playwright e2e scaffolding: playwright.config.ts + e2e/login.spec.ts
smoke tests, npm scripts test:e2e / test:e2e:install.
Made-with: Cursor
61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
import { api, API_BASE_URL } from "@/lib/api-client";
|
|
import { asPaginated, asRecordData } from "@/lib/odoo-api";
|
|
import type { User, PaginatedResponse, PaginationParams, ApiSuccessResponse } from "@/types";
|
|
|
|
export interface UserListParams extends PaginationParams {
|
|
type?: string;
|
|
entity_id?: number;
|
|
}
|
|
|
|
function mapPortalUser(raw: Record<string, unknown>): User {
|
|
return {
|
|
id: raw.id as number,
|
|
name: String(raw.name || ""),
|
|
email: String(raw.email ?? raw.login ?? ""),
|
|
login: String(raw.login ?? raw.email ?? ""),
|
|
user_type: (raw.user_type as User["user_type"]) || "student",
|
|
is_verified: Boolean(raw.is_verified ?? true),
|
|
entities: Array.isArray(raw.entities) ? (raw.entities as User["entities"]) : [],
|
|
classrooms: Array.isArray(raw.classrooms) ? (raw.classrooms as string[]) : [],
|
|
phone: raw.phone as string | undefined,
|
|
gender: raw.gender as string | undefined,
|
|
student_id: raw.student_id as string | undefined,
|
|
};
|
|
}
|
|
|
|
export const usersService = {
|
|
async list(params: UserListParams): Promise<PaginatedResponse<User>> {
|
|
const raw = await api.get<unknown>("/users/list", params);
|
|
const p = asPaginated<Record<string, unknown>>(raw);
|
|
const items = p.items.map(mapPortalUser);
|
|
return { items, total: p.total, page: p.page, size: p.size, pages: p.pages };
|
|
},
|
|
|
|
async getById(id: number): Promise<User> {
|
|
const raw = await api.get<unknown>(`/users/${id}`);
|
|
return mapPortalUser(asRecordData<Record<string, unknown>>(raw));
|
|
},
|
|
|
|
async update(id: number, data: Partial<User>): Promise<User> {
|
|
const raw = await api.patch<unknown>(`/users/update`, { id, ...data });
|
|
return mapPortalUser(asRecordData<Record<string, unknown>>(raw));
|
|
},
|
|
|
|
async create(data: Partial<User>): Promise<User> {
|
|
const raw = await api.post<unknown>("/users/create", data);
|
|
return mapPortalUser(asRecordData<Record<string, unknown>>(raw));
|
|
},
|
|
|
|
async createBatch(data: { users: Partial<User>[] }): Promise<ApiSuccessResponse> {
|
|
return api.post<ApiSuccessResponse>("/batch_users", data);
|
|
},
|
|
|
|
async exportList(params: UserListParams): Promise<Blob> {
|
|
const res = await fetch(`${API_BASE_URL}/users/export?${new URLSearchParams(params as Record<string, string>)}`, {
|
|
headers: { Authorization: `Bearer ${localStorage.getItem("encoach_token") ?? ""}` },
|
|
});
|
|
if (!res.ok) throw new Error(`Export failed: ${res.status} ${res.statusText}`);
|
|
return res.blob();
|
|
},
|
|
};
|