数据分析ui补充完善,接入数据库
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { computeDateRange } from './dateRange.uts'
|
||||
import { computeDateRange, toDateOnly } from './dateRange.uts'
|
||||
import { rpcOrEmptyArray, rpcOrNull } from './rpc.uts'
|
||||
|
||||
export type CouponAnalysisData = {
|
||||
@@ -11,31 +11,21 @@ export type CouponAnalysisData = {
|
||||
|
||||
export async function fetchCouponAnalysis(period: string): Promise<CouponAnalysisData> {
|
||||
const { startIso, endIso } = computeDateRange(period)
|
||||
const p_start_date = toDateOnly(startIso)
|
||||
const p_end_date = toDateOnly(endIso)
|
||||
|
||||
const overviewRow = await rpcOrNull('rpc_coupon_effectiveness_overview', {
|
||||
p_start: startIso,
|
||||
p_end: endIso
|
||||
} as UTSJSONObject)
|
||||
const params = {
|
||||
p_start_date,
|
||||
p_end_date
|
||||
} as any
|
||||
|
||||
const typeList = await rpcOrEmptyArray('rpc_coupon_type_stats', {
|
||||
p_start: startIso,
|
||||
p_end: endIso
|
||||
} as UTSJSONObject)
|
||||
|
||||
const channelList = await rpcOrEmptyArray('rpc_coupon_channel_stats', {
|
||||
p_start: startIso,
|
||||
p_end: endIso
|
||||
} as UTSJSONObject)
|
||||
|
||||
const trendList = await rpcOrEmptyArray('rpc_coupon_trend_daily', {
|
||||
p_start: startIso,
|
||||
p_end: endIso
|
||||
} as UTSJSONObject)
|
||||
|
||||
const conversionList = await rpcOrEmptyArray('rpc_coupon_conversion_effect', {
|
||||
p_start: startIso,
|
||||
p_end: endIso
|
||||
} as UTSJSONObject)
|
||||
const [overviewRow, typeList, channelList, trendList, conversionList] = await Promise.all([
|
||||
rpcOrNull('rpc_analytics_coupon_overview', params),
|
||||
rpcOrEmptyArray('rpc_analytics_coupon_by_type', params),
|
||||
rpcOrEmptyArray('rpc_analytics_coupon_by_channel', params),
|
||||
rpcOrEmptyArray('rpc_analytics_coupon_trend', params),
|
||||
rpcOrEmptyArray('rpc_analytics_coupon_conversion', params)
|
||||
])
|
||||
|
||||
return { overviewRow, typeList, channelList, trendList, conversionList }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { rpcOrValue } from './rpc.uts'
|
||||
import { rpcOrEmptyArray, rpcOrValue } from './rpc.uts'
|
||||
|
||||
export type CustomReportListItem = {
|
||||
id: string
|
||||
@@ -24,41 +23,41 @@ export type UpdateCustomReportParams = {
|
||||
period: string | null
|
||||
}
|
||||
|
||||
function safeString(v: any): string {
|
||||
return v != null ? `${v}` : ''
|
||||
}
|
||||
|
||||
// 改造:不再直查 analytics_reports 表,统一通过 RPC 获取当前用户的报表列表
|
||||
export async function listCustomReports(ownerUserId: string): Promise<Array<CustomReportListItem>> {
|
||||
const res: any = await supa
|
||||
.from('analytics_reports')
|
||||
.select('id, title, description, period, updated_at')
|
||||
.eq('type', 'custom')
|
||||
.eq('owner_user_id', ownerUserId)
|
||||
.order('updated_at', { ascending: false } as any)
|
||||
|
||||
if (res?.error != null) {
|
||||
throw res.error
|
||||
}
|
||||
|
||||
const rows: Array<any> = Array.isArray(res.data) ? (res.data as Array<any>) : []
|
||||
const rows = await rpcOrEmptyArray('rpc_get_custom_reports', {} as any)
|
||||
const list: Array<CustomReportListItem> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const r = rows[i]
|
||||
const r: any = rows[i]
|
||||
list.push({
|
||||
id: `${r.id}`,
|
||||
title: `${r.title}`,
|
||||
description: `${r.description || ''}`,
|
||||
period: `${r.period || ''}`,
|
||||
updated_at: `${r.updated_at || ''}`
|
||||
id: safeString(r.getAny?.('id') ?? r.getString?.('id')),
|
||||
title: safeString(r.getAny?.('title') ?? r.getString?.('title')),
|
||||
description: safeString(r.getAny?.('description') ?? r.getString?.('description')),
|
||||
// 兼容旧 UI 字段:custom-report 页面里可能还在用 period 字段
|
||||
period: '',
|
||||
updated_at: safeString(r.getAny?.('updated_at') ?? r.getString?.('updated_at'))
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// 改造:RPC 参数改为 p_definition(JSONB),承载 period/metrics/chartType
|
||||
export async function createCustomReport(params: CreateCustomReportParams): Promise<string> {
|
||||
const definition = {
|
||||
period: params.period,
|
||||
metrics: params.metrics,
|
||||
chartType: params.chartType || 'line'
|
||||
}
|
||||
|
||||
const data = await rpcOrValue('rpc_create_custom_report', {
|
||||
p_title: params.title,
|
||||
p_description: params.description || '',
|
||||
p_period: params.period,
|
||||
p_metrics: params.metrics,
|
||||
p_chart_type: params.chartType || 'line'
|
||||
} as UTSJSONObject)
|
||||
p_definition: definition
|
||||
} as any)
|
||||
|
||||
if (data == null) {
|
||||
throw new Error('保存失败:未返回报表ID')
|
||||
@@ -68,12 +67,17 @@ export async function createCustomReport(params: CreateCustomReportParams): Prom
|
||||
}
|
||||
|
||||
export async function updateCustomReport(params: UpdateCustomReportParams): Promise<boolean> {
|
||||
// 注意:旧 UI 只传 title/description/period,这里把 period 合并进 definition
|
||||
const definition = {
|
||||
period: params.period
|
||||
}
|
||||
|
||||
await rpcOrValue('rpc_update_custom_report', {
|
||||
p_report_id: params.reportId,
|
||||
p_title: params.title,
|
||||
p_description: params.description,
|
||||
p_period: params.period
|
||||
} as UTSJSONObject)
|
||||
p_definition: definition
|
||||
} as any)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -81,7 +85,7 @@ export async function updateCustomReport(params: UpdateCustomReportParams): Prom
|
||||
export async function deleteCustomReport(reportId: string): Promise<boolean> {
|
||||
await rpcOrValue('rpc_delete_custom_report', {
|
||||
p_report_id: reportId
|
||||
} as UTSJSONObject)
|
||||
} as any)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { computeDateRange, toDateOnly } from './dateRange.uts'
|
||||
import { rpcOrEmptyArray, rpcOrNull } from './rpc.uts'
|
||||
import { rpcOrEmptyArray, rpcOrNull, rpcOrValue } from './rpc.uts'
|
||||
|
||||
export type TrendData = { x: Array<string>; gmv: Array<number>; orders: Array<number> }
|
||||
export type SegmentItem = { name: string; value: number }
|
||||
@@ -17,10 +17,9 @@ export async function fetchDashboardTrend(period: string): Promise<TrendData> {
|
||||
const p_start_date = toDateOnly(startIso)
|
||||
const p_end_date = toDateOnly(endIso)
|
||||
|
||||
const rows = await rpcOrEmptyArray('rpc_analytics_trend_data', {
|
||||
const rows = await rpcOrEmptyArray('rpc_analytics_sales_trend', {
|
||||
p_start_date,
|
||||
p_end_date,
|
||||
p_merchant_id: null
|
||||
p_end_date
|
||||
} as any)
|
||||
|
||||
const x: Array<string> = []
|
||||
@@ -28,45 +27,30 @@ export async function fetchDashboardTrend(period: string): Promise<TrendData> {
|
||||
const orders: Array<number> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row: any = rows[i]
|
||||
const d = `${row.getString?.('date') ?? row.getString?.('day') ?? row.getString?.('date_key') ?? ''}`
|
||||
if (d && d.length >= 10) x.push(d.slice(5))
|
||||
else x.push(`${i + 1}`)
|
||||
gmv.push(safeNumber(row.getAny?.('gmv') ?? row.getAny?.('total_amount') ?? 0))
|
||||
orders.push(safeNumber(row.getAny?.('orders') ?? row.getAny?.('order_count') ?? 0))
|
||||
const d = `${row.getAny?.('date') ?? ''}`
|
||||
x.push(d.length >= 10 ? d.slice(5) : d)
|
||||
gmv.push(safeNumber(row.getAny?.('gmv') ?? 0))
|
||||
orders.push(safeNumber(row.getAny?.('orders') ?? 0))
|
||||
}
|
||||
return { x, gmv, orders }
|
||||
}
|
||||
|
||||
export async function fetchDashboardRealtime(): Promise<any> {
|
||||
const now = new Date()
|
||||
const today0 = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const todayISO = today0.toISOString()
|
||||
const [kpiRow, onlineUsersVal] = await Promise.all([
|
||||
rpcOrNull('rpc_analytics_realtime_kpis', {} as any),
|
||||
rpcOrValue('rpc_analytics_online_users', {} as any)
|
||||
])
|
||||
|
||||
const ySame = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
const y0 = new Date(ySame.getFullYear(), ySame.getMonth(), ySame.getDate())
|
||||
const obj: any = kpiRow != null ? kpiRow : ({} as any)
|
||||
|
||||
const row = await rpcOrNull('rpc_analytics_realtime_kpis', {
|
||||
p_start: todayISO,
|
||||
p_end: now.toISOString(),
|
||||
p_compare_start: y0.toISOString(),
|
||||
p_compare_end: ySame.toISOString(),
|
||||
p_merchant_id: null
|
||||
} as any)
|
||||
|
||||
const safe = (v: any): number => {
|
||||
const n = Number(v)
|
||||
return isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
const obj: any = row != null ? row : ({} as any)
|
||||
return {
|
||||
gmv: Math.round(safe(obj.getAny?.('gmv') ?? obj.getAny?.('total_gmv') ?? obj.getAny?.('revenue') ?? 0)),
|
||||
gmv_growth: safe(obj.getAny?.('gmv_growth') ?? obj.getAny?.('gmv_growth_rate') ?? obj.getAny?.('revenue_growth') ?? 0),
|
||||
orders: Math.round(safe(obj.getAny?.('orders') ?? obj.getAny?.('order_count') ?? obj.getAny?.('total_orders') ?? 0)),
|
||||
order_growth: safe(obj.getAny?.('order_growth') ?? obj.getAny?.('order_growth_rate') ?? 0),
|
||||
online_users: Math.round(safe(obj.getAny?.('online_users') ?? obj.getAny?.('active_users') ?? obj.getAny?.('current_users') ?? 0)),
|
||||
conversion_rate: safe(obj.getAny?.('conversion_rate') ?? obj.getAny?.('conversion') ?? 0),
|
||||
conversion_growth: safe(obj.getAny?.('conversion_growth') ?? obj.getAny?.('conversion_growth_rate') ?? 0)
|
||||
gmv: Math.round(safeNumber(obj.getAny?.('gmv') ?? 0)),
|
||||
gmv_growth: safeNumber(obj.getAny?.('gmv_growth') ?? 0),
|
||||
orders: Math.round(safeNumber(obj.getAny?.('orders') ?? 0)),
|
||||
order_growth: safeNumber(obj.getAny?.('order_growth') ?? 0),
|
||||
online_users: Math.round(safeNumber(onlineUsersVal ?? 0)),
|
||||
conversion_rate: safeNumber(obj.getAny?.('conversion_rate') ?? 0),
|
||||
conversion_growth: safeNumber(obj.getAny?.('conversion_growth') ?? 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,8 +59,7 @@ export async function fetchDashboardTopProducts(period: string, limit: number =
|
||||
const rows = await rpcOrEmptyArray('rpc_analytics_top_products', {
|
||||
p_start_date: toDateOnly(startIso),
|
||||
p_end_date: toDateOnly(endIso),
|
||||
p_limit: limit,
|
||||
p_merchant_id: null
|
||||
p_limit: limit
|
||||
} as any)
|
||||
|
||||
const list: Array<TopProductItem> = []
|
||||
@@ -86,7 +69,7 @@ export async function fetchDashboardTopProducts(period: string, limit: number =
|
||||
id: `${row.getAny?.('id') ?? i}`,
|
||||
rank: i + 1,
|
||||
name: `${row.getAny?.('name') ?? '未知商品'}`,
|
||||
sales: safeNumber(row.getAny?.('sales') ?? row.getAny?.('total_amount') ?? 0)
|
||||
sales: safeNumber(row.getAny?.('sales') ?? 0)
|
||||
})
|
||||
}
|
||||
return list
|
||||
@@ -107,8 +90,8 @@ export async function fetchDashboardTopMerchants(period: string, limit: number =
|
||||
id: `${row.getAny?.('id') ?? i}`,
|
||||
rank: i + 1,
|
||||
name: `${row.getAny?.('name') ?? row.getAny?.('shop_name') ?? '未知商家'}`,
|
||||
sales: safeNumber(row.getAny?.('sales') ?? row.getAny?.('total_amount') ?? 0),
|
||||
growth: safeNumber(row.getAny?.('growth') ?? row.getAny?.('growth_rate') ?? 0)
|
||||
sales: safeNumber(row.getAny?.('sales') ?? 0),
|
||||
growth: safeNumber(row.getAny?.('growth') ?? 0)
|
||||
})
|
||||
}
|
||||
return list
|
||||
|
||||
@@ -1,70 +1,58 @@
|
||||
import { rpcOrEmptyArray, rpcOrNull } from './rpc.uts'
|
||||
|
||||
export type DataDetailReportInfo = {
|
||||
period: string
|
||||
}
|
||||
|
||||
export type DataDetailRow = {
|
||||
export type ReportInfo = {
|
||||
id: string
|
||||
date: string
|
||||
gmv: number
|
||||
orders: number
|
||||
users: number
|
||||
title: string
|
||||
description: string
|
||||
definition: any
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type DataDetailDrillItem = {
|
||||
id: string
|
||||
label: string
|
||||
value: string
|
||||
type: string
|
||||
function safeString(v: any): string {
|
||||
return v != null ? `${v}` : ''
|
||||
}
|
||||
|
||||
export async function fetchDataDetailReportInfo(reportId: string): Promise<DataDetailReportInfo | null> {
|
||||
const info = await rpcOrNull('rpc_data_detail_report_info', {
|
||||
// 改造:调用 rpc_data_detail_report_info
|
||||
export async function fetchReportInfo(reportId: string): Promise<ReportInfo | null> {
|
||||
const row = await rpcOrNull('rpc_data_detail_report_info', {
|
||||
p_report_id: reportId
|
||||
} as UTSJSONObject)
|
||||
if (info == null) return null
|
||||
return { period: info.getString('period') ?? '' }
|
||||
} as any)
|
||||
|
||||
if (row == null) return null
|
||||
|
||||
return {
|
||||
id: safeString(row.getAny?.('id')),
|
||||
title: safeString(row.getAny?.('title')),
|
||||
description: safeString(row.getAny?.('description')),
|
||||
definition: row.getAny?.('definition'),
|
||||
updated_at: safeString(row.getAny?.('updated_at'))
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchDataDetailRows(reportId: string, sortBy: string, sortDir: string, limit: number, offset: number): Promise<Array<DataDetailRow>> {
|
||||
const rows = await rpcOrEmptyArray('rpc_data_detail_rows', {
|
||||
// 改造:调用 rpc_data_detail_rows
|
||||
export async function fetchReportRows(reportId: string, params: any): Promise<Array<UTSJSONObject>> {
|
||||
const result = await rpcOrNull('rpc_data_detail_rows', {
|
||||
p_report_id: reportId,
|
||||
p_sort_by: sortBy,
|
||||
p_sort_dir: sortDir,
|
||||
p_limit: limit,
|
||||
p_offset: offset
|
||||
} as UTSJSONObject)
|
||||
p_params: params
|
||||
} as any)
|
||||
|
||||
const out: Array<DataDetailRow> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const r = rows[i]
|
||||
const dayStr = r.getString('row_date') ?? ''
|
||||
out.push({
|
||||
id: dayStr + '_' + i.toString(),
|
||||
date: dayStr,
|
||||
gmv: r.getNumber('gmv') ?? 0,
|
||||
orders: r.getNumber('orders') ?? 0,
|
||||
users: r.getNumber('users') ?? 0
|
||||
})
|
||||
}
|
||||
return out
|
||||
if (result == null) return []
|
||||
const anyData = result as any
|
||||
return Array.isArray(anyData) ? (anyData as Array<UTSJSONObject>) : ([] as Array<UTSJSONObject>)
|
||||
}
|
||||
|
||||
export async function fetchDataDetailDrillItems(reportId: string): Promise<Array<DataDetailDrillItem>> {
|
||||
const rows = await rpcOrEmptyArray('rpc_data_detail_drill_items', {
|
||||
p_report_id: reportId
|
||||
} as UTSJSONObject)
|
||||
|
||||
const out: Array<DataDetailDrillItem> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const r = rows[i]
|
||||
out.push({
|
||||
id: `${r.getAny('id') ?? i}`,
|
||||
label: `${r.getString('label') ?? ''}`,
|
||||
value: `${r.getAny('value') ?? ''}`,
|
||||
type: `${r.getString('type') ?? ''}`
|
||||
})
|
||||
}
|
||||
return out
|
||||
// 保留调用,但 RPC 是模拟数据
|
||||
export async function fetchDrilldown(reportId: string, itemId: string): Promise<Array<UTSJSONObject>> {
|
||||
return await rpcOrEmptyArray('rpc_data_detail_drill_items', {
|
||||
p_report_id: reportId,
|
||||
p_item_id: itemId
|
||||
} as any)
|
||||
}
|
||||
|
||||
// 保留调用,但 RPC 是模拟数据
|
||||
export async function fetchComparison(itemId: string, period: string): Promise<Array<UTSJSONObject>> {
|
||||
return await rpcOrEmptyArray('rpc_data_detail_compare_gmv', {
|
||||
p_item_id: itemId,
|
||||
p_period: period
|
||||
} as any)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import supa, { ensureSupabaseReady } from '@/components/supadb/aksupainstance.uts'
|
||||
import { computeDateRange } from './dateRange.uts'
|
||||
import { rpcOrEmptyArray } from './rpc.uts'
|
||||
|
||||
export type DeliveryAnalysisData = {
|
||||
trendList: Array<UTSJSONObject>
|
||||
@@ -11,122 +11,16 @@ export type DeliveryAnalysisData = {
|
||||
export async function fetchDeliveryAnalysis(period: string): Promise<DeliveryAnalysisData> {
|
||||
const { startIso, endIso } = computeDateRange(period)
|
||||
|
||||
await ensureSupabaseReady()
|
||||
|
||||
// 优先走 RPC(需要在 Supabase 执行 DELIVERY_ANALYSIS_RPCS.sql 创建函数)
|
||||
let trendList: Array<UTSJSONObject> = []
|
||||
let topList: Array<UTSJSONObject> = []
|
||||
|
||||
const trendRes: any = await supa.rpc('rpc_delivery_efficiency_daily', {
|
||||
const trendList = await rpcOrEmptyArray('rpc_delivery_efficiency_daily', {
|
||||
p_start: startIso,
|
||||
p_end: endIso
|
||||
} as UTSJSONObject)
|
||||
} as any)
|
||||
|
||||
if (trendRes.status === 404) {
|
||||
// RPC 不存在:降级到直查表聚合(测试阶段兜底)
|
||||
const taskRes: any = await supa
|
||||
.from('ml_delivery_tasks')
|
||||
.select('id,driver_id,assigned_at,delivered_at,delivery_fee', {})
|
||||
.eq('status', 5)
|
||||
.gte('assigned_at', startIso)
|
||||
.order('assigned_at', { ascending: true } as any)
|
||||
.execute()
|
||||
|
||||
if (taskRes?.error != null) throw taskRes.error
|
||||
|
||||
const rowsAny = (taskRes.data != null ? taskRes.data : []) as any
|
||||
const tasks = Array.isArray(rowsAny) ? (rowsAny as Array<UTSJSONObject>) : []
|
||||
|
||||
const dayAgg = new Map<string, UTSJSONObject>()
|
||||
const driverAgg = new Map<string, number>()
|
||||
const driverFeeAgg = new Map<string, number>()
|
||||
const driverTimeAgg = new Map<string, number>()
|
||||
|
||||
for (let i = 0; i < tasks.length; i++) {
|
||||
const t = tasks[i]
|
||||
const assignedAt = t.getString('assigned_at') ?? ''
|
||||
const deliveredAt = t.getString('delivered_at') ?? ''
|
||||
const driverId = t.getString('driver_id') ?? ''
|
||||
if (assignedAt.trim() === '' || deliveredAt.trim() === '') continue
|
||||
|
||||
const day = assignedAt.length >= 10 ? assignedAt.substring(0, 10) : assignedAt
|
||||
const a = new Date(assignedAt)
|
||||
const d = new Date(deliveredAt)
|
||||
const diffMin = Math.max(0, (d.getTime() - a.getTime()) / 60000)
|
||||
const fee = t.getNumber('delivery_fee') ?? 0
|
||||
|
||||
const old = dayAgg.get(day)
|
||||
if (old == null) {
|
||||
const obj = new UTSJSONObject()
|
||||
obj.set('day', day)
|
||||
obj.set('completed_orders', 1)
|
||||
obj.set('sum_minutes', diffMin)
|
||||
obj.set('total_fee', fee)
|
||||
dayAgg.set(day, obj)
|
||||
} else {
|
||||
old.set('completed_orders', (old.getNumber('completed_orders') ?? 0) + 1)
|
||||
old.set('sum_minutes', (old.getNumber('sum_minutes') ?? 0) + diffMin)
|
||||
old.set('total_fee', (old.getNumber('total_fee') ?? 0) + fee)
|
||||
}
|
||||
|
||||
if (driverId.trim() !== '') {
|
||||
driverAgg.set(driverId, (driverAgg.get(driverId) ?? 0) + 1)
|
||||
driverFeeAgg.set(driverId, (driverFeeAgg.get(driverId) ?? 0) + fee)
|
||||
driverTimeAgg.set(driverId, (driverTimeAgg.get(driverId) ?? 0) + diffMin)
|
||||
}
|
||||
}
|
||||
|
||||
// dayAgg -> trendList
|
||||
const days = Array.from(dayAgg.keys()).sort()
|
||||
for (let i = 0; i < days.length; i++) {
|
||||
const day = days[i]
|
||||
const obj = dayAgg.get(day)
|
||||
if (obj != null) {
|
||||
const completed = obj.getNumber('completed_orders') ?? 0
|
||||
const sumMin = obj.getNumber('sum_minutes') ?? 0
|
||||
const totalFee = obj.getNumber('total_fee') ?? 0
|
||||
const out = new UTSJSONObject()
|
||||
out.set('day', day)
|
||||
out.set('avg_delivery_time', completed > 0 ? sumMin / completed : 0)
|
||||
out.set('total_fee', totalFee)
|
||||
out.set('completed_orders', completed)
|
||||
trendList.push(out)
|
||||
}
|
||||
}
|
||||
|
||||
// driverAgg -> topList (Top10)
|
||||
const drivers = Array.from(driverAgg.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10)
|
||||
for (let i = 0; i < drivers.length; i++) {
|
||||
const [driverId, orders] = drivers[i]
|
||||
const out = new UTSJSONObject()
|
||||
out.set('driver_id', driverId)
|
||||
out.set('orders', orders)
|
||||
out.set('total_fee', driverFeeAgg.get(driverId) ?? 0)
|
||||
out.set('total_minutes', driverTimeAgg.get(driverId) ?? 0)
|
||||
topList.push(out)
|
||||
}
|
||||
} else if (trendRes.error != null) {
|
||||
throw trendRes.error
|
||||
} else {
|
||||
const anyData = trendRes.data as any
|
||||
trendList = Array.isArray(anyData) ? (anyData as Array<UTSJSONObject>) : []
|
||||
|
||||
// Top drivers
|
||||
const topRes = await supa.rpc('rpc_delivery_efficiency_top_drivers', {
|
||||
p_start: startIso,
|
||||
p_end: endIso,
|
||||
p_limit: 10
|
||||
})
|
||||
|
||||
if (topRes.status === 404) {
|
||||
console.warn('rpc_delivery_efficiency_top_drivers not found, top drivers will be empty')
|
||||
} else if (topRes.error != null) {
|
||||
throw topRes.error
|
||||
} else {
|
||||
const topAny = topRes.data as any
|
||||
topList = Array.isArray(topAny) ? (topAny as Array<UTSJSONObject>) : []
|
||||
}
|
||||
}
|
||||
const topList = await rpcOrEmptyArray('rpc_delivery_efficiency_top_drivers', {
|
||||
p_start: startIso,
|
||||
p_end: endIso,
|
||||
p_limit: 10
|
||||
} as any)
|
||||
|
||||
return { trendList, topList, startIso, endIso }
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ function safeNumber(v: any): number {
|
||||
export async function fetchProductOverview(period: string): Promise<ProductOverview> {
|
||||
const { startIso, endIso } = computeDateRange(period)
|
||||
const row = await rpcOrNull('rpc_product_insights_overview', {
|
||||
p_start: startIso,
|
||||
p_end: endIso
|
||||
p_start: toDateOnly(startIso),
|
||||
p_end: toDateOnly(endIso)
|
||||
} as any)
|
||||
|
||||
const obj: any = row != null ? row : ({} as any)
|
||||
@@ -44,8 +44,7 @@ export async function fetchTopProducts(period: string, limit: number = 10): Prom
|
||||
const rows = await rpcOrEmptyArray('rpc_analytics_top_products', {
|
||||
p_start_date: toDateOnly(startIso),
|
||||
p_end_date: toDateOnly(endIso),
|
||||
p_limit: limit,
|
||||
p_merchant_id: null
|
||||
p_limit: limit
|
||||
} as any)
|
||||
|
||||
const list: Array<ProductRank> = []
|
||||
@@ -55,8 +54,8 @@ export async function fetchTopProducts(period: string, limit: number = 10): Prom
|
||||
id: `${r.getAny?.('id') ?? i}`,
|
||||
rank: i + 1,
|
||||
name: `${r.getAny?.('name') ?? '未知商品'}`,
|
||||
sales: safeNumber(r.getAny?.('sales') ?? r.getAny?.('total_amount') ?? 0),
|
||||
growth: safeNumber(r.getAny?.('growth') ?? r.getAny?.('growth_rate') ?? 0)
|
||||
sales: safeNumber(r.getAny?.('sales') ?? 0),
|
||||
growth: safeNumber(r.getAny?.('growth') ?? 0)
|
||||
})
|
||||
}
|
||||
return list
|
||||
@@ -73,12 +72,12 @@ export async function fetchProductTrend(period: string, productId: string): Prom
|
||||
const out: Array<ProductTrendRow> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const r: any = rows[i]
|
||||
const date = `${r.getAny?.('date') ?? r.getAny?.('day') ?? r.getAny?.('date_key') ?? ''}`
|
||||
const date = `${r.getAny?.('date') ?? ''}`
|
||||
out.push({
|
||||
date,
|
||||
gmv: safeNumber(r.getAny?.('gmv') ?? r.getAny?.('total_amount') ?? 0),
|
||||
qty: safeNumber(r.getAny?.('qty') ?? r.getAny?.('sales_qty') ?? 0),
|
||||
orders: safeNumber(r.getAny?.('orders') ?? r.getAny?.('order_count') ?? 0)
|
||||
gmv: safeNumber(r.getAny?.('gmv') ?? 0),
|
||||
qty: safeNumber(r.getAny?.('qty') ?? 0),
|
||||
orders: safeNumber(r.getAny?.('orders') ?? 0)
|
||||
})
|
||||
}
|
||||
return out
|
||||
@@ -93,11 +92,7 @@ export async function fetchCategorySales(period: string): Promise<Array<UTSJSONO
|
||||
}
|
||||
|
||||
export async function fetchStockInsights(period: string): Promise<Array<UTSJSONObject>> {
|
||||
const { startIso, endIso } = computeDateRange(period)
|
||||
return await rpcOrEmptyArray('rpc_product_insights_stock', {
|
||||
p_start: startIso,
|
||||
p_end: endIso
|
||||
} as any)
|
||||
return await rpcOrEmptyArray('rpc_product_insights_stock', {} as any)
|
||||
}
|
||||
|
||||
export async function fetchPriceTrend(period: string): Promise<Array<UTSJSONObject>> {
|
||||
|
||||
@@ -24,22 +24,9 @@ function safeNumber(v: any): number {
|
||||
|
||||
export async function fetchSalesKpis(period: string): Promise<SalesKpis> {
|
||||
const { startIso, endIso } = computeDateRange(period)
|
||||
const days = period === '7d' ? 7 : period === '30d' ? 30 : period === '90d' ? 90 : 365
|
||||
|
||||
const startDateObj = new Date(startIso)
|
||||
const endDateObj = new Date(endIso)
|
||||
|
||||
const periodStart = new Date(startDateObj.getFullYear(), startDateObj.getMonth(), startDateObj.getDate())
|
||||
const periodEnd = new Date(endDateObj.getFullYear(), endDateObj.getMonth(), endDateObj.getDate() + 1)
|
||||
const prevStart = new Date(periodStart.getTime() - days * 24 * 60 * 60 * 1000)
|
||||
const prevEnd = new Date(periodStart.getTime())
|
||||
|
||||
const row = await rpcOrNull('rpc_analytics_realtime_kpis', {
|
||||
p_start: periodStart.toISOString(),
|
||||
p_end: periodEnd.toISOString(),
|
||||
p_compare_start: prevStart.toISOString(),
|
||||
p_compare_end: prevEnd.toISOString(),
|
||||
p_merchant_id: null
|
||||
const row = await rpcOrNull('rpc_analytics_sales_kpis', {
|
||||
p_start_date: toDateOnly(startIso),
|
||||
p_end_date: toDateOnly(endIso)
|
||||
} as any)
|
||||
|
||||
const obj: any = row != null ? row : ({} as any)
|
||||
@@ -61,10 +48,9 @@ export async function fetchSalesKpis(period: string): Promise<SalesKpis> {
|
||||
|
||||
export async function fetchSalesTrend(period: string): Promise<TrendData> {
|
||||
const { startIso, endIso } = computeDateRange(period)
|
||||
const rows = await rpcOrEmptyArray('rpc_analytics_trend_data', {
|
||||
const rows = await rpcOrEmptyArray('rpc_analytics_sales_trend', {
|
||||
p_start_date: toDateOnly(startIso),
|
||||
p_end_date: toDateOnly(endIso),
|
||||
p_merchant_id: null
|
||||
p_end_date: toDateOnly(endIso)
|
||||
} as any)
|
||||
|
||||
const x: Array<string> = []
|
||||
@@ -87,8 +73,7 @@ export async function fetchSalesTopProducts(period: string, limit: number = 50):
|
||||
const rows = await rpcOrEmptyArray('rpc_analytics_top_products', {
|
||||
p_start_date: toDateOnly(startIso),
|
||||
p_end_date: toDateOnly(endIso),
|
||||
p_limit: limit,
|
||||
p_merchant_id: null
|
||||
p_limit: limit
|
||||
} as any)
|
||||
|
||||
const list: Array<ProductRank> = []
|
||||
|
||||
Reference in New Issue
Block a user