first commit

This commit is contained in:
Space
2026-01-17 13:37:57 +01:00
commit 3e34d84a29
49 changed files with 8579 additions and 0 deletions

286
app/api/client.ts Normal file
View File

@@ -0,0 +1,286 @@
import type {
AuthResponse,
UserLogin,
UserRegister,
User,
Subject,
SubjectCreate,
SubjectUpdate,
Grade,
GradeCreate,
GradeUpdate,
ReportPeriod,
ReportPeriodCreate,
ReportPeriodUpdate,
TeacherGrade,
TeacherGradeCreate,
TeacherGradeUpdate,
} from "~/types/api";
const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8000/api";
class ApiClient {
private getHeaders(includeAuth: boolean = false): HeadersInit {
const headers: HeadersInit = {
"Content-Type": "application/json",
};
if (includeAuth) {
const token = localStorage.getItem("access_token");
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
}
return headers;
}
private async handleResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
const error = await response.json().catch(() => ({ detail: "An error occurred" }));
throw new Error(error.detail || `HTTP ${response.status}`);
}
if (response.status === 204) {
return undefined as T;
}
return response.json();
}
// Auth endpoints
async register(data: UserRegister): Promise<AuthResponse> {
const response = await fetch(`${API_BASE_URL}/auth/register`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify(data),
});
const result = await this.handleResponse<AuthResponse>(response);
localStorage.setItem("access_token", result.access_token);
return result;
}
async login(data: UserLogin): Promise<AuthResponse> {
const response = await fetch(`${API_BASE_URL}/auth/login`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify(data),
});
const result = await this.handleResponse<AuthResponse>(response);
localStorage.setItem("access_token", result.access_token);
return result;
}
async getMe(): Promise<User> {
const response = await fetch(`${API_BASE_URL}/auth/me`, {
headers: this.getHeaders(true),
});
return this.handleResponse<User>(response);
}
logout(): void {
localStorage.removeItem("access_token");
}
isAuthenticated(): boolean {
return !!localStorage.getItem("access_token");
}
// Subject endpoints
async getSubjects(): Promise<Subject[]> {
const response = await fetch(`${API_BASE_URL}/subjects`, {
headers: this.getHeaders(true),
});
return this.handleResponse<Subject[]>(response);
}
async getSubject(id: string): Promise<Subject> {
const response = await fetch(`${API_BASE_URL}/subjects/${id}`, {
headers: this.getHeaders(true),
});
return this.handleResponse<Subject>(response);
}
async createSubject(data: SubjectCreate): Promise<Subject> {
const response = await fetch(`${API_BASE_URL}/subjects`, {
method: "POST",
headers: this.getHeaders(true),
body: JSON.stringify(data),
});
return this.handleResponse<Subject>(response);
}
async updateSubject(id: string, data: SubjectUpdate): Promise<Subject> {
const response = await fetch(`${API_BASE_URL}/subjects/${id}`, {
method: "PUT",
headers: this.getHeaders(true),
body: JSON.stringify(data),
});
return this.handleResponse<Subject>(response);
}
async deleteSubject(id: string): Promise<void> {
const response = await fetch(`${API_BASE_URL}/subjects/${id}`, {
method: "DELETE",
headers: this.getHeaders(true),
});
return this.handleResponse<void>(response);
}
// Grade endpoints
async getGrades(subjectId?: string): Promise<Grade[]> {
const url = subjectId
? `${API_BASE_URL}/grades?subject_id=${subjectId}`
: `${API_BASE_URL}/grades`;
const response = await fetch(url, {
headers: this.getHeaders(true),
});
return this.handleResponse<Grade[]>(response);
}
async getGrade(id: string): Promise<Grade> {
const response = await fetch(`${API_BASE_URL}/grades/${id}`, {
headers: this.getHeaders(true),
});
return this.handleResponse<Grade>(response);
}
async createGrade(data: GradeCreate): Promise<Grade> {
const response = await fetch(`${API_BASE_URL}/grades`, {
method: "POST",
headers: this.getHeaders(true),
body: JSON.stringify(data),
});
return this.handleResponse<Grade>(response);
}
async updateGrade(id: string, data: GradeUpdate): Promise<Grade> {
const response = await fetch(`${API_BASE_URL}/grades/${id}`, {
method: "PUT",
headers: this.getHeaders(true),
body: JSON.stringify(data),
});
return this.handleResponse<Grade>(response);
}
async deleteGrade(id: string): Promise<void> {
const response = await fetch(`${API_BASE_URL}/grades/${id}`, {
method: "DELETE",
headers: this.getHeaders(true),
});
return this.handleResponse<void>(response);
}
// Report Period endpoints
async getPeriods(): Promise<ReportPeriod[]> {
const response = await fetch(`${API_BASE_URL}/periods`, {
headers: this.getHeaders(true),
});
return this.handleResponse<ReportPeriod[]>(response);
}
async getPeriod(id: string): Promise<ReportPeriod> {
const response = await fetch(`${API_BASE_URL}/periods/${id}`, {
headers: this.getHeaders(true),
});
return this.handleResponse<ReportPeriod>(response);
}
async createPeriod(data: ReportPeriodCreate): Promise<ReportPeriod> {
const response = await fetch(`${API_BASE_URL}/periods`, {
method: "POST",
headers: this.getHeaders(true),
body: JSON.stringify(data),
});
return this.handleResponse<ReportPeriod>(response);
}
async updatePeriod(id: string, data: ReportPeriodUpdate): Promise<ReportPeriod> {
const response = await fetch(`${API_BASE_URL}/periods/${id}`, {
method: "PUT",
headers: this.getHeaders(true),
body: JSON.stringify(data),
});
return this.handleResponse<ReportPeriod>(response);
}
async deletePeriod(id: string): Promise<void> {
const response = await fetch(`${API_BASE_URL}/periods/${id}`, {
method: "DELETE",
headers: this.getHeaders(true),
});
return this.handleResponse<void>(response);
}
// Teacher Grade endpoints
async getTeacherGrades(subjectId?: string, periodId?: string): Promise<TeacherGrade[]> {
const params = new URLSearchParams();
if (subjectId) params.append("subject_id", subjectId);
if (periodId) params.append("period_id", periodId);
const url = `${API_BASE_URL}/teacher-grades${params.toString() ? `?${params.toString()}` : ""}`;
const response = await fetch(url, {
headers: this.getHeaders(true),
});
return this.handleResponse<TeacherGrade[]>(response);
}
async getTeacherGrade(id: string): Promise<TeacherGrade> {
const response = await fetch(`${API_BASE_URL}/teacher-grades/${id}`, {
headers: this.getHeaders(true),
});
return this.handleResponse<TeacherGrade>(response);
}
async createTeacherGrade(data: TeacherGradeCreate): Promise<TeacherGrade> {
const response = await fetch(`${API_BASE_URL}/teacher-grades`, {
method: "POST",
headers: this.getHeaders(true),
body: JSON.stringify(data),
});
return this.handleResponse<TeacherGrade>(response);
}
async deleteTeacherGrade(id: string): Promise<void> {
const response = await fetch(`${API_BASE_URL}/teacher-grades/${id}`, {
method: "DELETE",
headers: this.getHeaders(true),
});
return this.handleResponse<void>(response);
}
// Export endpoints
async exportGradesCSV(periodId?: string): Promise<void> {
const url = periodId
? `${API_BASE_URL}/export/grades?period_id=${periodId}`
: `${API_BASE_URL}/export/grades`;
const response = await fetch(url, {
headers: this.getHeaders(true),
});
if (!response.ok) {
throw new Error("Failed to export grades");
}
// Create a blob from the response and trigger download
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
// Extract filename from Content-Disposition header or use default
const contentDisposition = response.headers.get("Content-Disposition");
const filename = contentDisposition
? contentDisposition.split("filename=")[1]?.replace(/"/g, "")
: "grades_export.csv";
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(downloadUrl);
}
}
export const api = new ApiClient();