Files
diplom-frontend/app/components/channel-area.tsx
2025-05-21 08:46:12 +03:00

160 lines
4.8 KiB
TypeScript

import {
useInfiniteQuery,
type QueryFunctionContext,
} from "@tanstack/react-query";
import type { Channel, Message, MessageId } from "~/lib/api/types";
import ChatMessage from "./chat-message";
import MessageBox from "./message-box";
import { Separator } from "./ui/separator";
import VisibleTrigger from "./visible-trigger";
interface ChannelAreaProps {
channel: Channel;
}
export default function ChannelArea({ channel }: ChannelAreaProps) {
const channelId = channel.id;
const fetchMessages = async ({ pageParam }: QueryFunctionContext) => {
return await import("~/lib/api/client/channel").then((m) =>
m.default.paginatedMessages(
channelId,
50,
pageParam as MessageId | undefined,
),
);
};
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
isPending,
status,
} = useInfiniteQuery({
queryKey: ["messages", channelId],
initialPageParam: undefined,
queryFn: fetchMessages,
getNextPageParam: (lastPage) =>
lastPage.length < 50
? undefined
: lastPage[lastPage.length - 1]?.id,
staleTime: Infinity,
});
const fetchNextPageVisible = () => {
if (!isFetchingNextPage && hasNextPage) fetchNextPage();
};
let messageArea = null;
if (isPending) {
messageArea = (
<div className="flex items-center justify-center size-full">
<span>Loading...</span>
</div>
);
} else {
messageArea = (
<>
<div className="flex-1" />
<div className="flex flex-col-reverse overflow-auto gap-2 pb-2 pr-2">
{status === "success" && renderMessages(data.pages)}
<VisibleTrigger
triggerOnce={false}
onVisible={fetchNextPageVisible}
/>
</div>
</>
);
}
return (
<>
<div className="flex flex-col size-full">
<div className="w-full min-h-12 border-b-2 flex items-center justify-center">
{channel?.name}
</div>
<div className="flex-1 overflow-y-auto flex flex-col pl-2 pr-0.5">
{messageArea}
</div>
<div className="w-full">
<MessageBox channelId={channelId} />
</div>
</div>
</>
);
}
function renderMessages(pages: Message[][]) {
const messageElements: React.ReactNode[] = [];
let lastDate: string | null = null;
const formatMessageDate = (date: Date) => {
const now = new Date();
const today = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
);
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
const messageDate = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate(),
);
const capitalize = (str: string) =>
str.charAt(0).toUpperCase() + str.slice(1);
if (messageDate.getTime() === today.getTime()) {
const rtf = new Intl.RelativeTimeFormat(undefined, {
numeric: "auto",
});
return capitalize(rtf.format(0, "day"));
} else if (messageDate.getTime() === yesterday.getTime()) {
const rtf = new Intl.RelativeTimeFormat(undefined, {
numeric: "auto",
});
return capitalize(rtf.format(-1, "day"));
} else {
return date.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
});
}
};
pages.forEach((page) => {
page.forEach((message) => {
const messageDate = message.createdAt.toDateString();
if (messageDate != lastDate) {
if (lastDate)
messageElements.push(
<div className="flex items-center justify-center py-2 w-full">
<Separator className="flex-1" />
<span className="mx-4 text-sm text-muted-foreground">
{formatMessageDate(new Date(lastDate))}
</span>
<Separator className="flex-1" />
</div>,
);
lastDate = messageDate;
}
messageElements.push(
<div key={message.id} className="w-full">
<ChatMessage message={message} />
</div>,
);
});
});
return messageElements;
}