added notification modal window

This commit is contained in:
2023-12-05 17:13:15 +05:00
parent 184e08e5b6
commit e378807cd6
15 changed files with 400 additions and 42 deletions
+8 -7
View File
@@ -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>
+10 -1
View File
@@ -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;
+77 -7
View File
@@ -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>
);
};
+6 -11
View File
@@ -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;
+16
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
interface ICompanyEmployee {
image: string;
status: "В сети" | "Не в сети" | "На демонстрации";
status: "online" | "offline" | "at demonstration";
name: string;
id: string;
}
+11
View File
@@ -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;
+5 -1
View File
@@ -1,7 +1,11 @@
interface IProjectItem {
title: string,
image: string,
id: string
id: string,
subscriptionUntil: string,
subscriptionSessionCount: number,
applicationVersion: string
}
export default IProjectItem