feature/IO-3096-GlobalNotifications - styling checkpoint

This commit is contained in:
Dave Richer
2025-02-28 12:14:50 -05:00
parent f6acc1107c
commit a5904f55aa
2 changed files with 113 additions and 80 deletions

View File

@@ -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}

View File

@@ -2,112 +2,137 @@
position: absolute; position: absolute;
top: 64px; top: 64px;
right: 0; right: 0;
//width: 600px; width: 400px;
background: #fff; /* White background, Ants 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 Ants 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 Virtuosos 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 */
} }
} }
} }