refactor: integrate current user data across components
- Replaced instances of `authStore` with `currentUser` to streamline user authentication handling. - Updated permission checks and user-related data retrieval to utilize the new `useQuery` for better performance and clarity. - Cleaned up component structures and improved formatting for consistency and readability. - Enhanced error handling and user feedback mechanisms in various components to improve user experience.
This commit is contained in:
@@ -10,38 +10,51 @@
|
||||
import ScheduleMessageModal from "./ScheduleMessageModal.svelte";
|
||||
import SalaReuniaoManager from "./SalaReuniaoManager.svelte";
|
||||
import { getAvatarUrl } from "$lib/utils/avatarGenerator";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
import { setupConvexAuth } from "$lib/hooks/convexAuth";
|
||||
import { Bell, X, ArrowLeft, LogOut, MoreVertical, Users, Clock, XCircle } from "lucide-svelte";
|
||||
import {
|
||||
Bell,
|
||||
X,
|
||||
ArrowLeft,
|
||||
LogOut,
|
||||
MoreVertical,
|
||||
Users,
|
||||
Clock,
|
||||
XCircle,
|
||||
} from "lucide-svelte";
|
||||
|
||||
interface Props {
|
||||
conversaId: string;
|
||||
}
|
||||
|
||||
let { conversaId }: Props = $props();
|
||||
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
|
||||
// Token é passado automaticamente via interceptadores em +layout.svelte
|
||||
|
||||
|
||||
let showScheduleModal = $state(false);
|
||||
let showSalaManager = $state(false);
|
||||
let showAdminMenu = $state(false);
|
||||
let showNotificacaoModal = $state(false);
|
||||
|
||||
const conversas = useQuery(api.chat.listarConversas, {});
|
||||
const isAdmin = useQuery(api.chat.verificarSeEhAdmin, { conversaId: conversaId as Id<"conversas"> });
|
||||
const isAdmin = useQuery(api.chat.verificarSeEhAdmin, {
|
||||
conversaId: conversaId as Id<"conversas">,
|
||||
});
|
||||
|
||||
const conversa = $derived(() => {
|
||||
console.log("🔍 [ChatWindow] Buscando conversa ID:", conversaId);
|
||||
console.log("📋 [ChatWindow] Conversas disponíveis:", conversas?.data);
|
||||
|
||||
|
||||
if (!conversas?.data || !Array.isArray(conversas.data)) {
|
||||
console.log("⚠️ [ChatWindow] conversas.data não é um array ou está vazio");
|
||||
console.log(
|
||||
"⚠️ [ChatWindow] conversas.data não é um array ou está vazio",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const encontrada = conversas.data.find((c: { _id: string }) => c._id === conversaId);
|
||||
|
||||
const encontrada = conversas.data.find(
|
||||
(c: { _id: string }) => c._id === conversaId,
|
||||
);
|
||||
console.log("✅ [ChatWindow] Conversa encontrada:", encontrada);
|
||||
return encontrada;
|
||||
});
|
||||
@@ -50,7 +63,10 @@
|
||||
const c = conversa();
|
||||
if (!c) return "Carregando...";
|
||||
if (c.tipo === "grupo" || c.tipo === "sala_reuniao") {
|
||||
return c.nome || (c.tipo === "sala_reuniao" ? "Sala sem nome" : "Grupo sem nome");
|
||||
return (
|
||||
c.nome ||
|
||||
(c.tipo === "sala_reuniao" ? "Sala sem nome" : "Grupo sem nome")
|
||||
);
|
||||
}
|
||||
return c.outroUsuario?.nome || "Usuário";
|
||||
}
|
||||
@@ -67,10 +83,23 @@
|
||||
return "👤";
|
||||
}
|
||||
|
||||
function getStatusConversa(): "online" | "offline" | "ausente" | "externo" | "em_reuniao" | null {
|
||||
function getStatusConversa():
|
||||
| "online"
|
||||
| "offline"
|
||||
| "ausente"
|
||||
| "externo"
|
||||
| "em_reuniao"
|
||||
| null {
|
||||
const c = conversa();
|
||||
if (c && c.tipo === "individual" && c.outroUsuario) {
|
||||
return (c.outroUsuario.statusPresenca as "online" | "offline" | "ausente" | "externo" | "em_reuniao") || "offline";
|
||||
return (
|
||||
(c.outroUsuario.statusPresenca as
|
||||
| "online"
|
||||
| "offline"
|
||||
| "ausente"
|
||||
| "externo"
|
||||
| "em_reuniao") || "offline"
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -86,9 +115,13 @@
|
||||
async function handleSairGrupoOuSala() {
|
||||
const c = conversa();
|
||||
if (!c || (c.tipo !== "grupo" && c.tipo !== "sala_reuniao")) return;
|
||||
|
||||
|
||||
const tipoTexto = c.tipo === "sala_reuniao" ? "sala de reunião" : "grupo";
|
||||
if (!confirm(`Tem certeza que deseja sair da ${tipoTexto} "${c.nome || "Sem nome"}"?`)) {
|
||||
if (
|
||||
!confirm(
|
||||
`Tem certeza que deseja sair da ${tipoTexto} "${c.nome || "Sem nome"}"?`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,7 +137,8 @@
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao sair da conversa:", error);
|
||||
const errorMessage = error instanceof Error ? error.message : "Erro ao sair da conversa";
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Erro ao sair da conversa";
|
||||
alert(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -112,7 +146,10 @@
|
||||
|
||||
<div class="flex flex-col h-full" onclick={() => (showAdminMenu = false)}>
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-3 px-4 py-3 border-b border-base-300 bg-base-200" onclick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
class="flex items-center gap-3 px-4 py-3 border-b border-base-300 bg-base-200"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<!-- Botão Voltar -->
|
||||
<button
|
||||
type="button"
|
||||
@@ -121,14 +158,11 @@
|
||||
aria-label="Voltar"
|
||||
title="Voltar para lista de conversas"
|
||||
>
|
||||
<ArrowLeft
|
||||
class="w-6 h-6 text-primary"
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
<ArrowLeft class="w-6 h-6 text-primary" strokeWidth={2.5} />
|
||||
</button>
|
||||
|
||||
<!-- Avatar e Info -->
|
||||
<div class="relative flex-shrink-0">
|
||||
<div class="relative shrink-0">
|
||||
{#if conversa() && conversa()?.tipo === "individual" && conversa()?.outroUsuario}
|
||||
<UserAvatar
|
||||
avatar={conversa()?.outroUsuario?.avatar}
|
||||
@@ -151,9 +185,13 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-semibold text-base-content truncate">{getNomeConversa()}</p>
|
||||
<p class="font-semibold text-base-content truncate">
|
||||
{getNomeConversa()}
|
||||
</p>
|
||||
{#if getStatusMensagem()}
|
||||
<p class="text-xs text-base-content/60 truncate">{getStatusMensagem()}</p>
|
||||
<p class="text-xs text-base-content/60 truncate">
|
||||
{getStatusMensagem()}
|
||||
</p>
|
||||
{:else if getStatusConversa()}
|
||||
<p class="text-xs text-base-content/60">
|
||||
{getStatusConversa() === "online"
|
||||
@@ -169,30 +207,54 @@
|
||||
{: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"}
|
||||
{conversa()?.participantesInfo?.length || 0}
|
||||
{conversa()?.participantesInfo?.length === 1
|
||||
? "participante"
|
||||
: "participantes"}
|
||||
</p>
|
||||
{#if conversa()?.participantesInfo && conversa()?.participantesInfo.length > 0}
|
||||
<div class="flex items-center gap-2">
|
||||
<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}>
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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`}>
|
||||
<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 conversa()?.tipo === "sala_reuniao" && isAdmin?.data}
|
||||
<span class="text-[10px] text-primary font-semibold ml-1 whitespace-nowrap" title="Você é administrador desta sala">• Admin</span>
|
||||
<span
|
||||
class="text-[10px] text-primary font-semibold ml-1 whitespace-nowrap"
|
||||
title="Você é administrador desta sala">• Admin</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -215,7 +277,9 @@
|
||||
aria-label="Sair"
|
||||
title="Sair da conversa"
|
||||
>
|
||||
<div class="absolute inset-0 bg-red-500/0 group-hover:bg-red-500/10 transition-colors duration-300"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-red-500/0 group-hover:bg-red-500/10 transition-colors duration-300"
|
||||
></div>
|
||||
<LogOut
|
||||
class="w-5 h-5 text-red-500 relative z-10 group-hover:scale-110 transition-transform"
|
||||
strokeWidth={2}
|
||||
@@ -237,7 +301,9 @@
|
||||
aria-label="Menu administrativo"
|
||||
title="Recursos administrativos"
|
||||
>
|
||||
<div class="absolute inset-0 bg-blue-500/0 group-hover:bg-blue-500/10 transition-colors duration-300"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-blue-500/0 group-hover:bg-blue-500/10 transition-colors duration-300"
|
||||
></div>
|
||||
<MoreVertical
|
||||
class="w-5 h-5 text-blue-500 relative z-10 group-hover:scale-110 transition-transform"
|
||||
strokeWidth={2}
|
||||
@@ -283,11 +349,19 @@
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
(async () => {
|
||||
if (!confirm("Tem certeza que deseja encerrar esta reunião? Todos os participantes serão removidos.")) return;
|
||||
if (
|
||||
!confirm(
|
||||
"Tem certeza que deseja encerrar esta reunião? Todos os participantes serão removidos.",
|
||||
)
|
||||
)
|
||||
return;
|
||||
try {
|
||||
const resultado = await client.mutation(api.chat.encerrarReuniao, {
|
||||
conversaId: conversaId as Id<"conversas">,
|
||||
});
|
||||
const resultado = await client.mutation(
|
||||
api.chat.encerrarReuniao,
|
||||
{
|
||||
conversaId: conversaId as Id<"conversas">,
|
||||
},
|
||||
);
|
||||
if (resultado.sucesso) {
|
||||
alert("Reunião encerrada com sucesso!");
|
||||
voltarParaLista();
|
||||
@@ -295,7 +369,10 @@
|
||||
alert(resultado.erro || "Erro ao encerrar reunião");
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Erro ao encerrar reunião";
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Erro ao encerrar reunião";
|
||||
alert(errorMessage);
|
||||
}
|
||||
showAdminMenu = false;
|
||||
@@ -310,7 +387,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<!-- Botão Agendar MODERNO -->
|
||||
<button
|
||||
type="button"
|
||||
@@ -320,7 +397,9 @@
|
||||
aria-label="Agendar mensagem"
|
||||
title="Agendar mensagem"
|
||||
>
|
||||
<div class="absolute inset-0 bg-purple-500/0 group-hover:bg-purple-500/10 transition-colors duration-300"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-purple-500/0 group-hover:bg-purple-500/10 transition-colors duration-300"
|
||||
></div>
|
||||
<Clock
|
||||
class="w-5 h-5 text-purple-500 relative z-10 group-hover:scale-110 transition-transform"
|
||||
strokeWidth={2}
|
||||
@@ -335,7 +414,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Input -->
|
||||
<div class="border-t border-base-300 flex-shrink-0">
|
||||
<div class="border-t border-base-300 shrink-0">
|
||||
<MessageInput conversaId={conversaId as Id<"conversas">} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -359,9 +438,15 @@
|
||||
|
||||
<!-- Modal de Enviar Notificação -->
|
||||
{#if showNotificacaoModal && conversa()?.tipo === "sala_reuniao" && isAdmin?.data}
|
||||
<dialog class="modal modal-open" onclick={(e) => e.target === e.currentTarget && (showNotificacaoModal = false)}>
|
||||
<dialog
|
||||
class="modal modal-open"
|
||||
onclick={(e) =>
|
||||
e.target === e.currentTarget && (showNotificacaoModal = false)}
|
||||
>
|
||||
<div class="modal-box max-w-md" onclick={(e) => e.stopPropagation()}>
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-base-300">
|
||||
<div
|
||||
class="flex items-center justify-between px-6 py-4 border-b border-base-300"
|
||||
>
|
||||
<h2 class="text-xl font-semibold flex items-center gap-2">
|
||||
<Bell class="w-5 h-5 text-primary" />
|
||||
Enviar Notificação
|
||||
@@ -381,18 +466,21 @@
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const titulo = formData.get("titulo") as string;
|
||||
const mensagem = formData.get("mensagem") as string;
|
||||
|
||||
|
||||
if (!titulo.trim() || !mensagem.trim()) {
|
||||
alert("Preencha todos os campos");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resultado = await client.mutation(api.chat.enviarNotificacaoReuniao, {
|
||||
conversaId: conversaId as Id<"conversas">,
|
||||
titulo: titulo.trim(),
|
||||
mensagem: mensagem.trim(),
|
||||
});
|
||||
const resultado = await client.mutation(
|
||||
api.chat.enviarNotificacaoReuniao,
|
||||
{
|
||||
conversaId: conversaId as Id<"conversas">,
|
||||
titulo: titulo.trim(),
|
||||
mensagem: mensagem.trim(),
|
||||
},
|
||||
);
|
||||
|
||||
if (resultado.sucesso) {
|
||||
alert("Notificação enviada com sucesso!");
|
||||
@@ -401,7 +489,10 @@
|
||||
alert(resultado.erro || "Erro ao enviar notificação");
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Erro ao enviar notificação";
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Erro ao enviar notificação";
|
||||
alert(errorMessage);
|
||||
}
|
||||
}}
|
||||
@@ -431,7 +522,11 @@
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button type="button" class="btn btn-ghost flex-1" onclick={() => (showNotificacaoModal = false)}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost flex-1"
|
||||
onclick={() => (showNotificacaoModal = false)}
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary flex-1">
|
||||
@@ -442,8 +537,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button type="button" onclick={() => (showNotificacaoModal = false)}>fechar</button>
|
||||
<button type="button" onclick={() => (showNotificacaoModal = false)}
|
||||
>fechar</button
|
||||
>
|
||||
</form>
|
||||
</dialog>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user