287 lines
9.2 KiB
Svelte
287 lines
9.2 KiB
Svelte
<script lang="ts">
|
|
import { useConvexClient } from "convex-svelte";
|
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
|
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
|
import { onMount } from "svelte";
|
|
|
|
interface Props {
|
|
conversaId: Id<"conversas">;
|
|
}
|
|
|
|
let { conversaId }: Props = $props();
|
|
|
|
const client = useConvexClient();
|
|
|
|
let mensagem = $state("");
|
|
let textarea: HTMLTextAreaElement;
|
|
let enviando = $state(false);
|
|
let uploadingFile = $state(false);
|
|
let digitacaoTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
let showEmojiPicker = $state(false);
|
|
|
|
// Emojis mais usados
|
|
const emojis = [
|
|
"😀", "😃", "😄", "😁", "😅", "😂", "🤣", "😊", "😇", "🙂",
|
|
"🙃", "😉", "😌", "😍", "🥰", "😘", "😗", "😙", "😚", "😋",
|
|
"😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓", "😎", "🥳", "😏",
|
|
"👍", "👎", "👏", "🙌", "🤝", "🙏", "💪", "✨", "🎉", "🎊",
|
|
"❤️", "💙", "💚", "💛", "🧡", "💜", "🖤", "🤍", "💯", "🔥",
|
|
];
|
|
|
|
function adicionarEmoji(emoji: string) {
|
|
mensagem += emoji;
|
|
showEmojiPicker = false;
|
|
if (textarea) {
|
|
textarea.focus();
|
|
}
|
|
}
|
|
|
|
// Auto-resize do textarea
|
|
function handleInput() {
|
|
if (textarea) {
|
|
textarea.style.height = "auto";
|
|
textarea.style.height = Math.min(textarea.scrollHeight, 120) + "px";
|
|
}
|
|
|
|
// Indicador de digitação (debounce de 1s)
|
|
if (digitacaoTimeout) {
|
|
clearTimeout(digitacaoTimeout);
|
|
}
|
|
digitacaoTimeout = setTimeout(() => {
|
|
if (mensagem.trim()) {
|
|
client.mutation(api.chat.indicarDigitacao, { conversaId });
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
async function handleEnviar() {
|
|
const texto = mensagem.trim();
|
|
if (!texto || enviando) return;
|
|
|
|
console.log("📤 [MessageInput] Enviando mensagem:", {
|
|
conversaId,
|
|
conteudo: texto,
|
|
tipo: "texto",
|
|
});
|
|
|
|
try {
|
|
enviando = true;
|
|
const result = await client.mutation(api.chat.enviarMensagem, {
|
|
conversaId,
|
|
conteudo: texto,
|
|
tipo: "texto",
|
|
});
|
|
|
|
console.log("✅ [MessageInput] Mensagem enviada com sucesso! ID:", result);
|
|
|
|
mensagem = "";
|
|
if (textarea) {
|
|
textarea.style.height = "auto";
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ [MessageInput] Erro ao enviar mensagem:", error);
|
|
alert("Erro ao enviar mensagem");
|
|
} finally {
|
|
enviando = false;
|
|
}
|
|
}
|
|
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
// Enter sem Shift = enviar
|
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleEnviar();
|
|
}
|
|
}
|
|
|
|
async function handleFileUpload(e: Event) {
|
|
const input = e.target as HTMLInputElement;
|
|
const file = input.files?.[0];
|
|
if (!file) return;
|
|
|
|
// Validar tamanho (max 10MB)
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
alert("Arquivo muito grande. O tamanho máximo é 10MB.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
uploadingFile = true;
|
|
|
|
// 1. Obter upload URL
|
|
const uploadUrl = await client.mutation(api.chat.uploadArquivoChat, { conversaId });
|
|
|
|
// 2. Upload do arquivo
|
|
const result = await fetch(uploadUrl, {
|
|
method: "POST",
|
|
headers: { "Content-Type": file.type },
|
|
body: file,
|
|
});
|
|
|
|
if (!result.ok) {
|
|
throw new Error("Falha no upload");
|
|
}
|
|
|
|
const { storageId } = await result.json();
|
|
|
|
// 3. Enviar mensagem com o arquivo
|
|
const tipo = file.type.startsWith("image/") ? "imagem" : "arquivo";
|
|
await client.mutation(api.chat.enviarMensagem, {
|
|
conversaId,
|
|
conteudo: tipo === "imagem" ? "" : file.name,
|
|
tipo: tipo as any,
|
|
arquivoId: storageId,
|
|
arquivoNome: file.name,
|
|
arquivoTamanho: file.size,
|
|
arquivoTipo: file.type,
|
|
});
|
|
|
|
// Limpar input
|
|
input.value = "";
|
|
} catch (error) {
|
|
console.error("Erro ao fazer upload:", error);
|
|
alert("Erro ao enviar arquivo");
|
|
} finally {
|
|
uploadingFile = false;
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
if (textarea) {
|
|
textarea.focus();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<div class="p-4">
|
|
<div class="flex items-end gap-2">
|
|
<!-- Botão de anexar arquivo MODERNO -->
|
|
<label
|
|
class="flex items-center justify-center w-10 h-10 rounded-xl transition-all duration-300 group relative overflow-hidden cursor-pointer flex-shrink-0"
|
|
style="background: rgba(102, 126, 234, 0.1); border: 1px solid rgba(102, 126, 234, 0.2);"
|
|
title="Anexar arquivo"
|
|
>
|
|
<input
|
|
type="file"
|
|
class="hidden"
|
|
onchange={handleFileUpload}
|
|
disabled={uploadingFile || enviando}
|
|
accept="*/*"
|
|
/>
|
|
<div class="absolute inset-0 bg-primary/0 group-hover:bg-primary/10 transition-colors duration-300"></div>
|
|
{#if uploadingFile}
|
|
<span class="loading loading-spinner loading-sm relative z-10"></span>
|
|
{:else}
|
|
<!-- Ícone de clipe moderno -->
|
|
<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-primary relative z-10 group-hover:scale-110 transition-transform"
|
|
>
|
|
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/>
|
|
</svg>
|
|
{/if}
|
|
</label>
|
|
|
|
<!-- Botão de EMOJI MODERNO -->
|
|
<div class="relative flex-shrink-0">
|
|
<button
|
|
type="button"
|
|
class="flex items-center justify-center w-10 h-10 rounded-xl transition-all duration-300 group relative overflow-hidden"
|
|
style="background: rgba(255, 193, 7, 0.1); border: 1px solid rgba(255, 193, 7, 0.2);"
|
|
onclick={() => (showEmojiPicker = !showEmojiPicker)}
|
|
disabled={enviando || uploadingFile}
|
|
aria-label="Adicionar emoji"
|
|
title="Adicionar emoji"
|
|
>
|
|
<div class="absolute inset-0 bg-warning/0 group-hover:bg-warning/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-warning relative z-10 group-hover:scale-110 transition-transform"
|
|
>
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<path d="M8 14s1.5 2 4 2 4-2 4-2"/>
|
|
<line x1="9" y1="9" x2="9.01" y2="9"/>
|
|
<line x1="15" y1="9" x2="15.01" y2="9"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Picker de Emojis -->
|
|
{#if showEmojiPicker}
|
|
<div
|
|
class="absolute bottom-full left-0 mb-2 p-3 bg-base-100 rounded-xl shadow-2xl border border-base-300 z-50"
|
|
style="width: 280px; max-height: 200px; overflow-y-auto;"
|
|
>
|
|
<div class="grid grid-cols-10 gap-1">
|
|
{#each emojis as emoji}
|
|
<button
|
|
type="button"
|
|
class="text-2xl hover:scale-125 transition-transform cursor-pointer p-1 hover:bg-base-200 rounded"
|
|
onclick={() => adicionarEmoji(emoji)}
|
|
>
|
|
{emoji}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Textarea -->
|
|
<div class="flex-1 relative">
|
|
<textarea
|
|
bind:this={textarea}
|
|
bind:value={mensagem}
|
|
oninput={handleInput}
|
|
onkeydown={handleKeyDown}
|
|
placeholder="Digite uma mensagem..."
|
|
class="textarea textarea-bordered w-full resize-none min-h-[44px] max-h-[120px] pr-10"
|
|
rows="1"
|
|
disabled={enviando || uploadingFile}
|
|
></textarea>
|
|
</div>
|
|
|
|
<!-- Botão de enviar MODERNO -->
|
|
<button
|
|
type="button"
|
|
class="flex items-center justify-center w-12 h-12 rounded-xl transition-all duration-300 group relative overflow-hidden flex-shrink-0 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 8px 24px -4px rgba(102, 126, 234, 0.4);"
|
|
onclick={handleEnviar}
|
|
disabled={!mensagem.trim() || enviando || uploadingFile}
|
|
aria-label="Enviar"
|
|
>
|
|
<div class="absolute inset-0 bg-white/0 group-hover:bg-white/10 transition-colors duration-300"></div>
|
|
{#if enviando}
|
|
<span class="loading loading-spinner loading-sm relative z-10 text-white"></span>
|
|
{:else}
|
|
<!-- Ícone de avião de papel moderno -->
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24"
|
|
fill="currentColor"
|
|
class="w-5 h-5 text-white relative z-10 group-hover:scale-110 group-hover:translate-x-1 transition-all"
|
|
>
|
|
<path d="M3.478 2.405a.75.75 0 00-.926.94l2.432 7.905H13.5a.75.75 0 010 1.5H4.984l-2.432 7.905a.75.75 0 00.926.94 60.519 60.519 0 0018.445-8.986.75.75 0 000-1.218A60.517 60.517 0 003.478 2.405z"/>
|
|
</svg>
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Informação sobre atalhos -->
|
|
<p class="text-xs text-base-content/50 mt-2 text-center">
|
|
💡 Enter para enviar • Shift+Enter para quebrar linha • 😊 Clique no emoji
|
|
</p>
|
|
</div>
|
|
|