import { format, isSameDay } from "date-fns"; import { AnimatePresence, motion } from "framer-motion"; import { Copy, Forward, Pencil, Reply, Trash2 } from "lucide-react"; import { useRouter } from "next/router"; import { type FormEvent, useCallback, useEffect, useRef, useState, } from "react"; import toast from "react-hot-toast"; import { ChatAttachments } from "~/components/chat/chat-attachments"; import { ChatBubble, ChatBubbleMessage, ChatBubbleReadStatus, } from "~/components/chat/chat-bubble"; import LoadingButton from "~/components/custom_ui/loading-button"; import { LoadingPage } from "~/components/loading"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "~/components/ui/alert-dialog"; import { Button } from "~/components/ui/button"; import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "~/components/ui/command"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from "~/components/ui/context-menu"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import Input from "~/components/ui/input"; import { Textarea } from "~/components/ui/textarea"; import { getUserColorV2, UserAvatar } from "~/components/user_avatar"; import { cn } from "~/lib/utils"; import { useChatContext } from "~/providers/ChatProvider"; import { useTranslation } from "~/providers/I18nProvider"; import type { MessagesMessageid } from "~/schemas/public/Messages"; import type { UsersId } from "~/schemas/public/Users"; import type { Message, TypingData } from "~/server/api/routers/chat_sse"; import { api } from "~/utils/api"; export const ChatList = () => { const { chatId, session, liveChat, inputRef, setReplyTrg, messagesContainerRef, } = useChatContext(); const { messages, query, typingStatus } = liveChat; const [editModalOpen, setEditModalOpen] = useState(false); const [msgSelected, setMsgSelected] = useState(null); const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [inoltraModalOpen, setInoltraModalOpen] = useState(false); const handleEditOpen = (status: boolean) => { setEditModalOpen(status); }; const handleDeleteOpen = (status: boolean) => { setDeleteModalOpen(status); }; const { mutate: deleteMutation } = api.chatSSE.deleteMessage.useMutation({ onError: () => { toast.error("Error deleting message", { id: "deleteMessage" }); }, onMutate: () => { toast("Deleting message...", { icon: "🗑️", id: "deleteMessage" }); }, onSuccess: () => { toast("Message deleted", { icon: "🗑️", id: "deleteMessage" }); }, }); const { mutate: editMutation } = api.chatSSE.editMessage.useMutation({ onError: () => { toast.error("Error editing message", { id: "editMessage" }); }, onMutate: () => { toast("Editing message...", { icon: "📝", id: "editMessage" }); }, onSuccess: () => { toast("Message edited", { icon: "📝", id: "editMessage" }); }, }); const handleEditSubmit = useCallback( (e: FormEvent) => { e.preventDefault(); const form = e.currentTarget; const formData = new FormData(form); const raw_messageId = formData.get("message-id"); const raw_message = formData.get("message-edit"); if (!raw_message || !raw_messageId) { return; } const messageId = raw_messageId.toString() as MessagesMessageid; const message = raw_message.toString(); editMutation({ chatId, message, messageId, }); setEditModalOpen(false); }, [editMutation], ); const [locked, setLocked] = useState(false); const focusInputOnCloseRef = useRef(false); useEffect(() => { if ( messagesContainerRef.current && !locked && !query.isLoading && !query.isFetching ) { messagesContainerRef.current.scrollTo({ top: messagesContainerRef.current.scrollHeight, behavior: "smooth", }); } setLocked(false); }, [messages, typingStatus]); const handleFetchPrevious = useCallback(async () => { if (query.hasNextPage && !query.isFetchingNextPage) { setLocked(true); await query.fetchNextPage(); } }, [query]); const utils = api.useUtils(); const handleScrollTo = (messageId: MessagesMessageid): boolean => { const el = document.getElementById(messageId); if (el) { el.scrollIntoView({ behavior: "smooth", block: "center", }); el.classList.add("bg-primary/50"); setTimeout(() => { el.classList.remove("bg-primary/50"); }, 2000); return true; } console.warn("Element not found for messageId:", messageId); return false; }; const handleJumpToMessage = async (messageId: MessagesMessageid) => { const scroll = handleScrollTo(messageId); if (scroll) return; const context = await utils.chatSSE.getChat.fetch({ chatId, targetMsgId: messageId, }); utils.chatSSE.getChat.setInfiniteData({ chatId }, () => { return { pages: [context], pageParams: [null] as (MessagesMessageid | null)[], }; }); setTimeout(() => { handleScrollTo(messageId); }, 100); console.log(query.data); }; return (
{query.hasNextPage && ( {query.isFetchingNextPage ? "Caricamento..." : "Carica messaggi precedenti"} )} {messages.map((message, index) => { const isNextSameSender = index + 1 < messages.length && messages[index + 1]?.sender === message.sender; const isPrevSameDay = (() => { const prevMsg = messages[index - 1]; if (!prevMsg) return false; return isSameDay(new Date(prevMsg.time), new Date(message.time)); })(); const isPreviousSameSender = index - 1 >= 0 && messages[index - 1]?.sender === message.sender; const isSender = message.sender === session.id; const userColor = getUserColorV2(message.color_str); const reply_userColor = message.reply_source ? getUserColorV2(message.reply_source.color_str) : undefined; return (
{!isPrevSameDay && ( {new Date(message.time).toLocaleDateString("it", { day: "2-digit", month: "2-digit", year: "2-digit", })} )} {!isPreviousSameSender && (

{message.sender_nome}

)} {message.reply_source && ( )} {message.has_attachments && ( )} {message.message}
{message.time && (
{format(new Date(message.time), "HH:mm")}
)} {isSender && ( )}
{ if (focusInputOnCloseRef.current) { e.preventDefault(); inputRef.current?.textArea.focus(); focusInputOnCloseRef.current = false; if (messagesContainerRef.current) { messagesContainerRef.current.scrollTo({ top: messagesContainerRef.current.scrollHeight, behavior: "smooth", }); } } }} > { navigator.clipboard.writeText(message.message); toast.success("Messaggio copiato negli appunti", { id: "copyMessage", }); }} > Copia { setReplyTrg(message); focusInputOnCloseRef.current = true; }} > Rispondi {session.isAdmin && ( { setMsgSelected(message); setInoltraModalOpen(true); }} > Inoltra )} {(isSender || session.isAdmin) && ( { setMsgSelected(message); handleEditOpen(true); }} > Modifica )} {session.isAdmin && ( <> { setMsgSelected(message); handleDeleteOpen(true); }} > Cancella {" "} )}
); })}
{session.isAdmin && ( setInoltraModalOpen(status)} /> )} { deleteMutation({ chatId, messageId }); }} msgDelete={msgSelected} onOpenChange={handleDeleteOpen} open={deleteModalOpen} />
); }; const Typers = ({ currentTypers, messageLenght, userId, }: { currentTypers: TypingData; messageLenght: number; userId: UsersId; }) => { return ( <> {currentTypers.map((typer) => { if (typer.userId === userId) return null; const variant = "received"; return ( ✏️ {typer.username} sta scrivendo... ); })} ); }; const EditMessageDialog = ({ open, onOpenChange, msgEdit, handleEditSubmit, }: { open: boolean; onOpenChange: (status: boolean) => void; msgEdit: Message | null; handleEditSubmit: (e: FormEvent) => void; }) => { const { t } = useTranslation(); return ( {t.modifica} Edit {msgEdit && (