Files
sgse-app/apps/web/src/lib/components/chat/ScheduleMessageModal.svelte
killer-cf 11eef4aa2a refactor: improve Svelte components and enhance user experience
- Updated various Svelte components to improve code readability and maintainability.
- Standardized button classes across components for a consistent user interface.
- Enhanced error handling and user feedback in modals and forms.
- Cleaned up unnecessary imports and optimized component structure for better performance.
2025-11-12 16:36:29 -03:00

270 lines
7.9 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';
import { Clock, X, Trash2 } from 'lucide-svelte';
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);
// Rastrear mudanças nas mensagens agendadas
$effect(() => {
console.log('📅 [ScheduleModal] Mensagens agendadas atualizadas:', mensagensAgendadas?.data);
});
// 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 = '';
// Dar tempo para o Convex processar e recarregar a lista
setTimeout(() => {
alert('Mensagem agendada com sucesso!');
}, 500);
} 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>
<dialog class="modal modal-open" onclick={(e) => e.target === e.currentTarget && onClose()}>
<div
class="modal-box flex max-h-[90vh] max-w-2xl flex-col p-0"
onclick={(e) => e.stopPropagation()}
>
<!-- Header -->
<div class="border-base-300 flex items-center justify-between border-b px-6 py-4">
<h2 id="modal-title" class="flex items-center gap-2 text-xl font-bold">
<Clock class="text-primary h-5 w-5" />
Agendar Mensagem
</h2>
<button type="button" class="btn btn-sm btn-circle" onclick={onClose} aria-label="Fechar">
<X class="h-5 w-5" />
</button>
</div>
<!-- Content -->
<div class="flex-1 space-y-6 overflow-y-auto p-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" for="mensagem-input">
<span class="label-text">Mensagem</span>
</label>
<textarea
id="mensagem-input"
class="textarea textarea-bordered h-24"
placeholder="Digite a mensagem..."
bind:value={mensagem}
maxlength="500"
aria-describedby="char-count"
></textarea>
<div class="label">
<span id="char-count" class="label-text-alt">{mensagem.length}/500</span>
</div>
</div>
<div class="grid gap-4 md:grid-cols-2">
<div class="form-control">
<label class="label" for="data-input">
<span class="label-text">Data</span>
</label>
<input
id="data-input"
type="date"
class="input input-bordered"
bind:value={data}
min={minDate}
/>
</div>
<div class="form-control">
<label class="label" for="hora-input">
<span class="label-text">Hora</span>
</label>
<input
id="hora-input"
type="time"
class="input input-bordered"
bind:value={hora}
min={data === minDate ? minTime : undefined}
/>
</div>
</div>
{#if getPreviewText()}
<div class="alert alert-info">
<Clock class="h-6 w-6" />
<span>{getPreviewText()}</span>
</div>
{/if}
<div class="card-actions justify-end">
<!-- Botão AGENDAR ultra moderno -->
<button
type="button"
class="group relative overflow-hidden rounded-xl px-6 py-3 font-bold text-white transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-50"
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 8px 24px -4px rgba(102, 126, 234, 0.4);"
onclick={handleAgendar}
disabled={loading || !mensagem.trim() || !data || !hora}
>
<!-- Efeito de brilho no hover -->
<div
class="absolute inset-0 bg-white/0 transition-colors duration-300 group-hover:bg-white/10"
></div>
<div class="relative z-10 flex items-center gap-2">
{#if loading}
<span class="loading loading-spinner loading-sm"></span>
<span>Agendando...</span>
{:else}
<Clock class="h-5 w-5 transition-transform group-hover:scale-110" />
<span class="transition-transform group-hover:scale-105">Agendar</span>
{/if}
</div>
</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?.data && mensagensAgendadas.data.length > 0}
<div class="space-y-3">
{#each mensagensAgendadas.data as msg (msg._id)}
<div class="bg-base-100 flex items-start gap-3 rounded-lg p-3">
<div class="mt-1 shrink-0">
<Clock class="text-primary h-5 w-5" />
</div>
<div class="min-w-0 flex-1">
<p class="text-base-content/80 text-sm font-medium">
{formatarDataHora(msg.agendadaPara || 0)}
</p>
<p class="text-base-content mt-1 line-clamp-2 text-sm">
{msg.conteudo}
</p>
</div>
<!-- Botão cancelar moderno -->
<button
type="button"
class="group relative flex h-9 w-9 items-center justify-center overflow-hidden rounded-lg transition-all duration-300"
style="background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.2);"
onclick={() => handleCancelar(msg._id)}
aria-label="Cancelar"
>
<div
class="bg-error/0 group-hover:bg-error/20 absolute inset-0 transition-colors duration-300"
></div>
<Trash2
class="text-error relative z-10 h-5 w-5 transition-transform group-hover:scale-110"
/>
</button>
</div>
{/each}
</div>
{:else if !mensagensAgendadas?.data}
<div class="flex items-center justify-center py-8">
<span class="loading loading-spinner loading-lg"></span>
</div>
{:else}
<div class="text-base-content/50 py-8 text-center">
<Clock class="mx-auto mb-2 h-12 w-12 opacity-50" />
<p class="text-sm">Nenhuma mensagem agendada</p>
</div>
{/if}
</div>
</div>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button type="button" onclick={onClose}>fechar</button>
</form>
</dialog>