917 lines
24 KiB
TypeScript
917 lines
24 KiB
TypeScript
import { getApiBaseUrlSync } from '@/src/lib/server-config';
|
|
|
|
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;
|
|
project?: number | { id?: number; name?: string } | null;
|
|
customer?: number | { id?: number; name?: string } | null;
|
|
plant?: number | { id?: number; name?: 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 Project = {
|
|
id: number;
|
|
name: string;
|
|
notes?: string | null;
|
|
projectNumber?: string | null;
|
|
archived?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type ProjectFile = {
|
|
id: string;
|
|
name?: string | null;
|
|
path?: string | null;
|
|
project?: number | { id?: number; name?: string };
|
|
customer?: number | { id?: number; name?: string };
|
|
plant?: number | { id?: number; name?: string };
|
|
createddocument?: number | { id?: number; documentNumber?: string };
|
|
mimeType?: string | null;
|
|
url?: string;
|
|
archived?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type Customer = {
|
|
id: number;
|
|
name: string;
|
|
customerNumber?: string | null;
|
|
notes?: string | null;
|
|
archived?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type Plant = {
|
|
id: number;
|
|
name: string;
|
|
description?: string | null;
|
|
customer?: number | { id?: number; name?: string };
|
|
archived?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type CustomerInventoryItem = {
|
|
id: number;
|
|
name: string;
|
|
customer?: number | { id?: number; name?: string } | null;
|
|
customerInventoryId?: string | null;
|
|
serialNumber?: string | null;
|
|
description?: string | null;
|
|
manufacturer?: string | null;
|
|
manufacturerNumber?: string | null;
|
|
quantity?: number | null;
|
|
archived?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type CreatedDocument = {
|
|
id: number;
|
|
documentNumber?: string | null;
|
|
title?: string | null;
|
|
type?: string | null;
|
|
state?: string | null;
|
|
documentDate?: string | null;
|
|
customer?: number | { id?: number; name?: string };
|
|
archived?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
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[];
|
|
};
|
|
|
|
export type EncodedLabelRow = {
|
|
dataType: 'pixels' | 'void' | 'check';
|
|
rowNumber: number;
|
|
repeat: number;
|
|
rowData?: Uint8Array | number[] | Record<string, number>;
|
|
blackPixelsCount: number;
|
|
};
|
|
|
|
export type EncodedLabelImage = {
|
|
cols: number;
|
|
rows: number;
|
|
rowsData: EncodedLabelRow[];
|
|
};
|
|
|
|
export type PrintLabelResponse = {
|
|
encoded: EncodedLabelImage;
|
|
base64?: string;
|
|
};
|
|
|
|
export type WikiTreeItem = {
|
|
id: string;
|
|
parentId?: string | null;
|
|
title: string;
|
|
isFolder?: boolean;
|
|
isVirtual?: boolean;
|
|
sortOrder?: number;
|
|
entityType?: string | null;
|
|
entityId?: number | null;
|
|
entityUuid?: string | null;
|
|
updatedAt?: string;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type WikiPage = {
|
|
id: string;
|
|
title: string;
|
|
content?: unknown;
|
|
parentId?: string | null;
|
|
isFolder?: boolean;
|
|
entityType?: string | null;
|
|
entityId?: number | null;
|
|
entityUuid?: string | null;
|
|
updatedAt?: string;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
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 `${getApiBaseUrlSync()}${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 renderPrintLabel(
|
|
token: string,
|
|
context: Record<string, unknown>,
|
|
width = 584,
|
|
height = 354
|
|
): Promise<PrintLabelResponse> {
|
|
return apiRequest<PrintLabelResponse>('/api/print/label', {
|
|
method: 'POST',
|
|
token,
|
|
body: {
|
|
context,
|
|
width,
|
|
height,
|
|
},
|
|
});
|
|
}
|
|
|
|
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;
|
|
project?: number | null;
|
|
customer?: number | null;
|
|
plant?: number | 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 },
|
|
});
|
|
}
|
|
|
|
export async function fetchProjects(token: string, includeArchived = false): Promise<Project[]> {
|
|
const projects = await apiRequest<Project[]>('/api/resource/projects', { token });
|
|
if (includeArchived) return projects || [];
|
|
return (projects || []).filter((project) => !project.archived);
|
|
}
|
|
|
|
export async function createProject(
|
|
token: string,
|
|
payload: {
|
|
name: string;
|
|
projectNumber?: string | null;
|
|
customer?: number | null;
|
|
plant?: number | null;
|
|
notes?: string | null;
|
|
}
|
|
): Promise<Project> {
|
|
return apiRequest<Project>('/api/resource/projects', {
|
|
method: 'POST',
|
|
token,
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
export async function fetchCustomers(token: string, includeArchived = false): Promise<Customer[]> {
|
|
const customers = await apiRequest<Customer[]>('/api/resource/customers', { token });
|
|
if (includeArchived) return customers || [];
|
|
return (customers || []).filter((customer) => !customer.archived);
|
|
}
|
|
|
|
export async function createCustomer(
|
|
token: string,
|
|
payload: {
|
|
name: string;
|
|
customerNumber?: string | null;
|
|
notes?: string | null;
|
|
}
|
|
): Promise<Customer> {
|
|
return apiRequest<Customer>('/api/resource/customers', {
|
|
method: 'POST',
|
|
token,
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
export async function fetchCustomerById(token: string, customerId: number): Promise<Customer> {
|
|
return apiRequest<Customer>(`/api/resource/customers/${customerId}`, { token });
|
|
}
|
|
|
|
function resolveCustomerIdFromCustomerInventoryItem(item: CustomerInventoryItem): number | null {
|
|
const rawCustomer = item.customer;
|
|
if (!rawCustomer) return null;
|
|
if (typeof rawCustomer === 'object') {
|
|
return rawCustomer.id ? Number(rawCustomer.id) : null;
|
|
}
|
|
return Number(rawCustomer);
|
|
}
|
|
|
|
export async function fetchCustomerInventoryItems(
|
|
token: string,
|
|
customerId: number,
|
|
includeArchived = false
|
|
): Promise<CustomerInventoryItem[]> {
|
|
const rows = await apiRequest<CustomerInventoryItem[]>('/api/resource/customerinventoryitems', { token });
|
|
|
|
return (rows || []).filter((item) => {
|
|
if (!includeArchived && item.archived) return false;
|
|
return resolveCustomerIdFromCustomerInventoryItem(item) === Number(customerId);
|
|
});
|
|
}
|
|
|
|
export async function fetchAllCustomerInventoryItems(
|
|
token: string,
|
|
includeArchived = false
|
|
): Promise<CustomerInventoryItem[]> {
|
|
const rows = await apiRequest<CustomerInventoryItem[]>('/api/resource/customerinventoryitems', { token });
|
|
if (includeArchived) return rows || [];
|
|
return (rows || []).filter((item) => !item.archived);
|
|
}
|
|
|
|
export async function createCustomerInventoryItem(
|
|
token: string,
|
|
payload: {
|
|
customer: number;
|
|
name: string;
|
|
customerInventoryId?: string | null;
|
|
serialNumber?: string | null;
|
|
description?: string | null;
|
|
quantity?: number | null;
|
|
}
|
|
): Promise<CustomerInventoryItem> {
|
|
const autoInventoryId = `MOB-${Date.now()}`;
|
|
|
|
return apiRequest<CustomerInventoryItem>('/api/resource/customerinventoryitems', {
|
|
method: 'POST',
|
|
token,
|
|
body: {
|
|
customer: payload.customer,
|
|
name: payload.name,
|
|
customerInventoryId: payload.customerInventoryId?.trim() || autoInventoryId,
|
|
serialNumber: payload.serialNumber?.trim() || null,
|
|
description: payload.description?.trim() || null,
|
|
quantity: Number.isFinite(Number(payload.quantity)) ? Number(payload.quantity) : 1,
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function fetchPlants(token: string, includeArchived = false): Promise<Plant[]> {
|
|
const plants = await apiRequest<Plant[]>('/api/resource/plants', { token });
|
|
if (includeArchived) return plants || [];
|
|
return (plants || []).filter((plant) => !plant.archived);
|
|
}
|
|
|
|
export async function createPlant(
|
|
token: string,
|
|
payload: {
|
|
name: string;
|
|
description?: string | null;
|
|
customer?: number | null;
|
|
}
|
|
): Promise<Plant> {
|
|
return apiRequest<Plant>('/api/resource/plants', {
|
|
method: 'POST',
|
|
token,
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
export async function fetchPlantById(token: string, plantId: number): Promise<Plant> {
|
|
return apiRequest<Plant>(`/api/resource/plants/${plantId}`, { token });
|
|
}
|
|
|
|
function toQueryString(params: Record<string, unknown>): string {
|
|
const searchParams = new URLSearchParams();
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value === undefined || value === null || value === '') return;
|
|
searchParams.append(key, String(value));
|
|
});
|
|
const query = searchParams.toString();
|
|
return query ? `?${query}` : '';
|
|
}
|
|
|
|
export async function fetchWikiTree(
|
|
token: string,
|
|
filters: {
|
|
entityType?: string;
|
|
entityId?: number | null;
|
|
entityUuid?: string | null;
|
|
} = {}
|
|
): Promise<WikiTreeItem[]> {
|
|
const query = toQueryString({
|
|
entityType: filters.entityType,
|
|
entityId: filters.entityId,
|
|
entityUuid: filters.entityUuid,
|
|
});
|
|
return apiRequest<WikiTreeItem[]>(`/api/wiki/tree${query}`, { token });
|
|
}
|
|
|
|
export async function fetchWikiPageById(token: string, pageId: string): Promise<WikiPage> {
|
|
return apiRequest<WikiPage>(`/api/wiki/${encodeURIComponent(pageId)}`, { token });
|
|
}
|
|
|
|
export async function createWikiPage(
|
|
token: string,
|
|
payload: {
|
|
title: string;
|
|
parentId?: string | null;
|
|
isFolder?: boolean;
|
|
entityType?: string;
|
|
entityId?: number | null;
|
|
entityUuid?: string | null;
|
|
}
|
|
): Promise<WikiPage> {
|
|
return apiRequest<WikiPage>('/api/wiki', {
|
|
method: 'POST',
|
|
token,
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
export async function updateWikiPage(
|
|
token: string,
|
|
pageId: string,
|
|
payload: {
|
|
title?: string;
|
|
content?: unknown;
|
|
parentId?: string | null;
|
|
sortOrder?: number;
|
|
isFolder?: boolean;
|
|
}
|
|
): Promise<WikiPage> {
|
|
return apiRequest<WikiPage>(`/api/wiki/${encodeURIComponent(pageId)}`, {
|
|
method: 'PATCH',
|
|
token,
|
|
body: payload,
|
|
});
|
|
}
|
|
|
|
export async function deleteWikiPage(token: string, pageId: string): Promise<{ success: boolean; deletedId?: string }> {
|
|
return apiRequest<{ success: boolean; deletedId?: string }>(`/api/wiki/${encodeURIComponent(pageId)}`, {
|
|
method: 'DELETE',
|
|
token,
|
|
});
|
|
}
|
|
|
|
export async function fetchProjectById(token: string, projectId: number): Promise<Project> {
|
|
return apiRequest<Project>(`/api/resource/projects/${projectId}`, { token });
|
|
}
|
|
|
|
function resolveProjectIdFromTask(task: Task): number | null {
|
|
const rawProject = task.project;
|
|
if (!rawProject) return null;
|
|
if (typeof rawProject === 'object') {
|
|
return rawProject.id ? Number(rawProject.id) : null;
|
|
}
|
|
return Number(rawProject);
|
|
}
|
|
|
|
export async function fetchProjectTasks(token: string, projectId: number): Promise<Task[]> {
|
|
const tasks = await fetchTasks(token);
|
|
return (tasks || []).filter((task) => resolveProjectIdFromTask(task) === Number(projectId));
|
|
}
|
|
|
|
export async function createProjectTask(
|
|
token: string,
|
|
payload: {
|
|
projectId: number;
|
|
name: string;
|
|
description?: string | null;
|
|
userId?: string | null;
|
|
categorie?: TaskStatus;
|
|
}
|
|
): Promise<Task> {
|
|
return createTask(token, {
|
|
name: payload.name,
|
|
description: payload.description || null,
|
|
userId: payload.userId || null,
|
|
categorie: payload.categorie || 'Offen',
|
|
project: payload.projectId,
|
|
});
|
|
}
|
|
|
|
function resolveProjectIdFromFile(file: ProjectFile): number | null {
|
|
const rawProject = file.project;
|
|
if (!rawProject) return null;
|
|
if (typeof rawProject === 'object') {
|
|
return rawProject.id ? Number(rawProject.id) : null;
|
|
}
|
|
return Number(rawProject);
|
|
}
|
|
|
|
function resolveCustomerIdFromFile(file: ProjectFile): number | null {
|
|
const rawCustomer = file.customer;
|
|
if (!rawCustomer) return null;
|
|
if (typeof rawCustomer === 'object') {
|
|
return rawCustomer.id ? Number(rawCustomer.id) : null;
|
|
}
|
|
return Number(rawCustomer);
|
|
}
|
|
|
|
function resolvePlantIdFromFile(file: ProjectFile): number | null {
|
|
const rawPlant = file.plant;
|
|
if (!rawPlant) return null;
|
|
if (typeof rawPlant === 'object') {
|
|
return rawPlant.id ? Number(rawPlant.id) : null;
|
|
}
|
|
return Number(rawPlant);
|
|
}
|
|
|
|
function resolveCreatedDocumentIdFromFile(file: ProjectFile): number | null {
|
|
const rawCreatedDocument = file.createddocument;
|
|
if (!rawCreatedDocument) return null;
|
|
if (typeof rawCreatedDocument === 'object') {
|
|
return rawCreatedDocument.id ? Number(rawCreatedDocument.id) : null;
|
|
}
|
|
return Number(rawCreatedDocument);
|
|
}
|
|
|
|
function resolveCustomerIdFromCreatedDocument(doc: CreatedDocument): number | null {
|
|
const rawCustomer = doc.customer;
|
|
if (!rawCustomer) return null;
|
|
if (typeof rawCustomer === 'object') {
|
|
return rawCustomer.id ? Number(rawCustomer.id) : null;
|
|
}
|
|
return Number(rawCustomer);
|
|
}
|
|
|
|
export async function fetchProjectFiles(token: string, projectId: number): Promise<ProjectFile[]> {
|
|
const files = await apiRequest<ProjectFile[]>('/api/resource/files', { token });
|
|
const projectFiles = (files || []).filter((file) => {
|
|
if (file.archived) return false;
|
|
return resolveProjectIdFromFile(file) === Number(projectId);
|
|
});
|
|
|
|
if (projectFiles.length === 0) return [];
|
|
|
|
const presigned = await apiRequest<{ files?: ProjectFile[] }>('/api/files/presigned', {
|
|
method: 'POST',
|
|
token,
|
|
body: { ids: projectFiles.map((file) => file.id) },
|
|
});
|
|
|
|
return presigned.files || [];
|
|
}
|
|
|
|
export async function fetchCustomerFiles(token: string, customerId: number): Promise<ProjectFile[]> {
|
|
const files = await apiRequest<ProjectFile[]>('/api/resource/files', { token });
|
|
const customerFiles = (files || []).filter((file) => {
|
|
if (file.archived) return false;
|
|
return resolveCustomerIdFromFile(file) === Number(customerId);
|
|
});
|
|
|
|
if (customerFiles.length === 0) return [];
|
|
|
|
const presigned = await apiRequest<{ files?: ProjectFile[] }>('/api/files/presigned', {
|
|
method: 'POST',
|
|
token,
|
|
body: { ids: customerFiles.map((file) => file.id) },
|
|
});
|
|
|
|
return presigned.files || [];
|
|
}
|
|
|
|
export async function fetchPlantFiles(token: string, plantId: number): Promise<ProjectFile[]> {
|
|
const files = await apiRequest<ProjectFile[]>('/api/resource/files', { token });
|
|
const plantFiles = (files || []).filter((file) => {
|
|
if (file.archived) return false;
|
|
return resolvePlantIdFromFile(file) === Number(plantId);
|
|
});
|
|
|
|
if (plantFiles.length === 0) return [];
|
|
|
|
const presigned = await apiRequest<{ files?: ProjectFile[] }>('/api/files/presigned', {
|
|
method: 'POST',
|
|
token,
|
|
body: { ids: plantFiles.map((file) => file.id) },
|
|
});
|
|
|
|
return presigned.files || [];
|
|
}
|
|
|
|
export async function fetchCustomerCreatedDocuments(
|
|
token: string,
|
|
customerId: number
|
|
): Promise<CreatedDocument[]> {
|
|
const docs = await apiRequest<CreatedDocument[]>('/api/resource/createddocuments', { token });
|
|
|
|
return (docs || [])
|
|
.filter((doc) => !doc.archived && resolveCustomerIdFromCreatedDocument(doc) === Number(customerId))
|
|
.sort((a, b) => {
|
|
const dateA = new Date(String(a.documentDate || '')).getTime();
|
|
const dateB = new Date(String(b.documentDate || '')).getTime();
|
|
return dateB - dateA;
|
|
});
|
|
}
|
|
|
|
export async function fetchCreatedDocumentFiles(
|
|
token: string,
|
|
createdDocumentId: number
|
|
): Promise<ProjectFile[]> {
|
|
const files = await apiRequest<ProjectFile[]>('/api/resource/files', { token });
|
|
const createdDocumentFiles = (files || []).filter((file) => {
|
|
if (file.archived) return false;
|
|
return resolveCreatedDocumentIdFromFile(file) === Number(createdDocumentId);
|
|
});
|
|
|
|
if (createdDocumentFiles.length === 0) return [];
|
|
|
|
const presigned = await apiRequest<{ files?: ProjectFile[] }>('/api/files/presigned', {
|
|
method: 'POST',
|
|
token,
|
|
body: { ids: createdDocumentFiles.map((file) => file.id) },
|
|
});
|
|
|
|
return presigned.files || [];
|
|
}
|
|
|
|
export async function uploadProjectFile(
|
|
token: string,
|
|
payload: {
|
|
projectId: number;
|
|
uri: string;
|
|
filename: string;
|
|
mimeType?: string;
|
|
}
|
|
): Promise<ProjectFile> {
|
|
const formData = new FormData();
|
|
formData.append(
|
|
'file',
|
|
{
|
|
uri: payload.uri,
|
|
name: payload.filename,
|
|
type: payload.mimeType || 'application/octet-stream',
|
|
} as any
|
|
);
|
|
formData.append(
|
|
'meta',
|
|
JSON.stringify({
|
|
project: payload.projectId,
|
|
name: payload.filename,
|
|
mimeType: payload.mimeType || 'application/octet-stream',
|
|
})
|
|
);
|
|
|
|
const response = await fetch(buildUrl('/api/files/upload'), {
|
|
method: 'POST',
|
|
headers: {
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
Accept: 'application/json',
|
|
},
|
|
body: formData,
|
|
});
|
|
|
|
const parsed = await parseJson(response);
|
|
if (!response.ok) {
|
|
const message =
|
|
(parsed as { message?: string; error?: string } | null)?.message ||
|
|
(parsed as { message?: string; error?: string } | null)?.error ||
|
|
'Upload fehlgeschlagen.';
|
|
throw new Error(message);
|
|
}
|
|
|
|
return parsed as ProjectFile;
|
|
}
|
|
|
|
export async function uploadCustomerFile(
|
|
token: string,
|
|
payload: {
|
|
customerId: number;
|
|
uri: string;
|
|
filename: string;
|
|
mimeType?: string;
|
|
}
|
|
): Promise<ProjectFile> {
|
|
const formData = new FormData();
|
|
formData.append(
|
|
'file',
|
|
{
|
|
uri: payload.uri,
|
|
name: payload.filename,
|
|
type: payload.mimeType || 'application/octet-stream',
|
|
} as any
|
|
);
|
|
formData.append(
|
|
'meta',
|
|
JSON.stringify({
|
|
customer: payload.customerId,
|
|
name: payload.filename,
|
|
mimeType: payload.mimeType || 'application/octet-stream',
|
|
})
|
|
);
|
|
|
|
const response = await fetch(buildUrl('/api/files/upload'), {
|
|
method: 'POST',
|
|
headers: {
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
Accept: 'application/json',
|
|
},
|
|
body: formData,
|
|
});
|
|
|
|
const parsed = await parseJson(response);
|
|
if (!response.ok) {
|
|
const message =
|
|
(parsed as { message?: string; error?: string } | null)?.message ||
|
|
(parsed as { message?: string; error?: string } | null)?.error ||
|
|
'Upload fehlgeschlagen.';
|
|
throw new Error(message);
|
|
}
|
|
|
|
return parsed as ProjectFile;
|
|
}
|
|
|
|
export async function uploadPlantFile(
|
|
token: string,
|
|
payload: {
|
|
plantId: number;
|
|
uri: string;
|
|
filename: string;
|
|
mimeType?: string;
|
|
}
|
|
): Promise<ProjectFile> {
|
|
const formData = new FormData();
|
|
formData.append(
|
|
'file',
|
|
{
|
|
uri: payload.uri,
|
|
name: payload.filename,
|
|
type: payload.mimeType || 'application/octet-stream',
|
|
} as any
|
|
);
|
|
formData.append(
|
|
'meta',
|
|
JSON.stringify({
|
|
plant: payload.plantId,
|
|
name: payload.filename,
|
|
mimeType: payload.mimeType || 'application/octet-stream',
|
|
})
|
|
);
|
|
|
|
const response = await fetch(buildUrl('/api/files/upload'), {
|
|
method: 'POST',
|
|
headers: {
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
Accept: 'application/json',
|
|
},
|
|
body: formData,
|
|
});
|
|
|
|
const parsed = await parseJson(response);
|
|
if (!response.ok) {
|
|
const message =
|
|
(parsed as { message?: string; error?: string } | null)?.message ||
|
|
(parsed as { message?: string; error?: string } | null)?.error ||
|
|
'Upload fehlgeschlagen.';
|
|
throw new Error(message);
|
|
}
|
|
|
|
return parsed as ProjectFile;
|
|
}
|