import { apiClient } from "./client"; import type { OptionFilter } from "@/lib/registry/components/input/use-option-loader"; export type StatsAggregation = | "count" | "sum" | "avg" | "min" | "max" | "distinctCount"; /** * AggregateRequest — 백엔드 `/api/table-management/tables/{tableName}/aggregate` body. * * filters 는 `OptionFilter[]` 와 동일한 모양으로 보낸다. 단, `value_type` / `field_ref` / * `user_field` 는 호출자가 미리 실제 값으로 치환해서 `value` 만 남긴 상태여야 한다 — 백엔드는 * 그 외 필드를 무시한다. */ export interface AggregateRequest { aggregation: StatsAggregation; columnName?: string; filters?: Array>; } export interface AggregateResponse { value: number; } /** * aggregateTableStat — 단일 stat 카드 값을 백엔드에서 계산해 가져온다. * * 디자인 모드 / 빈 tableName 가드는 호출자가 책임진다. * 실패 시 axios error 가 그대로 throw 됨 — 상위 hook 에서 카드 단위 fallback. */ export async function aggregateTableStat( tableName: string, request: AggregateRequest, ): Promise { const res = await apiClient.post( `/table-management/tables/${encodeURIComponent(tableName)}/aggregate`, request, ); const body = res.data; // ApiResponse<{ value: number }> wrapper. value 는 number 또는 numeric string 가능. const payload = (body?.data ?? body) as { value?: unknown }; const raw = payload?.value; const value = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() !== "" ? Number(raw) : 0; return { value: Number.isFinite(value) ? value : 0 }; } /** * AggregateGroupRequest — Phase G.3 canonical chart 컴포넌트가 사용하는 * `/aggregate-group` body. * * 백엔드가 `GROUP BY ` 후 각 그룹마다 단일 집계 값을 계산해 row 배열로 반환. * `valueColumn` 는 `aggregation === "count"` 일 때 생략 가능. distinctCount / sum / * avg / min / max 는 valueColumn 필수 (백엔드 측 가드). */ export interface AggregateGroupRequest { aggregation: StatsAggregation; groupBy: string; valueColumn?: string; filters?: Array>; limit?: number; orderDir?: "asc" | "desc"; } export interface AggregateGroupRow { /** GROUP BY 컬럼의 raw 값. null 가능. */ group: string | number | null; /** 집계 결과 (숫자). */ value: number; } export interface AggregateGroupResponse { rows: AggregateGroupRow[]; } /** * aggregateTableGroup — 그룹별 집계. * * 호출자는 디자인 모드 / 빈 tableName / 빈 groupBy 가드를 책임진다. 실패는 axios * error 로 throw 된다. */ export async function aggregateTableGroup( tableName: string, request: AggregateGroupRequest, ): Promise { const res = await apiClient.post( `/table-management/tables/${encodeURIComponent(tableName)}/aggregate-group`, request, ); const body = res.data; const payload = (body?.data ?? body) as { rows?: unknown }; const rawRows = Array.isArray(payload?.rows) ? (payload!.rows as any[]) : []; const rows: AggregateGroupRow[] = rawRows.map((r) => { const v = r?.value; const num = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : 0; return { group: r?.group ?? null, value: Number.isFinite(num) ? num : 0, }; }); return { rows }; } /** * SelectRowsRequest — Phase G.3.1 canonical cardList / groupedTable 가 사용하는 * `/select-rows` body. * * 단순 SELECT — 필터 / 정렬 / limit 만 적용해서 raw row 들을 받는다. column 단일 * 집계가 아니라 multi-column row 가 필요한 view 컴포넌트용. */ export interface SelectRowsOrderBy { column: string; direction?: "asc" | "desc"; } export interface SelectRowsRequest { columns?: string[]; filters?: Array>; orderBy?: SelectRowsOrderBy[]; limit?: number; offset?: number; } export interface SelectRowsResponse { rows: Record[]; } /** * selectTableRows — multi-column row 들을 받아오는 가벼운 SELECT. * * 호출자는 디자인 모드 / 빈 tableName 가드를 책임진다. 실패는 axios error 로 throw. */ export async function selectTableRows( tableName: string, request: SelectRowsRequest, ): Promise { const res = await apiClient.post( `/table-management/tables/${encodeURIComponent(tableName)}/select-rows`, request, ); const body = res.data; const payload = (body?.data ?? body) as { rows?: unknown }; const rawRows = Array.isArray(payload?.rows) ? (payload!.rows as Record[]) : []; return { rows: rawRows }; }