feat: implement comprehensive chat system with user presence management, notification handling, and avatar integration; enhance UI components for improved user experience
This commit is contained in:
190
apps/web/src/lib/components/chat/ChatWidget.svelte
Normal file
190
apps/web/src/lib/components/chat/ChatWidget.svelte
Normal file
@@ -0,0 +1,190 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
chatAberto,
|
||||
chatMinimizado,
|
||||
conversaAtiva,
|
||||
fecharChat,
|
||||
minimizarChat,
|
||||
maximizarChat,
|
||||
abrirChat,
|
||||
} from "$lib/stores/chatStore";
|
||||
import { useQuery } from "convex-svelte";
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import ChatList from "./ChatList.svelte";
|
||||
import ChatWindow from "./ChatWindow.svelte";
|
||||
|
||||
const count = useQuery(api.chat.contarNotificacoesNaoLidas, {});
|
||||
|
||||
let isOpen = $state(false);
|
||||
let isMinimized = $state(false);
|
||||
let activeConversation = $state<string | null>(null);
|
||||
|
||||
// Sincronizar com stores
|
||||
$effect(() => {
|
||||
isOpen = $chatAberto;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
isMinimized = $chatMinimizado;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
activeConversation = $conversaAtiva;
|
||||
});
|
||||
|
||||
function handleToggle() {
|
||||
if (isOpen && !isMinimized) {
|
||||
minimizarChat();
|
||||
} else {
|
||||
abrirChat();
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
fecharChat();
|
||||
}
|
||||
|
||||
function handleMinimize() {
|
||||
minimizarChat();
|
||||
}
|
||||
|
||||
function handleMaximize() {
|
||||
maximizarChat();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Botão flutuante (quando fechado ou minimizado) -->
|
||||
{#if !isOpen || isMinimized}
|
||||
<button
|
||||
type="button"
|
||||
class="fixed bottom-6 right-6 btn btn-circle btn-primary btn-lg shadow-2xl z-50 hover:scale-110 transition-transform"
|
||||
onclick={handleToggle}
|
||||
aria-label="Abrir chat"
|
||||
>
|
||||
<!-- Ícone de chat -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-7 h-7"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Badge de contador -->
|
||||
{#if count && count > 0}
|
||||
<span
|
||||
class="absolute -top-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-error text-error-content text-xs font-bold"
|
||||
>
|
||||
{count > 9 ? "9+" : count}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Janela do Chat -->
|
||||
{#if isOpen && !isMinimized}
|
||||
<div
|
||||
class="fixed bottom-6 right-6 z-50 flex flex-col bg-base-100 rounded-2xl shadow-2xl border border-base-300 overflow-hidden
|
||||
w-[400px] h-[600px] max-w-[calc(100vw-3rem)] max-h-[calc(100vh-3rem)]
|
||||
md:w-[400px] md:h-[600px]
|
||||
sm:w-full sm:h-full sm:bottom-0 sm:right-0 sm:rounded-none sm:max-w-full sm:max-h-full"
|
||||
style="animation: slideIn 0.3s ease-out;"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex items-center justify-between px-4 py-3 bg-primary text-primary-content border-b border-primary-focus"
|
||||
>
|
||||
<h2 class="text-lg font-semibold flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
Chat
|
||||
</h2>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<!-- Botão minimizar -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-circle"
|
||||
onclick={handleMinimize}
|
||||
aria-label="Minimizar"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Botão fechar -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-circle"
|
||||
onclick={handleClose}
|
||||
aria-label="Fechar"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6 18 18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conteúdo -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
{#if !activeConversation}
|
||||
<ChatList />
|
||||
{:else}
|
||||
<ChatWindow conversaId={activeConversation} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user