platform/lms-adm-fe release promotion snapshot
This commit is contained in:
+129
@@ -0,0 +1,129 @@
|
||||
export type ChatSenderType = 'STUDENT' | 'AI' | 'INSTRUCTOR' | 'SYSTEM' | string;
|
||||
|
||||
export type InstructorChatMessage = {
|
||||
id: string;
|
||||
senderType: ChatSenderType;
|
||||
senderName?: string;
|
||||
senderAvatarUrl?: string | null;
|
||||
content: string;
|
||||
createdAt?: string;
|
||||
};
|
||||
|
||||
export type InstructorChatThread = {
|
||||
id: string;
|
||||
title: string;
|
||||
studentName: string;
|
||||
studentAvatarUrl?: string | null;
|
||||
groupName?: string;
|
||||
week?: number | string;
|
||||
status: string;
|
||||
lastMessage: string;
|
||||
lastMessageAt?: string;
|
||||
messages: InstructorChatMessage[];
|
||||
};
|
||||
|
||||
const CHAT_API_BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:4400/api').replace(/\/+$/, '');
|
||||
export const CHAT_SOCKET_BASE_URL = CHAT_API_BASE_URL.replace(/\/api$/, '');
|
||||
|
||||
type ChatThreadsPayload = {
|
||||
threads?: unknown[];
|
||||
data?: unknown[];
|
||||
};
|
||||
|
||||
export async function listInstructorChatThreads(accessToken: string, week?: number): Promise<InstructorChatThread[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (week) params.set('week', String(week));
|
||||
const queryString = params.toString();
|
||||
|
||||
const response = await fetch(`${CHAT_API_BASE_URL}/chat/instructor/threads${queryString ? `?${queryString}` : ''}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`강사 채팅 목록을 불러오지 못했습니다. (${response.status})`);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as ChatThreadsPayload | unknown[];
|
||||
const items = Array.isArray(payload) ? payload : Array.isArray(payload?.threads) ? payload.threads : Array.isArray(payload?.data) ? payload.data : [];
|
||||
return items.map(normalizeThread).filter((thread): thread is InstructorChatThread => Boolean(thread));
|
||||
}
|
||||
|
||||
export async function getInstructorChatThread(accessToken: string, threadId: string): Promise<InstructorChatThread> {
|
||||
const response = await fetch(`${CHAT_API_BASE_URL}/chat/instructor/threads/${threadId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`강사 채팅방을 불러오지 못했습니다. (${response.status})`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
const thread = normalizeThread(payload?.thread ?? payload);
|
||||
if (!thread) throw new Error('강사 채팅방 응답이 올바르지 않습니다.');
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
export async function createInstructorChatMessage(accessToken: string, threadId: string, content: string): Promise<InstructorChatThread> {
|
||||
const response = await fetch(`${CHAT_API_BASE_URL}/chat/instructor/threads/${threadId}/messages`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ content }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`강사 답변을 저장하지 못했습니다. (${response.status})`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
const thread = normalizeThread(payload?.thread ?? payload);
|
||||
if (!thread) throw new Error('강사 답변 응답이 올바르지 않습니다.');
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
function normalizeThread(raw: unknown): InstructorChatThread | null {
|
||||
if (!raw || typeof raw !== 'object') return null;
|
||||
const item = raw as Record<string, any>;
|
||||
|
||||
const messages = Array.isArray(item.messages) ? item.messages.map(normalizeMessage).filter((message): message is InstructorChatMessage => Boolean(message)) : [];
|
||||
const lastMessageFromList = messages.length > 0 ? messages[messages.length - 1]?.content : undefined;
|
||||
const rawLastMessage = item.lastMessage?.content ?? item.lastMessageContent ?? item.lastMessage;
|
||||
const studentName = item.student?.name ?? item.studentName ?? item.student?.email ?? item.studentEmail ?? item.studentId ?? '수강생';
|
||||
const title = item.title ?? item.threadTitle ?? studentName;
|
||||
const weekValue = typeof item.week === 'object' && item.week ? item.week.week : item.week;
|
||||
|
||||
return {
|
||||
id: String(item.id ?? item.threadId ?? title),
|
||||
title: String(title),
|
||||
studentName: String(studentName),
|
||||
studentAvatarUrl: typeof item.studentAvatarUrl === 'string' ? item.studentAvatarUrl : null,
|
||||
groupName: item.group?.name ?? item.groupName ?? item.metadata?.groupName,
|
||||
week: weekValue ?? item.weekId ?? item.metadata?.week,
|
||||
status: String(item.status ?? 'WAITING_INSTRUCTOR'),
|
||||
lastMessage: String(rawLastMessage ?? lastMessageFromList ?? '저장된 문의 내용을 확인해 주세요.'),
|
||||
lastMessageAt: item.lastMessageAt ?? item.updatedAt ?? item.createdAt,
|
||||
messages,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMessage(raw: unknown): InstructorChatMessage | null {
|
||||
if (!raw || typeof raw !== 'object') return null;
|
||||
const item = raw as Record<string, any>;
|
||||
|
||||
return {
|
||||
id: String(item.id ?? `${item.senderType ?? 'MESSAGE'}-${item.createdAt ?? item.content ?? 'unknown'}`),
|
||||
senderType: String(item.senderType ?? 'STUDENT'),
|
||||
senderName: item.senderName ?? item.sender?.name,
|
||||
senderAvatarUrl: typeof item.senderAvatarUrl === 'string' ? item.senderAvatarUrl : null,
|
||||
content: String(item.content ?? ''),
|
||||
createdAt: item.createdAt,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user