// Typed endpoint wrappers for the M5b browse experience. // // Mirrors the FastAPI routes in yunke-server/app/api/routers/. Each wrapper // drops `undefined`/empty params so the query string only carries set filters // (the backend treats missing as "no filter"). import { getJson } from './client' import type { Account, CallChannel, Channel, Customer, Department, Group, GroupMember, Message, Page, PhoneCall, Sms, Staff, StaffChatStats, SyncRun, SyncState, SyncStatus, } from './types' /** Drop entries whose value is `undefined`, `null`, or `''`. */ function clean(params: Record): Record { const out: Record = {} for (const [k, v] of Object.entries(params)) { if (v === undefined || v === null || v === '') continue out[k] = v } return out } export interface CustomerQuery { channel?: Channel | 'all' page?: number page_size?: number owner_account_id?: string user_id?: string department_id?: string add_time_since?: number last_chat_since?: number } export function getCustomers(query: CustomerQuery = {}): Promise> { return getJson>( '/customers', clean({ page: query.page ?? 1, page_size: query.page_size ?? 20, channel: query.channel === 'all' ? undefined : query.channel, owner_account_id: query.owner_account_id, user_id: query.user_id, department_id: query.department_id, add_time_since: query.add_time_since, last_chat_since: query.last_chat_since, }), ) } export interface MessageQuery { channel?: Channel owner_account_id: string talker_id?: string room_id?: string start?: number end?: number page?: number page_size?: number } export function getMessages(query: MessageQuery): Promise> { return getJson>( '/messages', clean({ channel: query.channel, owner_account_id: query.owner_account_id, talker_id: query.talker_id, room_id: query.room_id, start: query.start, end: query.end, page: query.page ?? 1, page_size: query.page_size ?? 50, }), ) } export function getDepartments(): Promise { return getJson('/departments') } export interface StaffQuery { department_id?: string is_on_job?: boolean page?: number page_size?: number } export function getStaff(query: StaffQuery = {}): Promise> { return getJson>( '/staff', clean({ department_id: query.department_id, is_on_job: query.is_on_job, page: query.page ?? 1, page_size: query.page_size ?? 20, }), ) } export interface AccountQuery { /** channel filter; `'all'` (default) drops the param so both channels union. */ channel?: Channel | 'all' page?: number page_size?: number } export function getAccounts(query: AccountQuery = {}): Promise> { return getJson>( '/accounts', clean({ channel: query.channel === 'all' ? undefined : query.channel, page: query.page ?? 1, page_size: query.page_size ?? 20, }), ) } // --------------------------------------------------------------------------- // Chat groups // --------------------------------------------------------------------------- export interface GroupQuery { channel?: Channel | 'all' owner_account_id?: string user_id?: string page?: number page_size?: number } export function getGroups(query: GroupQuery = {}): Promise> { return getJson>( '/groups', clean({ channel: query.channel === 'all' ? undefined : query.channel, owner_account_id: query.owner_account_id, user_id: query.user_id, page: query.page ?? 1, page_size: query.page_size ?? 20, }), ) } export function getGroupMembers(roomId: string): Promise { // room_id can contain '@' — axios encodes params, but path segments need // explicit encoding so the FastAPI `{room_id}` path param matches. return getJson( `/groups/${encodeURIComponent(roomId)}/members`, ) } // --------------------------------------------------------------------------- // Phone / voice calls // --------------------------------------------------------------------------- export interface CallQuery { /** call-type filter; `'all'` (default) drops the param. */ channel?: CallChannel | 'all' user_id?: string /** ms-epoch inclusive window compared against start_call_time. */ start?: number end?: number page?: number page_size?: number } export function getCalls(query: CallQuery = {}): Promise> { return getJson>( '/calls', clean({ channel: query.channel === 'all' ? undefined : query.channel, user_id: query.user_id, start: query.start, end: query.end, page: query.page ?? 1, page_size: query.page_size ?? 20, }), ) } // --------------------------------------------------------------------------- // SMS // --------------------------------------------------------------------------- export interface SmsQuery { user_id?: string /** ms-epoch inclusive window compared against create_time. */ start?: number end?: number page?: number page_size?: number } export function getSms(query: SmsQuery = {}): Promise> { return getJson>( '/sms', clean({ user_id: query.user_id, start: query.start, end: query.end, page: query.page ?? 1, page_size: query.page_size ?? 20, }), ) } // --------------------------------------------------------------------------- // Staff chat stats (微信员工 5 数字 + 客户明细,全本地 message 聚合) // --------------------------------------------------------------------------- export interface StaffChatStatsQuery { owner_account_id: string /** ms-epoch inclusive 窗口(默认近 3 天,由后端兜底)。 */ start?: number end?: number } export function getStaffChatStats(query: StaffChatStatsQuery): Promise { return getJson( '/staff-chat-stats', clean({ owner_account_id: query.owner_account_id, start: query.start, end: query.end, }), ) } // --------------------------------------------------------------------------- // Sync monitor // --------------------------------------------------------------------------- export function getSyncStatus(): Promise { return getJson('/sync/status') } // --------------------------------------------------------------------------- // Full-text search (messages + customers) // --------------------------------------------------------------------------- export type SearchType = 'messages' | 'customers' | 'all' export interface SearchQuery { q: string /** which section(s) to query; defaults to `all`. */ type?: SearchType channel?: Channel | 'all' /** ms-epoch inclusive window compared against message.timestamp. */ start?: number end?: number page?: number page_size?: number } /** * Combined search payload returned by `/api/search`. Each requested section is * present; unrequested sections are omitted entirely by the backend (so the * caller can branch on `section in payload`). */ export interface SearchPayload { messages?: Page customers?: Page } export function search(query: SearchQuery): Promise { return getJson( '/search', clean({ q: query.q, type: query.type ?? 'all', channel: query.channel === 'all' ? undefined : query.channel, start: query.start, end: query.end, page: query.page ?? 1, page_size: query.page_size ?? 20, }), ) } export type { Account, CallChannel, Channel, Customer, Department, Group, GroupMember, Message, Page, PhoneCall, Sms, Staff, StaffChatStats, SyncRun, SyncState, SyncStatus, }