feature/IO-3096-GlobalNotifications - styling checkpoint
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
import { Alert, Badge, Button, Checkbox, List, Typography } from "antd";
|
import { Alert, Badge, Button, Tooltip, Typography } from "antd";
|
||||||
|
import { EyeFilled, EyeOutlined, CheckCircleFilled, CheckCircleOutlined } from "@ant-design/icons";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import "./notification-center.styles.scss";
|
import "./notification-center.styles.scss";
|
||||||
@@ -23,22 +24,14 @@ const NotificationCenterComponent = ({
|
|||||||
|
|
||||||
const renderNotification = (index, notification) => {
|
const renderNotification = (index, notification) => {
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<div
|
||||||
key={`${notification.id}-${index}`}
|
key={`${notification.id}-${index}`}
|
||||||
className={notification.read ? "notification-read" : "notification-unread"}
|
className={`notification-item ${notification.read ? "notification-read" : "notification-unread"}`}
|
||||||
onClick={() => !notification.read && onNotificationClick(notification.id)}
|
onClick={() => !notification.read && onNotificationClick(notification.id)}
|
||||||
>
|
>
|
||||||
<Badge dot={!notification.read}>
|
<Badge dot={!notification.read}>
|
||||||
<div>
|
<div className="notification-content">
|
||||||
<Title
|
<Title level={5} className="notification-title">
|
||||||
level={5}
|
|
||||||
style={{
|
|
||||||
margin: "0 0 8px 0",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link
|
<Link
|
||||||
to={`/manage/jobs/${notification.jobid}`}
|
to={`/manage/jobs/${notification.jobid}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -47,12 +40,15 @@ const NotificationCenterComponent = ({
|
|||||||
onNotificationClick(notification.id);
|
onNotificationClick(notification.id);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
className="ro-number"
|
||||||
>
|
>
|
||||||
RO #{notification.roNumber}
|
RO #{notification.roNumber}
|
||||||
</Link>
|
</Link>
|
||||||
<Text type="secondary">{day(notification.created_at).fromNow()}</Text>
|
<Text type="secondary" className="relative-time">
|
||||||
|
{day(notification.created_at).fromNow()}
|
||||||
|
</Text>
|
||||||
</Title>
|
</Title>
|
||||||
<Text strong={!notification.read}>
|
<Text strong={!notification.read} className="notification-body">
|
||||||
<ul>
|
<ul>
|
||||||
{notification.scenarioText.map((text, idx) => (
|
{notification.scenarioText.map((text, idx) => (
|
||||||
<li key={`${notification.id}-${idx}`}>{text}</li>
|
<li key={`${notification.id}-${idx}`}>{text}</li>
|
||||||
@@ -61,24 +57,36 @@ const NotificationCenterComponent = ({
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
</List.Item>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasUnread = notifications.some((n) => !n.read);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`notification-center ${visible ? "visible" : ""}`}>
|
<div className={`notification-center ${visible ? "visible" : ""}`}>
|
||||||
<div className="notification-header">
|
<div className="notification-header">
|
||||||
<h3>{t("notifications.labels.notification-center")}</h3>
|
<h3>{t("notifications.labels.notification-center")}</h3>
|
||||||
<div className="notification-controls">
|
<div className="notification-controls">
|
||||||
<Checkbox checked={showUnreadOnly} onChange={(e) => toggleUnreadOnly(e.target.checked)}>
|
<Tooltip title={t("notifications.labels.show-unread-only")}>
|
||||||
{t("notifications.labels.show-unread-only")}
|
<Button
|
||||||
</Checkbox>
|
type="link"
|
||||||
<Button type="link" onClick={markAllRead} disabled={!notifications.some((n) => !n.read)}>
|
icon={showUnreadOnly ? <EyeFilled /> : <EyeOutlined />}
|
||||||
{t("notifications.labels.mark-all-read")}
|
onClick={() => toggleUnreadOnly(!showUnreadOnly)}
|
||||||
</Button>
|
className={showUnreadOnly ? "active" : ""}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={t("notifications.labels.mark-all-read")}>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon={hasUnread ? <CheckCircleFilled /> : <CheckCircleOutlined />}
|
||||||
|
onClick={markAllRead}
|
||||||
|
disabled={!hasUnread}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{error && <Alert message="Error" description={error} type="error" closable onClose={() => onClose()} />}
|
{error && <Alert message={error} type="error" closable onClose={() => onClose()} />}
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
style={{ height: "400px", width: "100%" }}
|
style={{ height: "400px", width: "100%" }}
|
||||||
data={notifications}
|
data={notifications}
|
||||||
|
|||||||
@@ -2,112 +2,137 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 64px;
|
top: 64px;
|
||||||
right: 0;
|
right: 0;
|
||||||
//width: 600px;
|
width: 400px;
|
||||||
background: #fff; /* White background, Ant’s default */
|
max-width: 400px;
|
||||||
color: rgba(0, 0, 0, 0.85); /* Primary text color in Ant 5 */
|
background: #fff;
|
||||||
border: 1px solid #d9d9d9; /* Neutral gray border */
|
color: rgba(0, 0, 0, 0.85);
|
||||||
border-radius: 6px; /* Slightly larger radius per Ant 5 */
|
border: 1px solid #d9d9d9;
|
||||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06); /* Subtle Ant 5 shadow */
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
display: none;
|
display: none;
|
||||||
|
overflow-x: hidden; /* Prevent horizontal overflow */
|
||||||
|
|
||||||
&.visible {
|
&.visible {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-header {
|
.notification-header {
|
||||||
padding: 16px;
|
padding: 4px 16px;
|
||||||
border-bottom: 1px solid #f0f0f0; /* Light gray border from Ant 5 */
|
border-bottom: 1px solid #f0f0f0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fafafa; /* Light gray background for header */
|
background: #fafafa;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
color: rgba(0, 0, 0, 0.85); /* Primary text color */
|
color: rgba(0, 0, 0, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-controls {
|
.notification-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 8px;
|
||||||
|
|
||||||
.ant-checkbox-wrapper {
|
|
||||||
color: rgba(0, 0, 0, 0.85); /* Match Ant’s text color */
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn-link {
|
.ant-btn-link {
|
||||||
color: #1677ff; /* Ant 5 primary blue */
|
padding: 0;
|
||||||
|
color: #1677ff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #69b1ff; /* Lighter blue on hover */
|
color: #69b1ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
color: rgba(0, 0, 0, 0.25); /* Disabled text color from Ant 5 */
|
color: rgba(0, 0, 0, 0.25);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #0958d9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-read {
|
.notification-read {
|
||||||
background: #fff; /* White background for read items */
|
background: #fff;
|
||||||
color: rgba(0, 0, 0, 0.65); /* Secondary text color */
|
color: rgba(0, 0, 0, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-unread {
|
.notification-unread {
|
||||||
background: #f5f5f5; /* Very light gray for unread items */
|
background: #f5f5f5;
|
||||||
color: rgba(0, 0, 0, 0.85); /* Primary text color */
|
color: rgba(0, 0, 0, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-list {
|
.notification-item {
|
||||||
overflow: auto; /* Match Virtuoso’s default scrolling behavior */
|
padding: 8px 16px;
|
||||||
max-height: 100%; /* Allow full height, let Virtuoso handle virtualization */
|
border-bottom: 1px solid #f0f0f0;
|
||||||
}
|
display: block;
|
||||||
|
overflow: visible;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.ant-list-item {
|
.notification-content {
|
||||||
padding: 2px 16px;
|
width: 100%;
|
||||||
border-bottom: 1px solid #f0f0f0; /* Light gray border */
|
|
||||||
display: block; /* Ensure visibility */
|
|
||||||
overflow: visible; /* Prevent clipping within items */
|
|
||||||
min-height: 80px; /* Minimum height for multi-line content */
|
|
||||||
|
|
||||||
.ant-typography {
|
|
||||||
color: inherit; /* Inherit from parent (read/unread) */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-typography-secondary {
|
.notification-title {
|
||||||
font-size: 12px;
|
|
||||||
color: rgba(0, 0, 0, 0.45); /* Ant 5 secondary text color */
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-badge-dot {
|
|
||||||
background: #ff4d4f; /* Keep red dot for unread, consistent with Ant */
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 20px; /* Standard list padding */
|
display: flex;
|
||||||
list-style-type: disc; /* Ensure bullet points */
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.ro-number {
|
||||||
|
margin: 0;
|
||||||
|
color: #1677ff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relative-time {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
.notification-body {
|
||||||
margin-bottom: 4px; /* Space between list items */
|
margin-top: 4px;
|
||||||
|
|
||||||
|
.ant-typography {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-badge {
|
||||||
|
width: 100%; /* Ensure Badge takes full width to allow .notification-title to stretch properly */
|
||||||
|
}
|
||||||
|
|
||||||
.ant-alert {
|
.ant-alert {
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
background: #fff1f0; /* Light red background for error per Ant 5 */
|
background: #fff1f0;
|
||||||
color: rgba(0, 0, 0, 0.85);
|
color: rgba(0, 0, 0, 0.85);
|
||||||
border: 1px solid #ffa39e; /* Light red border */
|
border: 1px solid #ffa39e;
|
||||||
|
|
||||||
.ant-alert-message {
|
.ant-alert-message {
|
||||||
color: #ff4d4f; /* Red text for message */
|
color: #ff4d4f;
|
||||||
}
|
|
||||||
|
|
||||||
.ant-alert-description {
|
|
||||||
color: rgba(0, 0, 0, 0.65); /* Slightly muted description */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user