feat: enhance chat functionality with new conversation and meeting room features

- Added support for creating and managing group conversations and meeting rooms, allowing users to initiate discussions with multiple participants.
- Implemented a modal for creating new conversations, including options for individual, group, and meeting room types.
- Enhanced the chat list component to filter and display conversations based on type, improving user navigation.
- Introduced admin functionalities for meeting rooms, enabling user management and role assignments within the chat interface.
- Updated backend schema and API to accommodate new conversation types and related operations, ensuring robust data handling.
This commit is contained in:
2025-11-05 07:20:37 -03:00
parent aa3e3470cd
commit 8ca737c62f
9 changed files with 1665 additions and 212 deletions

View File

@@ -8,6 +8,8 @@
import UserStatusBadge from "./UserStatusBadge.svelte";
import UserAvatar from "./UserAvatar.svelte";
import ScheduleMessageModal from "./ScheduleMessageModal.svelte";
import SalaReuniaoManager from "./SalaReuniaoManager.svelte";
import { getAvatarUrl } from "$lib/utils/avatarGenerator";
interface Props {
conversaId: string;
@@ -16,8 +18,10 @@
let { conversaId }: Props = $props();
let showScheduleModal = $state(false);
let showSalaManager = $state(false);
const conversas = useQuery(api.chat.listarConversas, {});
const isAdmin = useQuery(api.chat.verificarSeEhAdmin, { conversaId: conversaId as any });
const conversa = $derived(() => {
console.log("🔍 [ChatWindow] Buscando conversa ID:", conversaId);
@@ -36,8 +40,8 @@
function getNomeConversa(): string {
const c = conversa();
if (!c) return "Carregando...";
if (c.tipo === "grupo") {
return c.nome || "Grupo sem nome";
if (c.tipo === "grupo" || c.tipo === "sala_reuniao") {
return c.nome || (c.tipo === "sala_reuniao" ? "Sala sem nome" : "Grupo sem nome");
}
return c.outroUsuario?.nome || "Usuário";
}
@@ -135,11 +139,64 @@
? "Externo"
: "Offline"}
</p>
{:else if conversa() && (conversa()?.tipo === "grupo" || conversa()?.tipo === "sala_reuniao")}
<div class="flex items-center gap-2 mt-1">
<p class="text-xs text-base-content/60">
{conversa()?.participantesInfo?.length || 0} {conversa()?.participantesInfo?.length === 1 ? "participante" : "participantes"}
</p>
{#if conversa()?.participantesInfo && conversa()?.participantesInfo.length > 0}
<div class="flex -space-x-2">
{#each conversa()?.participantesInfo.slice(0, 5) as participante (participante._id)}
<div class="relative w-5 h-5 rounded-full border-2 border-base-200 overflow-hidden bg-base-200" title={participante.nome}>
{#if participante.fotoPerfilUrl}
<img src={participante.fotoPerfilUrl} alt={participante.nome} class="w-full h-full object-cover" />
{:else if participante.avatar}
<img src={getAvatarUrl(participante.avatar)} alt={participante.nome} class="w-full h-full object-cover" />
{:else}
<img src={getAvatarUrl(participante.nome)} alt={participante.nome} class="w-full h-full object-cover" />
{/if}
</div>
{/each}
{#if conversa()?.participantesInfo.length > 5}
<div class="w-5 h-5 rounded-full border-2 border-base-200 bg-base-300 flex items-center justify-center text-[8px] font-semibold text-base-content/70" title={`+${conversa()?.participantesInfo.length - 5} mais`}>
+{conversa()?.participantesInfo.length - 5}
</div>
{/if}
</div>
{/if}
</div>
{/if}
</div>
<!-- Botões de ação -->
<div class="flex items-center gap-1">
<!-- Botão Gerenciar Sala (apenas para salas de reunião) -->
{#if conversa()?.tipo === "sala_reuniao"}
<button
type="button"
class="flex items-center justify-center w-9 h-9 rounded-lg transition-all duration-300 group relative overflow-hidden"
style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.2);"
onclick={() => (showSalaManager = true)}
aria-label="Gerenciar sala"
title="Gerenciar sala de reunião"
>
<div class="absolute inset-0 bg-blue-500/0 group-hover:bg-blue-500/10 transition-colors duration-300"></div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5 text-blue-500 relative z-10 group-hover:scale-110 transition-transform"
>
<circle cx="12" cy="12" r="3"/>
<path d="M12 1v6m0 6v6M5.64 5.64l4.24 4.24m4.24 4.24l4.24 4.24M1 12h6m6 0h6M5.64 18.36l4.24-4.24m4.24-4.24l4.24-4.24"/>
</svg>
</button>
{/if}
<!-- Botão Agendar MODERNO -->
<button
type="button"
@@ -186,3 +243,12 @@
/>
{/if}
<!-- Modal de Gerenciamento de Sala -->
{#if showSalaManager && conversa()?.tipo === "sala_reuniao"}
<SalaReuniaoManager
conversaId={conversaId as Id<"conversas">}
isAdmin={isAdmin?.data ?? false}
onClose={() => (showSalaManager = false)}
/>
{/if}