guardia-mes/frontend/src/api/client.ts
2026-07-04 08:15:37 +09:00

343 lines
26 KiB
TypeScript

import axios from 'axios'
const api = axios.create({ baseURL: 'http://localhost:8013' })
api.interceptors.request.use(config => {
const token = localStorage.getItem('mes_token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
api.interceptors.response.use(
res => res,
err => {
if (err.response?.status === 401) {
localStorage.removeItem('mes_token')
if (location.pathname !== '/login') location.href = '/login'
}
return Promise.reject(err)
}
)
export default api
// 공통 래퍼 {success,message,data} → data 추출
const unwrap = (p: Promise<any>) => p.then(r => r.data?.data)
const qs = (o: Record<string, any>) => {
const p = new URLSearchParams()
Object.entries(o).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') p.set(k, String(v)) })
const s = p.toString()
return s ? `?${s}` : ''
}
// ── Auth ──────────────────────────────────────────────────────────────
export const login = (username: string, password: string) =>
api.post('/api/mes/auth/login', { username, password })
export const getMe = () => api.get('/api/mes/auth/me')
// ── 2FA / OTP / 계정 보안 (access 토큰 필요, prefix /api/mes/auth) ──────
// 마이페이지 OTP 등록/재설정/해제 + 비밀번호 변경.
// 보안 불변: setup 응답(secret/qrImage)은 화면 표시용만 — 로그/저장 금지.
export const otpSetup = () => api.post('/api/mes/auth/otp/setup')
export const otpConfirm = (code: string) => api.post('/api/mes/auth/otp/confirm', { code })
export const otpDisable = () => api.post('/api/mes/auth/otp/disable')
export const changePassword = (currentPassword: string, newPassword: string) =>
api.post('/api/mes/auth/change-password', { currentPassword, newPassword })
// ── Dashboard ─────────────────────────────────────────────────────────
export const getDashboardSummary = () => unwrap(api.get('/api/mes/dashboard/summary'))
export const getWorkorderStatus = () => unwrap(api.get('/api/mes/dashboard/workorder-status'))
export const getShippingStatusCard = () => unwrap(api.get('/api/mes/dashboard/shipping-status'))
export const getEquipmentStatusCard = () => unwrap(api.get('/api/mes/dashboard/equipment-status'))
// ── ★ 작업관리 (job) ──────────────────────────────────────────────────
export const getJobs = (f: { status?: string; workerId?: number | string; woNo?: string; equipmentCode?: string } = {}) =>
unwrap(api.get(`/api/mes/jobs${qs(f)}`))
export const getJobBoard = () => unwrap(api.get('/api/mes/jobs/board'))
export const getJobWorkload = () => unwrap(api.get('/api/mes/jobs/workload'))
export const getJob = (id: number) => unwrap(api.get(`/api/mes/jobs/${id}`))
export const createJob = (d: object) => unwrap(api.post('/api/mes/jobs', d))
export const updateJob = (id: number, d: object) => unwrap(api.put(`/api/mes/jobs/${id}`, d))
export const assignJob = (id: number, body: { workerId?: number | string; equipmentCode?: string }) =>
unwrap(api.post(`/api/mes/jobs/${id}/assign`, body))
export const transitionJob = (id: number, action: string) =>
unwrap(api.post(`/api/mes/jobs/${id}/transition`, { action }))
export const reportJob = (id: number, qty: number) =>
unwrap(api.post(`/api/mes/jobs/${id}/report`, { qty }))
export const deleteJob = (id: number) => api.delete(`/api/mes/jobs/${id}`)
// ── ★ 진행관리 (progress) ─────────────────────────────────────────────
export const getProgressSummary = () => unwrap(api.get('/api/mes/progress/summary'))
export const getProgressWorkorders = (status = '') =>
unwrap(api.get(`/api/mes/progress/workorders${status ? `?status=${status}` : ''}`))
export const getProgressWorkorder = (id: number) => unwrap(api.get(`/api/mes/progress/workorders/${id}`))
export const getProgressLive = () => unwrap(api.get('/api/mes/progress/live'))
export const getPlanVsActual = (days = 30) => unwrap(api.get(`/api/mes/progress/plan-vs-actual?days=${days}`))
export const getProgressDelays = () => unwrap(api.get('/api/mes/progress/delays'))
// ── ★ 출하관리 (shipping) ─────────────────────────────────────────────
export const getShippings = (f: { status?: string; partnerCode?: string; keyword?: string } = {}) =>
unwrap(api.get(`/api/mes/shipping${qs(f)}`))
export const getShippingBoard = () => unwrap(api.get('/api/mes/shipping/board'))
export const getShipping = (id: number) => unwrap(api.get(`/api/mes/shipping/${id}`))
export const createShipping = (d: object) => unwrap(api.post('/api/mes/shipping', d))
export const updateShipping = (id: number, d: object) => unwrap(api.put(`/api/mes/shipping/${id}`, d))
export const pickShipping = (id: number, qty: number) => unwrap(api.post(`/api/mes/shipping/${id}/pick`, { qty }))
export const packShipping = (id: number, qty: number) => unwrap(api.post(`/api/mes/shipping/${id}/pack`, { qty }))
export const inspectShipping = (id: number, result: 'PASS' | 'FAIL') =>
unwrap(api.post(`/api/mes/shipping/${id}/inspect`, { result }))
export const shipShipping = (id: number) => unwrap(api.post(`/api/mes/shipping/${id}/ship`))
export const cancelShipping = (id: number) => unwrap(api.post(`/api/mes/shipping/${id}/cancel`))
// ── MES 작업지시 (workorder) ──────────────────────────────────────────
export const getWorkOrders = (f: { status?: string; itemCode?: string; keyword?: string } = {}) =>
unwrap(api.get(`/api/mes/workorders${qs(f)}`))
export const getWorkOrderKanban = () => unwrap(api.get('/api/mes/workorders/kanban'))
export const getWorkOrder = (id: number) => unwrap(api.get(`/api/mes/workorders/${id}`))
export const createWorkOrder = (d: object) => unwrap(api.post('/api/mes/workorders', d))
export const updateWorkOrder = (id: number, d: object) => unwrap(api.put(`/api/mes/workorders/${id}`, d))
export const transitionWorkOrder = (id: number, action: string) =>
unwrap(api.post(`/api/mes/workorders/${id}/transition`, { action }))
export const deleteWorkOrder = (id: number) => api.delete(`/api/mes/workorders/${id}`)
// ── MES 생산실적 (production) ─────────────────────────────────────────
export const getProductions = (f: { woNo?: string; itemCode?: string; limit?: number } = {}) =>
unwrap(api.get(`/api/mes/production${qs(f)}`))
export const getProduction = (id: number) => unwrap(api.get(`/api/mes/production/${id}`))
export const getDailyOutput = (days = 14) => unwrap(api.get(`/api/mes/production/daily-output?days=${days}`))
export const getDefectPareto = (days = 30) => unwrap(api.get(`/api/mes/production/defect-pareto?days=${days}`))
export const reportProduction = (d: object) => unwrap(api.post('/api/mes/production/report', d))
// ── MES 공정진행 (process) ────────────────────────────────────────────
export const getProcessesByWo = (workorderId: number) => unwrap(api.get(`/api/mes/process/workorder/${workorderId}`))
export const getRunningProcesses = () => unwrap(api.get('/api/mes/process/running'))
export const createProcess = (d: object) => unwrap(api.post('/api/mes/process', d))
export const updateProcess = (id: number, d: object) => unwrap(api.put(`/api/mes/process/${id}`, d))
export const setProcessStatus = (id: number, status: string) =>
unwrap(api.put(`/api/mes/process/${id}/status`, { status }))
// ── MES OEE (oee) ─────────────────────────────────────────────────────
export const getOees = (f: { equipmentCode?: string; limit?: number } = {}) =>
unwrap(api.get(`/api/mes/oee${qs(f)}`))
export const createOee = (d: object) => unwrap(api.post('/api/mes/oee', d))
export const computeOee = (d: object) => unwrap(api.post('/api/mes/oee/compute', d))
export const getOeeDetail = (id: number) => unwrap(api.get(`/api/mes/oee/${id}/oee`))
export const getDowntimePareto = (days = 30) => unwrap(api.get(`/api/mes/oee/downtime-pareto?days=${days}`))
// ── MES LOT 추적 (tracking) ───────────────────────────────────────────
export const getLotHistory = (lotNo: string) => unwrap(api.get(`/api/mes/tracking/lot/${encodeURIComponent(lotNo)}/history`))
export const getLotForward = (lotNo: string) => unwrap(api.get(`/api/mes/tracking/lot/${encodeURIComponent(lotNo)}/forward`))
export const getLotBackward = (lotNo: string) => unwrap(api.get(`/api/mes/tracking/lot/${encodeURIComponent(lotNo)}/backward`))
// ── MES 생산계획 (plan) ───────────────────────────────────────────────
export const getPlans = (f: { status?: string; itemCode?: string } = {}) =>
unwrap(api.get(`/api/mes/plans${qs(f)}`))
export const getPlan = (id: number) => unwrap(api.get(`/api/mes/plans/${id}`))
export const createPlan = (d: object) => unwrap(api.post('/api/mes/plans', d))
export const updatePlan = (id: number, d: object) => unwrap(api.put(`/api/mes/plans/${id}`, d))
export const transitionPlan = (id: number, action: string) =>
unwrap(api.post(`/api/mes/plans/${id}/transition`, { action }))
export const deletePlan = (id: number) => api.delete(`/api/mes/plans/${id}`)
// ── WMS 입고 (receiving) ──────────────────────────────────────────────
export const getReceivings = (f: { status?: string; itemCode?: string } = {}) =>
unwrap(api.get(`/api/mes/receiving${qs(f)}`))
export const getReceiving = (id: number) => unwrap(api.get(`/api/mes/receiving/${id}`))
export const createReceiving = (d: object) => unwrap(api.post('/api/mes/receiving', d))
export const acceptReceiving = (id: number, inspectionResult?: string) =>
unwrap(api.post(`/api/mes/receiving/${id}/accept`, { inspectionResult }))
export const rejectReceiving = (id: number, reason?: string) =>
unwrap(api.post(`/api/mes/receiving/${id}/reject`, { reason }))
// ── WMS 재고 (inventory) ──────────────────────────────────────────────
export const getInventory = (f: { itemCode?: string; warehouseCode?: string; keyword?: string } = {}) =>
unwrap(api.get(`/api/mes/inventory${qs(f)}`))
export const getAvailable = (itemCode: string) => unwrap(api.get(`/api/mes/inventory/available/${encodeURIComponent(itemCode)}`))
export const getBelowSafety = () => unwrap(api.get('/api/mes/inventory/below-safety'))
export const getInventoryTxns = (f: { itemCode?: string; refType?: string; refNo?: string; limit?: number } = {}) =>
unwrap(api.get(`/api/mes/inventory/txns${qs(f)}`))
export const transferInventory = (d: object) => unwrap(api.post('/api/mes/inventory/transfer', d))
// ── WMS 재고실사 (stocktake) ──────────────────────────────────────────
export const getStocktakes = (f: { stocktakeNo?: string; status?: string } = {}) =>
unwrap(api.get(`/api/mes/stocktake${qs(f)}`))
export const getStocktake = (id: number) => unwrap(api.get(`/api/mes/stocktake/${id}`))
export const countStocktake = (d: object) => unwrap(api.post('/api/mes/stocktake/count', d))
export const adjustStocktake = (id: number) => unwrap(api.post(`/api/mes/stocktake/${id}/adjust`))
// ── WMS LOT/시리얼 (lotserial) ────────────────────────────────────────
export const getLotSerials = (f: { itemCode?: string; status?: string; keyword?: string } = {}) =>
unwrap(api.get(`/api/mes/lotserial${qs(f)}`))
export const getLotSerial = (id: number) => unwrap(api.get(`/api/mes/lotserial/${id}`))
export const getExpiringLots = (days = 30) => unwrap(api.get(`/api/mes/lotserial/expiring?days=${days}`))
export const createLotSerial = (d: object) => unwrap(api.post('/api/mes/lotserial', d))
export const setLotSerialStatus = (id: number, status: string) =>
unwrap(api.put(`/api/mes/lotserial/${id}/status`, { status }))
// ── QMS 검사 (inspection) ─────────────────────────────────────────────
export const getInspections = (f: { inspectionType?: string; result?: string; itemCode?: string } = {}) =>
unwrap(api.get(`/api/mes/inspections${qs(f)}`))
export const getInspectionPassRate = (days = 30) => unwrap(api.get(`/api/mes/inspections/pass-rate?days=${days}`))
export const getInspection = (id: number) => unwrap(api.get(`/api/mes/inspections/${id}`))
export const createInspection = (d: object) => unwrap(api.post('/api/mes/inspections', d))
export const judgeInspection = (id: number, d: object) => unwrap(api.post(`/api/mes/inspections/${id}/judge`, d))
export const resultInspection = (id: number, d: object) => unwrap(api.post(`/api/mes/inspections/${id}/result`, d))
// ── QMS 검사기준 (spec) ───────────────────────────────────────────────
export const getSpecs = (f: { itemCode?: string; inspectionType?: string } = {}) =>
unwrap(api.get(`/api/mes/specs${qs(f)}`))
export const getSpec = (id: number) => unwrap(api.get(`/api/mes/specs/${id}`))
export const createSpec = (d: object) => unwrap(api.post('/api/mes/specs', d))
export const updateSpec = (id: number, d: object) => unwrap(api.put(`/api/mes/specs/${id}`, d))
export const deleteSpec = (id: number) => api.delete(`/api/mes/specs/${id}`)
// ── QMS 부적합 (ncr) ──────────────────────────────────────────────────
export const getNcrs = (f: { status?: string; severity?: string; itemCode?: string } = {}) =>
unwrap(api.get(`/api/mes/ncr${qs(f)}`))
export const getNcrBoard = () => unwrap(api.get('/api/mes/ncr/board'))
export const getNcr = (id: number) => unwrap(api.get(`/api/mes/ncr/${id}`))
export const createNcr = (d: object) => unwrap(api.post('/api/mes/ncr', d))
export const dispositionNcr = (id: number, disposition: string) =>
unwrap(api.post(`/api/mes/ncr/${id}/disposition`, { disposition }))
export const transitionNcr = (id: number, status: string) =>
unwrap(api.post(`/api/mes/ncr/${id}/transition`, { status }))
export const linkNcrCapa = (id: number, capaId: number) =>
unwrap(api.post(`/api/mes/ncr/${id}/link-capa`, { capaId }))
// ── QMS 시정조치 (capa) ───────────────────────────────────────────────
export const getCapas = (f: { status?: string; capaType?: string } = {}) =>
unwrap(api.get(`/api/mes/capa${qs(f)}`))
export const getCapa = (id: number) => unwrap(api.get(`/api/mes/capa/${id}`))
export const createCapa = (d: object) => unwrap(api.post('/api/mes/capa', d))
export const updateCapa = (id: number, d: object) => unwrap(api.put(`/api/mes/capa/${id}`, d))
export const transitionCapa = (id: number, body: { status: string; effectiveness?: string }) =>
unwrap(api.post(`/api/mes/capa/${id}/transition`, body))
// ── QMS SPC 관리도 (spc) ──────────────────────────────────────────────
export const getSpcCharts = () => unwrap(api.get('/api/mes/spc/charts'))
export const getSpcSamples = (f: { chartCode?: string; limit?: number } = {}) =>
unwrap(api.get(`/api/mes/spc/samples${qs(f)}`))
export const getSpcChart = (f: { chartCode: string; lsl?: number; usl?: number }) =>
unwrap(api.get(`/api/mes/spc/chart${qs(f)}`))
export const addSpcSample = (d: object) => unwrap(api.post('/api/mes/spc/samples', d))
// ── QMS 성적서 (certificate) ──────────────────────────────────────────
export const getCertificates = (f: { certType?: string; itemCode?: string; lotNo?: string } = {}) =>
unwrap(api.get(`/api/mes/certificates${qs(f)}`))
export const getCertificate = (id: number) => unwrap(api.get(`/api/mes/certificates/${id}`))
export const createCertificate = (d: object) => unwrap(api.post('/api/mes/certificates', d))
// ── 기준정보 — 품목 (items) ───────────────────────────────────────────
export const getItems = (f: { itemType?: string; status?: string; keyword?: string } = {}) =>
unwrap(api.get(`/api/mes/items${qs(f)}`))
export const getItem = (id: number) => unwrap(api.get(`/api/mes/items/${id}`))
export const createItem = (d: object) => unwrap(api.post('/api/mes/items', d))
export const updateItem = (id: number, d: object) => unwrap(api.put(`/api/mes/items/${id}`, d))
export const deleteItem = (id: number) => api.delete(`/api/mes/items/${id}`)
// ── 기준정보 — BOM ────────────────────────────────────────────────────
export const getBomTree = (f: { parentItemCode?: string; bomVersion?: string } = {}) =>
unwrap(api.get(`/api/mes/bom${qs(f)}`))
export const getAllBom = () => unwrap(api.get('/api/mes/bom/all'))
export const createBom = (d: object) => unwrap(api.post('/api/mes/bom', d))
export const updateBom = (id: number, d: object) => unwrap(api.put(`/api/mes/bom/${id}`, d))
export const deleteBom = (id: number) => api.delete(`/api/mes/bom/${id}`)
// ── 기준정보 — 라우팅 (routings) ──────────────────────────────────────
export const getRoutings = (itemCode = '') => unwrap(api.get(`/api/mes/routings${itemCode ? `?itemCode=${encodeURIComponent(itemCode)}` : ''}`))
export const createRouting = (d: object) => unwrap(api.post('/api/mes/routings', d))
export const updateRouting = (id: number, d: object) => unwrap(api.put(`/api/mes/routings/${id}`, d))
export const deleteRouting = (id: number) => api.delete(`/api/mes/routings/${id}`)
// ── 기준정보 — 설비 (equipment) ───────────────────────────────────────
export const getEquipment = (f: { status?: string; runStatus?: string } = {}) =>
unwrap(api.get(`/api/mes/equipment${qs(f)}`))
export const createEquipment = (d: object) => unwrap(api.post('/api/mes/equipment', d))
export const updateEquipment = (id: number, d: object) => unwrap(api.put(`/api/mes/equipment/${id}`, d))
export const setEquipmentRunStatus = (id: number, runStatus: string) =>
unwrap(api.put(`/api/mes/equipment/${id}/run-status`, { runStatus }))
export const deleteEquipment = (id: number) => api.delete(`/api/mes/equipment/${id}`)
// ── 기준정보 — 거래처 (partners) ──────────────────────────────────────
export const getPartners = (f: { partnerType?: string; status?: string; keyword?: string } = {}) =>
unwrap(api.get(`/api/mes/partners${qs(f)}`))
export const createPartner = (d: object) => unwrap(api.post('/api/mes/partners', d))
export const updatePartner = (id: number, d: object) => unwrap(api.put(`/api/mes/partners/${id}`, d))
export const deletePartner = (id: number) => api.delete(`/api/mes/partners/${id}`)
// ── 기준정보 — 창고/로케이션 (warehouses) ─────────────────────────────
export const getWarehouses = () => unwrap(api.get('/api/mes/warehouses'))
export const createWarehouse = (d: object) => unwrap(api.post('/api/mes/warehouses', d))
export const updateWarehouse = (id: number, d: object) => unwrap(api.put(`/api/mes/warehouses/${id}`, d))
export const deleteWarehouse = (id: number) => api.delete(`/api/mes/warehouses/${id}`)
export const getLocations = (warehouseCode: string) =>
unwrap(api.get(`/api/mes/warehouses/${encodeURIComponent(warehouseCode)}/locations`))
export const createLocation = (d: object) => unwrap(api.post('/api/mes/warehouses/locations', d))
export const deleteLocation = (id: number) => api.delete(`/api/mes/warehouses/locations/${id}`)
// ── 구성원 (members) ──────────────────────────────────────────────────
export const getMembers = (role = '') => unwrap(api.get(`/api/mes/members${role ? `?role=${role}` : ''}`))
export const getWorkers = () => unwrap(api.get('/api/mes/members/workers'))
// ── 바코드 (barcode) ──────────────────────────────────────────────────
export const generateBarcode = (type: string, value: string) =>
unwrap(api.post('/api/mes/barcode/generate', { type, value }))
export const resolveBarcode = (code: string) =>
unwrap(api.get(`/api/mes/barcode/resolve?code=${encodeURIComponent(code)}`))
// ── 분석/KPI (analytics) ──────────────────────────────────────────────
export const getProductionKpi = (days = 30) => unwrap(api.get(`/api/mes/analytics/production-kpi?days=${days}`))
export const getQualityKpi = (days = 30) => unwrap(api.get(`/api/mes/analytics/quality-kpi?days=${days}`))
export const getInventoryKpi = () => unwrap(api.get('/api/mes/analytics/inventory-kpi'))
export const getDailyProduction = (days = 14) => unwrap(api.get(`/api/mes/analytics/daily-production?days=${days}`))
export const getTopItems = (days = 30, limit = 10) => unwrap(api.get(`/api/mes/analytics/top-items?days=${days}&limit=${limit}`))
export const getBiFeed = (days = 30) => unwrap(api.get(`/api/mes/analytics/bi-feed?days=${days}`))
// ── 연계 (integration) ────────────────────────────────────────────────
export const getIntegrationStatus = () => unwrap(api.get('/api/mes/integration/status'))
export const getErpPlans = () => unwrap(api.get('/api/mes/integration/erp/plans'))
export const getOcrDoc = (docId: string) => unwrap(api.get(`/api/mes/integration/ocr/${encodeURIComponent(docId)}`))
// ── AI (Ollama 폴백, 항상 200) ────────────────────────────────────────
export const getAiStatus = () => unwrap(api.get('/api/mes/ai/status'))
export const aiDefectRootCause = (d: object) => unwrap(api.post('/api/mes/ai/defect-root-cause', d))
export const aiForecast = (d: object) => unwrap(api.post('/api/mes/ai/forecast', d))
export const aiPredictiveMaintenance = (d: object) => unwrap(api.post('/api/mes/ai/predictive-maintenance', d))
export const aiSpcAnomaly = (d: object) => unwrap(api.post('/api/mes/ai/spc-anomaly', d))
export const aiParseQuery = (d: object) => unwrap(api.post('/api/mes/ai/parse-query', d))
export const aiInspectionJudge = (d: object) => unwrap(api.post('/api/mes/ai/inspection-judge', d))
export const aiSafetyStock = (d: object) => unwrap(api.post('/api/mes/ai/safety-stock', d))
export const aiScheduleOptimize = (d: object) => unwrap(api.post('/api/mes/ai/schedule-optimize', d))
// ── RAG 최신 AI 기법 (중앙 guardia-rag 경유, 별개 레이어 — 항상 200, 폴백 degraded) ──
// 불량 RCA + SPC 이상감지: SPC 수치는 결정론, 원인 서술은 /rag/agent(+structured)
export const ragDefectAnalysis = (d: object) => unwrap(api.post('/api/mes/rag/defect-analysis', d))
// 설비 예지보전 + 수요/생산 예측: 수치는 결정론 베이스라인, 해석은 /rag/agent
export const ragPredictAnalysis = (d: object) => unwrap(api.post('/api/mes/rag/predict-analysis', d))
// 👍/👎 피드백 (solution=mes 격리)
export const ragFeedback = (d: object) => unwrap(api.post('/api/mes/rag/feedback', d))
// 기법 토글 스냅샷 / 변경(MANAGER+)
export const getRagToggles = () => unwrap(api.get('/api/mes/rag/toggles'))
export const updateRagToggle = (key: string, value: any) =>
unwrap(api.put(`/api/mes/rag/toggles/${key}`, { value }))
// ── AI 피드백 (로컬 DuckDB 학습저장소 + 중앙 rag(8020) 전달, 인증 사용자 전체) ──
export const postAiFeedback = (d: { feature: string; question?: string; answer?: string; verdict: 'up' | 'down'; correction?: string }) =>
unwrap(api.post('/api/ai/feedback', d))
// ── Admin (SUPERADMIN / 감사로그·설정 GET 은 MANAGER+) ────────────────
export const getUsers = () => api.get('/api/admin/users')
export const createUser = (data: { username: string; password: string; displayName?: string; role?: string }) =>
api.post('/api/admin/users', data)
export const updateUserRole = (id: number, role: string) => api.put(`/api/admin/users/${id}/role`, { role })
export const updateUserActive = (id: number, active: boolean) => api.put(`/api/admin/users/${id}/active`, { active })
export const resetPassword = (id: number, password: string) => api.put(`/api/admin/users/${id}/password`, { password })
export const deleteUser = (id: number) => api.delete(`/api/admin/users/${id}`)
// 관리자 OTP 초기화 — 대상 사용자 OTP 해제(다음 로그인 시 재등록). 시크릿 미조회.
export const adminOtpReset = (id: number) => api.post(`/api/admin/users/${id}/otp-reset`)
export const getAuditLogs = (action = '', actor = '', limit = 200) =>
api.get(`/api/admin/audit${qs({ action, actor, limit })}`)
export const getSettings = () => api.get('/api/admin/settings')
export const updateSetting = (key: string, value: string) =>
api.put(`/api/admin/settings/${encodeURIComponent(key)}`, { value })