Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/hostview individual #46

Merged
merged 4 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 111 additions & 65 deletions src/lib/components/session/HostView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { getUserProgress } from '$lib/utils/getUserProgress';
import { Modal } from 'flowbite-svelte';
import Chatroom from '$lib/components/Chatroom.svelte';
import { X } from 'lucide-svelte';

let { session }: { session: Readable<Session> } = $props();
type GroupWithId = Group & { id: string };
Expand All @@ -40,57 +41,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() {
Expand Down Expand Up @@ -137,6 +148,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',
Expand Down Expand Up @@ -205,6 +236,12 @@
</div>

<div class="flex items-center gap-4">
{#if $session?.status === 'individual'}
<div class="flex items-center gap-2">
<span class="inline-block h-3 w-3 rounded-full bg-blue-500"></span>
<span class="capitalize">Individual Stage</span>
</div>
{/if}
{#if $session && stageButton[$session.status].show}
<Button color="primary" on:click={stageButton[$session.status].action}>
<Play class="mr-2 h-4 w-4" />
Expand All @@ -215,34 +252,36 @@
</div>

<div class="grid gap-8 md:grid-cols-4">
<!-- Status Section -->
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-xl font-semibold">Session Status</h2>
<div class="flex items-center gap-2">
<span
class="inline-block h-3 w-3 rounded-full {$session?.status === 'preparing'
? 'bg-yellow-500'
: $session?.status === 'individual'
? 'bg-blue-500'
{#if $session?.status && $session.status !== 'individual'}
<div class="rounded-lg border p-6">
<h2 class="mb-4 text-xl font-semibold">Session Status</h2>
<div class="flex items-center gap-2">
<span
class="inline-block h-3 w-3 rounded-full {$session?.status === 'preparing'
? 'bg-yellow-500'
: $session?.status === 'before-group'
? 'bg-purple-500'
: $session?.status === 'group'
? 'bg-green-500'
: 'bg-gray-500'}"
></span>
<span class="capitalize">{$session?.status}</span>
</div>

{#if $session?.status === 'preparing'}
<div class="mt-4">
<h3 class="mb-2 font-medium">Session QR Code</h3>
<QRCode value={`${$page.url.origin}/session/${$page.params.id}`} />
></span>
<span class="capitalize">{$session?.status}</span>
</div>
{/if}
</div>

<!-- Participant Status Dashboard Section -->
<div class="col-span-3 rounded-lg border p-6">
{#if $session?.status === 'preparing'}
<div class="mt-4">
<h3 class="mb-2 font-medium">Session QR Code</h3>
<QRCode value={`${$page.url.origin}/session/${$page.params.id}`} />
</div>
{/if}
</div>
{/if}

<div
class="col-span-3 rounded-lg border p-6 {$session?.status === 'individual'
? 'md:col-span-4'
: ''}"
>
<h2 class="mb-4 text-xl font-semibold">Groups</h2>
{#if $groups.length === 0}
<Alert color="blue">Loading groups...</Alert>
Expand Down Expand Up @@ -283,6 +322,13 @@
></div>
{/each}
</div>
<button
class="ml-auto rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-red-500"
onclick={() => handleRemoveParticipant(group.id, participant)}
title="移除參與者"
>
<X class="h-4 w-4" />
</button>
</div>
{:catch}
<div class="text-xs text-red-500">Error loading progress</div>
Expand Down
15 changes: 14 additions & 1 deletion src/lib/components/session/ParticipantView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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<Session>;
Expand Down Expand Up @@ -115,7 +116,19 @@
{#each $group[0][1].participants as participant}
<li class="flex items-center gap-2">
<Users class="h-4 w-4" />
<span>{participant === user.uid ? 'You' : participant}</span>
<span>
{#if participant === user.uid}
You
{:else}
{#await getUser(participant)}
<span class="text-gray-500">載入中...</span>
{:then profile}
{profile.displayName}
{:catch}
<span class="text-red-500">未知使用者</span>
{/await}
{/if}
</span>
</li>
{/each}
</ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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, '參與者不在此群組中');
}

// 計算移除參與者後的新參與者列表
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');
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, '無法移除參與者');
}
};
Loading