fix:sample/plate 之前的开发
This commit is contained in:
278
frontend/src/app/(app)/phenotyping/event-image/api.ts
Normal file
278
frontend/src/app/(app)/phenotyping/event-image/api.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { getAuthToken } from "@/utils/token";
|
||||
import { NONE_SELECT_VALUE, type EventRecord, type ImageRecord, type SelectOption } from "./types";
|
||||
|
||||
interface BrapiPagination {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
interface BrapiListResponse<T> {
|
||||
metadata: {
|
||||
pagination: BrapiPagination;
|
||||
status: Array<Record<string, unknown>>;
|
||||
datafiles: Array<Record<string, unknown>>;
|
||||
};
|
||||
result: {
|
||||
data: T[];
|
||||
};
|
||||
}
|
||||
|
||||
interface BrapiSingleResponse<T> {
|
||||
metadata: {
|
||||
pagination: BrapiPagination;
|
||||
status: Array<Record<string, unknown>>;
|
||||
datafiles: Array<Record<string, unknown>>;
|
||||
};
|
||||
result: T;
|
||||
}
|
||||
|
||||
interface StudyResponse {
|
||||
studyDbId: string;
|
||||
studyName: string | null;
|
||||
}
|
||||
|
||||
interface ObservationUnitResponse {
|
||||
observationUnitDbId: string;
|
||||
observationUnitName: string | null;
|
||||
}
|
||||
|
||||
interface ObservationResponse {
|
||||
observationDbId: string;
|
||||
observationUnitName: string | null;
|
||||
observationVariableName: string | null;
|
||||
}
|
||||
|
||||
type EventPayload = Partial<Record<
|
||||
| "id"
|
||||
| "event_type"
|
||||
| "event_type_db_id"
|
||||
| "event_description"
|
||||
| "study_db_id"
|
||||
| "observation_unit_db_ids"
|
||||
| "start_date"
|
||||
| "end_date",
|
||||
unknown
|
||||
>>;
|
||||
|
||||
type ImagePayload = Partial<Record<
|
||||
| "id"
|
||||
| "image_name"
|
||||
| "image_file_name"
|
||||
| "image_url"
|
||||
| "mime_type"
|
||||
| "description"
|
||||
| "study_db_id"
|
||||
| "observation_unit_db_id"
|
||||
| "observation_db_ids"
|
||||
| "image_time_stamp",
|
||||
unknown
|
||||
>>;
|
||||
|
||||
const apiBase = () => {
|
||||
if (typeof window !== "undefined") return "";
|
||||
return process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
|
||||
};
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const token = getAuthToken();
|
||||
const response = await fetch(`${apiBase()}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(init?.headers || {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw new Error(detail || `Request failed: ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
const optionalText = (value: unknown) => {
|
||||
const normalized = String(value ?? "").trim();
|
||||
if (!normalized || normalized === NONE_SELECT_VALUE) return null;
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const requiredText = (value: unknown, message: string) => {
|
||||
const normalized = optionalText(value);
|
||||
if (!normalized) throw new Error(message);
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const csvToArray = (value: unknown) => {
|
||||
const normalized = optionalText(value);
|
||||
if (!normalized) return [];
|
||||
return normalized
|
||||
.split(",")
|
||||
.map((part) => part.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const arrayToCsv = (value: unknown) => {
|
||||
if (!Array.isArray(value)) return null;
|
||||
return value.map((item) => String(item)).filter(Boolean).join(", ");
|
||||
};
|
||||
|
||||
const mapEvent = (event: EventRecord): EventRecord => {
|
||||
const startDate = event.start_date || event.eventDateRange?.startDate || null;
|
||||
const endDate = event.end_date || event.eventDateRange?.endDate || null;
|
||||
const unitIds = Array.isArray(event.observationUnitDbIds) ? event.observationUnitDbIds : [];
|
||||
return {
|
||||
...event,
|
||||
id: event.eventDbId || event.id,
|
||||
event_type: event.event_type || event.eventType || null,
|
||||
event_type_db_id: event.event_type_db_id || event.eventTypeDbId || null,
|
||||
event_description: event.event_description || event.eventDescription || null,
|
||||
study_db_id: event.study_db_id || event.studyDbId || null,
|
||||
observation_unit_db_ids: event.observation_unit_db_ids || arrayToCsv(unitIds),
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
};
|
||||
};
|
||||
|
||||
const mapImage = (image: ImageRecord): ImageRecord => {
|
||||
const observationIds = Array.isArray(image.observationDbIds) ? image.observationDbIds : [];
|
||||
return {
|
||||
...image,
|
||||
id: image.imageDbId || image.id,
|
||||
image_name: image.image_name || image.imageName || null,
|
||||
image_file_name: image.image_file_name || image.imageFileName || null,
|
||||
image_url: image.image_url || image.imageURL || null,
|
||||
mime_type: image.mime_type || image.mimeType || null,
|
||||
study_db_id: image.study_db_id || image.studyDbId || null,
|
||||
observation_unit_db_id: image.observation_unit_db_id || image.observationUnitDbId || null,
|
||||
observation_db_ids: image.observation_db_ids || arrayToCsv(observationIds),
|
||||
image_time_stamp: image.image_time_stamp || image.imageTimeStamp || null,
|
||||
image_file_size: image.image_file_size ?? image.imageFileSize ?? null,
|
||||
image_width: image.image_width ?? image.imageWidth ?? null,
|
||||
image_height: image.image_height ?? image.imageHeight ?? null,
|
||||
};
|
||||
};
|
||||
|
||||
const eventBody = (payload: EventPayload) => ({
|
||||
eventType: requiredText(payload.event_type, "Event type is required"),
|
||||
eventTypeDbId: optionalText(payload.event_type_db_id),
|
||||
eventDescription: optionalText(payload.event_description),
|
||||
studyDbId: optionalText(payload.study_db_id),
|
||||
observationUnitDbIds: csvToArray(payload.observation_unit_db_ids),
|
||||
eventDateRange: {
|
||||
startDate: optionalText(payload.start_date),
|
||||
endDate: optionalText(payload.end_date),
|
||||
},
|
||||
});
|
||||
|
||||
const imageBody = (payload: ImagePayload) => ({
|
||||
imageName: requiredText(payload.image_name, "Image name is required"),
|
||||
imageFileName: optionalText(payload.image_file_name),
|
||||
imageURL: optionalText(payload.image_url),
|
||||
mimeType: optionalText(payload.mime_type),
|
||||
description: optionalText(payload.description),
|
||||
studyDbId: optionalText(payload.study_db_id),
|
||||
observationUnitDbId: optionalText(payload.observation_unit_db_id),
|
||||
observationDbIds: csvToArray(payload.observation_db_ids),
|
||||
imageTimeStamp: optionalText(payload.image_time_stamp),
|
||||
imageFileSize: null,
|
||||
imageWidth: null,
|
||||
imageHeight: null,
|
||||
});
|
||||
|
||||
export async function fetchEventRows(): Promise<EventRecord[]> {
|
||||
const response = await request<BrapiListResponse<EventRecord>>("/brapi/v2/events?page=0&pageSize=1000");
|
||||
return response.result.data.map(mapEvent);
|
||||
}
|
||||
|
||||
export async function fetchImageRows(): Promise<ImageRecord[]> {
|
||||
const response = await request<BrapiListResponse<ImageRecord>>("/brapi/v2/images?page=0&pageSize=1000");
|
||||
return response.result.data.map(mapImage);
|
||||
}
|
||||
|
||||
export async function fetchEventImageOptions(): Promise<{
|
||||
studies: SelectOption[];
|
||||
observationUnits: SelectOption[];
|
||||
observations: SelectOption[];
|
||||
}> {
|
||||
const [studies, observationUnits, observations] = await Promise.all([
|
||||
request<BrapiListResponse<StudyResponse>>("/brapi/v2/studies?page=0&pageSize=1000"),
|
||||
request<BrapiListResponse<ObservationUnitResponse>>("/brapi/v2/observationunits?page=0&pageSize=1000"),
|
||||
request<BrapiListResponse<ObservationResponse>>("/brapi/v2/observations?page=0&pageSize=1000"),
|
||||
]);
|
||||
|
||||
return {
|
||||
studies: studies.result.data.map((study) => ({
|
||||
value: study.studyDbId,
|
||||
label: study.studyName || study.studyDbId,
|
||||
})),
|
||||
observationUnits: observationUnits.result.data.map((unit) => ({
|
||||
value: unit.observationUnitDbId,
|
||||
label: unit.observationUnitName || unit.observationUnitDbId,
|
||||
})),
|
||||
observations: observations.result.data.map((observation) => ({
|
||||
value: observation.observationDbId,
|
||||
label: `${observation.observationDbId}${observation.observationUnitName ? ` / ${observation.observationUnitName}` : ""}${observation.observationVariableName ? ` / ${observation.observationVariableName}` : ""}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createEventRow(payload: EventPayload): Promise<EventRecord> {
|
||||
const response = await request<BrapiListResponse<EventRecord>>("/brapi/v2/events", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
eventDbId: requiredText(payload.id, "Event ID is required"),
|
||||
...eventBody(payload),
|
||||
}),
|
||||
});
|
||||
return mapEvent(response.result.data[0]);
|
||||
}
|
||||
|
||||
export async function updateEventRow(id: string, payload: EventPayload): Promise<EventRecord> {
|
||||
const requestedId = optionalText(payload.id);
|
||||
if (requestedId && requestedId !== id) {
|
||||
throw new Error("Event ID is immutable. Create a new record instead.");
|
||||
}
|
||||
const response = await request<BrapiListResponse<EventRecord>>(`/brapi/v2/events/${encodeURIComponent(id)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(eventBody(payload)),
|
||||
});
|
||||
return mapEvent(response.result.data[0]);
|
||||
}
|
||||
|
||||
export async function deleteEventRow(id: string): Promise<void> {
|
||||
await request<BrapiSingleResponse<EventRecord>>(`/brapi/v2/events/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
export async function createImageRow(payload: ImagePayload): Promise<ImageRecord> {
|
||||
const response = await request<BrapiListResponse<ImageRecord>>("/brapi/v2/images", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
imageDbId: requiredText(payload.id, "Image ID is required"),
|
||||
...imageBody(payload),
|
||||
}),
|
||||
});
|
||||
return mapImage(response.result.data[0]);
|
||||
}
|
||||
|
||||
export async function updateImageRow(id: string, payload: ImagePayload): Promise<ImageRecord> {
|
||||
const requestedId = optionalText(payload.id);
|
||||
if (requestedId && requestedId !== id) {
|
||||
throw new Error("Image ID is immutable. Create a new record instead.");
|
||||
}
|
||||
const response = await request<BrapiListResponse<ImageRecord>>(`/brapi/v2/images/${encodeURIComponent(id)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(imageBody(payload)),
|
||||
});
|
||||
return mapImage(response.result.data[0]);
|
||||
}
|
||||
|
||||
export async function deleteImageRow(id: string): Promise<void> {
|
||||
await request<BrapiSingleResponse<ImageRecord>>(`/brapi/v2/images/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user