Update environment configuration and enhance Card and Timeline components; change API URL for development, integrate Kebab menu for session management in Card, and streamline Timeline session handling by removing unnecessary button.
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
VITE_API_URL=http://localhost:3001
|
||||
VITE_API_URL=http://192.168.1.53:3001
|
||||
# VITE_API_URL=https://crm.stream.graff.tech/api
|
||||
VITE_STREAM_URL=https://stream.graff.tech
|
||||
|
||||
+128
-130
@@ -3,15 +3,14 @@ import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { isAfter, isBefore, subMinutes } from "date-fns";
|
||||
import api from "../utils/api";
|
||||
import Button from "./Button";
|
||||
import SelectUser from "./SelectUser";
|
||||
import ManagerSelector from "./ManagerSelector";
|
||||
import CardKebabMenu from "./CardKebabMenu";
|
||||
import EntryIcon from "./icons/EntryIcon";
|
||||
import MoreIcon from "./icons/MoreIcon";
|
||||
import ShareIcon from "./icons/ShareIcon";
|
||||
import ShareModal from "./modals/ShareModal";
|
||||
import KebabIcon from "./icons/KebabIcon";
|
||||
import useAuthStore from "../stores/useAuthStore";
|
||||
import useModalStore from "../stores/useModalStore";
|
||||
import IUser from "../types/IUser";
|
||||
import Button from "./Button";
|
||||
|
||||
interface CardProps {
|
||||
companyId: string;
|
||||
@@ -24,16 +23,33 @@ interface CardProps {
|
||||
manager?: IUser;
|
||||
managers: IUser[];
|
||||
handleSelect: (scheduledSessionId: string, managerId: string | null) => void;
|
||||
onDelete?: (scheduledSessionId: string) => void;
|
||||
fitContainer?: boolean;
|
||||
}
|
||||
|
||||
const STREAM_URL = import.meta.env.VITE_STREAM_URL as string;
|
||||
|
||||
const cn = {
|
||||
border: "border-[#DAE0E6]",
|
||||
contact: "text-[10px] font-semibold leading-[1.2] text-[#77828C]",
|
||||
mutedBtn: "text-[#77828C] hover:text-[#4C5359] transition-colors",
|
||||
statusLabel: "text-[10px] font-semibold leading-[1.2]",
|
||||
textPrimary: "text-[#111C26]",
|
||||
} as const;
|
||||
|
||||
const STATUS = {
|
||||
noManager: { bg: "bg-[#F2DADA]", dot: "bg-[#EB5757]", text: "text-[#EB5757]", label: "Нет менеджера" },
|
||||
ready: { bg: "bg-[#E6F2FE]", dot: "bg-[#49A1F5]", text: "text-[#49A1F5]", label: "Готов" },
|
||||
done: { bg: "bg-[#F0F1F2]", dot: "bg-[#77828C]", text: "text-[#77828C]", label: "Проведена" },
|
||||
scheduled: { bg: "bg-[#F0F1F2]", dot: "bg-[#77828C]", text: "text-[#77828C]", label: "Запланировано" },
|
||||
noManager: {
|
||||
dot: "bg-[#EB5757]",
|
||||
text: "text-[#EB5757]",
|
||||
label: "Нет менеджера",
|
||||
},
|
||||
ready: { dot: "bg-[#49A1F5]", text: "text-[#49A1F5]", label: "Готов" },
|
||||
done: { dot: "bg-[#77828C]", text: "text-[#77828C]", label: "Проведена" },
|
||||
scheduled: {
|
||||
dot: "bg-[#77828C]",
|
||||
text: "text-[#77828C]",
|
||||
label: "Запланировано",
|
||||
},
|
||||
} as const;
|
||||
|
||||
function getStatus(
|
||||
@@ -58,11 +74,12 @@ function Card({
|
||||
manager,
|
||||
managers,
|
||||
handleSelect,
|
||||
onDelete,
|
||||
fitContainer = false,
|
||||
}: CardProps) {
|
||||
const { user } = useAuthStore();
|
||||
const setModal = useModalStore((state) => state.setModal);
|
||||
const [isShowManagerSelect, setIsShowManagerSelect] = useState(false);
|
||||
const [isKebabMenuOpen, setIsKebabMenuOpen] = useState(false);
|
||||
const [availableManagers, setAvailableManagers] = useState<IUser[]>();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -73,12 +90,16 @@ function Card({
|
||||
`companies/${companyId}/builds/${buildId}/scheduledSessions/${scheduledSessionId}/availableManagers?startAt=${scheduleSessionStartAt}`
|
||||
)
|
||||
.json<string[]>()
|
||||
.then((ids) => setAvailableManagers(managers.filter((m) => ids.includes(m.id))));
|
||||
.then((ids) =>
|
||||
setAvailableManagers(managers.filter((m) => ids.includes(m.id)))
|
||||
);
|
||||
}, [isShowManagerSelect]);
|
||||
|
||||
const now = new Date();
|
||||
const canStart = !!manager && isAfter(now, subMinutes(new Date(scheduleSessionStartAt), 10));
|
||||
const isCompleted = !!scheduleSessionEndAt && isBefore(new Date(scheduleSessionEndAt), now);
|
||||
const canStart =
|
||||
!!manager && isAfter(now, subMinutes(new Date(scheduleSessionStartAt), 10));
|
||||
const isCompleted =
|
||||
!!scheduleSessionEndAt && isBefore(new Date(scheduleSessionEndAt), now);
|
||||
const status = getStatus(manager, canStart, isCompleted);
|
||||
|
||||
const sessionUrl = `${STREAM_URL}/scheduled/${scheduledSessionId}?admin=true`;
|
||||
@@ -86,132 +107,109 @@ function Card({
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
className={`w-[264px] bg-white flex flex-col gap-2 px-3 pt-3 pb-2 border-r border-b border-[#DAE0E5] ${
|
||||
fitContainer ? "h-full min-h-[128px] overflow-hidden" : ""
|
||||
}`}
|
||||
className={`box-border flex flex-col items-start p-3 gap-3 w-[264px] bg-white ${
|
||||
fitContainer ? "h-full min-h-[144px]" : "h-[144px]"
|
||||
} ${fitContainer && !isShowManagerSelect && !isKebabMenuOpen ? "overflow-hidden" : ""}`}
|
||||
>
|
||||
{/* SessionInfo */}
|
||||
<div className="relative flex flex-col gap-2 pb-2 border-b border-[#DAE0E5]">
|
||||
<p className="text-[12px] font-semibold leading-[1.3] text-[#77828C]">
|
||||
{buildName ? `ЖК «${buildName}»` : "ЖК"}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[10px] font-semibold leading-[1.2] text-[#77828C]">
|
||||
Клиент
|
||||
</span>
|
||||
<span className="text-[14px] leading-[1.4] text-[#111C26]">
|
||||
{/* ── SessionInfo: клиент + ЖК, gap 8px ── */}
|
||||
<div
|
||||
className={`relative w-full flex flex-col items-start gap-2 ${cn.border}`}
|
||||
>
|
||||
{/* Frame 2702: имя + контакты, gap 4px */}
|
||||
<div className="flex flex-col gap-1 items-start">
|
||||
<p
|
||||
className={`text-[14px] font-normal leading-[1.4] ${cn.textPrimary}`}
|
||||
>
|
||||
{client?.name || "Имя не указано"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[12px] leading-[1.3] text-[#77828C]">
|
||||
{client?.phone || "Телефон не указан"}
|
||||
</span>
|
||||
<span className="text-[12px] leading-[1.3] text-[#77828C]">
|
||||
{client?.email || "Email не указан"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Status indicator */}
|
||||
<div
|
||||
className={`absolute top-0 right-0 flex items-center gap-1 rounded-full px-2 py-1 ${status.bg}`}
|
||||
>
|
||||
<div className={`w-1 h-1 rounded-full shrink-0 ${status.dot}`} />
|
||||
<span className={`text-[10px] font-semibold leading-[1.2] ${status.text}`}>
|
||||
{status.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Share button */}
|
||||
<div className="absolute left-[208px] top-16">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
icon={<ShareIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setModal(<ShareModal scheduledSessionId={scheduledSessionId} />)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Manager row */}
|
||||
<div className="flex gap-2 justify-between items-center min-h-8">
|
||||
<div className="flex gap-2 items-center min-w-0">
|
||||
<div className="relative w-6 h-6 shrink-0">
|
||||
{manager ? (
|
||||
<>
|
||||
<img
|
||||
src={manager.avatar || "/images/no-avatar.png"}
|
||||
alt=""
|
||||
className="w-6 h-6 rounded-full object-cover bg-[#E6ECF2]"
|
||||
/>
|
||||
{/* Online dot */}
|
||||
<div
|
||||
className={`absolute left-4 top-4 w-2 h-2 rounded-full border border-white ${
|
||||
canStart ? "bg-[#49A1F5]" : "bg-[#77828C]"
|
||||
}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="w-6 h-6 rounded-full bg-[#E6ECF2]" />
|
||||
)}
|
||||
</p>
|
||||
{/* Contacts: column, gap 4px */}
|
||||
<div className="flex flex-col gap-1 items-start">
|
||||
<span className={cn.contact}>
|
||||
{client?.phone || "Телефон не указан"}
|
||||
</span>
|
||||
<span className={cn.contact}>
|
||||
{client?.email || "Email не указан"}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[12px] leading-[1.3] text-[#111C26] truncate">
|
||||
{manager ? manager.name : "Не назначен"}
|
||||
</span>
|
||||
{/* Kebab — absolute right-0 top-0, 32×32, p-1, rounded-lg */}
|
||||
{user?.role === "admin" && (
|
||||
<div className="absolute top-0 right-0">
|
||||
<div className="relative">
|
||||
<Button
|
||||
onlyIcon
|
||||
variant="tertiary"
|
||||
type="button"
|
||||
onClick={() => setIsKebabMenuOpen((v) => !v)}
|
||||
>
|
||||
<KebabIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<CardKebabMenu
|
||||
shown={isKebabMenuOpen}
|
||||
scheduledSessionId={scheduledSessionId}
|
||||
onClose={() => setIsKebabMenuOpen(false)}
|
||||
onDelete={onDelete ? () => onDelete(scheduledSessionId) : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1 items-center shrink-0">
|
||||
{user?.role === "manager" && (
|
||||
<>
|
||||
{!manager && (
|
||||
<Button variant="secondary" onClick={() => handleSelect(scheduledSessionId, user.id)}>
|
||||
Выбрать
|
||||
</Button>
|
||||
)}
|
||||
{manager && canStart && (
|
||||
<Link to={sessionUrl} target="_blank">
|
||||
<Button>Начать</Button>
|
||||
</Link>
|
||||
)}
|
||||
{manager && !canStart && manager.id === user.id && (
|
||||
<Button variant="secondary" onClick={() => handleSelect(scheduledSessionId, null)}>
|
||||
Отменить
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="flex gap-1 justify-between items-center w-full">
|
||||
{/* ЖК — order 2, gap 8 от Frame 2702 */}
|
||||
<p className="text-[12px] font-normal leading-[1.3] text-[#77828C]">
|
||||
{buildName ? `ЖК «${buildName}»` : "ЖК"}
|
||||
</p>
|
||||
|
||||
{user?.role === "admin" && (
|
||||
<>
|
||||
<Link to={sessionUrl} target="_blank">
|
||||
<Button variant="tertiary" icon={<EntryIcon />} onlyIcon />
|
||||
</Link>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
icon={<MoreIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setIsShowManagerSelect((v) => !v)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/* Indicator — absolute right-0 top-[46px], gap 4px, dot 6×6 */}
|
||||
<div className={`flex items-center gap-1 ${status.text}`}>
|
||||
<span
|
||||
className={`w-[6px] h-[6px] rounded-full shrink-0 ${status.dot}`}
|
||||
/>
|
||||
<span className={cn.statusLabel}>{status.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute z-10 pl-[calc(264px+8px)]">
|
||||
<SelectUser
|
||||
shown={isShowManagerSelect}
|
||||
selectedManagerId={manager?.id}
|
||||
managers={availableManagers ?? []}
|
||||
loading={isShowManagerSelect && !availableManagers}
|
||||
handleClick={(managerId) => {
|
||||
handleSelect(scheduledSessionId, managerId);
|
||||
setIsShowManagerSelect(false);
|
||||
}}
|
||||
handleShown={() => setIsShowManagerSelect(false)}
|
||||
/>
|
||||
{/* ── MasterPerson/Small: gap 8px, h-8 ── */}
|
||||
<div className="flex gap-2 items-center w-full h-8">
|
||||
<div className="relative flex-1 min-w-0">
|
||||
<ManagerSelector
|
||||
selectedManager={manager}
|
||||
canStart={canStart}
|
||||
isAdmin={user?.role === "admin"}
|
||||
onTriggerClick={() => setIsShowManagerSelect((v) => !v)}
|
||||
sessionUrl={sessionUrl}
|
||||
currentUserId={user?.id}
|
||||
onSelectSelf={() => handleSelect(scheduledSessionId, user!.id)}
|
||||
onCancel={() => handleSelect(scheduledSessionId, null)}
|
||||
/>
|
||||
{/* Попап под селектором */}
|
||||
<div className="absolute left-0 top-full z-20 mt-1">
|
||||
<SelectUser
|
||||
shown={isShowManagerSelect}
|
||||
selectedManagerId={manager?.id}
|
||||
managers={availableManagers ?? []}
|
||||
loading={isShowManagerSelect && !availableManagers}
|
||||
handleClick={(managerId) => {
|
||||
handleSelect(scheduledSessionId, managerId);
|
||||
setIsShowManagerSelect(false);
|
||||
}}
|
||||
handleShown={() => setIsShowManagerSelect(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Button/Secondary — 32×32, bg #F0F1F2, icon #77828C */}
|
||||
{user?.role === "admin" && (
|
||||
<Link to={sessionUrl} target="_blank" className="shrink-0">
|
||||
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-[#F0F1F2] hover:bg-[#E6ECF2] transition-colors text-[#77828C]">
|
||||
<span className="w-5 h-5">
|
||||
<EntryIcon />
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Transition } from "react-transition-group";
|
||||
import useModalStore from "../stores/useModalStore";
|
||||
import ShareModal from "./modals/ShareModal";
|
||||
import ShareIcon from "./icons/ShareIcon";
|
||||
import TrashIcon from "./icons/TrashIcon";
|
||||
|
||||
interface CardKebabMenuProps {
|
||||
shown: boolean;
|
||||
scheduledSessionId: string;
|
||||
onClose: () => void;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
function CardKebabMenu({
|
||||
shown,
|
||||
scheduledSessionId,
|
||||
onClose,
|
||||
onDelete,
|
||||
}: CardKebabMenuProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!shown) return;
|
||||
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
document.addEventListener("mousedown", handleMouseDown);
|
||||
}, 0);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
document.removeEventListener("mousedown", handleMouseDown);
|
||||
};
|
||||
}, [shown, onClose]);
|
||||
|
||||
function handleShare() {
|
||||
onClose();
|
||||
setModal(<ShareModal scheduledSessionId={scheduledSessionId} />);
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
if (!confirm("Вы уверены, что хотите удалить сеанс?")) return;
|
||||
onClose();
|
||||
onDelete?.();
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition in={shown} timeout={150} mountOnEnter unmountOnExit>
|
||||
{(state) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`absolute left-full top-0 ml-2 z-50 bg-white w-[232px] rounded-lg flex flex-col shadow-[0px_1px_4px_0px_rgba(0,0,0,0.16)] transition-opacity py-2 ${state}`}
|
||||
>
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className="px-4 flex items-center gap-2 w-full h-8 transition-colors hover:bg-[#E6ECF2] text-left text-[#111C26]"
|
||||
>
|
||||
<ShareIcon className="w-5 h-5 shrink-0 text-[#77828C]" />
|
||||
<span className="text-[12px] leading-[1.3]">Поделиться</span>
|
||||
</button>
|
||||
{onDelete && (
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="px-4 flex items-center gap-2 w-full h-8 transition-colors hover:bg-[#F2DADA] text-left text-[#EB5757]"
|
||||
>
|
||||
<TrashIcon className="w-5 h-5 shrink-0" />
|
||||
<span className="text-[12px] leading-[1.3]">Удалить</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
export default CardKebabMenu;
|
||||
@@ -0,0 +1,132 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import ChevronDownIcon from "./icons/ChevronDownIcon";
|
||||
import IUser from "../types/IUser";
|
||||
|
||||
const cn = {
|
||||
borderLight: "border-[#F0F1F2]",
|
||||
textPrimary: "text-[#111C26]",
|
||||
avatar: "w-6 h-6 rounded-full object-cover bg-[#E6ECF2]",
|
||||
avatarPlaceholder: "block w-6 h-6 rounded-full bg-[#E6ECF2]",
|
||||
} as const;
|
||||
|
||||
interface ManagerSelectorProps {
|
||||
selectedManager?: IUser;
|
||||
canStart: boolean;
|
||||
isAdmin: boolean;
|
||||
onTriggerClick: () => void;
|
||||
sessionUrl: string;
|
||||
currentUserId?: string;
|
||||
onSelectSelf: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
function ManagerSelector({
|
||||
selectedManager,
|
||||
canStart,
|
||||
isAdmin,
|
||||
onTriggerClick,
|
||||
sessionUrl,
|
||||
currentUserId,
|
||||
onSelectSelf,
|
||||
onCancel,
|
||||
}: ManagerSelectorProps) {
|
||||
const isManager = !isAdmin;
|
||||
const showSelect = isManager && !selectedManager;
|
||||
const showStart = isManager && selectedManager && canStart;
|
||||
const showCancel =
|
||||
isManager &&
|
||||
selectedManager &&
|
||||
!canStart &&
|
||||
selectedManager.id === currentUserId;
|
||||
|
||||
return (
|
||||
<div
|
||||
role={isAdmin ? "button" : undefined}
|
||||
tabIndex={isAdmin ? 0 : undefined}
|
||||
onClick={isAdmin ? onTriggerClick : undefined}
|
||||
onKeyDown={
|
||||
isAdmin
|
||||
? (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
onTriggerClick();
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={`flex-1 min-w-0 flex items-center gap-2 h-8 py-1.5 px-2 rounded-lg border hover:bg-[#E6ECF2] transition-colors ${
|
||||
cn.borderLight
|
||||
} ${isAdmin ? "cursor-pointer" : ""}`}
|
||||
aria-label={isAdmin ? "Выбрать менеджера" : undefined}
|
||||
>
|
||||
{/* Аватар */}
|
||||
<div className="relative w-6 h-6 shrink-0">
|
||||
{selectedManager ? (
|
||||
<>
|
||||
<img
|
||||
src={selectedManager.avatar || "/images/no-avatar.png"}
|
||||
alt=""
|
||||
className={cn.avatar}
|
||||
/>
|
||||
<span
|
||||
className={`absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full border border-white ${
|
||||
canStart ? "bg-[#49A1F5]" : "bg-[#77828C]"
|
||||
}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<span className={cn.avatarPlaceholder} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Имя менеджера */}
|
||||
<span
|
||||
className={`text-[12px] leading-[1.3] truncate flex-1 ${cn.textPrimary}`}
|
||||
>
|
||||
{selectedManager ? selectedManager.name : "Не назначен"}
|
||||
</span>
|
||||
|
||||
{/* Admin: chevron — индикатор выпадающего списка */}
|
||||
{isAdmin && (
|
||||
<span
|
||||
className={`w-5 h-5 shrink-0 flex items-center justify-center text-[#77828C]`}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Manager: действия */}
|
||||
{isManager && (
|
||||
<>
|
||||
{showSelect && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSelectSelf}
|
||||
className="shrink-0 text-[11px] font-semibold text-[#77828C] hover:text-[#4C5359] transition-colors"
|
||||
>
|
||||
Выбрать
|
||||
</button>
|
||||
)}
|
||||
{showStart && (
|
||||
<Link to={sessionUrl} target="_blank">
|
||||
<span className="shrink-0 text-[11px] font-semibold text-[#49A1F5] hover:text-[#4190DB] transition-colors">
|
||||
Начать
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{showCancel && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="shrink-0 text-[11px] font-semibold text-[#77828C] hover:text-[#4C5359] transition-colors"
|
||||
>
|
||||
Отменить
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ManagerSelector;
|
||||
@@ -11,10 +11,8 @@ import {
|
||||
parseISO,
|
||||
startOfDay,
|
||||
} from "date-fns";
|
||||
import Button from "./Button";
|
||||
import IScheduledSession from "../types/IScheduledSession";
|
||||
import IUser from "../types/IUser";
|
||||
import CloseIcon from "./icons/CloseIcon";
|
||||
import api from "../utils/api";
|
||||
import toast from "react-hot-toast";
|
||||
import Card from "./Card";
|
||||
@@ -259,15 +257,9 @@ function Timeline({
|
||||
manager={manager}
|
||||
managers={managers}
|
||||
handleSelect={onScheduledSessionUpdate}
|
||||
onDelete={handleClickRemove}
|
||||
fitContainer
|
||||
/>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
icon={<CloseIcon className="" />}
|
||||
onlyIcon
|
||||
onClick={() => handleClickRemove(event.id)}
|
||||
className="absolute -top-2 -right-2 z-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function KebabIcon({ className = "" }: Props) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="5"
|
||||
r="1.5"
|
||||
transform="rotate(90 12 5)"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="1.5"
|
||||
transform="rotate(90 12 12)"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="19"
|
||||
r="1.5"
|
||||
transform="rotate(90 12 19)"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default KebabIcon;
|
||||
@@ -1,27 +1,17 @@
|
||||
function ShareIcon({ className = "" }: { className?: string }) {
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ShareIcon({ className = "" }: Props) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M10 4V9"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.5 6.5L10 4L7.5 6.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5 10V14C5 14.5523 5.44772 15 6 15H14C14.5523 15 15 14.5523 15 14V10"
|
||||
d="M12 4v11m4-7-4-4-4 4m-4 4v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-6"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function TrashIcon({ className = "" }: Props) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M10.5 18v-6m3 6v-6M6 4h12m-8-1h4M7.931 21h8.138a1 1 0 0 0 .997-.929l.858-12A1 1 0 0 0 16.926 7H7.074a1 1 0 0 0-.997 1.071l.857 12A1 1 0 0 0 7.93 21"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default TrashIcon;
|
||||
Reference in New Issue
Block a user