261 lines
6.7 KiB
TypeScript
261 lines
6.7 KiB
TypeScript
import { API_BASE_URL } from '@/src/config/env';
|
|
|
|
export type Tenant = {
|
|
id: number;
|
|
name: string;
|
|
short?: string | null;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type TaskStatus = 'Offen' | 'In Bearbeitung' | 'Abgeschlossen';
|
|
|
|
export type Task = {
|
|
id: number;
|
|
name: string;
|
|
description?: string | null;
|
|
categorie?: string | null;
|
|
userId?: string | null;
|
|
user_id?: string | null;
|
|
profile?: string | null;
|
|
archived?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type TenantProfile = {
|
|
id?: number | string;
|
|
user_id?: string;
|
|
full_name?: string;
|
|
fullName?: string;
|
|
email?: string;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type StaffTimeSpan = {
|
|
id: string | null;
|
|
eventIds: string[];
|
|
state: string;
|
|
started_at: string;
|
|
stopped_at: string | null;
|
|
duration_minutes: number;
|
|
user_id: string | null;
|
|
type: string;
|
|
description: string;
|
|
};
|
|
|
|
export type MeResponse = {
|
|
user: {
|
|
id: string;
|
|
email: string;
|
|
must_change_password?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
tenants: Tenant[];
|
|
activeTenant: number | string | null;
|
|
profile: Record<string, unknown> | null;
|
|
permissions: string[];
|
|
};
|
|
|
|
type RequestOptions = {
|
|
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
token?: string | null;
|
|
body?: unknown;
|
|
};
|
|
|
|
function buildUrl(path: string): string {
|
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
return path;
|
|
}
|
|
|
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
return `${API_BASE_URL}${normalizedPath}`;
|
|
}
|
|
|
|
async function parseJson(response: Response): Promise<unknown> {
|
|
const text = await response.text();
|
|
if (!text) return null;
|
|
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
|
const response = await fetch(buildUrl(path), {
|
|
method: options.method || 'GET',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
...(options.token ? { Authorization: `Bearer ${options.token}` } : {}),
|
|
},
|
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
});
|
|
|
|
const payload = await parseJson(response);
|
|
|
|
if (!response.ok) {
|
|
const message =
|
|
(payload as { message?: string; error?: string } | null)?.message ||
|
|
(payload as { message?: string; error?: string } | null)?.error ||
|
|
`Request failed (${response.status}) for ${path}`;
|
|
throw new Error(message);
|
|
}
|
|
|
|
return payload as T;
|
|
}
|
|
|
|
export async function checkBackendHealth(): Promise<{ status: string; [key: string]: unknown }> {
|
|
return apiRequest<{ status: string; [key: string]: unknown }>('/health');
|
|
}
|
|
|
|
export async function loginWithEmailPassword(email: string, password: string): Promise<string> {
|
|
const payload = await apiRequest<{ token?: string }>('/auth/login', {
|
|
method: 'POST',
|
|
body: { email, password },
|
|
});
|
|
|
|
if (!payload?.token) {
|
|
throw new Error('Login did not return a token.');
|
|
}
|
|
|
|
return payload.token;
|
|
}
|
|
|
|
export async function fetchMe(token: string): Promise<MeResponse> {
|
|
return apiRequest<MeResponse>('/api/me', {
|
|
token,
|
|
});
|
|
}
|
|
|
|
export async function switchTenantRequest(tenantId: number, token: string): Promise<string> {
|
|
const payload = await apiRequest<{ token?: string }>('/api/tenant/switch', {
|
|
method: 'POST',
|
|
token,
|
|
body: { tenant_id: String(tenantId) },
|
|
});
|
|
|
|
if (!payload?.token) {
|
|
throw new Error('Tenant switch did not return a token.');
|
|
}
|
|
|
|
return payload.token;
|
|
}
|
|
|
|
export async function fetchTasks(token: string): Promise<Task[]> {
|
|
const tasks = await apiRequest<Task[]>('/api/resource/tasks', { token });
|
|
return (tasks || []).filter((task) => !task.archived);
|
|
}
|
|
|
|
export async function createTask(
|
|
token: string,
|
|
payload: {
|
|
name: string;
|
|
description?: string | null;
|
|
categorie?: TaskStatus;
|
|
userId?: string | null;
|
|
}
|
|
): Promise<Task> {
|
|
return apiRequest<Task>('/api/resource/tasks', {
|
|
method: 'POST',
|
|
token,
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
export async function updateTask(token: string, taskId: number, payload: Partial<Task>): Promise<Task> {
|
|
return apiRequest<Task>(`/api/resource/tasks/${taskId}`, {
|
|
method: 'PUT',
|
|
token,
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
export async function fetchTenantProfiles(token: string): Promise<TenantProfile[]> {
|
|
const response = await apiRequest<{ data?: TenantProfile[] }>('/api/tenant/profiles', { token });
|
|
return response?.data || [];
|
|
}
|
|
|
|
export async function fetchStaffTimeSpans(
|
|
token: string,
|
|
targetUserId?: string
|
|
): Promise<StaffTimeSpan[]> {
|
|
const query = targetUserId ? `?targetUserId=${encodeURIComponent(targetUserId)}` : '';
|
|
const spans = await apiRequest<any[]>(`/api/staff/time/spans${query}`, { token });
|
|
|
|
return (spans || [])
|
|
.map((span) => {
|
|
const started = span.startedAt ? new Date(span.startedAt) : null;
|
|
const ended = span.endedAt ? new Date(span.endedAt) : new Date();
|
|
const durationMinutes =
|
|
started && ended ? Math.max(0, Math.floor((ended.getTime() - started.getTime()) / 60000)) : 0;
|
|
|
|
return {
|
|
id: span.sourceEventIds?.[0] ?? null,
|
|
eventIds: span.sourceEventIds || [],
|
|
state: span.status || 'draft',
|
|
started_at: span.startedAt,
|
|
stopped_at: span.endedAt || null,
|
|
duration_minutes: durationMinutes,
|
|
user_id: targetUserId || null,
|
|
type: span.type || 'work',
|
|
description: span.payload?.description || '',
|
|
} as StaffTimeSpan;
|
|
})
|
|
.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
|
|
}
|
|
|
|
export async function createStaffTimeEvent(
|
|
token: string,
|
|
payload: {
|
|
eventtype: string;
|
|
eventtime: string;
|
|
user_id: string;
|
|
description?: string;
|
|
}
|
|
): Promise<void> {
|
|
await apiRequest('/api/staff/time/event', {
|
|
method: 'POST',
|
|
token,
|
|
body: {
|
|
eventtype: payload.eventtype,
|
|
eventtime: payload.eventtime,
|
|
user_id: payload.user_id,
|
|
payload: payload.description ? { description: payload.description } : undefined,
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function submitStaffTime(token: string, eventIds: string[]): Promise<void> {
|
|
await apiRequest('/api/staff/time/submit', {
|
|
method: 'POST',
|
|
token,
|
|
body: { eventIds },
|
|
});
|
|
}
|
|
|
|
export async function approveStaffTime(
|
|
token: string,
|
|
eventIds: string[],
|
|
employeeUserId: string
|
|
): Promise<void> {
|
|
await apiRequest('/api/staff/time/approve', {
|
|
method: 'POST',
|
|
token,
|
|
body: { eventIds, employeeUserId },
|
|
});
|
|
}
|
|
|
|
export async function rejectStaffTime(
|
|
token: string,
|
|
eventIds: string[],
|
|
employeeUserId: string,
|
|
reason: string
|
|
): Promise<void> {
|
|
await apiRequest('/api/staff/time/reject', {
|
|
method: 'POST',
|
|
token,
|
|
body: { eventIds, employeeUserId, reason },
|
|
});
|
|
}
|