프로젝트/사용자 관리 컨테이너와 같은 형태로 통일한다. 등록 폼을 화면에 상시
노출하지 않고, 우측 상단 [+] 모달로 받아 그룹(임시 프로젝트명) 아래 파일 표를
그린다.
프론트엔드
- B01_Dashboard_UI_TempModal.ts 신설: 등록/추가 모달(임시 프로젝트명 + 파일 선택,
고른 파일을 모달 안 표로 표시, 하단 [취소][확인] — 기존 대시보드 모달과 동일 규격)
- B01_Dashboard_UI_TempUpload.ts: 섹션 전체(제목·[+]·목록)를 반환하도록 변경.
그룹 카드 + 파일 표(종류/파일명/크기/상태/작업), 그룹별 [파일 추가],
파일별 [삭제], 진행률은 해당 파일 행 안에서 표시
- 청크마다 목록을 다시 조회하지 않고 막대 DOM만 갱신하도록 정리
- 대시보드 배치: 프로젝트 컨테이너 바로 아래(역할 3분기 모두)
백엔드
- DELETE /api/temp-uploads/{batch_id}/files/{file_type}: DB 행과 임시 저장소
실제 파일을 함께 삭제. 필수 파일이 빠지면 상태를 uploading으로 되돌리고
만료일은 최초 완료 시점 기준을 유지
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
190 lines
5.9 KiB
TypeScript
190 lines
5.9 KiB
TypeScript
/* =============================================================================
|
|
* B01_Dashboard_Api_Temp.ts
|
|
* 임시 보관함(프로젝트 생성 전 업로드) API 클라이언트
|
|
*
|
|
* 백엔드 계약 (B03_FileInput_Router_Temp.py):
|
|
* POST /api/temp-uploads 묶음 생성
|
|
* GET /api/temp-uploads 내 보관함 목록
|
|
* DELETE /api/temp-uploads/{batch_id} 묶음 삭제
|
|
* POST /api/temp-uploads/{batch_id}/files 작은 파일 저장
|
|
* DELETE /api/temp-uploads/{batch_id}/files/{type} 파일 1건 삭제
|
|
* POST /api/temp-uploads/{batch_id}/upload-sessions 청크 세션 생성
|
|
* POST /api/temp-uploads/{batch_id}/chunks 청크 저장
|
|
* POST /api/temp-uploads/{batch_id}/finalize 병합·완료
|
|
* GET /api/temp-uploads/{batch_id}/upload-status/{s} 이어올리기 조회
|
|
* POST /api/projects/{id}/temp-uploads/{batch_id}/attach 프로젝트로 이동
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
|
|
export interface TempBatchFile {
|
|
file_type: string;
|
|
original_filename: string;
|
|
file_size_bytes: number;
|
|
crs_epsg: number | null;
|
|
}
|
|
|
|
export interface TempBatchPendingSession {
|
|
upload_session_id: string;
|
|
original_filename: string;
|
|
file_size_bytes: number;
|
|
total_chunks: number;
|
|
completed_chunks: number;
|
|
progress_percent: number;
|
|
}
|
|
|
|
export interface TempBatchItem {
|
|
batch_id: string;
|
|
name: string;
|
|
memo: string | null;
|
|
status: string;
|
|
files: TempBatchFile[];
|
|
pending_sessions: TempBatchPendingSession[];
|
|
total_size_bytes: number;
|
|
required_complete: boolean;
|
|
completed_at: string | null;
|
|
expires_at: string | null;
|
|
linked_project_id: string | null;
|
|
created_at: string | null;
|
|
}
|
|
|
|
export interface TempBatchListResponse {
|
|
status: string;
|
|
batches: TempBatchItem[];
|
|
retention_days: number;
|
|
}
|
|
|
|
async function requestJson<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const response = await fetch(`${API_BASE_URL}${path}`, {
|
|
...init,
|
|
credentials: "include",
|
|
headers:
|
|
init.body instanceof FormData
|
|
? init.headers
|
|
: {
|
|
"Content-Type": "application/json",
|
|
...(init.headers ?? {}),
|
|
},
|
|
});
|
|
const payload = await response.json().catch(() => ({}));
|
|
if (!response.ok || payload.status === "error") {
|
|
throw new Error(payload.message ?? `HTTP ${response.status}`);
|
|
}
|
|
return payload as T;
|
|
}
|
|
|
|
export async function createTempBatch(
|
|
name: string,
|
|
memo?: string,
|
|
): Promise<{ batch_id: string; name: string }> {
|
|
return requestJson("/temp-uploads", {
|
|
method: "POST",
|
|
body: JSON.stringify({ name, memo: memo || null }),
|
|
});
|
|
}
|
|
|
|
export async function fetchTempBatches(): Promise<TempBatchListResponse> {
|
|
return requestJson("/temp-uploads", { method: "GET" });
|
|
}
|
|
|
|
export async function deleteTempBatch(batchId: string): Promise<void> {
|
|
await requestJson(`/temp-uploads/${encodeURIComponent(batchId)}`, { method: "DELETE" });
|
|
}
|
|
|
|
/** 묶음 안 파일 1건 삭제 — 임시 저장소의 실제 파일도 함께 지워진다. */
|
|
export async function deleteTempBatchFile(batchId: string, fileType: string): Promise<void> {
|
|
await requestJson(
|
|
`/temp-uploads/${encodeURIComponent(batchId)}/files/${encodeURIComponent(fileType)}`,
|
|
{ method: "DELETE" },
|
|
);
|
|
}
|
|
|
|
/** 작은 파일(csv·prj·tfw·tif)은 한 번에 보낸다. */
|
|
export async function uploadTempBatchFiles(
|
|
batchId: string,
|
|
files: readonly File[],
|
|
): Promise<{ required_complete: boolean }> {
|
|
const form = new FormData();
|
|
for (const file of files) form.append("files", file, file.name);
|
|
return requestJson(`/temp-uploads/${encodeURIComponent(batchId)}/files`, {
|
|
method: "POST",
|
|
body: form,
|
|
});
|
|
}
|
|
|
|
export async function createTempUploadSession(
|
|
batchId: string,
|
|
file: File,
|
|
chunkSizeBytes: number,
|
|
): Promise<{ upload_session_id: string; total_chunks: number }> {
|
|
return requestJson(`/temp-uploads/${encodeURIComponent(batchId)}/upload-sessions`, {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
original_filename: file.name,
|
|
size_bytes: file.size,
|
|
chunk_size_bytes: chunkSizeBytes,
|
|
}),
|
|
});
|
|
}
|
|
|
|
export async function uploadTempChunk(
|
|
batchId: string,
|
|
sessionId: string,
|
|
chunkIndex: number,
|
|
chunk: Blob,
|
|
): Promise<{ completed_chunks: number }> {
|
|
const form = new FormData();
|
|
form.append("session_id", sessionId);
|
|
form.append("chunk_index", String(chunkIndex));
|
|
form.append("chunk_data", chunk, `chunk_${chunkIndex}`);
|
|
return requestJson(`/temp-uploads/${encodeURIComponent(batchId)}/chunks`, {
|
|
method: "POST",
|
|
body: form,
|
|
});
|
|
}
|
|
|
|
export async function finalizeTempUpload(
|
|
batchId: string,
|
|
sessionId: string,
|
|
totalChunks: number,
|
|
): Promise<{ required_complete: boolean }> {
|
|
return requestJson(`/temp-uploads/${encodeURIComponent(batchId)}/finalize`, {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
session_id: sessionId,
|
|
total_chunks: totalChunks,
|
|
complete_upload: true,
|
|
}),
|
|
});
|
|
}
|
|
|
|
/** 이어올리기 — 이미 서버에 올라간 청크 번호. */
|
|
export async function fetchTempUploadStatus(
|
|
batchId: string,
|
|
sessionId: string,
|
|
): Promise<{ completed_chunk_indexes: number[]; total_chunks: number }> {
|
|
return requestJson(
|
|
`/temp-uploads/${encodeURIComponent(batchId)}/upload-status/${encodeURIComponent(sessionId)}`,
|
|
{ method: "GET" },
|
|
);
|
|
}
|
|
|
|
export interface TempBatchAttachResponse {
|
|
status: string;
|
|
project_id: string;
|
|
batch_id: string;
|
|
moved_files: number;
|
|
analysis_started: boolean;
|
|
}
|
|
|
|
/** 보관함 자료를 프로젝트 영구저장소로 옮기고 초기 분석을 시작한다. */
|
|
export async function attachTempBatch(
|
|
projectId: string,
|
|
batchId: string,
|
|
): Promise<TempBatchAttachResponse> {
|
|
return requestJson(
|
|
`/projects/${encodeURIComponent(projectId)}/temp-uploads/${encodeURIComponent(batchId)}/attach`,
|
|
{ method: "POST" },
|
|
);
|
|
}
|