added notification modal window
This commit is contained in:
+8
-7
@@ -1,13 +1,14 @@
|
||||
import CompanyInfoModal from './components/CompanyInfoModal/CompanyInfoModal'
|
||||
import './App.css'
|
||||
// import CompanyInfoModal from "./components/CompanyInfoModal/CompanyInfoModal";
|
||||
import NotificationModal from "./components/NotificationModal/NotificationModal";
|
||||
import "./App.css";
|
||||
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<div className='flex justify-center border border-black h-[100vh]'>
|
||||
<CompanyInfoModal />
|
||||
<div className="flex justify-center border border-black h-screen">
|
||||
{/* <CompanyInfoModal /> */}
|
||||
{/* <NotificationModal /> */}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
||||
@@ -12,8 +12,8 @@ const CompanyInfoItem = ({ companyInfoItem }: CompanyInfoItemProps) => {
|
||||
return (
|
||||
<div className="flex w-[100%] justify-between">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-[#77828C]">{label}</div>
|
||||
<div>{title}</div>
|
||||
<div className="text-[#77828C] text-[12px]">{label}</div>
|
||||
<div className="text-[14px]">{title}</div>
|
||||
</div>
|
||||
<IconWrapper className="self-center text-[#77828C] cursor-pointer">
|
||||
<CopyIcon />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import IconWrapper from "../../icons/IconWrapper/IconWrapper";
|
||||
import CrossIcon from "../../icons/CrossIcon/CrossIcon";
|
||||
import CompanyInfoItem from "../CompanyInfoItem/CompanyInfoItem";
|
||||
import EmployeesList from "../EmployeesList/EmployeesList";
|
||||
import ICompanyEmployee from "../../types/companyEmployee";
|
||||
import ICompanyInfoItem from "../../types/companyInfoItem";
|
||||
@@ -12,27 +11,41 @@ import IProjectItem from "../../types/projectItem";
|
||||
const companyEmployeeItems: ICompanyEmployee[] = [
|
||||
{
|
||||
name: "Анастасия Кошкина",
|
||||
status: "В сети",
|
||||
status: "online",
|
||||
image: "Employee.png",
|
||||
id: "1",
|
||||
},
|
||||
{
|
||||
name: "Анастасия Кошкина",
|
||||
status: "Не в сети",
|
||||
status: "offline",
|
||||
image: "Employee.png",
|
||||
id: "2",
|
||||
},
|
||||
{
|
||||
name: "Анастасия Кошкина",
|
||||
status: "На демонстрации",
|
||||
status: "at demonstration",
|
||||
image: "Employee.png",
|
||||
id: "3",
|
||||
},
|
||||
];
|
||||
|
||||
const projectItems: IProjectItem[] = [
|
||||
{ title: "ЖК «Акватория»", image: "building1.png", id: "1" },
|
||||
{ title: "ЖК «Новатор»", image: "building2.png", id: "2" },
|
||||
{
|
||||
title: "ЖК «Акватория»",
|
||||
image: "building1.png",
|
||||
id: "1",
|
||||
subscriptionSessionCount: 3,
|
||||
subscriptionUntil: "2023-11-07T07:54:58.392+00:00",
|
||||
applicationVersion: "1.23",
|
||||
},
|
||||
{
|
||||
title: "ЖК «Новатор»",
|
||||
image: "building2.png",
|
||||
id: "2",
|
||||
subscriptionSessionCount: 3,
|
||||
subscriptionUntil: "2023-11-07T07:54:58.392+00:00",
|
||||
applicationVersion: "1.23",
|
||||
},
|
||||
];
|
||||
|
||||
const companyInfoItems: ICompanyInfoItem[] = [
|
||||
@@ -45,6 +58,12 @@ const companyInfoItems: ICompanyInfoItem[] = [
|
||||
const CompanyInfo = () => {
|
||||
const [tab, setTab] = useState<"Сотрудники" | "Проекты">("Сотрудники");
|
||||
|
||||
const handleOnTabClick = (currentTab: "Сотрудники" | "Проекты") => {
|
||||
return () => {
|
||||
setTab(currentTab);
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white w-[716px] h-[520px] m-auto rounded-[8px]">
|
||||
<div className="p-4 flex justify-between border-b border-b-[#DAE0E5]">
|
||||
@@ -58,24 +77,30 @@ const CompanyInfo = () => {
|
||||
<div className="w-[88px] h-[88px] bg-[#D9D9D9] rounded-full"></div>
|
||||
<div className="w-[260px] flex flex-col gap-4">
|
||||
<CompanyInfoList companyInfoItems={companyInfoItems} />
|
||||
<button className="text-left outline-none text-[#49A1F5]">
|
||||
<button className="text-left outline-none text-[#49A1F5] text-[12px]">
|
||||
Сообщить о проблеме
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="h-12 border-b border-b-[#DAE0E5] flex px-4 gap-6 font-semibold">
|
||||
<div className="border-b border-b-[#DAE0E5] flex px-4 gap-6 font-semibold text-[14px] h-fit">
|
||||
<button
|
||||
className={`${tab !== "Сотрудники" ? "text-[#77828C]" : ""}`}
|
||||
className={`${tab !== "Сотрудники" ? "text-[#77828C]" : ""} py-4`}
|
||||
onClick={handleOnTabClick("Сотрудники")}
|
||||
>
|
||||
Сотрудники
|
||||
</button>
|
||||
<button className={`${tab !== "Проекты" ? "text-[#77828C]" : ""}`}>
|
||||
<button
|
||||
className={`${tab !== "Проекты" ? "text-[#77828C]" : ""} `}
|
||||
onClick={handleOnTabClick("Проекты")}
|
||||
>
|
||||
Проекты
|
||||
</button>
|
||||
</div>
|
||||
<EmployeesList employees={companyEmployeeItems} />
|
||||
{/* <ProjectList projectItems={projectItems} /> */}
|
||||
{tab === "Сотрудники" && (
|
||||
<EmployeesList employees={companyEmployeeItems} />
|
||||
)}
|
||||
{tab === "Проекты" && <ProjectList projectItems={projectItems} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,13 +6,22 @@ interface EmployeeItemProps {
|
||||
|
||||
const EmployeeItem = ({ employee }: EmployeeItemProps) => {
|
||||
const { image, id, name, status } = employee;
|
||||
const statusColor = {
|
||||
online: { color: "#49A1F5", label: "В сети" },
|
||||
offline: { color: "#27AE60", label: "Не в сети" },
|
||||
"at demonstration": { color: "#EB5757", label: "На демонстрации" },
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-2" key={id}>
|
||||
<img src={image} alt={name} className="rounder-full w-8 h-8" />
|
||||
<div className="flex flex-col">
|
||||
<div className="h-5 text-sm ">{name}</div>
|
||||
<div className="h-3 text-[10px]">{status}</div>
|
||||
<div
|
||||
className={`h-3 text-[10px] font-semibold leading-3 text-[${statusColor[status].color}]`}
|
||||
>
|
||||
• {statusColor[status].label}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import INotification from "../../types/notification";
|
||||
import IconWrapper from "../../icons/IconWrapper/IconWrapper";
|
||||
import IsNotificationReadIcon from "../../icons/IsNotificationReadIcon/IsNotificationReadIcon";
|
||||
import { format, parseISO } from "date-fns";
|
||||
import { useState } from "react";
|
||||
import SystemNotificationIcon from "../../icons/SystemNotificationIcon/SystemNotificationIcon";
|
||||
|
||||
interface NotificationItemProps {
|
||||
notification: INotification;
|
||||
}
|
||||
|
||||
const NotificationItem = ({ notification }: NotificationItemProps) => {
|
||||
const date = format(parseISO(notification.date), "dd.mm.yyyy");
|
||||
const [selectedNotificationIds, setSelectedNotificationIds] = useState<
|
||||
string[]
|
||||
>([]);
|
||||
|
||||
const handleOnNotificationClick = (notificationId: string) => {
|
||||
return () => {
|
||||
if (selectedNotificationIds.includes(notificationId)) {
|
||||
const updatedSelectedNotificatios = selectedNotificationIds.filter(
|
||||
(notification) => notification !== notificationId
|
||||
);
|
||||
setSelectedNotificationIds(updatedSelectedNotificatios);
|
||||
} else {
|
||||
const updatedSelectedNotificatios = [
|
||||
...selectedNotificationIds,
|
||||
notificationId,
|
||||
];
|
||||
setSelectedNotificationIds(updatedSelectedNotificatios);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex py-4 border-b border-b-[#DAE0E5] gap-2 cursor-pointer select-none"
|
||||
key={notification.id}
|
||||
onClick={handleOnNotificationClick(notification.id)}
|
||||
>
|
||||
{notification.type === "Employee" ? (
|
||||
<img
|
||||
src={notification.image}
|
||||
alt={notification.title}
|
||||
className="w-8 h-8"
|
||||
/>
|
||||
) : (
|
||||
<IconWrapper className="w-8 h-8">
|
||||
<SystemNotificationIcon />
|
||||
</IconWrapper>
|
||||
)}
|
||||
<div className="flex flex-col align-middle w-[400px]">
|
||||
<div className="flex justify-between ">
|
||||
<div className="text-[12px] h-8 flex">
|
||||
<p className="self-center">{notification.title}</p>
|
||||
</div>
|
||||
{!notification.isRead &&
|
||||
!selectedNotificationIds.includes(notification.id) && (
|
||||
<IconWrapper className="flex justify-end">
|
||||
<IsNotificationReadIcon />
|
||||
</IconWrapper>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-[13px]">
|
||||
<div
|
||||
className={`text-[#77828C] leading-[120%] text-[12px] w-[360px] ${
|
||||
!selectedNotificationIds.includes(notification.id)
|
||||
? "overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
: ""
|
||||
} `}
|
||||
>
|
||||
{notification.content}
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-[12px] text-[#77828C]">{date}</div>
|
||||
{notification.type === "Employee" ? (
|
||||
<button className="outline-none text-[#49A1F5] text-[12px]">
|
||||
открыть чат
|
||||
</button>
|
||||
) : (
|
||||
<button className="outline-none text-[#49A1F5] text-[12px]">
|
||||
действие
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationItem;
|
||||
@@ -0,0 +1,20 @@
|
||||
import INotification from "../../types/notification";
|
||||
import NotificationItem from "../NotificationItem/NotificationItem";
|
||||
|
||||
interface NotificationListProps {
|
||||
notifications: INotification[];
|
||||
}
|
||||
|
||||
const NotificationList = ({ notifications }: NotificationListProps) => {
|
||||
return (
|
||||
<div className="bg-white rounded-[8px] p-6 pt-0">
|
||||
{notifications.map((notification) => {
|
||||
return (
|
||||
<NotificationItem notification={notification} key={notification.id} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationList;
|
||||
@@ -0,0 +1,72 @@
|
||||
import { useState } from "react";
|
||||
import CrossIcon from "../../icons/CrossIcon/CrossIcon";
|
||||
import IconWrapper from "../../icons/IconWrapper/IconWrapper";
|
||||
import INotification from "../../types/notification";
|
||||
import NotificationList from "../NotificationList/NotificationList";
|
||||
|
||||
const notificationData: INotification[] = [
|
||||
{
|
||||
type: "Employee",
|
||||
isRead: false,
|
||||
date: "2023-11-07T07:54:58.392+00:00",
|
||||
image: "Employee.png",
|
||||
id: "1",
|
||||
title: "Анастасия Кошкина",
|
||||
content:
|
||||
"Взяла дополнительные демонстрации вместо Елизаветы, планирую закрыть",
|
||||
},
|
||||
{
|
||||
type: "Employee",
|
||||
isRead: true,
|
||||
date: "2023-11-07T07:54:58.392+00:00",
|
||||
image: "Employee.png",
|
||||
id: "2",
|
||||
title: "Анастасия Кошкина",
|
||||
content:
|
||||
"Взяла дополнительные демонстрации вместо Елизаветы, планирую закрыть их и еще несколько, также хотелось бы сообщить о чем-то, но я не знаю о чем, я просто текст набираю",
|
||||
},
|
||||
{
|
||||
type: "System",
|
||||
isRead: false,
|
||||
date: "2023-11-07T07:54:58.392+00:00",
|
||||
image: undefined,
|
||||
id: "3",
|
||||
title: "Тип системного уведомления",
|
||||
content: "Сообщение",
|
||||
},
|
||||
];
|
||||
|
||||
const NotificationModal = () => {
|
||||
const [notifications, setNotifications] = useState(notificationData);
|
||||
|
||||
const handleMarkAsRead = () => {
|
||||
const updatedNotification = notifications.map((notification) => {
|
||||
return { ...notification, isRead: true };
|
||||
});
|
||||
|
||||
setNotifications(updatedNotification);
|
||||
};
|
||||
return (
|
||||
<div className="bg-white w-[448px] h-[496px] m-auto rounded-[8px]">
|
||||
<div className="p-4 flex justify-between border-b border-b-[#DAE0E5]">
|
||||
<div className="font-semibold text-sm">Уведомления</div>
|
||||
<IconWrapper className="w-[11.3px] h-[11.3px] cursor-pointer text-[#77828C] self-center">
|
||||
<CrossIcon />
|
||||
</IconWrapper>
|
||||
</div>
|
||||
<div className="h-[392px] border-b border-b-[#DAE0E5] overflow-y-auto">
|
||||
<NotificationList notifications={notifications} />
|
||||
</div>
|
||||
<div className="h-[50px] flex flex-col px-6 justify-center">
|
||||
<button
|
||||
className="py-2 px-4 bg-[#F0F1F2] w-fit rounded-[8px] text-[#77828C] font-semibold leading-[130%] text-[12px] h-8 cursor-pointer select-none outline-none"
|
||||
onClick={handleMarkAsRead}
|
||||
>
|
||||
Отметить всё как прочитанное
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationModal;
|
||||
@@ -1,18 +1,88 @@
|
||||
import { useState } from "react";
|
||||
import IProjectItem from "../../types/projectItem";
|
||||
import { format, parseISO } from "date-fns";
|
||||
|
||||
interface ProjectProps {
|
||||
projectItems: IProjectItem[];
|
||||
}
|
||||
|
||||
const ProjectList = ({ projectItems }: ProjectProps) => {
|
||||
const [selectedProjectIds, setSelectedProjectIds] = useState([
|
||||
projectItems[1].id,
|
||||
]);
|
||||
|
||||
const handleOnProjectClick = (id: string) => {
|
||||
return () => {
|
||||
if (selectedProjectIds.includes(id)) {
|
||||
const updatedProjectIds = [...selectedProjectIds].filter(
|
||||
(projectId) => projectId !== id
|
||||
);
|
||||
setSelectedProjectIds(updatedProjectIds);
|
||||
} else {
|
||||
const updatedProjectIds = [...selectedProjectIds, id];
|
||||
setSelectedProjectIds(updatedProjectIds);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-4 first:border-b-black">
|
||||
{projectItems.map((item) => (
|
||||
<div className="flex gap-2">
|
||||
<img src={item.image} alt={item.title} />
|
||||
<div className="text-[12px] font-semibold">{item.title}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-col gap-2">
|
||||
{projectItems.map((item) => {
|
||||
const date = format(parseISO(item.subscriptionUntil), "dd.MM.yyyy");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="border-b border-b-[#DAE0E5] flex flex-col p-4 gap-4"
|
||||
onClick={handleOnProjectClick(item.id)}
|
||||
>
|
||||
<div
|
||||
className="flex gap-2 cursor-pointer select-none"
|
||||
key={item.id}
|
||||
>
|
||||
<img src={item.image} alt={item.title} className="w-8 h-8" />
|
||||
<div className="text-[12px] font-semibold">{item.title}</div>
|
||||
</div>
|
||||
{selectedProjectIds.includes(item.id) ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-[12px] font-semibold text-[#111C26]">
|
||||
Подписка
|
||||
</div>
|
||||
<div className="text-[12px] text-[#77828C] flex gap-4">
|
||||
<div className="w-[144px]">Действует до:</div>
|
||||
<div className="text-[#000]">{date}</div>
|
||||
</div>
|
||||
<div className="text-[12px] text-[#77828C] flex gap-4">
|
||||
<div className="w-[144px]">Сессий одновременно:</div>{" "}
|
||||
<div className="text-[#000]">
|
||||
{item.subscriptionSessionCount}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-[12px] font-semibold text-[#111C26]">
|
||||
Приложение
|
||||
</div>
|
||||
<div className="text-[12px] text-[#77828C] flex gap-4">
|
||||
<div className="w-[144px]">Версия:</div>
|
||||
<div className="text-[#000]">
|
||||
{item.applicationVersion}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button className="text-left outline-none text-[#49A1F5] text-[12px]">
|
||||
Сообщить о проблеме
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
interface IconProps {
|
||||
children: React.ReactElement,
|
||||
className?: string
|
||||
children: React.ReactElement;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const IconWrapper = ({ children, className } : IconProps) => {
|
||||
const IconWrapper = ({ children, className }: IconProps) => {
|
||||
return <div className={`w-4 h-4 p-auto ${className}`}>{children}</div>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`w-4 h-4 align-bottom p-auto ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconWrapper;
|
||||
export default IconWrapper;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
const IsNotificationReadIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="8"
|
||||
height="8"
|
||||
viewBox="0 0 8 8"
|
||||
fill="none"
|
||||
>
|
||||
<circle cx="4" cy="4" r="4" fill="#EB5757" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default IsNotificationReadIcon;
|
||||
@@ -0,0 +1,28 @@
|
||||
const SystemNotificationIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="32" height="32" rx="16" fill="#F2994A" />
|
||||
<path
|
||||
d="M16 7L16 20"
|
||||
stroke="white"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.008 24H16V24.008H16.008V24Z"
|
||||
stroke="white"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemNotificationIcon;
|
||||
@@ -5,3 +5,19 @@
|
||||
body {
|
||||
@apply bg-[#ccc];
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: #858585;
|
||||
border: 3.5px solid #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
interface ICompanyEmployee {
|
||||
image: string;
|
||||
status: "В сети" | "Не в сети" | "На демонстрации";
|
||||
status: "online" | "offline" | "at demonstration";
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
interface INotification {
|
||||
title: string,
|
||||
type: 'Employee' | 'System',
|
||||
isRead: boolean,
|
||||
date: string,
|
||||
image: string | undefined,
|
||||
id: string,
|
||||
content: string,
|
||||
}
|
||||
|
||||
export default INotification;
|
||||
@@ -1,7 +1,11 @@
|
||||
interface IProjectItem {
|
||||
title: string,
|
||||
image: string,
|
||||
id: string
|
||||
id: string,
|
||||
subscriptionUntil: string,
|
||||
subscriptionSessionCount: number,
|
||||
applicationVersion: string
|
||||
|
||||
}
|
||||
|
||||
export default IProjectItem
|
||||
Reference in New Issue
Block a user