308 lines
10 KiB
Svelte
308 lines
10 KiB
Svelte
<script lang="ts">
|
|
import { useQuery, useConvexClient } from "convex-svelte";
|
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
|
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
|
import { format } from "date-fns";
|
|
import { ptBR } from "date-fns/locale";
|
|
|
|
interface Props {
|
|
conversaId: Id<"conversas">;
|
|
onClose: () => void;
|
|
}
|
|
|
|
let { conversaId, onClose }: Props = $props();
|
|
|
|
const client = useConvexClient();
|
|
const mensagensAgendadas = useQuery(api.chat.obterMensagensAgendadas, { conversaId });
|
|
|
|
let mensagem = $state("");
|
|
let data = $state("");
|
|
let hora = $state("");
|
|
let loading = $state(false);
|
|
|
|
// Definir data/hora mínima (agora)
|
|
const now = new Date();
|
|
const minDate = format(now, "yyyy-MM-dd");
|
|
const minTime = format(now, "HH:mm");
|
|
|
|
function getPreviewText(): string {
|
|
if (!data || !hora) return "";
|
|
|
|
try {
|
|
const dataHora = new Date(`${data}T${hora}`);
|
|
return `Será enviada em ${format(dataHora, "dd/MM/yyyy 'às' HH:mm", { locale: ptBR })}`;
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
async function handleAgendar() {
|
|
if (!mensagem.trim() || !data || !hora) {
|
|
alert("Preencha todos os campos");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
loading = true;
|
|
const dataHora = new Date(`${data}T${hora}`);
|
|
|
|
// Validar data futura
|
|
if (dataHora.getTime() <= Date.now()) {
|
|
alert("A data e hora devem ser futuras");
|
|
return;
|
|
}
|
|
|
|
await client.mutation(api.chat.agendarMensagem, {
|
|
conversaId,
|
|
conteudo: mensagem.trim(),
|
|
agendadaPara: dataHora.getTime(),
|
|
});
|
|
|
|
mensagem = "";
|
|
data = "";
|
|
hora = "";
|
|
alert("Mensagem agendada com sucesso!");
|
|
} catch (error) {
|
|
console.error("Erro ao agendar mensagem:", error);
|
|
alert("Erro ao agendar mensagem");
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
async function handleCancelar(mensagemId: string) {
|
|
if (!confirm("Deseja cancelar esta mensagem agendada?")) return;
|
|
|
|
try {
|
|
await client.mutation(api.chat.cancelarMensagemAgendada, { mensagemId: mensagemId as any });
|
|
} catch (error) {
|
|
console.error("Erro ao cancelar mensagem:", error);
|
|
alert("Erro ao cancelar mensagem");
|
|
}
|
|
}
|
|
|
|
function formatarDataHora(timestamp: number): string {
|
|
try {
|
|
return format(new Date(timestamp), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR });
|
|
} catch {
|
|
return "Data inválida";
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="fixed inset-0 z-[100] flex items-center justify-center bg-black/50" onclick={onClose}>
|
|
<div
|
|
class="bg-base-100 rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] flex flex-col m-4"
|
|
onclick={(e) => e.stopPropagation()}
|
|
>
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between px-6 py-4 border-b border-base-300">
|
|
<h2 class="text-xl font-semibold">Agendar Mensagem</h2>
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm btn-circle"
|
|
onclick={onClose}
|
|
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>
|
|
|
|
<!-- Content -->
|
|
<div class="flex-1 overflow-y-auto p-6 space-y-6">
|
|
<!-- Formulário de Agendamento -->
|
|
<div class="card bg-base-200">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">Nova Mensagem Agendada</h3>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Mensagem</span>
|
|
</label>
|
|
<textarea
|
|
class="textarea textarea-bordered h-24"
|
|
placeholder="Digite a mensagem..."
|
|
bind:value={mensagem}
|
|
maxlength="500"
|
|
></textarea>
|
|
<label class="label">
|
|
<span class="label-text-alt">{mensagem.length}/500</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid md:grid-cols-2 gap-4">
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Data</span>
|
|
</label>
|
|
<input
|
|
type="date"
|
|
class="input input-bordered"
|
|
bind:value={data}
|
|
min={minDate}
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Hora</span>
|
|
</label>
|
|
<input
|
|
type="time"
|
|
class="input input-bordered"
|
|
bind:value={hora}
|
|
min={data === minDate ? minTime : undefined}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{#if getPreviewText()}
|
|
<div class="alert alert-info">
|
|
<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="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
|
/>
|
|
</svg>
|
|
<span>{getPreviewText()}</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="card-actions justify-end">
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary"
|
|
onclick={handleAgendar}
|
|
disabled={loading || !mensagem.trim() || !data || !hora}
|
|
>
|
|
{#if loading}
|
|
<span class="loading loading-spinner"></span>
|
|
Agendando...
|
|
{:else}
|
|
<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="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
|
/>
|
|
</svg>
|
|
Agendar
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lista de Mensagens Agendadas -->
|
|
<div class="card bg-base-200">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">Mensagens Agendadas</h3>
|
|
|
|
{#if mensagensAgendadas && mensagensAgendadas.length > 0}
|
|
<div class="space-y-3">
|
|
{#each mensagensAgendadas as msg (msg._id)}
|
|
<div class="flex items-start gap-3 p-3 bg-base-100 rounded-lg">
|
|
<div class="flex-shrink-0 mt-1">
|
|
<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 text-primary"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-medium text-base-content/80">
|
|
{formatarDataHora(msg.agendadaPara || 0)}
|
|
</p>
|
|
<p class="text-sm text-base-content mt-1 line-clamp-2">
|
|
{msg.conteudo}
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm btn-circle text-error"
|
|
onclick={() => handleCancelar(msg._id)}
|
|
aria-label="Cancelar"
|
|
>
|
|
<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="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{:else if !mensagensAgendadas}
|
|
<div class="flex items-center justify-center py-8">
|
|
<span class="loading loading-spinner loading-lg"></span>
|
|
</div>
|
|
{:else}
|
|
<div class="text-center py-8 text-base-content/50">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke-width="1.5"
|
|
stroke="currentColor"
|
|
class="w-12 h-12 mx-auto mb-2 opacity-50"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
|
/>
|
|
</svg>
|
|
<p class="text-sm">Nenhuma mensagem agendada</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|