138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
import { create } from "zustand"
|
|
import { generateId } from "@/lib/id"
|
|
import { mockChatMessages, mockChatSessions, mockUsers } from "@/lib/mock"
|
|
import type { ChatMessage, ChatSession, Order } from "@/lib/types"
|
|
|
|
interface Sender {
|
|
id: string
|
|
name: string
|
|
avatar: string
|
|
}
|
|
|
|
interface ChatState {
|
|
sessions: ChatSession[]
|
|
messages: ChatMessage[]
|
|
ensureOrderSession: (order: Order) => ChatSession
|
|
sendTextMessage: (sessionId: string, sender: Sender, content: string) => void
|
|
sendImageMessage: (sessionId: string, sender: Sender, imageUrl: string) => void
|
|
}
|
|
|
|
function resolveAvatar(userId: string) {
|
|
return mockUsers.find((user) => user.id === userId)?.avatar ?? ""
|
|
}
|
|
|
|
function shouldReadonly(status: Order["status"]) {
|
|
return status === "pending_review" || status === "completed" || status === "cancelled"
|
|
}
|
|
|
|
export const useChatStore = create<ChatState>((set, get) => ({
|
|
sessions: mockChatSessions,
|
|
messages: mockChatMessages,
|
|
ensureOrderSession: (order) => {
|
|
const existing = get().sessions.find(
|
|
(session) => session.type === "order" && session.orderId === order.id,
|
|
)
|
|
|
|
const readonly = shouldReadonly(order.status)
|
|
|
|
if (existing) {
|
|
if (existing.readonly !== readonly) {
|
|
set((state) => ({
|
|
sessions: state.sessions.map((session) =>
|
|
session.id === existing.id ? { ...session, readonly } : session,
|
|
),
|
|
}))
|
|
}
|
|
return get().sessions.find((session) => session.id === existing.id) ?? existing
|
|
}
|
|
|
|
const session: ChatSession = {
|
|
id: `chat-${order.id}`,
|
|
type: "order",
|
|
orderId: order.id,
|
|
participants: [
|
|
{
|
|
id: order.consumerId,
|
|
name: order.consumerName,
|
|
avatar: resolveAvatar(order.consumerId),
|
|
},
|
|
{
|
|
id: order.playerId,
|
|
name: order.playerName,
|
|
avatar: resolveAvatar(order.playerId),
|
|
},
|
|
],
|
|
unreadCount: 0,
|
|
readonly,
|
|
}
|
|
|
|
set((state) => ({
|
|
sessions: [session, ...state.sessions],
|
|
}))
|
|
|
|
return session
|
|
},
|
|
sendTextMessage: (sessionId, sender, content) => {
|
|
const text = content.trim()
|
|
if (!text) return
|
|
const session = get().sessions.find((item) => item.id === sessionId)
|
|
if (!session || session.readonly) return
|
|
|
|
const now = new Date().toISOString()
|
|
const message: ChatMessage = {
|
|
id: generateId("msg"),
|
|
sessionId,
|
|
senderId: sender.id,
|
|
senderName: sender.name,
|
|
senderAvatar: sender.avatar,
|
|
type: "text",
|
|
content: text,
|
|
createdAt: now,
|
|
}
|
|
|
|
set((state) => ({
|
|
messages: [...state.messages, message],
|
|
sessions: state.sessions.map((session) =>
|
|
session.id === sessionId
|
|
? {
|
|
...session,
|
|
lastMessage: text,
|
|
lastMessageAt: now,
|
|
}
|
|
: session,
|
|
),
|
|
}))
|
|
},
|
|
sendImageMessage: (sessionId, sender, imageUrl) => {
|
|
const content = imageUrl.trim()
|
|
if (!content) return
|
|
const session = get().sessions.find((item) => item.id === sessionId)
|
|
if (!session || session.readonly) return
|
|
|
|
const now = new Date().toISOString()
|
|
const message: ChatMessage = {
|
|
id: generateId("msg"),
|
|
sessionId,
|
|
senderId: sender.id,
|
|
senderName: sender.name,
|
|
senderAvatar: sender.avatar,
|
|
type: "image",
|
|
content,
|
|
createdAt: now,
|
|
}
|
|
|
|
set((state) => ({
|
|
messages: [...state.messages, message],
|
|
sessions: state.sessions.map((item) =>
|
|
item.id === sessionId
|
|
? {
|
|
...item,
|
|
lastMessage: "[图片]",
|
|
lastMessageAt: now,
|
|
}
|
|
: item,
|
|
),
|
|
}))
|
|
},
|
|
}))
|