/* ============================================================================= * M01_MasterData_UI_Logic_Api.ts * 로직 화면이 부르는 서버 길 — 계약 `resources/master_data/_화면_계약.md` · `/api/m01` * ========================================================================== */ import { API_BASE_URL } from "@config/config_frontend"; /** 로직 한 줄 — 파일 그대로(한글 칸) · 틀 `_틀.md` 6장 */ export interface LogicInput { 이름: string; 단위?: string; 고르기?: string[]; 범위?: [number, number]; } export interface HoLine { 종류: string; 요소: string; 이름?: string; 규격?: string; 단위?: string; 수량: string; 비목?: string; } export interface NamedFormula { 이름: string; 식: string; 비목?: string; 출처?: string; } export interface LogicRow { 열쇠: string; 이름: string; 결과단위: string; 출처: string; 소유?: string; 입력: LogicInput[]; 중간: NamedFormula[]; 호표?: HoLine[]; 결과?: { 식: string }; 덧줄?: NamedFormula[]; 끝수?: string | null; 비고?: string; [extra: string]: unknown; } export interface LogicSummary { file: string; book: string; chapter: string; 열쇠: string; 이름: string; 결과단위: string; 출처: string; blocked: boolean; reasons: string[]; } export interface ElementBrief { ref: string; file?: string; 이름?: string; 규격?: string; 단위?: unknown; 값?: unknown; 값칸?: Record | null; } export interface LogicOne { file: string; version: string; logic: LogicRow; blocked: boolean; reasons: string[]; prices: Record; } export interface CalcLine { 이름: string; 단위: string; 수량: number; 단가: number; 금액: number; 비목: Record; } export type CalcAnswer = | { ok: true; lines?: CalcLine[]; sums?: Record; result?: unknown; middle: Record; } | { ok: false; reason: string }; export interface LogicFile { file: string; book: string; chapter: string; version: string; } export interface SaveChange { op: "edit" | "add" | "delete"; key?: string; row?: LogicRow; } export interface SaveFile { file: string; version: string; changes: SaveChange[]; } /** 서버가 준 몸과 상태 번호 — 409·422 는 몸(`detail`)을 화면이 풀어 보임 */ export class ApiError extends Error { constructor( public status: number, public detail: unknown, ) { super(typeof detail === "string" ? detail : JSON.stringify(detail)); } } async function request(path: string, body?: unknown): Promise { const response = await fetch(`${API_BASE_URL}/m01${path}`, { method: body === undefined ? "GET" : "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), }); const data = (await response.json().catch(() => ({}))) as { detail?: unknown } & T; if (!response.ok) throw new ApiError(response.status, data.detail ?? response.statusText); return data; } const query = (params: Record): string => new URLSearchParams(params).toString(); export const fetchLogics = (): Promise => request<{ logics: LogicSummary[] }>("/logics").then((d) => d.logics); export const fetchLogic = (book: string, key: string): Promise => request(`/logic?${query({ book, key })}`); export const fetchLogicFiles = (): Promise => request<{ files: LogicFile[] }>(`/groups/${encodeURIComponent("로직")}/files`).then( (d) => d.files, ); export const searchElements = ( group: string, q: string, ): Promise<{ total: number; items: ElementBrief[] }> => request(`/elements?${query({ group, q, limit: "100" })}`); export const runCalc = (body: { book: string; key: string; inputs: Record; row?: LogicRow; file?: string; }): Promise => request("/calc", body); export const saveFiles = ( files: SaveFile[], ): Promise<{ files: { file: string; version: string }[] }> => request("/save", { files });