feat: implement notification dropdown in header with centralized navigation logic

This commit is contained in:
2026-07-21 21:51:13 -03:00
parent 98f0ec3102
commit b26c3e58d1
5 changed files with 580 additions and 102 deletions
+195 -2
View File
@@ -6,7 +6,7 @@ import NotificationIcon from "@mui/icons-material/NotificationsNone";
import Badge from "@mui/material/Badge"; import Badge from "@mui/material/Badge";
import { useUserMenuStore } from "@store/UserMenu.Store"; import { useUserMenuStore } from "@store/UserMenu.Store";
import { HEADER_MODES, HEADER_WIDGETS, useHeaderStore } from "@store/Header.Store"; import { HEADER_MODES, HEADER_WIDGETS, useHeaderStore } from "@store/Header.Store";
import { useEffect, useState } from "react"; import { useEffect, useRef, useState } from "react";
import useWindowSize from "@hooks/WindowSize"; import useWindowSize from "@hooks/WindowSize";
import Textbox from "../Textbox/Textbox"; import Textbox from "../Textbox/Textbox";
import { useNavigation } from "@core/app/hooks/goto"; import { useNavigation } from "@core/app/hooks/goto";
@@ -15,6 +15,18 @@ import { NO_CATEGORY } from "@core/Models/Categories.type";
import { IconButton } from "@mui/material"; import { IconButton } from "@mui/material";
import { useSessionStore } from "@core/Store/Sesion.Store"; import { useSessionStore } from "@core/Store/Sesion.Store";
import { blurElementsOnLoad } from "@core/helpers/blur"; import { blurElementsOnLoad } from "@core/helpers/blur";
import {
ISystemNotification,
PaginateSystemNotificationsParams,
} from "@core/Models/SystemNotifications.model";
import {
findSystemNotifications,
updateSystemNotification,
} from "@core/app/user/profile/notifications/Notifications.Service";
import {
NOTIFICATIONS_ROUTE,
resolveNotificationRoute,
} from "@core/app/user/profile/notifications/Notifications.Navigation";
//import { useSessionStore, useSessionTokenStore } from "@core/Store/Sesion.Store"; //import { useSessionStore, useSessionTokenStore } from "@core/Store/Sesion.Store";
@@ -27,6 +39,11 @@ export default function Header(): React.ReactElement {
const [windowWidth, windowHeight] = useWindowSize(); const [windowWidth, windowHeight] = useWindowSize();
const [filterText, setFilterText] = useState(""); const [filterText, setFilterText] = useState("");
const [publicFilterText, setPublicFilterText] = useState(""); const [publicFilterText, setPublicFilterText] = useState("");
const [notificationsOpen, setNotificationsOpen] = useState(false);
const [notificationsLoading, setNotificationsLoading] = useState(false);
const [notificationsError, setNotificationsError] = useState("");
const [notifications, setNotifications] = useState<ISystemNotification[]>([]);
const notificationsRef = useRef<HTMLDivElement>(null);
const { goTo } = useNavigation(); const { goTo } = useNavigation();
const pathName = usePathname(); const pathName = usePathname();
@@ -78,6 +95,35 @@ export default function Header(): React.ReactElement {
setPublicFilterText(headerState.publicFindText); setPublicFilterText(headerState.publicFindText);
}, [headerState.publicFindText]); }, [headerState.publicFindText]);
useEffect(() => {
if (!notificationsOpen) {
return;
}
const handleClickOutside = (event: MouseEvent) => {
if (
notificationsRef.current &&
!notificationsRef.current.contains(event.target as Node)
) {
setNotificationsOpen(false);
}
};
const handleEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setNotificationsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("keydown", handleEscape);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("keydown", handleEscape);
};
}, [notificationsOpen]);
const toggleMenu = () => { const toggleMenu = () => {
menuState.setVisible(!menuState.visible); menuState.setVisible(!menuState.visible);
}; };
@@ -126,6 +172,85 @@ export default function Header(): React.ReactElement {
} }
}; };
const loadLatestNotifications = () => {
if (!SessionInfo.userId) {
return;
}
const filterData: PaginateSystemNotificationsParams = {
userId: SessionInfo.userId,
sessionUser: SessionInfo.userId,
page: 1,
limit: 10,
};
setNotificationsLoading(true);
setNotificationsError("");
findSystemNotifications(filterData)
.then((res) => {
setNotifications(res.data || []);
})
.catch(() => {
setNotifications([]);
setNotificationsError("No pudimos cargar tus notificaciones.");
})
.finally(() => {
setNotificationsLoading(false);
});
};
const toggleNotifications = () => {
const nextOpen = !notificationsOpen;
setNotificationsOpen(nextOpen);
if (nextOpen) {
loadLatestNotifications();
}
};
const viewAllNotifications = () => {
setNotificationsOpen(false);
goTo(NOTIFICATIONS_ROUTE);
};
const openNotification = async (notification: ISystemNotification) => {
setNotificationsOpen(false);
try {
if (!notification.readed) {
await updateSystemNotification({
id: notification._id,
readed: true,
sessionUser: SessionInfo.userId,
});
const currentCount = useHeaderStore.getState().notificationsCount;
headerState.setNotificationsCount(Math.max(currentCount - 1, 0));
}
const route = await resolveNotificationRoute({
notification,
sessionUser: SessionInfo.userId,
organizationSubscriptions: SessionInfo.organizationSubscriptions,
});
goTo(route);
} catch (error) {
console.error("Error navigating from notification", error);
goTo(NOTIFICATIONS_ROUTE);
}
};
const formatNotificationDate = (date: Date) => {
if (!date) {
return "";
}
return new Date(date).toLocaleDateString("es-AR", {
day: "2-digit",
month: "short",
});
};
return ( return (
<> <>
<div className={style.header}> <div className={style.header}>
@@ -197,9 +322,12 @@ export default function Header(): React.ReactElement {
)} )}
{showNotificationIcon && ( {showNotificationIcon && (
<div className={style.notificationsWrap} ref={notificationsRef}>
<IconButton <IconButton
sx={{ marginRight: "20px" }} sx={{ marginRight: "20px" }}
onClick={() => goTo("/user/profile/notifications")} onClick={toggleNotifications}
aria-label="Abrir notificaciones"
aria-expanded={notificationsOpen}
> >
<Badge <Badge
color="warning" color="warning"
@@ -209,6 +337,71 @@ export default function Header(): React.ReactElement {
<NotificationIcon sx={{ color: "white" }} /> <NotificationIcon sx={{ color: "white" }} />
</Badge> </Badge>
</IconButton> </IconButton>
{notificationsOpen && (
<div className={style.notificationsMenu} role="menu">
<div className={style.notificationsHeader}>
<div>
<strong>Notificaciones</strong>
<span>Últimas novedades</span>
</div>
{headerState.notificationsCount > 0 && (
<b>{headerState.notificationsCount}</b>
)}
</div>
<div className={style.notificationsList}>
{notificationsLoading && (
<div className={style.notificationsState}>Cargando...</div>
)}
{!notificationsLoading && notificationsError && (
<div className={style.notificationsState}>{notificationsError}</div>
)}
{!notificationsLoading && !notificationsError && notifications.length === 0 && (
<div className={style.notificationsState}>
No tenés notificaciones recientes.
</div>
)}
{!notificationsLoading &&
!notificationsError &&
notifications.map((notification) => (
<button
key={notification._id}
type="button"
className={`${style.notificationItem} ${
notification.readed ? style.notificationRead : ""
}`}
onClick={() => openNotification(notification)}
>
<span className={style.notificationDot} />
<span className={style.notificationContent}>
<span className={style.notificationTitle}>
{notification.subject}
</span>
<span className={style.notificationMessage}>
{notification.message}
</span>
<span className={style.notificationDate}>
{formatNotificationDate(notification.notificationDate)}
</span>
</span>
</button>
))}
</div>
<button
type="button"
className={style.notificationsFooter}
onClick={viewAllNotifications}
>
Ver todas las notificaciones
</button>
</div>
)}
</div>
)} )}
<MenuIcon sx={{ color: "white" }} onClick={toggleMenu} className={style.menuIcon} /> <MenuIcon sx={{ color: "white" }} onClick={toggleMenu} className={style.menuIcon} />
@@ -8,7 +8,7 @@
.header { .header {
position: fixed; position: fixed;
overflow: hidden; overflow: visible;
left: 0px; left: 0px;
top: 0px; top: 0px;
right: 0px; right: 0px;
@@ -25,3 +25,191 @@
.menuIcon { .menuIcon {
cursor: pointer; cursor: pointer;
} }
.notificationsWrap {
position: relative;
}
.notificationsMenu {
position: absolute;
top: calc(100% + 12px);
right: 12px;
width: min(380px, calc(100vw - 32px));
max-height: calc(100vh - 90px);
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 22px;
background: var(--white);
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.26);
color: var(--black-light);
}
.notificationsMenu::before {
content: "";
position: absolute;
top: -7px;
right: 28px;
width: 14px;
height: 14px;
transform: rotate(45deg);
background: var(--wine-darkest);
}
.notificationsHeader {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 18px 20px;
background: linear-gradient(135deg, var(--wine-darkest), var(--wine-dark));
color: var(--white);
}
.notificationsHeader strong,
.notificationsHeader span {
display: block;
}
.notificationsHeader strong {
font-size: 17px;
}
.notificationsHeader span {
margin-top: 3px;
color: var(--white-dark);
font-size: 12px;
}
.notificationsHeader b {
min-width: 28px;
padding: 5px 8px;
border-radius: 999px;
background: var(--wine-lighterX2);
color: var(--wine-superdark);
font-size: 13px;
text-align: center;
}
.notificationsList {
max-height: 390px;
overflow-y: auto;
padding: 8px;
background: var(--white-dark);
}
.notificationsState {
padding: 26px 18px;
color: var(--gray-dark);
font-size: 14px;
text-align: center;
}
.notificationItem {
width: 100%;
display: flex;
gap: 11px;
padding: 13px 12px;
border: 1px solid transparent;
border-radius: 16px;
background: var(--white);
color: inherit;
cursor: pointer;
text-align: left;
transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}
.notificationItem + .notificationItem {
margin-top: 7px;
}
.notificationItem:hover {
transform: translateY(-1px);
border-color: var(--wine-lighterX2);
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.08);
}
.notificationDot {
flex: 0 0 auto;
width: 9px;
height: 9px;
margin-top: 5px;
border-radius: 999px;
background: var(--wine-red);
box-shadow: 0 0 0 4px var(--wine-lighterX2);
}
.notificationContent {
min-width: 0;
display: flex;
flex: 1;
flex-direction: column;
gap: 4px;
}
.notificationTitle {
overflow: hidden;
color: var(--wine-darkest);
font-size: 14px;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.notificationMessage {
display: -webkit-box;
overflow: hidden;
color: var(--black-light);
font-size: 13px;
line-height: 1.35;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.notificationDate {
color: var(--gray-dark);
font-size: 11px;
}
.notificationRead {
background: var(--white);
opacity: 0.76;
}
.notificationRead .notificationDot {
background: var(--gray-light);
box-shadow: 0 0 0 4px var(--gray-lighter);
}
.notificationRead .notificationTitle {
color: var(--black-light);
font-weight: 600;
}
.notificationsFooter {
width: 100%;
padding: 14px 18px;
border: 0;
border-top: 1px solid var(--gray-lighter);
background: var(--white);
color: var(--wine-dark);
cursor: pointer;
font-size: 14px;
font-weight: 700;
transition: background-color 0.18s ease, color 0.18s ease;
}
.notificationsFooter:hover {
background: var(--wine-lighterX2);
color: var(--wine-darkest);
}
@media (max-width: 600px) {
.header {
padding-left: 18px;
padding-right: 18px;
}
.notificationsMenu {
right: -48px;
}
}
@@ -40,3 +40,10 @@
.userProfileOptionsContainer a { .userProfileOptionsContainer a {
margin-top: 10px; margin-top: 10px;
} }
@media (max-width: 640px) {
.userProfileOptionsContainer {
left: 10px;
right: 10px;
}
}
@@ -0,0 +1,79 @@
import { ISystemNotification, NotificationType } from "@core/Models/SystemNotifications.model";
import API from "@services/Api.Service";
type ResolveNotificationRouteParams = {
notification: ISystemNotification;
sessionUser: string;
organizationSubscriptions?: Record<string, unknown>;
};
export const NOTIFICATIONS_ROUTE = "/user/profile/notifications";
export const resolveNotificationRoute = async ({
notification,
sessionUser,
organizationSubscriptions,
}: ResolveNotificationRouteParams): Promise<string> => {
if (!notification.type || !notification.code) {
return NOTIFICATIONS_ROUTE;
}
let companyId = notification.companyId;
switch (notification.type) {
case NotificationType.APPOINTMENT: {
const aptRes = await API.post<any>("appointments/get-event", {
appointmentId: notification.code,
sessionUser,
});
companyId = companyId || aptRes?.companyId;
if (aptRes?.clientId && companyId) {
return `/admin/org/${companyId}/client/${aptRes.clientId}/appointments/${notification.code}`;
}
break;
}
case NotificationType.COMPANY:
return `/admin/org/profile/${notification.code}`;
case NotificationType.EMPLOYEE:
case NotificationType.SCHEDULE:
case NotificationType.SCHEDULE_EXCEPTION:
case NotificationType.SCHEDULE_RESTRICTION:
if (!companyId) {
const empRes = await API.post<any>("employees/get-by-id", {
id: notification.code,
sessionUser,
});
companyId = empRes?.companyId;
}
if (companyId) {
if (
notification.type === NotificationType.EMPLOYEE ||
notification.type === NotificationType.SCHEDULE
) {
return `/admin/org/profile/${companyId}/collaborators/${notification.code}`;
}
if (notification.type === NotificationType.SCHEDULE_EXCEPTION) {
return `/admin/org/profile/${companyId}/collaborators/${notification.code}/exceptions`;
}
return `/admin/org/profile/${companyId}/collaborators/${notification.code}/restrictions`;
}
break;
case NotificationType.BILLING:
companyId = companyId || Object.keys(organizationSubscriptions || {})[0];
if (companyId) {
return `/admin/org/profile/${companyId}/dashboard`;
}
break;
case NotificationType.MESSAGE:
if (notification.conversationId) {
return `/messages?conversation=${notification.conversationId}`;
}
break;
}
return NOTIFICATIONS_ROUTE;
};
@@ -14,7 +14,6 @@ import {
UpdateSystemNotificationParams, UpdateSystemNotificationParams,
NotificationType, NotificationType,
} from "@core/Models/SystemNotifications.model"; } from "@core/Models/SystemNotifications.model";
import API from "@services/Api.Service";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { import {
deleteNotificationsByDate, deleteNotificationsByDate,
@@ -23,18 +22,19 @@ import {
setStateNotificationByUser, setStateNotificationByUser,
updateSystemNotification, updateSystemNotification,
} from "./Notifications.Service"; } from "./Notifications.Service";
import { resolveNotificationRoute } from "./Notifications.Navigation";
import DeleteIcon from "@mui/icons-material/DeleteOutlined"; import DeleteIcon from "@mui/icons-material/DeleteOutlined";
import MarkReadIcon from "@mui/icons-material/MarkChatReadOutlined"; import MarkReadIcon from "@mui/icons-material/MarkChatReadOutlined";
import MarkUnReadIcon from "@mui/icons-material/MarkUnreadChatAltOutlined"; import MarkUnReadIcon from "@mui/icons-material/MarkUnreadChatAltOutlined";
import ReloadIcon from "@mui/icons-material/Autorenew"; import ReloadIcon from "@mui/icons-material/Autorenew";
import DoneAllIcon from "@mui/icons-material/DoneAll"; import DoneAllIcon from "@mui/icons-material/DoneAll";
import NotificationsActiveIcon from "@mui/icons-material/NotificationsActive";
import InboxIcon from "@mui/icons-material/Inbox"; import InboxIcon from "@mui/icons-material/Inbox";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import Pagination from "@mui/material/Pagination"; import Pagination from "@mui/material/Pagination";
import { useConfirmStore } from "@core/Store/Confirm.Store"; import { useConfirmStore } from "@core/Store/Confirm.Store";
import HeaderConfProvider from "@core/app/components/HeaderConfProvider/HeaderConfProvider"; import HeaderConfProvider from "@core/app/components/HeaderConfProvider/HeaderConfProvider";
import { useMenuStore } from "@core/Store/Menu.Store"; import { useMenuStore } from "@core/Store/Menu.Store";
import useWindowSize from "@hooks/WindowSize";
import dayjs from "dayjs"; import dayjs from "dayjs";
import "dayjs/locale/es"; import "dayjs/locale/es";
import { motion, AnimatePresence } from "motion/react"; import { motion, AnimatePresence } from "motion/react";
@@ -49,6 +49,7 @@ export default function EditProfile() {
const location = useLocationStore(); const location = useLocationStore();
const menu = useMenuStore(); const menu = useMenuStore();
const router = useRouter(); const router = useRouter();
const [windowWidth] = useWindowSize();
const [filterType, setFilterType] = useState<string>("ALL"); const [filterType, setFilterType] = useState<string>("ALL");
const [pageSize] = useState<number>( const [pageSize] = useState<number>(
parseInt( parseInt(
@@ -129,66 +130,22 @@ export default function EditProfile() {
}; };
const handleNotificationClick = async (notif: SystemNotificationRow) => { const handleNotificationClick = async (notif: SystemNotificationRow) => {
if (!notif.readed) {
handleMarkRead(notif);
}
if (!notif.type || !notif.code) return;
eventHandler.setEventType(EVENT_TYPES.LOADING); eventHandler.setEventType(EVENT_TYPES.LOADING);
try { try {
let companyId = notif.companyId; if (!notif.readed) {
await updateSystemNotification({
id: notif._id,
readed: true,
sessionUser: SessionInfo.userId,
});
}
switch (notif.type) { const route = await resolveNotificationRoute({
case NotificationType.APPOINTMENT: notification: notif,
// Fetch appointment to get clientId and companyId
const aptRes = await API.post<any>("appointments/get-event", {
appointmentId: notif.code,
sessionUser: SessionInfo.userId, sessionUser: SessionInfo.userId,
organizationSubscriptions: SessionInfo.organizationSubscriptions,
}); });
if (aptRes && aptRes.clientId) { router.push(route);
companyId = companyId || aptRes.companyId;
if (companyId) {
router.push(`/admin/org/${companyId}/client/${aptRes.clientId}/appointments/${notif.code}`);
}
}
break;
case NotificationType.COMPANY:
router.push(`/admin/org/profile/${notif.code}`);
break;
case NotificationType.EMPLOYEE:
case NotificationType.SCHEDULE:
case NotificationType.SCHEDULE_EXCEPTION:
case NotificationType.SCHEDULE_RESTRICTION:
if (!companyId) {
const empRes = await API.post<any>("employees/get-by-id", {
id: notif.code,
sessionUser: SessionInfo.userId,
});
companyId = companyId || empRes?.companyId;
}
if (companyId) {
if (notif.type === NotificationType.EMPLOYEE || notif.type === NotificationType.SCHEDULE) {
router.push(`/admin/org/profile/${companyId}/collaborators/${notif.code}`);
} else if (notif.type === NotificationType.SCHEDULE_EXCEPTION) {
router.push(`/admin/org/profile/${companyId}/collaborators/${notif.code}/exceptions`);
} else {
router.push(`/admin/org/profile/${companyId}/collaborators/${notif.code}/restrictions`);
}
}
break;
case NotificationType.BILLING:
companyId = companyId || Object.keys(SessionInfo.organizationSubscriptions || {})[0];
if (companyId) {
router.push(`/admin/org/profile/${companyId}/dashboard`);
}
break;
case NotificationType.MESSAGE:
if (notif.conversationId) {
router.push(`/messages?conversation=${notif.conversationId}`);
}
break;
}
} catch (e: any) { } catch (e: any) {
console.error("Error navigating from notification", e); console.error("Error navigating from notification", e);
alert.showError(e.format ? e.format() : "Error al navegar: " + (e.message || "Desconocido")); alert.showError(e.format ? e.format() : "Error al navegar: " + (e.message || "Desconocido"));
@@ -332,7 +289,7 @@ export default function EditProfile() {
}); });
location.add({ location.add({
url: "/user/profile/edit", url: "/user/profile/notifications",
title: "Notificaciones", title: "Notificaciones",
icon: <AccountCircleOutlinedIcon sx={{ fontSize: "32px", mr: "10px" }} />, icon: <AccountCircleOutlinedIcon sx={{ fontSize: "32px", mr: "10px" }} />,
}); });
@@ -395,27 +352,55 @@ export default function EditProfile() {
const unreadCount = allDataList.filter(n => !n.readed).length; const unreadCount = allDataList.filter(n => !n.readed).length;
const isInboxZero = allDataList.length > 0 && unreadCount === 0; const isInboxZero = allDataList.length > 0 && unreadCount === 0;
const isMobile = windowWidth < 640;
return ( return (
<> <>
<style jsx global>{`
.notifications-filter-scroll::-webkit-scrollbar {
display: none;
}
`}</style>
<Location /> <Location />
<HeaderConfProvider /> <HeaderConfProvider />
<div style={{ padding: "20px 40px", maxWidth: "900px", margin: "0 auto", paddingBottom: "100px" }}> <div
style={{
width: "100%",
boxSizing: "border-box",
padding: isMobile ? "18px 10px 90px" : "20px 40px 100px",
maxWidth: "900px",
margin: "0 auto",
position: "relative",
}}
>
{/* Header and Controls */} {/* Controls */}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "30px", borderBottom: "1px solid #eaeaea", paddingBottom: "20px" }}> <div
<div> style={{
<h1 style={{ margin: 0, fontSize: "28px", color: "var(--black-light)", display: "flex", alignItems: "center", gap: "10px" }}> display: "flex",
<NotificationsActiveIcon sx={{ color: "var(--wine-dark)" }} /> justifyContent: "flex-end",
Bandeja de Entrada alignItems: "center",
</h1> flexDirection: "row",
<p style={{ margin: "5px 0 0 0", color: "#666", fontSize: "14px" }}> gap: "10px",
{unreadCount > 0 ? `Tienes ${unreadCount} notificación(es) sin leer.` : "No tienes notificaciones pendientes."} marginBottom: isMobile ? 0 : "24px",
</p> borderBottom: isMobile ? "none" : "1px solid #eaeaea",
</div> paddingBottom: isMobile ? 0 : "20px",
position: isMobile ? "absolute" : "static",
<div style={{ display: "flex", gap: "10px" }}> top: isMobile ? "-44px" : "auto",
right: isMobile ? "10px" : "auto",
zIndex: 1,
}}
>
<div
style={{
display: "flex",
gap: "10px",
width: "auto",
justifyContent: "flex-end",
flexShrink: 0,
}}
>
{unreadCount > 0 && ( {unreadCount > 0 && (
<button <button
onClick={() => setNotiState(true)} onClick={() => setNotiState(true)}
@@ -423,7 +408,7 @@ export default function EditProfile() {
backgroundColor: "var(--wine-lighterX2)", backgroundColor: "var(--wine-lighterX2)",
color: "var(--wine-darkest)", color: "var(--wine-darkest)",
border: "none", border: "none",
padding: "8px 16px", padding: isMobile ? "8px 14px" : "8px 16px",
borderRadius: "20px", borderRadius: "20px",
cursor: "pointer", cursor: "pointer",
fontWeight: "bold", fontWeight: "bold",
@@ -441,7 +426,7 @@ export default function EditProfile() {
<IconButton <IconButton
onClick={() => loadNotifications(notifications?.page, pageSize)} onClick={() => loadNotifications(notifications?.page, pageSize)}
color="secondary" color="secondary"
style={{ backgroundColor: "#f5f5f5" }} style={{ backgroundColor: "#f5f5f5", boxShadow: isMobile ? "0 8px 20px rgba(0, 0, 0, 0.08)" : "none" }}
> >
<ReloadIcon sx={{ color: "var(--black)" }} /> <ReloadIcon sx={{ color: "var(--black)" }} />
</IconButton> </IconButton>
@@ -450,7 +435,20 @@ export default function EditProfile() {
{/* Filters */} {/* Filters */}
{allDataList.length > 0 && ( {allDataList.length > 0 && (
<div style={{ display: "flex", gap: "10px", marginBottom: "25px", flexWrap: "wrap" }}> <div
style={{
display: "flex",
gap: isMobile ? "8px" : "10px",
marginBottom: isMobile ? "18px" : "25px",
flexWrap: isMobile ? "nowrap" : "wrap",
overflowX: isMobile ? "auto" : "visible",
paddingBottom: isMobile ? "8px" : 0,
WebkitOverflowScrolling: "touch",
scrollbarWidth: "none",
msOverflowStyle: "none",
}}
className="notifications-filter-scroll"
>
{[ {[
{ id: "ALL", label: "Todas" }, { id: "ALL", label: "Todas" },
{ id: "APPOINTMENT", label: "Turnos" }, { id: "APPOINTMENT", label: "Turnos" },
@@ -463,7 +461,7 @@ export default function EditProfile() {
key={f.id} key={f.id}
onClick={() => setFilterType(f.id)} onClick={() => setFilterType(f.id)}
style={{ style={{
padding: "8px 16px", padding: isMobile ? "9px 14px" : "8px 16px",
borderRadius: "20px", borderRadius: "20px",
border: "1px solid", border: "1px solid",
borderColor: filterType === f.id ? "var(--wine-dark)" : "#eaeaea", borderColor: filterType === f.id ? "var(--wine-dark)" : "#eaeaea",
@@ -471,7 +469,8 @@ export default function EditProfile() {
color: filterType === f.id ? "#fff" : "#666", color: filterType === f.id ? "#fff" : "#666",
cursor: "pointer", cursor: "pointer",
fontWeight: filterType === f.id ? "bold" : "normal", fontWeight: filterType === f.id ? "bold" : "normal",
transition: "all 0.2s" transition: "all 0.2s",
whiteSpace: "nowrap",
}} }}
> >
{f.label} {f.label}
@@ -485,7 +484,7 @@ export default function EditProfile() {
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
style={{ textAlign: "center", padding: "40px 20px", backgroundColor: "#f9fafc", borderRadius: "15px", marginBottom: "30px" }} style={{ textAlign: "center", padding: isMobile ? "34px 18px" : "40px 20px", backgroundColor: "#f9fafc", borderRadius: "15px", marginBottom: "30px" }}
> >
<InboxIcon sx={{ fontSize: "64px", color: "var(--wine-light)", marginBottom: "15px" }} /> <InboxIcon sx={{ fontSize: "64px", color: "var(--wine-light)", marginBottom: "15px" }} />
<h2 style={{ margin: 0, color: "var(--wine-darkest)" }}>¡Estás al día! 🌟</h2> <h2 style={{ margin: 0, color: "var(--wine-darkest)" }}>¡Estás al día! 🌟</h2>
@@ -514,11 +513,13 @@ export default function EditProfile() {
style={{ style={{
backgroundColor: notif.readed ? "#ffffff" : "var(--wine-lighterX2)", backgroundColor: notif.readed ? "#ffffff" : "var(--wine-lighterX2)",
border: notif.readed ? "1px solid #eaeaea" : "1px solid var(--wine-light)", border: notif.readed ? "1px solid #eaeaea" : "1px solid var(--wine-light)",
borderRadius: "12px", borderRadius: isMobile ? "18px" : "12px",
padding: "16px 20px", padding: isMobile ? "14px" : "16px 20px",
display: "flex", display: "flex",
alignItems: "flex-start", alignItems: isMobile ? "stretch" : "flex-start",
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: isMobile ? "column" : "row",
gap: isMobile ? "12px" : "8px",
boxShadow: notif.readed ? "none" : "0 4px 12px rgba(127, 42, 255, 0.1)", boxShadow: notif.readed ? "none" : "0 4px 12px rgba(127, 42, 255, 0.1)",
transition: "all 0.2s ease", transition: "all 0.2s ease",
cursor: notif.type && notif.code ? "pointer" : "default" cursor: notif.type && notif.code ? "pointer" : "default"
@@ -527,25 +528,35 @@ export default function EditProfile() {
onMouseOver={(e) => { e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.boxShadow = "0 6px 16px rgba(0,0,0,0.08)"; }} onMouseOver={(e) => { e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.boxShadow = "0 6px 16px rgba(0,0,0,0.08)"; }}
onMouseOut={(e) => { e.currentTarget.style.transform = "none"; e.currentTarget.style.boxShadow = notif.readed ? "none" : "0 4px 12px rgba(127, 42, 255, 0.1)"; }} onMouseOut={(e) => { e.currentTarget.style.transform = "none"; e.currentTarget.style.boxShadow = notif.readed ? "none" : "0 4px 12px rgba(127, 42, 255, 0.1)"; }}
> >
<div style={{ display: "flex", gap: "15px", alignItems: "flex-start", flex: 1 }}> <div style={{ display: "flex", gap: isMobile ? "12px" : "15px", alignItems: "flex-start", flex: 1, minWidth: 0 }}>
<div style={{ marginTop: "4px" }}> <div style={{ marginTop: "4px", flexShrink: 0 }}>
{notif.readed ? {notif.readed ?
<MarkReadIcon sx={{ color: "#ccc" }} /> : <MarkReadIcon sx={{ color: "#ccc" }} /> :
<MarkUnReadIcon sx={{ color: "var(--wine-dark)" }} /> <MarkUnReadIcon sx={{ color: "var(--wine-dark)" }} />
} }
</div> </div>
<div style={{ flex: 1 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "4px" }}> <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "4px" }}>
<h3 style={{ margin: 0, fontSize: "16px", fontWeight: notif.readed ? "normal" : "bold", color: notif.readed ? "#444" : "var(--wine-darkest)" }}> <h3 style={{ margin: 0, fontSize: isMobile ? "15px" : "16px", lineHeight: "1.35", fontWeight: notif.readed ? "normal" : "bold", color: notif.readed ? "#444" : "var(--wine-darkest)", overflowWrap: "anywhere" }}>
{notif.subject} {notif.subject}
</h3> </h3>
</div> </div>
<p style={{ margin: 0, fontSize: "14px", color: notif.readed ? "#777" : "#333", lineHeight: "1.5" }}> <p style={{ margin: 0, fontSize: isMobile ? "13px" : "14px", color: notif.readed ? "#777" : "#333", lineHeight: "1.5", overflowWrap: "anywhere" }}>
{notif.message} {notif.message}
</p> </p>
</div> </div>
</div> </div>
<div style={{ display: "flex", gap: "5px", opacity: 0.8 }} onClick={(e) => e.stopPropagation()}> <div
style={{
display: "flex",
gap: "5px",
opacity: 0.8,
justifyContent: isMobile ? "flex-end" : "flex-start",
borderTop: isMobile ? "1px solid #f0f0f0" : "none",
paddingTop: isMobile ? "8px" : 0,
}}
onClick={(e) => e.stopPropagation()}
>
<IconButton size="small" onClick={() => handleMarkRead(notif)} title={notif.readed ? "Marcar como no leída" : "Marcar como leída"}> <IconButton size="small" onClick={() => handleMarkRead(notif)} title={notif.readed ? "Marcar como no leída" : "Marcar como leída"}>
{notif.readed ? <MarkUnReadIcon fontSize="small" /> : <MarkReadIcon fontSize="small" color="primary" />} {notif.readed ? <MarkUnReadIcon fontSize="small" /> : <MarkReadIcon fontSize="small" color="primary" />}
</IconButton> </IconButton>