From 398bc29b549a37d76af79416cbf47cdcb235d804 Mon Sep 17 00:00:00 2001 From: howard9199 <53228984+howard9199@users.noreply.github.com> Date: Sun, 22 Dec 2024 22:52:55 +0800 Subject: [PATCH 1/3] feat: add the remove participant in HostView --- src/lib/components/session/HostView.svelte | 126 ++++++++++++------ .../join/[participant]/+server.ts | 47 +++++++ 2 files changed, 129 insertions(+), 44 deletions(-) create mode 100644 src/routes/api/session/[id]/group/[group_number]/join/[participant]/+server.ts diff --git a/src/lib/components/session/HostView.svelte b/src/lib/components/session/HostView.svelte index 4bddc84..8f9def6 100644 --- a/src/lib/components/session/HostView.svelte +++ b/src/lib/components/session/HostView.svelte @@ -16,6 +16,7 @@ import type { Conversation } from '$lib/schema/conversation'; import { getUserProgress } from '$lib/utils/getUserProgress'; import { Modal } from 'flowbite-svelte'; + import { X } from 'lucide-svelte'; let { session }: { session: Readable } = $props(); type GroupWithId = Group & { id: string }; @@ -37,57 +38,67 @@ }>; } | null>(null); - onMount(async () => { - try { - const groupsCollection = collection(db, `sessions/${$page.params.id}/groups`); - const snapshot = await getDocs(groupsCollection); - const groupsData: GroupWithId[] = snapshot.docs.map( - (doc) => ({ id: doc.id, ...doc.data() }) as GroupWithId - ); - groups.set(groupsData); + onMount(() => { + const initializeSession = async () => { + try { + const groupsCollection = collection(db, `sessions/${$page.params.id}/groups`); + const unsubscribe = onSnapshot(groupsCollection, (snapshot) => { + const groupsData: GroupWithId[] = snapshot.docs.map( + (doc) => ({ id: doc.id, ...doc.data() }) as GroupWithId + ); + groups.set(groupsData); - for (const group of groupsData) { - for (const participant of group.participants) { - try { - const userData = await getUser(participant); - participantNames.set(participant, userData.displayName); - } catch (error) { - console.error(`無法獲取使用者 ${participant} 的資料:`, error); - participantNames.set(participant, '未知使用者'); - } - } + groupsData.forEach(async (group) => { + for (const participant of group.participants) { + try { + const userData = await getUser(participant); + participantNames.set(participant, userData.displayName); + } catch (error) { + console.error(`無法獲取使用者 ${participant} 的資料:`, error); + participantNames.set(participant, '未知使用者'); + } + } + }); + }); + return unsubscribe; + } catch (error) { + console.error('無法加載群組資料:', error); } + }; - for (const group of groupsData) { - for (const participant of group.participants) { - const conversationsRef = collection( - db, - `sessions/${$page.params.id}/groups/${group.id}/conversations` - ); - onSnapshot(conversationsRef, async (snapshot) => { - const conversations = snapshot.docs - .map((doc) => doc.data() as Conversation) - .filter((conv) => conv.userId === participant); + initializeSession(); - if (conversations.length > 0) { - const conv = conversations[0]; - const userData = await getUser(participant); - const totalTasks = conv.subtasks.length; - const completedCount = conv.subtaskCompleted.filter(Boolean).length; - const progress = totalTasks > 0 ? (completedCount / totalTasks) * 100 : 0; + for (const group of $groups) { + for (const participant of group.participants) { + const conversationsRef = collection( + db, + `sessions/${$page.params.id}/groups/${group.id}/conversations` + ); + onSnapshot(conversationsRef, async (snapshot) => { + const conversations = snapshot.docs + .map((doc) => doc.data() as Conversation) + .filter((conv) => conv.userId === participant); - participantProgress.set(participant, { - displayName: userData.displayName, - progress, - completedTasks: conv.subtaskCompleted - }); - } - }); - } + if (conversations.length > 0) { + const conv = conversations[0]; + const userData = await getUser(participant); + const totalTasks = conv.subtasks.length; + const completedCount = conv.subtaskCompleted.filter(Boolean).length; + const progress = totalTasks > 0 ? (completedCount / totalTasks) * 100 : 0; + + participantProgress.set(participant, { + displayName: userData.displayName, + progress, + completedTasks: conv.subtaskCompleted + }); + } + }); } - } catch (error) { - console.error('無法加載群組資料:', error); } + + return () => { + initializeSession().then((unsubscribe) => unsubscribe?.()); + }; }); async function handleStartSession() { @@ -134,6 +145,26 @@ } } + async function handleRemoveParticipant(groupId: string, participant: string) { + try { + const response = await fetch( + `/api/session/${$page.params.id}/group/${groupId}/join/${participant}`, + { + method: 'DELETE' + } + ); + + if (!response.ok) { + throw new Error('Failed to remove participant'); + } + + notifications.success('成功移除參與者', 3000); + } catch (error) { + console.error('無法移除參與者:', error); + notifications.error('無法移除參與者'); + } + } + const stageButton = $derived({ preparing: { text: 'Start Individual Stage', @@ -275,6 +306,13 @@ > {/each} + {:catch}
Error loading progress
diff --git a/src/routes/api/session/[id]/group/[group_number]/join/[participant]/+server.ts b/src/routes/api/session/[id]/group/[group_number]/join/[participant]/+server.ts new file mode 100644 index 0000000..9a896c9 --- /dev/null +++ b/src/routes/api/session/[id]/group/[group_number]/join/[participant]/+server.ts @@ -0,0 +1,47 @@ +import { adminDb } from '$lib/server/firebase'; +import { error, json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; + +export const DELETE: RequestHandler = async ({ params }) => { + try { + const { id, group_number, participant } = params; + if (!participant) { + throw error(400, '缺少參與者ID'); + } + + // 找到群組 + const groupsRef = adminDb.collection('sessions').doc(id).collection('groups'); + const groupDoc = await groupsRef.doc(group_number).get(); + + if (!groupDoc.exists) { + throw error(404, '找不到群組'); + } + + const groupData = groupDoc.data(); + if (!groupData) { + throw error(404, '找不到群組資料'); + } + + // 確認參與者在群組中 + if (!groupData.participants.includes(participant)) { + throw error(400, '參與者不在此群組中'); + } + + // 從群組中移除參與者 + await groupDoc.ref.update({ + participants: groupData.participants.filter((p: string) => p !== participant) + }); + + // 刪除參與者的對話記錄 + const conversationsRef = groupDoc.ref.collection('conversations'); + const conversationQuery = await conversationsRef.where('userId', '==', participant).get(); + + const deletePromises = conversationQuery.docs.map((doc) => doc.ref.delete()); + await Promise.all(deletePromises); + + return json({ success: true }); + } catch (e) { + console.error('移除參與者時發生錯誤:', e); + throw error(500, '無法移除參與者'); + } +}; From 295f9455829ae8c7fdb996b58f975e11e2dd90b0 Mon Sep 17 00:00:00 2001 From: howard9199 <53228984+howard9199@users.noreply.github.com> Date: Sun, 22 Dec 2024 23:12:53 +0800 Subject: [PATCH 2/3] feat: enhance HostView to display individual session status and update status section layout --- src/lib/components/session/HostView.svelte | 50 +++++++++++++--------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/lib/components/session/HostView.svelte b/src/lib/components/session/HostView.svelte index 7d7fe50..dc35fbb 100644 --- a/src/lib/components/session/HostView.svelte +++ b/src/lib/components/session/HostView.svelte @@ -236,6 +236,12 @@
+ {#if $session?.status === 'individual'} +
+ + Individual Stage +
+ {/if} {#if $session && stageButton[$session.status].show}
- -
-

Session Status

-
- +

Session Status

+
+ - {$session?.status} -
- - {#if $session?.status === 'preparing'} -
-

Session QR Code

- + > + {$session?.status}
- {/if} -
- -
+ {#if $session?.status === 'preparing'} +
+

Session QR Code

+ +
+ {/if} +
+ {/if} + +

Groups

{#if $groups.length === 0} Loading groups... From a7923d3701c4598b6c434388582feb706f8a9aad Mon Sep 17 00:00:00 2001 From: howard9199 <53228984+howard9199@users.noreply.github.com> Date: Sun, 22 Dec 2024 23:43:05 +0800 Subject: [PATCH 3/3] feat: enhance ParticipantView to display user names with loading state and handle participant removal in group --- .../components/session/ParticipantView.svelte | 15 ++++++++++++++- .../[group_number]/join/[participant]/+server.ts | 16 ++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/lib/components/session/ParticipantView.svelte b/src/lib/components/session/ParticipantView.svelte index 46e1d56..4762670 100644 --- a/src/lib/components/session/ParticipantView.svelte +++ b/src/lib/components/session/ParticipantView.svelte @@ -10,6 +10,7 @@ import { collection, query, where } from 'firebase/firestore'; import { subscribeAll } from '$lib/firebase/store'; import { onDestroy } from 'svelte'; + import { getUser } from '$lib/utils/getUser'; let { session, user } = $props<{ session: Readable; @@ -115,7 +116,19 @@ {#each $group[0][1].participants as participant}
  • - {participant === user.uid ? 'You' : participant} + + {#if participant === user.uid} + You + {:else} + {#await getUser(participant)} + 載入中... + {:then profile} + {profile.displayName} + {:catch} + 未知使用者 + {/await} + {/if} +
  • {/each} diff --git a/src/routes/api/session/[id]/group/[group_number]/join/[participant]/+server.ts b/src/routes/api/session/[id]/group/[group_number]/join/[participant]/+server.ts index 9a896c9..7ea462e 100644 --- a/src/routes/api/session/[id]/group/[group_number]/join/[participant]/+server.ts +++ b/src/routes/api/session/[id]/group/[group_number]/join/[participant]/+server.ts @@ -27,10 +27,18 @@ export const DELETE: RequestHandler = async ({ params }) => { throw error(400, '參與者不在此群組中'); } - // 從群組中移除參與者 - await groupDoc.ref.update({ - participants: groupData.participants.filter((p: string) => p !== participant) - }); + // 計算移除參與者後的新參與者列表 + const updatedParticipants = groupData.participants.filter((p: string) => p !== participant); + + if (updatedParticipants.length === 0) { + // 如果群組將變成空的,直接刪除整個群組 + await groupDoc.ref.delete(); + } else { + // 否則只更新參與者列表 + await groupDoc.ref.update({ + participants: updatedParticipants + }); + } // 刪除參與者的對話記錄 const conversationsRef = groupDoc.ref.collection('conversations');