Initialize fresh project without Clerk
This commit is contained in:
131
src/client/components/ArchivedNotificationsModal.tsx
Normal file
131
src/client/components/ArchivedNotificationsModal.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Archive, Trash2, RefreshCcw } from 'lucide-react';
|
||||
import api from '../services/api';
|
||||
import type { INotification } from '../types';
|
||||
|
||||
interface ArchivedNotificationsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const ArchivedNotificationsModal: React.FC<ArchivedNotificationsModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
const [notifications, setNotifications] = useState<INotification[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const fetchArchived = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await api.get<INotification[]>('/notifications?includeArchived=true');
|
||||
// Filtrar apenas as arquivadas (no frontend por segurança, embora o backend já devesse ajudar)
|
||||
// Na verdade, passamos includeArchived=true, o backend retornará unread + archived.
|
||||
// Vamos filtrar para mostrar apenas o "Log" (arquivadas).
|
||||
setNotifications(response.data.filter(n => n.isArchived || n.archivedBy?.length > 0));
|
||||
} catch (error) {
|
||||
console.error('Error fetching archived notifications:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
fetchArchived();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const deleteForever = async (id: string) => {
|
||||
if (!window.confirm('Excluir permanentemente este registro do log?')) return;
|
||||
try {
|
||||
await api.delete(`/notifications/${id}`);
|
||||
setNotifications(prev => prev.filter(n => n._id !== id));
|
||||
} catch (error) {
|
||||
console.error('Error deleting archived notification:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[60] flex items-center justify-center p-4 animate-in fade-in">
|
||||
<div className="bg-zinc-900 rounded-2xl shadow-2xl max-w-2xl w-full border border-zinc-800 animate-in slide-in-from-bottom-4 max-h-[80vh] flex flex-col overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-zinc-800 bg-zinc-900">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-zinc-800 flex items-center justify-center">
|
||||
<Archive className="text-zinc-400" size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-white">Log de Mensagens (Arquivadas)</h2>
|
||||
<p className="text-sm text-zinc-500">
|
||||
Histórico de notificações sistema
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 hover:bg-zinc-800 rounded-lg transition-colors"
|
||||
>
|
||||
<X size={20} className="text-zinc-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-y-auto p-6 space-y-4 bg-zinc-950/50">
|
||||
{isLoading ? (
|
||||
<div className="text-center py-8">
|
||||
<RefreshCcw className="animate-spin text-primary mx-auto mb-2" size={24} />
|
||||
<p className="text-zinc-500">Carregando histórico...</p>
|
||||
</div>
|
||||
) : notifications.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<Archive size={48} className="text-zinc-800 mx-auto mb-4" />
|
||||
<p className="text-zinc-500 font-semibold">Nenhuma mensagem arquivada</p>
|
||||
<p className="text-zinc-600 text-sm mt-1">
|
||||
Mensagens arquivadas aparecerão aqui para consulta.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{notifications.map((msg) => (
|
||||
<div
|
||||
key={msg._id}
|
||||
className="bg-zinc-900 border border-zinc-800 rounded-xl p-4 hover:border-zinc-700 transition-all group"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold text-zinc-200 text-sm">{msg.title}</span>
|
||||
<span className="text-[10px] text-zinc-500">
|
||||
{new Date(msg.createdAt).toLocaleString('pt-BR')}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => deleteForever(msg._id)}
|
||||
className="opacity-0 group-hover:opacity-100 p-1.5 text-zinc-600 hover:text-red-500 transition-all"
|
||||
title="Excluir permanentemente"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-zinc-400 text-xs leading-relaxed">{msg.message}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-4 border-t border-zinc-800 bg-zinc-900 flex justify-end">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-6 py-2 bg-zinc-800 hover:bg-zinc-700 text-white rounded-lg font-bold text-sm transition-colors"
|
||||
>
|
||||
Fechar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user