feat: add SessionComment
This commit is contained in:
@@ -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;
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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%];
|
||||
}
|
||||
|
||||
@@ -5,4 +5,8 @@ export interface IComment {
|
||||
updatedAt: Date;
|
||||
ownerId: string;
|
||||
sessionId: string;
|
||||
owner: {
|
||||
ownerId: string;
|
||||
fullname: string;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user