refactor(disputes): align type with backend and derive timeline in page

This commit is contained in:
zetaloop
2026-05-03 05:48:39 +08:00
parent be329865b3
commit 7acde68d45
3 changed files with 20 additions and 69 deletions
+1 -9
View File
@@ -44,15 +44,7 @@ const disputeStatusVariants: Record<string, StatusBadgeProps["status"]> = {
appealed: "info",
}
function deriveMinimalTimeline<TCreatedAt>(dispute: {
id: string
status: string
createdAt: TCreatedAt
timeline?: { id: string; content: string; createdAt: TCreatedAt }[]
}) {
const existing = dispute.timeline
if (existing?.length) return existing
function deriveMinimalTimeline(dispute: { id: string; status: string; createdAt: string }) {
const steps = [
{ status: "open", content: "争议已提交" },
{ status: "reviewing", content: "平台审核中" },
+9 -60
View File
@@ -4,20 +4,6 @@ import type { Dispute } from "@/lib/types"
import { httpJson } from "./http"
export type DisputeTimelineItem = {
id: string
content: string
createdAt: string
}
export type DisputeRecord = Dispute & {
respondentReason?: string
respondentEvidence: string[]
appealReason?: string
appealedAt?: string
timeline: DisputeTimelineItem[]
}
export type ListDisputesOptions = {
offset?: number
limit?: number
@@ -64,49 +50,12 @@ function unwrapDispute(value: unknown): unknown {
return value
}
function deriveMinimalTimeline(dispute: {
id: string
status: string
createdAt: string
timeline?: DisputeTimelineItem[]
}): DisputeTimelineItem[] {
const existing = dispute.timeline
if (existing?.length) return existing
const steps = [
{ status: "open", content: "争议已提交" },
{ status: "reviewing", content: "平台审核中" },
{ status: "resolved", content: "争议已解决" },
{ status: "appealed", content: "已发起申诉" },
]
const currentIndex = steps.findIndex((step) => step.status === dispute.status)
const lastIndex = currentIndex >= 0 ? currentIndex : 0
return steps.slice(0, lastIndex + 1).map((step) => ({
id: `${dispute.id}-${step.status}`,
content: step.content,
createdAt: dispute.createdAt,
}))
}
function normalizeDisputeRecord(value: unknown): DisputeRecord {
const dispute = unwrapDispute(value) as DisputeRecord
const respondentEvidence = Array.isArray(dispute.respondentEvidence)
? dispute.respondentEvidence
: []
const evidence = Array.isArray(dispute.evidence) ? dispute.evidence : []
const timeline = deriveMinimalTimeline({
id: dispute.id,
status: dispute.status,
createdAt: dispute.createdAt,
timeline: dispute.timeline,
})
function normalizeDispute(value: unknown): Dispute {
const dispute = unwrapDispute(value) as Dispute
return {
...dispute,
evidence,
respondentEvidence,
timeline,
evidence: Array.isArray(dispute.evidence) ? dispute.evidence : [],
respondentEvidence: Array.isArray(dispute.respondentEvidence) ? dispute.respondentEvidence : [],
}
}
@@ -118,20 +67,20 @@ function denyFromError(error: unknown): ApiDecision {
return deny(apiError.code, apiError.msg)
}
export async function listDisputes(options?: ListDisputesOptions): Promise<DisputeRecord[]> {
const res = await httpJson<Paginated<DisputeRecord> | DisputeRecord[]>(
export async function listDisputes(options?: ListDisputesOptions): Promise<Dispute[]> {
const res = await httpJson<Paginated<Dispute> | Dispute[]>(
withOffsetLimit("/api/v1/disputes", options),
{ cache: "no-store" },
)
return unwrapItems<DisputeRecord>(res).map((item) => normalizeDisputeRecord(item))
return unwrapItems<Dispute>(res).map((item) => normalizeDispute(item))
}
export async function getDisputeByOrderId(orderId: string): Promise<DisputeRecord | undefined> {
export async function getDisputeByOrderId(orderId: string): Promise<Dispute | undefined> {
try {
const res = await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(orderId)}/dispute`, {
cache: "no-store",
})
return normalizeDisputeRecord(res)
return normalizeDispute(res)
} catch (error) {
const apiError = isApiError(error) ? error : toApiError(error)
if (apiError.code === 404) return undefined
+10
View File
@@ -120,11 +120,21 @@ export interface Review {
export interface Dispute {
id: SnowflakeId
orderId: SnowflakeId
initiatorId?: SnowflakeId
initiatorName?: string
respondentId?: SnowflakeId
reason: string
evidence: string[]
status: "open" | "reviewing" | "resolved" | "appealed"
result?: "full_refund" | "full_payment" | "partial_refund"
respondentReason?: string
respondentEvidence: string[]
appealReason?: string
appealedAt?: string
resolvedBy?: SnowflakeId
resolvedAt?: string
createdAt: string
updatedAt: string
}
export interface ChatParticipant {