Merge remote-tracking branch 'origin/dev' into sub_laptop_2

This commit is contained in:
2026-11-01 15:42:09 +09:00
16 changed files with 1110 additions and 381 deletions
+20 -6
View File
@@ -4,6 +4,7 @@
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
import { logicApiPaths } from "./M01_MasterData_UI_Logic_Api";
/** 줄·표 한 개 — 파일 그대로(한글 칸). */
export type Row = Record<string, unknown>;
@@ -89,8 +90,12 @@ export type SaveResult =
| { status: 422; errors: string[] }
| { status: number; detail: string };
async function call<T>(path: string, init: RequestInit = {}): Promise<{ status: number; body: T }> {
const response = await fetch(`${API_BASE_URL}/m01${path}`, {
async function call<T>(
path: string,
init: RequestInit = {},
base = "/m01",
): Promise<{ status: number; body: T }> {
const response = await fetch(`${API_BASE_URL}${base}${path}`, {
credentials: "include",
headers: { "Content-Type": "application/json" },
...init,
@@ -98,9 +103,13 @@ async function call<T>(path: string, init: RequestInit = {}): Promise<{ status:
return { status: response.status, body: (await response.json()) as T };
}
async function get<T>(path: string, params: Record<string, string | number>): Promise<T> {
async function get<T>(
path: string,
params: Record<string, string | number>,
base = "/m01",
): Promise<T> {
const query = new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)]));
const { status, body } = await call<T & { detail?: unknown }>(`${path}?${query}`);
const { status, body } = await call<T & { detail?: unknown }>(`${path}?${query}`, {}, base);
if (status !== 200) throw new Error(String(body.detail ?? `HTTP ${status}`));
return body;
}
@@ -121,7 +130,11 @@ export const fetchRows = (
detail = "",
state = "",
): Promise<RowsPage> =>
get<RowsPage>("/rows", { file, page, size, q, unlinked: unlinked ? 1 : 0, sub, detail, state });
get<RowsPage>(
"/rows",
{ file, page, size, q, unlinked: unlinked ? 1 : 0, sub, detail, state },
logicApiPaths().read, // 고르기 표 · 품목 찾기 — 상세 부품 길(717-1)
);
/** 한 테이블 파일은 `file` · 장 그룹(소요량 · 계수 · 로직)은 `group` 으로 부름. */
export const fetchSubsAll = (where: {
@@ -174,7 +187,8 @@ export interface PickItem {
export const fetchPick = (
kind: PickKind,
q: string,
): Promise<{ total: number; items: PickItem[] }> => get("/pick", { kind, q, limit: 50 });
): Promise<{ total: number; items: PickItem[] }> =>
get("/pick", { kind, q, limit: 50 }, logicApiPaths().read);
/** 자재 찾기 — `/pick?kind=price` 감쌈 · 이름·규격 낱말이 모두 든 줄 */
export const searchMaterials = async (
+28 -11
View File
@@ -190,8 +190,22 @@ export class ApiError extends Error {
}
}
async function request<T>(path: string, body?: unknown): Promise<T> {
const response = await fetch(`${API_BASE_URL}/m01${path}`, {
/** 상세 부품이 부르는 길 앞머리(717-1) — 읽기(로직 · 요소 · 재료 · 표 · 줄) · 셈(`/calc` · `/text`)
* · 기본 = M01(`/m01` · 시스템 관리자) · M02 = `{ read: "/m02/logic-lib", calc: "/m02/logic-lib" }`(로그인) */
export interface LogicApiPaths {
read: string;
calc: string;
}
const M01_PATHS: LogicApiPaths = { read: "/m01", calc: "/m01" };
let paths = M01_PATHS;
/** 상세를 그리기 전에 한 번 — 안 넘기면 M01 길로 되돌림 · 저장 · 만들기 길은 늘 `/m01` */
export const setLogicApiPaths = (next?: LogicApiPaths): void => {
paths = next ?? M01_PATHS;
};
export const logicApiPaths = (): LogicApiPaths => paths;
async function request<T>(path: string, body?: unknown, base = "/m01"): Promise<T> {
const response = await fetch(`${API_BASE_URL}${base}${path}`, {
method: body === undefined ? "GET" : "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
@@ -205,13 +219,16 @@ async function request<T>(path: string, body?: unknown): Promise<T> {
const query = (params: Record<string, string>): string => new URLSearchParams(params).toString();
export const fetchLogics = (): Promise<LogicSummary[]> =>
request<{ logics: LogicSummary[] }>("/logics").then((d) => d.logics);
request<{ logics: LogicSummary[] }>("/logics", undefined, paths.read).then((d) => d.logics);
/** 왼쪽 트리 목록 — 서버가 준 구분·상세구분(장 차례대로) */
export const fetchLogicSubs = (): Promise<SubBrief[]> =>
request<{ subs: SubBrief[] }>(`/subs?${query({ group: "로직" })}`).then((d) => d.subs);
request<{ subs: SubBrief[] }>(`/subs?${query({ group: "로직" })}`, undefined, paths.read).then(
(d) => d.subs,
);
export const fetchLogic = (key: string): Promise<LogicOne> => request(`/logic?${query({ key })}`);
export const fetchLogic = (key: string): Promise<LogicOne> =>
request(`/logic?${query({ key })}`, undefined, paths.read);
export const fetchLogicFiles = (): Promise<LogicFile[]> =>
request<{ files: LogicFile[] }>(`/groups/${encodeURIComponent("로직")}/files`).then(
@@ -222,10 +239,10 @@ export const searchElements = (
group: string,
q: string,
): Promise<{ total: number; items: ElementBrief[] }> =>
request(`/elements?${query({ group, q, limit: "100" })}`);
request(`/elements?${query({ group, q, limit: "100" })}`, undefined, paths.read);
export const searchPrice = (q: string): Promise<{ total: number; items: ElementBrief[] }> =>
request(`/pick?${query({ kind: "price", q, limit: "100" })}`);
request(`/pick?${query({ kind: "price", q, limit: "100" })}`, undefined, paths.read);
export const fetchMaterials = (cond: {
sub: string;
@@ -235,11 +252,11 @@ export const fetchMaterials = (cond: {
/** 호표 줄 단위 — 주면 그 단위 줄만 */
unit?: string;
}): Promise<{ total: number; 기본: string | null; items: ElementBrief[] }> =>
request(`/materials?${query({ ...cond, limit: "100" })}`);
request(`/materials?${query({ ...cond, limit: "100" })}`, undefined, paths.read);
/** 자동값 길(`GET /logic/auto`) — 입력 이름 → 견본값 · 서버에 아직 없거나 값이 없으면 빈 채로(화면은 그냥 안 채움) */
export const fetchAuto = (key: string): Promise<Record<string, unknown>> =>
request<{ 값?: Record<string, unknown> }>(`/logic/auto?${query({ key })}`)
request<{ 값?: Record<string, unknown> }>(`/logic/auto?${query({ key })}`, undefined, paths.read)
.then((d) => d.값 ?? {})
.catch(() => ({}));
@@ -250,7 +267,7 @@ export const runCalc = (body: {
file?: string;
/** 시험 계산에서 고른 재료 — 호표 줄 차례 → 자재품목 키 · 저장 안 함 */
고름?: Record<string, string>;
}): Promise<CalcAnswer> => request("/calc", body);
}): Promise<CalcAnswer> => request("/calc", body, paths.calc);
export const runText = (body: {
key: string;
@@ -259,7 +276,7 @@ export const runText = (body: {
file?: string;
/** 시험 계산에서 고른 재료 — 호표 줄 차례 → 자재품목 키 · 저장 안 함 */
고름?: Record<string, string>;
}): Promise<TextAnswer> => request("/text", body);
}): Promise<TextAnswer> => request("/text", body, paths.calc);
export const saveFiles = (
files: SaveFile[],
@@ -9,7 +9,7 @@
import { API_BASE_URL } from "@config/config_frontend";
import { el } from "@ui/ui_template_elements";
import type { ElementBrief, NamedFormula } from "./M01_MasterData_UI_Logic_Api";
import { logicApiPaths, type ElementBrief, type NamedFormula } from "./M01_MasterData_UI_Logic_Api";
import { formatNumber } from "./M01_MasterData_UI_Logic_Edit";
import { tx } from "./M01_MasterData_UI_Logic_Text";
@@ -31,7 +31,7 @@ const tableCache = new Map<string, Promise<TableRow | null>>();
async function getJson<T>(path: string): Promise<T | null> {
try {
const res = await fetch(`${API_BASE_URL}/m01${path}`);
const res = await fetch(`${API_BASE_URL}${logicApiPaths().read}${path}`);
if (!res.ok) return null;
return (await res.json()) as T;
} catch {