This commit is contained in:
2024-10-30 20:57:32 +05:00
parent d6809ff538
commit 2435aa2814
46 changed files with 802 additions and 334 deletions
+67 -35
View File
@@ -1,53 +1,85 @@
import SpinnerIcon from "./icons/SpinnerIcon";
import RotateIcon from "./icons/RotateIcon";
interface ButtonProps {
type?: "button" | "reset" | "submit";
color?: "primary" | "secondary" | "tertiary";
size?: "small" | "medium";
disabled?: boolean;
loading?: boolean;
interface Props {
type?: "button" | "submit" | "reset";
variant?: "primary" | "secondary" | "tertiary";
size?: "large" | "medium" | "small" | "xsmall";
roundedFull?: boolean;
widthFull?: boolean;
icon?: JSX.Element;
onlyIcon?: boolean;
icon?: React.ReactNode;
children?: React.ReactNode;
className?: string;
handleClick?: () => void;
disabled?: boolean;
loading?: boolean;
onClick?: () => void;
}
const variantClasses = {
primary:
"bg-[#49A1F5] text-white hover:bg-[#4190DB] disabled:bg-[#F2F2F2] disabled:text-[#CCCCCC]",
secondary:
"bg-[#F0F1F2] text-[#77828C] hover:bg-[#E6ECF2] hover:text-[#4C5359] disabled:bg-[#F2F2F2] disabled:text-[#CCCCCC]",
tertiary:
"bg-transparent text-[#77828C] hover:bg-[#E6ECF2] hover:text-[#4C5359] disabled:bg-transparent disabled:text-[#CCCCCC]",
};
const sizeClasses = {
large: "",
medium: "px-6 py-3 text-sm font-semibold leading-[16px] h-10",
small: "px-4 py-2 text-xs font-semibold h-8",
xsmall: "",
};
const onlyIconSizeClasses = {
large: "",
medium: "",
small: "p-1.5",
xsmall: "",
};
const iconClasses = {
large: "",
medium: "w-6 h-6",
small: "w-5 h-5",
xsmall: "",
};
function Button({
type = "button",
color = "primary",
variant = "primary",
size = "small",
disabled,
loading = false,
onlyIcon,
roundedFull = false,
widthFull = false,
icon,
onlyIcon = false,
children,
className,
handleClick,
}: ButtonProps) {
className = "",
disabled,
loading,
onClick,
}: Props) {
return (
<button
disabled={disabled || loading}
type={type}
onClick={handleClick}
className={`outline-none rounded-lg transition-all font-semibold flex justify-center items-center gap-1 disabled:bg-[#F2F2F2] disabled:text-[#CCCCCC] ${
(color === "primary" && "bg-[#49A1F5] text-white hover:bg-[#4190DB]") ||
(color === "secondary" &&
"bg-[#F0F1F2] text-[#77828C] hover:bg-[#E6ECF2]") ||
(color === "tertiary" &&
"text-[#77828C] hover:bg-[#E6ECF2]")
} ${
(size === "small" &&
`h-8 ${
onlyIcon ? "p-1" : icon ? "pl-2 pr-4 py-1" : "px-3 py-2.5"
} text-xs`) ||
(size === "medium" &&
`h-10 ${
onlyIcon ? "p-2" : icon ? "pl-4 pr-6 py-2" : "px-6 py-3"
} text-sm`)
} ${className}`}
disabled={disabled || loading}
className={`flex items-center justify-center transition-all outline-none ${
variantClasses[variant]
} ${onlyIcon ? onlyIconSizeClasses[size] : sizeClasses[size]} ${
roundedFull ? "rounded-full" : "rounded-lg"
} ${widthFull ? "w-full" : "w-fit"} ${className}`}
onClick={onClick}
>
{loading ? <SpinnerIcon /> : icon} {children}
{loading ? (
<span className={`${iconClasses[size]}`}>
<RotateIcon className="animate-spin" />
</span>
) : (
<>
{icon && <span className={`${iconClasses[size]}`}>{icon}</span>}
{children}
</>
)}
</button>
);
}
+4 -4
View File
@@ -38,13 +38,13 @@ function Calendar() {
return (
<div className="p-4 space-y-2 border-b border-[#DAE0E5]">
<div className="flex justify-between items-center">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold">Календарь</p>
<Button
color="tertiary"
variant="tertiary"
icon={isShowCalendar ? <ChevronUpIcon /> : <ChevronDownIcon />}
onlyIcon
handleClick={() => setIsShowCalendar(!isShowCalendar)}
onClick={() => setIsShowCalendar(!isShowCalendar)}
/>
</div>
@@ -71,7 +71,7 @@ function Calendar() {
{["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"].map((value, index) => (
<div
key={index}
className="w-8 h-8 flex items-center justify-center text-xs font-semibold"
className="flex items-center justify-center w-8 h-8 text-xs font-semibold"
>
{value}
</div>
+5 -5
View File
@@ -127,8 +127,8 @@ function Card({
) : (
manager.id === user.id && (
<Button
color="secondary"
handleClick={() => handleSelect(scheduledSessionId, null)}
variant="secondary"
onClick={() => handleSelect(scheduledSessionId, null)}
>
Отменить
</Button>
@@ -136,8 +136,8 @@ function Card({
)
) : (
<Button
color="secondary"
handleClick={() => handleSelect(scheduledSessionId, user.id)}
variant="secondary"
onClick={() => handleSelect(scheduledSessionId, user.id)}
>
Выбрать
</Button>
@@ -152,7 +152,7 @@ function Card({
}/scheduled/${scheduledSessionId}?admin=true`}
target="_blank"
>
<Button color="secondary" icon={<EntryIcon />} onlyIcon />
<Button variant="secondary" icon={<EntryIcon />} onlyIcon />
</Link>
)}
<button
+40
View File
@@ -0,0 +1,40 @@
import { useCopyToClipboard } from "@uidotdev/usehooks";
import Button from "./Button";
import CopyIcon from "./icons/CopyIcon";
import toast from "react-hot-toast";
interface Props {
label: string;
value?: string | false;
copyIcon?: boolean;
}
function DataField({ label, value, copyIcon }: Props) {
const [, copyToClipboard] = useCopyToClipboard();
async function handleClickCopy() {
if (!value) return;
await copyToClipboard(value);
toast.success("Скопировано в буфер обмена");
}
return (
<div className="flex items-center justify-between gap-1">
<div className="space-y-1">
<p className="text-xs text-[#77828C]">{label}</p>
<p className="text-sm">{value}</p>
</div>
{copyIcon && (
<Button
variant="tertiary"
icon={<CopyIcon className="w-5 h-5" />}
onlyIcon
className="text-[#77828C]"
onClick={handleClickCopy}
/>
)}
</div>
);
}
export default DataField;
+3 -3
View File
@@ -18,10 +18,10 @@ function EmptyCard({ buildId, startAt, duration }: Props) {
<div className="w-[264px] h-[164px] text-sm font-semibold bg-[#F0F1F2] border-r border-b border-[#DAE0E5] flex items-center justify-center group">
{isAfter(startAt, new Date()) && (
<Button
color="tertiary"
variant="tertiary"
icon={<PlusIcon />}
className="group-hover:opacity-100 opacity-0"
handleClick={() =>
className="opacity-0 group-hover:opacity-100"
onClick={() =>
setModal(
<CreateScheduledSessionModal
buildId={buildId}
+1 -1
View File
@@ -25,7 +25,7 @@ function Header() {
{user?.accessToken ? (
<Button
type="button"
handleClick={logout}
onClick={logout}
className="text-black bg-transparent hover:bg-neutral-200"
>
Logout
+1 -1
View File
@@ -40,7 +40,7 @@ function Input({
value={value}
onChange={(e) => handleChange && handleChange(e.target.value)}
onFocus={handleFocus}
className={`px-3 py-2.5 outline-none rounded-lg border border-[#DAE0E5] focus:border-[#49A1F5] transition-colors text-sm ${className}`}
className={`px-3 py-2.5 outline-none rounded-lg ring-1 ring-[#DAE0E5] focus:ring-[#49A1F5] ring-inset transition-all text-sm ${className}`}
/>
);
}
+9 -16
View File
@@ -13,13 +13,13 @@ function Managers() {
return (
<div className="p-4 flex flex-col gap-4 border-b border-[#DAE0E5]">
<div className="flex justify-between items-center">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold">Менеджеры</p>
<Button
color="tertiary"
variant="tertiary"
icon={isShowManagers ? <ChevronUpIcon /> : <ChevronDownIcon />}
onlyIcon
handleClick={() => setIsShowManagers(!isShowManagers)}
onClick={() => setIsShowManagers(!isShowManagers)}
/>
</div>
@@ -29,32 +29,25 @@ function Managers() {
{managers.map((manager) => (
<div key={manager.id} className="flex justify-between">
<div className="flex items-center gap-2">
<img
src={manager.avatar || "/images/no-avatar.png"}
alt=""
className="w-8 h-8 rounded-full"
/>
<div className="w-8 h-8 rounded-full text-[10px] font-semibold flex items-center justify-center bg-[#E6ECF2]">
{user?.name.split(" ")[0][0]}
{user?.name.split(" ")[1][0]}
</div>
<p className="text-sm">{manager.name}</p>
</div>
{user?.role === "admin" && (
<Button
disabled
color="tertiary"
variant="tertiary"
onlyIcon
icon={<MoreIcon />}
handleClick={() => {}}
/>
)}
</div>
))}
</div>
{user?.role === "admin" && (
<Button
disabled
color="secondary"
className="w-full"
handleClick={() => {}}
>
<Button disabled variant="secondary" widthFull>
Добавить
</Button>
)}
+22 -6
View File
@@ -7,6 +7,9 @@ import WorkIcon from "./icons/WorkIcon";
import ExitIcon from "./icons/ExitIcon";
import useAuthStore from "../stores/useAuthStore";
import { useClickAway } from "@uidotdev/usehooks";
import useModalStore from "../stores/useModalStore";
import SettingsModal from "./modals/SettingsModal";
import CompanyModal from "./modals/CompanyModal";
function Menu() {
const [isShow, setIsShow] = useState<boolean>(false);
@@ -15,10 +18,22 @@ function Menu() {
setIsShow(false);
});
const { setModal } = useModalStore();
function logout() {
setUser(null);
}
function handleClickSettings() {
setIsShow(false);
setModal(<SettingsModal />);
}
function handleClickCompany() {
setIsShow(false);
setModal(<CompanyModal />);
}
return (
<div>
<span className="relative z-20 cursor-pointer">
@@ -36,8 +51,8 @@ function Menu() {
<Transition in={isShow} timeout={150} mountOnEnter unmountOnExit>
{(state) => (
<div className={`transition-opacity ${state}`}>
<div className="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-10"></div>
<div className={`${state}`}>
<div className="absolute top-0 left-0 z-20 w-full h-full bg-black bg-opacity-10"></div>
<div ref={ref} className="absolute z-20 ml-2 mt-3.5">
<div className="relative">
<svg
@@ -57,12 +72,13 @@ function Menu() {
<div className="border-b border-[#DAE0E5] p-6 flex flex-col items-center justify-center gap-4">
<div className="rounded-full bg-[#E6ECF2] w-[88px] h-[88px] flex justify-center items-center">
<p className="text-2xl font-semibold ml-0.5 mt-0.5">
{user?.name[0]}
{user?.name.split(" ")[0][0]}
{user?.name.split(" ")[1][0]}
</p>
</div>
<div className="space-y-1 text-center">
<p className="text-sm">{user?.name}</p>
<p className="text-[#77828C] text-xs">{user?.role}</p>
<p className="text-[#77828C] text-xs">{user?.username}</p>
</div>
</div>
<div className="border-b border-[#DAE0E5] py-3 space-y-2">
@@ -76,8 +92,8 @@ function Menu() {
Уведомления
</button>
<button
disabled
className="text-sm flex items-center gap-2 px-4 w-full hover:bg-[#E6ECF2] transition-colors disabled:hover:bg-inherit disabled:opacity-50"
onClick={handleClickSettings}
>
<span className="text-[#77828C] py-1">
<ParamsIcon />
@@ -87,8 +103,8 @@ function Menu() {
</div>
<div className="border-b border-[#DAE0E5] py-2">
<button
disabled
className="text-sm flex items-center gap-2 px-4 w-full hover:bg-[#E6ECF2] transition-colors disabled:hover:bg-inherit disabled:opacity-50"
onClick={handleClickCompany}
>
<span className="text-[#77828C] py-1">
<WorkIcon />
+1 -1
View File
@@ -17,7 +17,7 @@ function ModalContainer() {
{(state) => (
<div
onClick={() => setModal(null)}
className={`min-h-screen p-8 absolute top-0 left-0 w-full flex justify-center items-center bg-black bg-opacity-30 overflow-auto cursor-pointer transition-opacity ${state}`}
className={`min-h-screen p-8 absolute z-20 top-0 left-0 w-full flex justify-center items-center bg-black bg-opacity-30 overflow-auto cursor-pointer transition-opacity ${state}`}
>
<div onClick={(e) => e.stopPropagation()} className="cursor-default">
{modal}
+19
View File
@@ -0,0 +1,19 @@
interface Props {
active?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
function ModalTabButton({ active, children, onClick }: Props) {
return (
<button
type="button"
className={`py-3.5 text-sm font-semibold transition-[color] ${active ? "" : "text-[#77828C]"}`}
onClick={onClick}
>
{children}
</button>
);
}
export default ModalTabButton;
+27 -19
View File
@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ChangeEvent, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import Timeline from "./Timeline";
import { format, getDate, setDate, setHours, startOfDay } from "date-fns";
// import useEventStore from "../stores/useEventStore";
@@ -10,8 +10,9 @@ import Input from "./Input";
import Label from "./Label";
import api from "../utils/api";
import useStore from "../stores/useStore";
import Select2 from "./Select2";
import IScheduledSession from "../types/IScheduledSession";
import toast from "react-hot-toast";
import Select3 from "./Select3";
interface Props {
selectedDay: Date;
@@ -48,9 +49,9 @@ function Schedule({ selectedDay, slots, events }: Props) {
}
async function handleClickSave() {
console.log("slot", slot);
console.log("startAt", startAt);
console.log("duration", duration);
// console.log("slot", slot);
// console.log("startAt", startAt);
// console.log("duration", duration);
if (!slot || !startAt || !duration) return;
@@ -97,7 +98,8 @@ function Schedule({ selectedDay, slots, events }: Props) {
window.open(`${result.url}?admin=true`);
} catch (error) {
console.log(error);
// console.log(error);
toast.error((error as Error).message);
}
// setIsLoading(false);
@@ -108,14 +110,8 @@ function Schedule({ selectedDay, slots, events }: Props) {
if (!builds) return;
setSelectedBuild(builds[0]);
console.log(builds);
}, [builds]);
useEffect(() => {
console.log("events", events);
}, [events]);
useEffect(() => {
if (!dateForInstantStart) return;
@@ -199,10 +195,10 @@ function Schedule({ selectedDay, slots, events }: Props) {
: "Запланировать демонстрацию"}
</p>
<Button
color="tertiary"
variant="tertiary"
icon={<CloseIcon />}
onlyIcon
handleClick={handleClickCancel}
onClick={handleClickCancel}
/>
</div>
<div className="px-4 space-y-2">
@@ -210,7 +206,7 @@ function Schedule({ selectedDay, slots, events }: Props) {
<div className="">
<div className="py-1 space-y-1 text-xs">
<p className="text-[#77828C]">Выберите ЖК</p>
<Select2
{/* <Select2
defaultValue={selectedBuild?.build}
options={builds.map((build) => ({
value: build.build,
@@ -224,6 +220,18 @@ function Schedule({ selectedDay, slots, events }: Props) {
)!
)
}
/> */}
<Select3
defaultOption={
selectedBuild?.name ||
builds.find((build) => build.name)?.name
}
options={builds.map((build) => build.name)}
onSelect={(option) =>
setSelectedBuild(
builds.find((build) => build.name === option)!
)
}
/>
</div>
<div className="grid items-center grid-cols-2 gap-4 py-1 text-xs">
@@ -275,13 +283,13 @@ function Schedule({ selectedDay, slots, events }: Props) {
)}
</div>
<div className="flex gap-2 p-4">
<Button type="submit" handleClick={handleClickSave}>
<Button type="submit" onClick={handleClickSave}>
{dateForInstantStart ? "Начать" : "Запланировать"}
</Button>
<Button
type="button"
color="secondary"
handleClick={handleClickCancel}
variant="secondary"
onClick={handleClickCancel}
>
Отмена
</Button>
@@ -292,7 +300,7 @@ function Schedule({ selectedDay, slots, events }: Props) {
<div className="fixed top-28 right-[336px] z-10">
<Button
disabled={draftMode}
handleClick={() => setDateForInstantStart(new Date())}
onClick={() => setDateForInstantStart(new Date())}
>
Начать демонстрацию с мгновенным запуском
</Button>
+6 -6
View File
@@ -16,13 +16,13 @@ function Schedules() {
return (
<div className="p-4 flex flex-col gap-4 border-b border-[#DAE0E5]">
<div className="flex justify-between items-center">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold">Расписание</p>
<Button
color="tertiary"
variant="tertiary"
icon={isShowSchedules ? <ChevronUpIcon /> : <ChevronDownIcon />}
onlyIcon
handleClick={() => setIsShowSchedules(!isShowSchedules)}
onClick={() => setIsShowSchedules(!isShowSchedules)}
/>
</div>
@@ -30,7 +30,7 @@ function Schedules() {
<div className="space-y-4">
{schedules?.map((schedule) => (
<div key={schedule.id} className="flex flex-col gap-3 text-xs">
<p className="font-semibold flex gap-1">
<p className="flex gap-1 font-semibold">
<span>
Действует с{" "}
{format(new Date(schedule.startDate), "dd.MM.yyyy")}
@@ -68,9 +68,9 @@ function Schedules() {
{user?.role === "admin" && (
<Button
color="secondary"
variant="secondary"
className="w-full"
handleClick={() => setModal(<CreateScheduleModal />)}
onClick={() => setModal(<CreateScheduleModal />)}
>
Добавить
</Button>
+73
View File
@@ -0,0 +1,73 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
import ChevronDownIcon from "./icons/ChevronDownIcon";
import { useClickAway } from "@uidotdev/usehooks";
import CheckIcon from "./icons/CheckIcon";
interface Props {
defaultOption?: string;
options: string[];
onSelect: (option: string) => void;
}
function Select3({ defaultOption, options, onSelect }: Props) {
const [showOptions, setShowOptions] = useState<boolean>(false);
const [selectedOption, setSelectedOption] = useState<string>();
const ref = useClickAway<HTMLDivElement>(() => {
setShowOptions(false);
});
function handleSelect(option: string) {
setSelectedOption(option);
onSelect(option);
setShowOptions(false);
}
useEffect(() => {
if (!defaultOption) return;
handleSelect(defaultOption);
}, [defaultOption]);
return (
<div ref={ref} className="relative">
<div
className="relative flex items-center justify-end"
onClick={() => setShowOptions((prev) => !prev)}
>
<input
type="text"
readOnly
value={defaultOption || selectedOption}
className="text-sm px-3 py-2.5 h-10 ring-1 ring-inset ring-[#DAE0E5] rounded-lg w-full outline-none pr-8 cursor-pointer"
/>
<div className="absolute pr-2 cursor-pointer">
<div className="w-5 h-5">
<ChevronDownIcon />
</div>
</div>
</div>
{showOptions && (
<div className="absolute w-full py-2 mt-2 bg-white rounded-lg shadow">
{options.map((option, index) => (
<button
key={index}
className="px-4 py-2 hover:bg-[#E6ECF2] transition-colors w-full flex items-center justify-between"
onClick={() => handleSelect(option)}
>
<p className="text-sm">{option}</p>
{option === selectedOption && (
<div className="w-5 h-5 text-[#49A1F5]">
<CheckIcon />
</div>
)}
</button>
))}
</div>
)}
</div>
);
}
export default Select3;
+4 -4
View File
@@ -2,21 +2,21 @@ interface TabButtonProps {
children: React.ReactNode;
active?: boolean;
className?: string;
handleClick?: () => void;
onClick?: () => void;
}
function TabButton({
children,
active = false,
className,
handleClick,
onClick,
}: TabButtonProps) {
return (
<button
className={`px-6 py-4 text-sm font-semibold leading-none transition-colors ${
className={`px-6 py-3.5 text-sm font-semibold transition-colors ${
active ? "bg-white" : "hover:bg-neutral-200"
} ${className}`}
onClick={handleClick}
onClick={onClick}
>
{children}
</button>
+1 -1
View File
@@ -119,7 +119,7 @@ function Timeline({
setStartAt(undefined);
setDuration(0);
ref.current.style.height = "0px";
console.log(ref.current.clientHeight);
// console.log(ref.current.clientHeight);
}, [draftMode]);
return (
@@ -1,8 +1,6 @@
function CheckIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@@ -1,12 +1,6 @@
function ChevronDownIcon() {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7 11L12 16L17 11"
stroke="currentColor"
+15 -18
View File
@@ -1,18 +1,15 @@
const SVGComponent = () => (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13 7L8 12L13 17"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export default SVGComponent;
function ChevronLeftIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13 7L8 12L13 17"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export default ChevronLeftIcon;
@@ -1,16 +1,10 @@
function ChevronRightIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M11 7L16 12L11 17"
stroke="currentColor"
strokeWidth="2"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
@@ -1,12 +1,6 @@
function ChevronUpIcon() {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7 13L12 8L17 13"
stroke="currentColor"
+7 -4
View File
@@ -1,16 +1,19 @@
function CloseIcon() {
interface Props {
className?: string;
}
function CloseIcon({ className = "" }: Props) {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M12 11.9999L17.6569 6.34331M12 11.9999L6.34312 6.34302M12 11.9999L17.6568 17.6567M12 11.9999L6.34302 17.6568"
stroke="currentColor"
strokeWidth="1.5"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
+25
View File
@@ -0,0 +1,25 @@
interface Props {
className?: string;
}
function CopyIcon({ className = "" }: Props) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M6 17.3333V6C6 4.89543 6.89543 4 8 4H15.4286M9.42857 7.55556H17C17.5523 7.55556 18 8.00327 18 8.55556V19C18 19.5523 17.5523 20 17 20H10.4286C9.87629 20 9.42857 19.5523 9.42857 19V7.55556Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
/>
</svg>
);
}
export default CopyIcon;
+11 -6
View File
@@ -1,15 +1,20 @@
function MoreIcon() {
interface Props {
className?: string;
}
function MoreIcon({ className = "" }: Props) {
return (
<svg
width="24"
height="24"
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<circle cx="5" cy="12" r="1.5" fill="currentColor" />
<circle cx="12" cy="12" r="1.5" fill="currentColor" />
<circle cx="19" cy="12" r="1.5" fill="currentColor" />
<circle cx={5} cy={12} r={1.5} fill="currentColor" />
<circle cx={12} cy={12} r={1.5} fill="currentColor" />
<circle cx={19} cy={12} r={1.5} fill="currentColor" />
</svg>
);
}
@@ -0,0 +1,39 @@
interface Props {
className?: string;
}
function RotateIcon({ className = "" }: Props) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_1287_60293)">
<g clipPath="url(#clip1_1287_60293)">
<path
d="M20.3294 8.58537C19.7506 7.16982 18.8029 5.88296 17.5061 4.8802C13.5739 1.83969 7.92145 2.5625 4.88094 6.49465L4.26923 7.28573M3.67201 15.4146C4.25081 16.8301 5.19856 18.117 6.49538 19.1198C10.4275 22.1603 16.08 21.4375 19.1205 17.5053L19.7322 16.7142M4.26923 7.28573L3.91047 4.48015M4.26923 7.28573L7.07481 6.92697M19.7322 16.7142L16.9266 17.073M19.7322 16.7142L20.091 19.5198"
stroke="currentColor"
strokeWidth={1.5}
/>
</g>
</g>
<defs>
<clipPath id="clip0_1287_60293">
<rect width={24} height={24} fill="currentColor" />
</clipPath>
<clipPath id="clip1_1287_60293">
<rect
width={24}
height={24}
fill="currentColor"
transform="translate(9.84766 -4.8335) rotate(37.7128)"
/>
</clipPath>
</defs>
</svg>
);
}
export default RotateIcon;
@@ -0,0 +1,143 @@
import { useState } from "react";
import Button from "../Button";
import CloseIcon from "../icons/CloseIcon";
import useModalStore from "../../stores/useModalStore";
import DataField from "../DataField";
import useAuthStore from "../../stores/useAuthStore";
import MoreIcon from "../icons/MoreIcon";
import useStore from "../../stores/useStore";
import ModalTabButton from "../ModalTabButton";
function CompanyModal() {
const [selectedTab, setSelectedTab] = useState<"company" | "employees">(
"company"
);
const { user } = useAuthStore();
const { setModal } = useModalStore();
const { company, managers } = useStore();
return (
<div className="bg-white rounded-lg w-[624px] h-[408px] flex flex-col">
<div className="flex items-center justify-between pl-6 border-b border-[#DAE0E5]">
<div className="flex gap-6">
<ModalTabButton
active={selectedTab === "company"}
onClick={() => setSelectedTab("company")}
>
Компания
</ModalTabButton>
<ModalTabButton
active={selectedTab === "employees"}
onClick={() => setSelectedTab("employees")}
>
Сотрудники
</ModalTabButton>
</div>
<div className="p-2">
<Button
variant="tertiary"
icon={<CloseIcon />}
onlyIcon
onClick={() => setModal(null)}
/>
</div>
</div>
{selectedTab === "company" && (
<div>
<div className="flex gap-6 p-6 border-b border-[#DAE0E5]">
<div className="min-w-[88px] min-h-[88px] w-[88px] h-[88px] bg-[#E6ECF2] rounded-full"></div>
<div className="w-full space-y-6">
<div className="grid grid-cols-2 gap-6">
<div className="space-y-4">
<DataField label="Сайт" value={company?.site} copyIcon />
<DataField label="Телефон" value={company?.phone} copyIcon />
</div>
<div className="space-y-4">
<DataField label="Email" value={company?.email} copyIcon />
<DataField label="Адрес" value={company?.address} copyIcon />
</div>
</div>
<div>
<a href="#" className="text-[#49A1F5] text-xs">
Сообщить о проблеме
</a>
</div>
</div>
</div>
{/* <div className="p-6 space-y-4">
<div className="space-y-4">
<p className="text-xs font-semibold">Проекты</p>
{Array.from({ length: 3 }).map((_, index) => (
<div key={index} className="flex items-center gap-2">
<div className="w-8 h-8 bg-[#E6ECF2] rounded-full"></div>
<p className="text-xs font-semibold"></p>
</div>
))}
</div>
<div className=""></div>
</div> */}
</div>
)}
{selectedTab === "employees" && (
<>
<div className="grid grid-cols-8 pt-6 pb-2 mx-6 border-b">
<div className="col-span-3">
<p className="text-xs font-semibold">Имя</p>
</div>
<div className="col-span-2">
<p className="text-xs font-semibold">Должность</p>
</div>
<div className="col-span-2">
<p className="text-xs font-semibold">Телефон</p>
</div>
<div className=""></div>
</div>
<div className="flex-1 mx-6 py-4 space-y-3 overflow-y-scroll border-b border-[#DAE0E5]">
{managers.map((manager, index) => (
<div key={index} className="grid items-center grid-cols-8">
<div className="col-span-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-[#E6ECF2] rounded-full flex items-center justify-center">
<p className="text-[10px] font-semibold">
{user?.name.split(" ")[0][0]}
{user?.name.split(" ")[1][0]}
</p>
</div>
<div className="">
<p className="text-sm">{manager.name}</p>
<p className="text-xs text-[#77828C]">
{manager.username}
</p>
</div>
</div>
</div>
<div className="col-span-2">
<p className="text-xs">{manager.position}</p>
</div>
<div className="col-span-2">
<p className="text-xs">{manager.phone}</p>
</div>
<div className="flex justify-center">
{user?.role === "admin" && (
<Button
variant="tertiary"
icon={<MoreIcon className="w-5 h-5" />}
onlyIcon
/>
)}
</div>
</div>
))}
</div>
<div className="px-6 py-4">
<Button disabled variant="secondary">
Добавить
</Button>
</div>
</>
)}
</div>
);
}
export default CompanyModal;
@@ -47,7 +47,7 @@ function CreateBuildModal({ companyId }: Props) {
}
return (
<div className="bg-white p-8 space-y-4 rounded-xl">
<div className="p-8 space-y-4 bg-white rounded-xl">
<p className="text-xl font-semibold">Создание ЖК</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<Input
@@ -70,8 +70,8 @@ function CreateBuildModal({ companyId }: Props) {
handleChange={(value) => setSessionLimit(+value)}
required
/>
<div className="self-end flex gap-2">
<Button color="secondary" handleClick={() => setModal(null)}>
<div className="flex self-end gap-2">
<Button variant="secondary" onClick={() => setModal(null)}>
Отмена
</Button>
<Button type="submit">Сохранить</Button>
@@ -38,7 +38,7 @@ function CreateCompanyModal() {
}
return (
<div className="bg-white p-8 space-y-4 rounded-xl">
<div className="p-8 space-y-4 bg-white rounded-xl">
<p className="text-xl font-semibold">Создание компании</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<Input
@@ -48,8 +48,8 @@ function CreateCompanyModal() {
autoFocus
required
/>
<div className="self-end flex gap-2">
<Button color="secondary" handleClick={() => setModal(null)}>
<div className="flex self-end gap-2">
<Button variant="secondary" onClick={() => setModal(null)}>
Отмена
</Button>
<Button type="submit">Сохранить</Button>
@@ -114,11 +114,10 @@ function CreateScheduleModal() {
<div className="border-b border-[#DAE0E5] pl-2 pr-3 h-12 flex justify-between items-center">
<p className="p-4 font-semibold leading-[115%]">Создание расписания</p>
<Button
color="secondary"
onlyIcon
variant="secondary"
icon={<CloseIcon />}
className="bg-transparent"
handleClick={() => setModal(null)}
onlyIcon
onClick={() => setModal(null)}
/>
</div>
@@ -265,7 +264,7 @@ function CreateScheduleModal() {
{/* <div className="p-4 border-b border-[#DAE0E5] ">
<p className="text-sm font-semibold">Статистика</p>
</div> */}
<div className="p-4 flex flex-col gap-4">
<div className="flex flex-col gap-4 p-4">
<p className="text-sm font-semibold">Предварительный просмотр</p>
<div className="flex flex-col gap-3 text-xs">
<div className="flex flex-col gap-2">
@@ -291,7 +290,7 @@ function CreateScheduleModal() {
</div>
{/* <div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">Выходные дни</p>
<p className="flex gap-1 flex-wrap">
<p className="flex flex-wrap gap-1">
{weekends.map((value) => (
<span>{value}</span>
))}
@@ -303,9 +302,9 @@ function CreateScheduleModal() {
</div>
</div>
<div className="px-6 py-3 flex gap-2">
<Button handleClick={handleClickCreateSchedule}>Создать</Button>
<Button color="secondary" handleClick={() => setModal(null)}>
<div className="flex gap-2 px-6 py-3">
<Button onClick={handleClickCreateSchedule}>Создать</Button>
<Button variant="secondary" onClick={() => setModal(null)}>
Отмена
</Button>
</div>
@@ -58,10 +58,10 @@ function CreateScheduledSessionModal({ buildId, startAt, duration }: Props) {
<p className="text-sm font-semibold">Запланировать демонстрацию</p>
<span className="text-[#77828C]">
<Button
color="tertiary"
variant="tertiary"
icon={<CloseIcon />}
onlyIcon
handleClick={() => setModal(null)}
onClick={() => setModal(null)}
/>
</span>
</div>
@@ -137,7 +137,7 @@ function CreateScheduledSessionModal({ buildId, startAt, duration }: Props) {
<Button disabled={isLoading} type="submit">
Запланировать
</Button>
<Button color="secondary" handleClick={() => setModal(null)}>
<Button variant="secondary" onClick={() => setModal(null)}>
Отмена
</Button>
</div>
@@ -93,7 +93,7 @@ function CreateUserModal({ companyId }: Props) {
}, [builds]);
return (
<div className="bg-white p-8 space-y-4 rounded-xl">
<div className="p-8 space-y-4 bg-white rounded-xl">
<p className="text-xl font-semibold">Создание пользователя</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<Input
@@ -117,7 +117,7 @@ function CreateUserModal({ companyId }: Props) {
required
/>
<select
className="px-3 py-2 outline-none border border-gray-300 rounded-lg"
className="px-3 py-2 border border-gray-300 rounded-lg outline-none"
defaultValue={role}
onChange={(e) => setRole(e.target.selectedOptions[0].value)}
>
@@ -126,7 +126,7 @@ function CreateUserModal({ companyId }: Props) {
))}
</select>
<select
className="px-3 py-2 outline-none border border-gray-300 rounded-lg"
className="px-3 py-2 border border-gray-300 rounded-lg outline-none"
onChange={handleChangeBuilds}
multiple
>
@@ -135,8 +135,8 @@ function CreateUserModal({ companyId }: Props) {
<option value={build.id}>{build.name}</option>
))}
</select>
<div className="self-end flex gap-2">
<Button color="secondary" handleClick={() => setModal(null)}>
<div className="flex self-end gap-2">
<Button variant="secondary" onClick={() => setModal(null)}>
Отмена
</Button>
<Button type="submit">Сохранить</Button>
@@ -119,7 +119,7 @@ function EditUserModal({ companyId, userId }: Props) {
}, [user, builds, buildIds]);
return (
<div className="bg-white p-8 space-y-4 rounded-xl">
<div className="p-8 space-y-4 bg-white rounded-xl">
<p className="text-xl font-semibold">Редактирование</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<Input
@@ -137,7 +137,7 @@ function EditUserModal({ companyId, userId }: Props) {
required
/>
<select
className="px-3 py-2 outline-none border border-gray-300 rounded-lg"
className="px-3 py-2 border border-gray-300 rounded-lg outline-none"
value={role}
onChange={(e) => setRole(e.target.selectedOptions[0].value)}
>
@@ -146,7 +146,7 @@ function EditUserModal({ companyId, userId }: Props) {
))}
</select>
<select
className="px-3 py-2 outline-none border border-gray-300 rounded-lg"
className="px-3 py-2 border border-gray-300 rounded-lg outline-none"
onChange={handleChangeBuilds}
multiple
>
@@ -160,8 +160,8 @@ function EditUserModal({ companyId, userId }: Props) {
</option>
))}
</select>
<div className="self-end flex gap-2">
<Button color="secondary" handleClick={() => setModal(null)}>
<div className="flex self-end gap-2">
<Button variant="secondary" onClick={() => setModal(null)}>
Отмена
</Button>
<Button type="submit">Сохранить</Button>
@@ -0,0 +1,124 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { FormEvent, useState } from "react";
import useAuthStore from "../../stores/useAuthStore";
import useModalStore from "../../stores/useModalStore";
import Button from "../Button";
import CloseIcon from "../icons/CloseIcon";
import Input from "../Input";
import Label from "../Label";
import api from "../../utils/api";
import toast from "react-hot-toast";
import DataField from "../DataField";
function SettingsModal() {
const { setModal } = useModalStore();
const { user } = useAuthStore();
const [oldPassword, setOldPassword] = useState<string>("");
const [newPassword, setNewPassword] = useState<string>("");
const [loading, setLoading] = useState(false);
async function handleSubmitChangePassword(e: FormEvent) {
e.preventDefault();
setLoading(true);
await changePassword();
setLoading(false);
}
async function changePassword() {
try {
const result: any = await api
.post("changePassword", {
json: {
oldPassword,
newPassword,
},
})
.json();
if ("error" in result) {
toast.error(result.error);
return;
}
toast.success("Пароль успешно изменен");
} catch (error) {
toast.error((error as Error).message);
}
}
return (
<div className="bg-white rounded-lg w-[440px]">
<div className="flex items-center justify-between px-2 border-b border-[#DAE0E5]">
<div className="flex">
<p className="p-3.5 text-sm font-semibold">Профиль</p>
</div>
<Button
variant="tertiary"
icon={<CloseIcon className="w-5 h-5" />}
onlyIcon
onClick={() => setModal(null)}
/>
</div>
<div className="flex items-start gap-6 p-6 border-b border-[#DAE0E5]">
<div className="min-w-[88px] min-h-[88px] flex items-center justify-center bg-[#E6ECF2] rounded-full">
<p className="text-2xl font-semibold">
{user?.name.split(" ")[0][0]}
{user?.name.split(" ")[1][0]}
</p>
</div>
<div className="w-full space-y-4">
<DataField label="Имя" value={user?.name} copyIcon />
<DataField label="Email" value={user?.username} copyIcon />
{/* <DataField label="Телефон" copyIcon /> */}
<DataField label="Должность" value={user?.position} />
<div className="flex gap-2">
<Button disabled variant="secondary">
Статистика
</Button>
{/* <Button color="tertiary">Выйти из аккаунта</Button> */}
</div>
</div>
</div>
{/* <div className=""></div> */}
<div className="flex gap-6 p-6">
<div className="min-w-[88px] w-[88px]">
<p className="text-xs font-semibold">Безопасность</p>
</div>
<form
className="w-full space-y-3"
onSubmit={handleSubmitChangePassword}
>
<div className="space-y-1">
<Label value="Старый пароль" />
<Input
type="password"
required
className="w-full"
value={oldPassword}
handleChange={(value) => setOldPassword(value)}
/>
</div>
<div className="space-y-1">
<Label value="Новый пароль" />
<Input
type="password"
required
className="w-full"
value={newPassword}
handleChange={(value) => setNewPassword(value)}
/>
</div>
<Button
disabled={!oldPassword || !newPassword || loading}
type="submit"
>
Изменить
</Button>
</form>
</div>
</div>
);
}
export default SettingsModal;
+13 -13
View File
@@ -75,13 +75,13 @@ function AdminCompanyPage() {
}, [users, builds]);
return (
<div className="min-h-screen flex flex-col gap-8 bg-gray-100 p-8">
<div className="flex flex-col min-h-screen gap-8 p-8 bg-gray-100">
<div className="flex gap-8">
<Button
color="secondary"
variant="secondary"
size="medium"
className="border border-gray-300"
handleClick={() => navigate("..")}
onClick={() => navigate("..")}
>
Назад
</Button>
@@ -90,23 +90,23 @@ function AdminCompanyPage() {
<div className="flex items-center gap-4">
<p className="text-xl font-semibold">Жилые комплексы</p>
<Button
handleClick={() =>
onClick={() =>
companyId && setModal(<CreateBuildModal companyId={companyId} />)
}
>
Добавить ЖК
</Button>
</div>
<div className="grid grid-cols-4 gap-4 border-b border-gray-300 pb-8">
<div className="grid grid-cols-4 gap-4 pb-8 border-b border-gray-300">
{builds &&
builds.map((build) => (
<div className="relative bg-white rounded-xl overflow-hidden">
<div className="relative overflow-hidden bg-white rounded-xl">
<img
src=""
alt=""
className="aspect-video bg-gray-400 object-cover"
className="object-cover bg-gray-400 aspect-video"
/>
<div className="absolute bottom-4 left-6 text-white text-left">
<div className="absolute text-left text-white bottom-4 left-6">
<p className="text-xl font-semibold">{build.name}</p>
<p className="">
Сборка приложения:{" "}
@@ -121,11 +121,11 @@ function AdminCompanyPage() {
))}
</div>
</div>
<div className="space-y-4 border-b border-gray-300 pb-8">
<div className="pb-8 space-y-4 border-b border-gray-300">
<div className="flex items-center gap-4">
<p className="text-xl font-semibold">Пользователи</p>
<Button
handleClick={() =>
onClick={() =>
companyId && setModal(<CreateUserModal companyId={companyId} />)
}
>
@@ -143,7 +143,7 @@ function AdminCompanyPage() {
<img
src={user.avatar || "/images/no-avatar.png"}
alt=""
className="w-8 h-8 rounded-full bg-gray-500"
className="w-8 h-8 bg-gray-500 rounded-full"
/>
<div className="text-sm">
@@ -161,10 +161,10 @@ function AdminCompanyPage() {
</div>
<Button
color="secondary"
variant="secondary"
icon={<MoreIcon />}
onlyIcon
handleClick={() =>
onClick={() =>
companyId &&
setModal(
<EditUserModal companyId={companyId} userId={user.id} />
+5 -5
View File
@@ -46,10 +46,10 @@ function AdminPage() {
}, [companies]);
return (
<div className="min-h-screen flex flex-col gap-8 bg-gray-100 p-8">
<div className="flex flex-col min-h-screen gap-8 p-8 bg-gray-100">
<div className="flex items-center gap-4">
<p className="text-xl font-semibold">Компании</p>
<Button handleClick={() => setModal(<CreateCompanyModal />)}>
<Button onClick={() => setModal(<CreateCompanyModal />)}>
Добавить компанию
</Button>
</div>
@@ -57,15 +57,15 @@ function AdminPage() {
{companies &&
companies.map((company) => (
<button
className="relative bg-white rounded-xl overflow-hidden"
className="relative overflow-hidden bg-white rounded-xl"
onClick={() => navigate(`${company.id}`)}
>
<img
src=""
alt=""
className="aspect-video bg-gray-400 object-cover"
className="object-cover bg-gray-400 aspect-video"
/>
<p className="text-xl font-semibold text-white absolute bottom-4 left-6">
<p className="absolute text-xl font-semibold text-white bottom-4 left-6">
{company.name}
</p>
</button>
+15 -111
View File
@@ -17,13 +17,13 @@ import IUser from "../types/IUser";
import IError from "../types/IError";
import Schedule from "../components/Schedule";
import IScheduledSession from "../types/IScheduledSession";
import toast, { Toaster } from "react-hot-toast";
function DashboardPage() {
const { user } = useAuthStore();
const {
company,
setCompany,
selectedBuild,
builds,
setBuilds,
managers,
@@ -55,7 +55,7 @@ function DashboardPage() {
async function getCompany() {
if (!user) {
console.log("No User", user);
// console.log("No User", user);
return;
}
@@ -64,15 +64,13 @@ function DashboardPage() {
setCompany(result);
} catch (error) {
if (error instanceof Error) {
console.log("Error: ", error.message);
}
toast.error((error as Error).message);
}
}
async function getBuilds() {
if (!user) {
console.log("No User", user);
// console.log("No User", user);
return;
}
@@ -83,20 +81,18 @@ function DashboardPage() {
setBuilds(result);
} catch (error) {
if (error instanceof Error) {
console.log("Error: ", error.message);
}
toast.error((error as Error).message);
}
}
async function getManagers() {
if (!company || !selectedBuild) {
if (!company) {
return;
}
try {
const result: IUser[] | IError = await api
.get(`users?companyId=${company.id}&buildIds=${selectedBuild.id}`)
.get(`companies/${company.id}/users`)
.json();
if ("error" in result) {
@@ -104,8 +100,6 @@ function DashboardPage() {
return;
}
console.log("result", result);
setManagers(result);
} catch (error) {
alert((error as Error).message);
@@ -114,7 +108,7 @@ function DashboardPage() {
async function getScheduledSessions(useLoader?: boolean) {
if (!company) {
console.log("No ScheduledSessions");
// console.log("No ScheduledSessions");
return;
}
@@ -129,13 +123,9 @@ function DashboardPage() {
)
.json();
console.log(result);
setScheduledSessions(result);
} catch (error) {
if (error instanceof Error) {
console.log("Error: ", error.message);
}
toast.error((error as Error).message);
}
if (useLoader) setIsLoadingScheduledSessions(false);
@@ -200,110 +190,23 @@ function DashboardPage() {
</p>
<div className="flex gap-1">
<Button
handleClick={selectPrevDay}
icon={<ChevronLeftIcon />}
color="secondary"
variant="secondary"
onlyIcon
onClick={selectPrevDay}
/>
<Button
handleClick={selectNextDay}
variant="secondary"
icon={<ChevronRightIcon />}
color="secondary"
onlyIcon
onClick={selectNextDay}
/>
</div>
<Button handleClick={selectCurrentDate}>Сегодня</Button>
<Button onClick={selectCurrentDate}>Сегодня</Button>
</div>
</div>
{/* <div className="flex bg-[#F2F2F2]">
<div className="w-[84px] h-[40px] flex justify-center items-center text-sm font-semibold bg-white border-r border-b border-[#DAE0E5]">
{currentTime}
</div>
{selectedBuild &&
[...Array(selectedBuild.sessionLimit)].map((_, index) => (
<div key={index}>
<div className="w-[264px] h-[40px] px-3 flex items-center text-sm font-semibold bg-[#F0F1F2] border-r border-b border-[#DAE0E5]">
Слот {index + 1}
</div>
</div>
))}
</div> */}
{/* <div
ref={scheduledSessionsRef}
className={`overflow-y-auto overflow-x-hidden flex-1 bg-[#F2F2F2] border-r border-[#DAE0E5]`}
>
<Transition
in={isLoadingScheduledSessions}
timeout={150}
mountOnEnter
unmountOnExit
>
{(state) => (
<div
className={`fixed z-10 top-0 left-0 w-full h-full bg-black bg-opacity-80 transition-opacity flex justify-center items-center text-white ${state}`}
>
<SpinnerIcon />
</div>
)}
</Transition>
{generatedScheduledSessions?.map(
(generatedScheduledSession, index) => (
<div key={index} className="flex">
<div className="w-[84px] h-[164px] flex justify-center items-center text-sm font-semibold bg-white border-r border-b border-[#DAE0E5]">
<p>{format(generatedScheduledSession.time, "HH:mm")}</p>
</div>
<div className="flex">
{company &&
managers &&
selectedBuild &&
generatedScheduledSession.sessions.map(
(session: any, index2: number) => {
const selectedManager = managers.find(
(manager) => manager.id == session.userId
);
if (!_.isEmpty(session)) {
return (
<Card
key={index2}
companyId={company.id}
buildId={selectedBuild.id}
scheduledSessionId={session.id}
scheduleSessionStartAt={session.startAt}
client={session.client}
manager={selectedManager}
managers={managers}
handleSelect={(scheduledSessionId, managerId) =>
updateScheduledSessionManager(
scheduledSessionId,
managerId
)
}
/>
);
} else {
return (
<EmptyCard
key={index2}
buildId={selectedBuild?.id}
startAt={generatedScheduledSession.time}
duration={duration!}
/>
);
}
}
)}
</div>
</div>
)
)}
</div> */}
{company && scheduledSessions && (
<Schedule
selectedDay={selectedDay}
@@ -332,6 +235,7 @@ function DashboardPage() {
</div>
<ModalContainer />
<Toaster />
</div>
);
}
+6 -11
View File
@@ -7,7 +7,7 @@ import Input from "../components/Input";
import Button from "../components/Button";
import { Link, useNavigate } from "react-router-dom";
import useAuthStore from "../stores/useAuthStore";
import toast from "react-hot-toast";
import toast, { Toaster } from "react-hot-toast";
import IError from "../types/IError";
import IUser from "../types/IUser";
import api from "../utils/api";
@@ -32,19 +32,11 @@ function LoginPage() {
if ("error" in result) {
setLoading(false);
toast.error(`Error: ${result.error}`);
toast.error(result.error);
return;
}
setUser({
id: result.id,
username: "username",
accessToken: result.accessToken,
companyId: result.companyId,
name: result.name,
role: result.role,
buildIds: result.buildIds,
});
setUser(result);
navigate("/dashboard");
} catch (error) {
@@ -88,6 +80,7 @@ function LoginPage() {
size="medium"
className="mt-10"
loading={loading}
widthFull
>
Войти
</Button>
@@ -102,6 +95,8 @@ function LoginPage() {
</div>
</div>
</div>
<Toaster />
</div>
);
}
+3 -3
View File
@@ -3,8 +3,8 @@ import Button from "../components/Button";
function RegistrationPage() {
return (
<div className="min-h-screen flex flex-col justify-center items-center p-8 flex-1">
<div className=" bg-white rounded-lg shadow-md overflow-hidden">
<div className="flex flex-col items-center justify-center flex-1 min-h-screen p-8">
<div className="overflow-hidden bg-white rounded-lg shadow-md ">
<div className="flex flex-col gap-6 p-12 w-[528px]">
<p className="text-2xl font-semibold">Получение данных для входа</p>
@@ -22,7 +22,7 @@ function RegistrationPage() {
</div>
<Link to="/login">
<Button size="medium" color="secondary">
<Button variant="secondary" size="medium">
Назад
</Button>
</Link>
+5
View File
@@ -2,6 +2,11 @@ interface ICompany {
id: string;
name: string;
sessionLimit: number;
site?: string;
email?: string;
phone?: string;
address?: string;
avatar?: string;
}
export default ICompany;
+2
View File
@@ -7,6 +7,8 @@ interface IUser {
name: string;
role: string;
buildIds?: string[];
position?: string;
phone?: string;
}
export default IUser;
-3
View File
@@ -2,7 +2,6 @@
import ky from "ky";
import useAuthStore from "../stores/useAuthStore";
import IError from "../types/IError";
import toast from "react-hot-toast";
const api = ky.extend({
prefixUrl: import.meta.env.VITE_API_URL,
@@ -38,8 +37,6 @@ const api = ky.extend({
} catch (error) {
window.location.href = "/login";
}
} else {
toast.error(response.statusText);
}
},
],