Skip to content

Commit

Permalink
Merge pull request #46 from hinagiku-dev/feat/hostview-individual
Browse files Browse the repository at this point in the history
Feat/hostview individual
  • Loading branch information
howard9199 authored Dec 22, 2024
2 parents a1ad86f + a7923d3 commit 072fc60
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 66 deletions.
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, '無法移除參與者');
}
};

0 comments on commit 072fc60

Please sign in to comment.