feat: add ManagerSelect component and integrate it into CreateSessionModal, replacing the deprecated Selector component

This commit is contained in:
2025-06-27 13:38:32 +05:00
parent c82f1dfbb5
commit fc7d55b10f
5 changed files with 192 additions and 3 deletions
+114
View File
@@ -0,0 +1,114 @@
import { useEffect, useState } from "react";
import { Manager } from "../types/Manager";
import SortIcon from "./icons/SortIcon";
import clsx from "clsx";
import CheckIcon from "./icons/CheckIcon";
import { useClickAway } from "@uidotdev/usehooks";
import { AnimatePresence, motion } from "motion/react";
function ManagerSelect({
placeholder,
data,
}: {
placeholder: string;
data: Manager[];
}) {
const [isOpen, setIsOpen] = useState(false);
const [selectedManager, setSelectedManager] = useState<Manager | null>(
data[0] || null
);
const [position, setPosition] = useState<"top" | "bottom">("bottom");
const selectRef = useClickAway<HTMLDivElement>(() => setIsOpen(false));
useEffect(() => {
const rect = selectRef.current?.getBoundingClientRect();
if (rect) {
setPosition(rect.top > window.innerHeight / 2 ? "top" : "bottom");
}
}, [isOpen, selectRef]);
useEffect(() => {
const handleScroll = () => {
if (isOpen) {
const rect = selectRef.current?.getBoundingClientRect();
if (rect) {
setPosition(rect.top > window.innerHeight / 2 ? "top" : "bottom");
}
}
};
window.addEventListener("scroll", handleScroll, true);
return () => window.removeEventListener("scroll", handleScroll, true);
}, [isOpen, selectRef]);
const handleToggle = () => {
setIsOpen(!isOpen);
};
return (
<div
ref={selectRef}
className={clsx(
"relative w-full rounded-[0.833vw] p-[1.111vw] bg-[#F6F6F6] cursor-pointer flex items-center justify-between select-none",
isOpen && "outline outline-[#7B60F3]"
)}
style={{ boxShadow: "0px 2px 2px 0px #0000000D" }}
onClick={handleToggle}
>
<div className="flex flex-col gap-[0.278vw]">
<div className="caption-s font-medium text-[#7D7D7D]">
{placeholder}
</div>
<div className="flex items-center gap-[0.556vw]">
<div className="size-[1.111vw] rounded-full bg-[#7D7D7D]" />
<div className="text-s">
{selectedManager?.fullname || data[0].fullname}
</div>
</div>
</div>
<span className="size-[1.389vw] text-[#7D7D7D]">
<SortIcon />
</span>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={clsx(
"absolute left-0 w-full z-10",
position === "top"
? "top-[calc(-11.389vw-0.278vw)]"
: "top-[calc(100%+0.278vw)]"
)}
>
<div
className="w-full rounded-[0.833vw] p-[0.833vw] max-h-[11.389vw] bg-white overflow-y-auto [scrollbar-width:thin]"
style={{ boxShadow: "0px 4px 40px 0px #0000000D" }}
>
{data.map((item) => (
<div
key={item.id}
className="p-[0.833vw] flex items-center gap-[0.556vw] hover:bg-[#F6F6F6] rounded-[0.278vw]"
onClick={(e) => {
e.stopPropagation();
setSelectedManager(item);
setIsOpen(false);
}}
>
<div className="size-[1.111vw] rounded-full text-[#7B60F3] flex items-center justify-center">
{selectedManager?.id === item.id && <CheckIcon />}
</div>
<div className="size-[1.111vw] rounded-full bg-[#7D7D7D]" />
<div className="text-s">{item.fullname}</div>
</div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default ManagerSelect;
-3
View File
@@ -1,3 +0,0 @@
export default function Selector() {
return <div>Selector</div>;
}
+16
View File
@@ -0,0 +1,16 @@
function SortIcon() {
return (
<svg
viewBox="0 0 20 20"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.8571 11.5257L9.99943 14.3905L7.14179 11.5257C7.01635 11.4002 6.84623 11.3297 6.66884 11.3297C6.49146 11.3297 6.32133 11.4002 6.1959 11.5257C6.07047 11.6511 6 11.8213 6 11.9987C6 12.1761 6.07047 12.3463 6.1959 12.4717L9.52649 15.8029C9.58841 15.8654 9.66209 15.9149 9.74326 15.9488C9.82443 15.9826 9.9115 16 9.99943 16C10.0874 16 10.1744 15.9826 10.2556 15.9488C10.3368 15.9149 10.4105 15.8654 10.4724 15.8029L13.803 12.4717C13.8651 12.4096 13.9143 12.3359 13.948 12.2547C13.9816 12.1736 13.9989 12.0866 13.9989 11.9987C13.9989 11.9109 13.9816 11.8239 13.948 11.7427C13.9143 11.6615 13.8651 11.5878 13.803 11.5257C13.7409 11.4636 13.6671 11.4143 13.586 11.3807C13.5048 11.3471 13.4179 11.3297 13.33 11.3297C13.2422 11.3297 13.1552 11.3471 13.0741 11.3807C12.9929 11.4143 12.9192 11.4636 12.8571 11.5257ZM7.14179 8.47432L9.99943 5.6095L12.8571 8.47432C12.919 8.53676 12.9927 8.58633 13.0738 8.62015C13.155 8.65397 13.2421 8.67139 13.33 8.67139C13.418 8.67139 13.505 8.65397 13.5862 8.62015C13.6674 8.58633 13.741 8.53676 13.803 8.47432C13.8654 8.41238 13.915 8.33869 13.9488 8.25751C13.9826 8.17632 14 8.08924 14 8.00129C14 7.91334 13.9826 7.82626 13.9488 7.74507C13.915 7.66388 13.8654 7.59019 13.803 7.52826L10.4724 4.19707C10.4105 4.13463 10.3368 4.08506 10.2556 4.05124C10.1744 4.01741 10.0874 4 9.99943 4C9.9115 4 9.82443 4.01741 9.74326 4.05124C9.66209 4.08506 9.58841 4.13463 9.52649 4.19707L6.1959 7.52826C6.13379 7.59038 6.08453 7.66412 6.05091 7.74529C6.0173 7.82645 6 7.91344 6 8.00129C6 8.17871 6.07047 8.34886 6.1959 8.47432C6.32133 8.59977 6.49146 8.67025 6.66884 8.67025C6.84623 8.67025 7.01635 8.59977 7.14179 8.47432Z"
fill="currentColor"
/>
</svg>
);
}
export default SortIcon;
@@ -13,6 +13,7 @@ import ProjectSelector from "../ProjectSelector.tsx";
import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
import { AnimatePresence, motion } from "motion/react";
import useClientSearch from "../../hooks/useClientSearch.tsx";
import ManagerSelect from "../ManagerSelect.tsx";
interface Props {
targetServerId: string | null;
@@ -207,6 +208,65 @@ export default function CreateSessionModal({ targetServerId, client }: Props) {
required
isLoading={isLoading}
/>
<ManagerSelect
placeholder="Менеджер сеанса"
data={[
{
id: "1",
email: "2@2",
fullname: "СЕМЁН Лобанов",
companyId: "1",
},
{
id: "2",
email: "2@2",
fullname: "ВОВА Лобанов",
companyId: "1",
},
{
id: "3",
email: "2@2",
fullname: "САНЯ Лобанов",
companyId: "1",
},
{
id: "4",
email: "2@2",
fullname: "АНТОН Лобанов",
companyId: "1",
},
{
id: "5",
email: "2@2",
fullname: "БОЛЬШОЙ АНТОН",
companyId: "1",
},
{
id: "6",
email: "2@2",
fullname: "Константин Лобанов",
companyId: "1",
},
{
id: "7",
email: "2@2",
fullname: "Константин Лобанов",
companyId: "1",
},
{
id: "8",
email: "2@2",
fullname: "Константин Лобанов",
companyId: "1",
},
{
id: "9",
email: "2@2",
fullname: "Константин Лобанов",
companyId: "1",
},
]}
/>
<AnimatePresence>
{isFullPhone && (
<>
@@ -258,6 +318,7 @@ export default function CreateSessionModal({ targetServerId, client }: Props) {
/>
)}
</div>
{isSessionExists && (
<div className="absolute inset-0 top-[11.806vw] bg-[#FFFFFF] flex flex-col gap-[1.111vw] items-center justify-center h-[31.458vw]">
<img
+1
View File
@@ -60,6 +60,7 @@ function DashboardPage() {
</div>
</div>
</div>
<div className="w-full">
<div className="flex flex-col gap-[1.667vw]">
<h1 className="title-l font-medium">Последние сеансы</h1>