feat: add SessionComment

This commit is contained in:
2025-06-10 13:47:41 +05:00
parent 364db1adf7
commit 538e2b67bc
6 changed files with 117 additions and 60 deletions
+26
View File
@@ -0,0 +1,26 @@
import { motion } from "motion/react";
import { IComment } from "../types/IComments";
import { format } from "date-fns";
function SessionCommentItem({ comment }: { comment: IComment }) {
return (
<motion.div layout className="flex gap-[0.833vw] items-end">
<div className="relative flex flex-col gap-[0.556vw] p-[0.833vw] bg-white rounded-xl w-full rounded-br-none">
<p className="button-m font-medium">{comment.owner.fullname}</p>
<div className="flex flex-col max-w-[19.583vw]">
<p className="caption-s break-words whitespace-pre-wrap overflow-hidden">
{comment.text}
</p>
<div className="flex justify-end w-full">
<p className="caption-s text-[#BDBDBD] mt-[0.566vw] text-right">
{format(comment.createdAt, "HH:mm")}
</p>
</div>
</div>
</div>
<div className="bg-white size-[2.222vw] rounded-full flex-shrink-0" />
</motion.div>
);
}
export default SessionCommentItem;
+62 -41
View File
@@ -1,17 +1,13 @@
import { useRef } from "react";
import SendIcon from "./icons/SendIcon";
import NewButton from "./NewButton";
import { IComment } from "../types/IComments";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import api from "../utils/api";
import { IComment } from "../types/IComments";
import SessionCommentItem from "./SessionCommentItem";
import { AnimatePresence } from "motion/react";
function SessionComments({
comments,
sessionId,
}: {
comments: IComment[];
sessionId: string;
}) {
function SessionComments({ sessionId }: { sessionId: string }) {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const formRef = useRef<HTMLFormElement>(null);
@@ -21,25 +17,43 @@ function SessionComments({
if (!textarea || !form) return;
textarea.style.height = "auto";
form.style.height = "auto";
const newHeight = Math.min(
textarea.scrollHeight + (32 / 1440) * innerWidth,
(225 / 1440) * innerWidth
const minTextareaHeight = (24 / 1440) * window.innerWidth;
const maxTextareaHeight = (180 / 1440) * window.innerWidth;
const formPadding = (1.111 / 100) * window.innerWidth * 2;
const minFormHeight = (80 / 1440) * window.innerWidth;
const maxFormHeight = (225 / 1440) * window.innerWidth;
const scrollHeight = textarea.scrollHeight;
let newTextareaHeight;
if (scrollHeight <= minTextareaHeight) {
newTextareaHeight = minTextareaHeight;
} else if (scrollHeight <= maxTextareaHeight) {
newTextareaHeight = scrollHeight;
} else {
newTextareaHeight = maxTextareaHeight;
}
const newFormHeight = Math.max(
Math.min(newTextareaHeight + formPadding, maxFormHeight),
minFormHeight
);
const formHeight = Math.min(
form.clientHeight >= 80 ? newHeight + (32 / 1440) * innerWidth : 80,
(225 / 1440) * innerWidth
);
textarea.style.height = `${newHeight}px`;
form.style.height = `${formHeight}px`;
textarea.style.height = `${newTextareaHeight}px`;
form.style.height = `${newFormHeight}px`;
textarea.style.overflowY =
textarea.scrollHeight > (225 / 1440) * innerWidth ? "auto" : "hidden";
scrollHeight > maxTextareaHeight ? "auto" : "hidden";
};
const queryClient = useQueryClient();
const { data: comments } = useQuery({
queryKey: ["sessions", "comments", sessionId],
queryFn: () => api.get(`comments/${sessionId}`).json<IComment[]>(),
});
const { mutate: createComment } = useMutation({
mutationFn: (comment: string) =>
api.post("comments", {
@@ -48,14 +62,17 @@ function SessionComments({
text: comment,
},
}),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["sessions"] }),
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: ["sessions", "comments"],
}),
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const textarea = textareaRef.current;
if (!textarea?.value) return;
if (!textarea?.value || textarea.value.length > 200) return;
createComment(textarea.value);
textarea.value = "";
@@ -71,35 +88,39 @@ function SessionComments({
return (
<div className="outline flex flex-col flex-1 outline-[#D6D6D6]">
<div className="relative h-full">
{comments.length > 0 ? (
<div>Комменты</div>
) : (
<div className="flex flex-col gap-[1.111vw] items-center justify-center h-full">
<img
src="/images/smile-ghost.png"
className="w-[13.889vw]"
alt="ghost"
/>
<div className="flex flex-col gap-[0.556vw] items-center">
<h3 className="title-m font-medium">Оставьте заметку</h3>
<p className="caption-s font-medium text-[#BDBDBD] text-center whitespace-pre-line">
{`В дальнейшем это поможет быстро найти
<div className="relative h-full flex flex-col-reverse gap-[0.833vw] overflow-y-auto p-[1.111vw] [scrollbar-width:thin]">
<AnimatePresence mode="wait">
{comments && comments.length > 0 ? (
comments.map((comment) => (
<SessionCommentItem key={comment.id} comment={comment} />
))
) : (
<div className="flex flex-col gap-[1.111vw] items-center justify-center h-full">
<img
src="/images/smile-ghost.png"
className="w-[13.889vw]"
alt="ghost"
/>
<div className="flex flex-col gap-[0.556vw] items-center">
<h3 className="title-m font-medium">Оставьте заметку</h3>
<p className="caption-s font-medium text-[#BDBDBD] text-center whitespace-pre-line">
{`В дальнейшем это поможет быстро найти
клиента и не запутаться.`}
</p>
</p>
</div>
</div>
</div>
)}
)}
</AnimatePresence>
</div>
<form
ref={formRef}
className="flex gap-[2.222vw] bg-white justify-center items-start p-[1.111vw]"
className="flex gap-[2.222vw] max-h-[27.778vw] bg-white justify-center items-start p-[1.111vw]"
onSubmit={handleSubmit}
>
<textarea
ref={textareaRef}
rows={1}
className="w-[17.083vw] outline-none font-medium text-s resize-none self-center"
className="w-[17.083vw] outline-none text-s resize-none self-center"
placeholder="Расскажите, как все прошло"
style={{
wordWrap: "break-word",
+5 -11
View File
@@ -1,18 +1,12 @@
function ChevronRightIcon() {
return (
<svg
width={20}
height={20}
viewBox='0 0 20 20'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d='m8.75 5 5 5-5 5'
stroke='currentColor'
d="m8.75 5 5 5-5 5"
stroke="currentColor"
strokeWidth={1.2}
strokeLinecap='round'
strokeLinejoin='round'
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
+7 -4
View File
@@ -21,7 +21,7 @@ function SessionModal({ session }: { session: ISession }) {
</div>
</div>
<div className="bg-[#F0F0F0] flex h-[calc(100vh-8.861vw)] overflow-hidden rounded-b-4xl">
<div className="flex-1 flex flex-col gap-[0.833vw] px-[1.111vw] overflow-y-auto pr-[0.556vw] pb-[1.111vw]">
<div className="flex-1 flex flex-col gap-[0.833vw] px-[1.111vw] overflow-y-auto pr-[0.556vw] pb-[1.111vw] [scrollbar-width:thin]">
<div className="flex flex-col gap-[0.556vw] justify-center items-center pt-[1.111vw]">
<div className="size-[3.333vw] rounded-full bg-white"></div>
<div className="flex flex-col gap-[0.278vw] items-center">
@@ -94,14 +94,17 @@ function SessionModal({ session }: { session: ISession }) {
<span className="caption-s font-medium">8 500 000 </span>
</p>
</div>
<div className="bg-[#F6F6F6] rounded-xl px-[1.111vw] py-[0.833vw] text-xs tracking-[-0.02em]">
<div className="bg-[#F6F6F6] rounded-xl px-[1.111vw] py-[0.833vw] text-xs tracking-[-0.02em] leading-[110%]">
Клиент проявил высокий интерес к объекту, особенно к варианту с
улучшенной отделкой. Основной вопрос для принятия решения
согласование с семьей и выбор этажа. Необходимо подготовить
предварительный договор к следующей встрече.
</div>
<NewButton variant="primary" size="large">
Весь отчёт по встречи <ChevronRightIcon />
Весь отчет по встрече
<span className="w-[1.111vw] h-[1.111vw] text-[#7B60F3]">
<ChevronRightIcon />
</span>
</NewButton>
</div>
<div className="flex flex-col gap-[1.111vw] bg-white rounded-3xl p-[1.111vw]">
@@ -123,7 +126,7 @@ function SessionModal({ session }: { session: ISession }) {
</div>
</div>
</div>
<SessionComments comments={session.comments} sessionId={session.id} />
<SessionComments sessionId={session.id} />
</div>
</div>
);
+13 -4
View File
@@ -11,6 +11,19 @@ button {
cursor: pointer;
}
@layer utilities {
.text-xs {
font-size: 12px;
line-height: 110%;
}
@media (min-width: 1536px) {
.text-xs {
font-size: 0.833vw;
}
}
}
@utility title-l {
@apply 2xl:text-[1.667vw] text-[24px] leading-[120%];
}
@@ -31,10 +44,6 @@ button {
@apply 2xl:text-[0.972vw] text-[14px] leading-[110%];
}
@utility text-xs {
@apply 2xl:text-[0.833vw] text-[12px] leading-[110%];
}
@utility button-m {
@apply 2xl:text-[0.972vw] text-[14px] leading-[110%];
}
+4
View File
@@ -5,4 +5,8 @@ export interface IComment {
updatedAt: Date;
ownerId: string;
sessionId: string;
owner: {
ownerId: string;
fullname: string;
};
}