Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 8 additions & 19 deletions src/components/common/notification-modal/NotificationCard.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { useNavigate } from 'react-router-dom';
import { putAlerts } from '@/api/alertApi';
import { Link } from 'react-router-dom';
import calculateTimeDifference from '@/utils/calculateTimeDifference';
import formatWorkTime from '@/utils/formatWorkTime';

interface NotificationCardProps {
alertId: string; // ์•Œ๋ฆผ id
shopId: string; // ๊ฐ€๊ฒŒ id
noticeId: string; // ๊ณต๊ณ  id
status: 'accepted' | 'rejected'; // ๊ณต๊ณ  ์ง€์› ์ƒํƒœ
restaurantName: string; // ์Œ์‹์  ์ด๋ฆ„
startsAt: string; // ๊ณต๊ณ  ์‹œ์ž‘ ์‹œ๊ฐ„ (ISO 8601 ๋ฌธ์ž์—ด)
workhour: number; // ๊ทผ๋ฌด ์‹œ๊ฐ„ (์‹œ๊ฐ„ ๋‹จ์œ„)
createdAt: string; // ์•Œ๋ฆผ ์ƒ์„ฑ ์‹œ๊ฐ„ (ISO 8601 ๋ฌธ์ž์—ด)
onMarkAsRead: (alertId: string) => void;
}
Comment on lines 5 to 15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ฌ ๋‹ค ํ’€์–ด์„œ ๋ฐ›๊ธฐ๋ณด๋‹จ AlertItem์˜ ๊ฐ์ฒด ํ˜•ํƒœ..? ๋กœ prop์„ ๋ฐ›์œผ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค


/*
Expand All @@ -21,39 +22,27 @@ interface NotificationCardProps {

export default function NotificationCard({
alertId,
shopId,
noticeId,
status,
restaurantName,
startsAt,
workhour,
createdAt,
onMarkAsRead,
}: NotificationCardProps) {
const formattedTime = formatWorkTime({
startsAt,
workhour,
});
const navigate = useNavigate();

const formattedCreatedAt = calculateTimeDifference(createdAt);
const formattedStatus = status === 'accepted' ? '์Šน์ธ' : '๊ฑฐ์ ˆ';
const formattedStatusClass =
status === 'accepted' ? 'text-blue-20' : 'text-red-20';
const handleClick = () => {
const userId = localStorage.getItem('userId');

if (!userId) {
console.error('userId๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
return;
}

try {
putAlerts(userId, alertId); // ์•Œ๋ฆผ ์ฝ์Œ ์ฒ˜๋ฆฌ
navigate(`/${noticeId}`);
} catch (error) {
console.error(error);
}
};
return (
<div onClick={handleClick} className="cursor-pointer">
<Link to={`${shopId}/${noticeId}`} onClick={() => onMarkAsRead(alertId)}>
<div className="flex flex-col gap-4 rounded-[5px] border border-gray-20 bg-white px-12 py-16 md:w-328">
{status === 'accepted' ? (
<div className="h-5 w-5 rounded-full bg-blue-20"></div>
Expand All @@ -67,6 +56,6 @@ export default function NotificationCard({
</h2>
<p className="text-caption/16 text-gray-40">{formattedCreatedAt}</p>
</div>
</div>
</Link>
);
}
29 changes: 16 additions & 13 deletions src/components/common/notification-modal/NotificationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type { AlertListItem } from '@/api/alertApi';

interface NotificationModalProps {
data?: AlertListItem[]; // ์•Œ๋ฆผ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด
count?: number; // ์•Œ๋ฆผ ๊ฐœ์ˆ˜
onClose: () => void; // x ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ๋•Œ ์‹คํ–‰ํ•  ํ•จ์ˆ˜
onMarkAsRead: (alertId: string) => void;
}

/*
Expand All @@ -17,29 +17,32 @@ interface NotificationModalProps {

export default function NotificationModal({
data = [],
count = 0,
onClose,
onMarkAsRead,
}: NotificationModalProps) {
const unreadData = data.filter((alertItem) => alertItem.item.read === false);
return (
<div className="flex h-screen w-screen flex-col gap-16 bg-red-10 px-20 py-40 md:h-420 md:w-368 md:rounded-[10px] md:border-1 md:border-gray-30 md:py-24 md:shadow-custom">
<div className="flex items-center justify-between">
<h1 className="text-h3 font-bold">์•Œ๋ฆผ {count}๊ฐœ</h1>
<h1 className="text-h3 font-bold">์•Œ๋ฆผ {unreadData.length}๊ฐœ</h1>
<button onClick={onClose} className="md:hidden">
<img src={close} alt="๋‹ซ๊ธฐ" />
</button>
</div>
<div className="scrollbar-hide flex flex-col gap-8 overflow-y-auto">
{data.length > 0 &&
data.map((data) => (
{unreadData.length > 0 &&
unreadData.map((alertItem) => (
<NotificationCard
key={data.item.id}
alertId={data.item.id}
noticeId={data.item.notice.item.id}
status={data.item.result}
restaurantName={data.item.shop.item.name}
startsAt={data.item.notice.item.startsAt}
workhour={data.item.notice.item.workhour}
createdAt={data.item.createdAt}
key={alertItem.item.id}
onMarkAsRead={onMarkAsRead}
alertId={alertItem.item.id}
shopId={alertItem.item.shop.item.id}
noticeId={alertItem.item.notice.item.id}
status={alertItem.item.result}
restaurantName={alertItem.item.shop.item.name}
startsAt={alertItem.item.notice.item.startsAt}
workhour={alertItem.item.notice.item.workhour}
createdAt={alertItem.item.createdAt}
/>
))}
</div>
Expand Down
16 changes: 13 additions & 3 deletions src/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import alarmActive from '@/assets/icons/alarm-active.svg';
import alarmInactive from '@/assets/icons/alarm-inactive.svg';

export default function Navbar() {
const { isLoggedIn, role, alarms, logout } = useContext(AuthContext);
const { isLoggedIn, role, alarms, logout, markAlertAsRead } =
useContext(AuthContext);
const [isShowModal, setIsShowModal] = useState(false);
const modalRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);

const unreadAlarmCount = alarms.items.filter(
(alert) => !alert.item.read,
).length;

// ๋ฐ”๊นฅ ํด๋ฆญ ์‹œ ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ
useEffect(() => {
function handleClickOutside(event: MouseEvent): void {
Expand Down Expand Up @@ -42,6 +47,11 @@ export default function Navbar() {
return () => document.removeEventListener('keydown', handleKeyDown);
}, []);

const handleNotificationClick = (alertId: string) => {
markAlertAsRead(alertId); // AuthContext์˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด ์•Œ๋ฆผ์„ ์ฝ์Œ ์ฒ˜๋ฆฌ
setIsShowModal(false); // Navbar์˜ state๋ฅผ ๋ณ€๊ฒฝํ•ด ๋ชจ๋‹ฌ์„ ๋‹ซ์Œ
};

return (
<header className="bg-white">
<nav className="relative mx-20 flex flex-wrap items-center justify-between gap-y-22 py-10 md:mx-32 md:py-15 lg:mx-auto lg:max-w-1023">
Expand Down Expand Up @@ -73,7 +83,7 @@ export default function Navbar() {
ref={buttonRef}
onClick={() => setIsShowModal((prev) => !prev)}
>
{alarms.count > 0 ? (
{unreadAlarmCount > 0 ? (
<img src={alarmActive} />
) : (
<img src={alarmInactive} />
Expand All @@ -94,8 +104,8 @@ export default function Navbar() {
>
<NotificationModal
data={alarms.items}
count={alarms.count}
onClose={() => setIsShowModal(false)}
onMarkAsRead={handleNotificationClick}
/>
</div>
)}
Expand Down
51 changes: 43 additions & 8 deletions src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, useState, useEffect, type ReactNode } from 'react';
import { getAlerts, type AlertViewResponse } from '@/api/alertApi';
import { getAlerts, putAlerts, type AlertViewResponse } from '@/api/alertApi';
type UserRole = 'employer' | 'employee' | null;

interface AuthContextType {
Expand All @@ -8,6 +8,7 @@ interface AuthContextType {
alarms: AlertViewResponse; // ์•Œ๋ฆผ ์ •๋ณด
login: (token: string, role: UserRole, userId: string) => void; // ๋กœ๊ทธ์ธ ํ•จ์ˆ˜
logout: () => void; // ๋กœ๊ทธ์•„์›ƒ ํ•จ์ˆ˜
markAlertAsRead: (alertId: string) => void;
}

const defaultAuthContext: AuthContextType = {
Expand All @@ -23,6 +24,7 @@ const defaultAuthContext: AuthContextType = {
},
login: () => {},
logout: () => {},
markAlertAsRead: () => {},
};

export const AuthContext = createContext<AuthContextType>(defaultAuthContext);
Expand All @@ -35,12 +37,24 @@ export function AuthProvider({ children }: { children: ReactNode }) {
);

useEffect(() => {
const token = localStorage.getItem('accessToken');
const role = localStorage.getItem('userRole') as UserRole;
if (token && role) {
setIsLoggedIn(true);
setRole(role);
}
const fetchAuth = async () => {
const token = localStorage.getItem('accessToken');
const userRole = localStorage.getItem('userRole') as UserRole;
const userId = localStorage.getItem('userId');

if (token && userRole && userId) {
setIsLoggedIn(true);
setRole(userRole);
try {
const alertRes = await getAlerts(userId);
setAlarms(alertRes);
} catch (error) {
console.error('์•ฑ ๋กœ๋”ฉ ์ค‘ ์•Œ๋ฆผ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ:', error);
}
}
};

fetchAuth();
}, []);

const login = async (token: string, role: UserRole, userId: string) => {
Expand All @@ -66,8 +80,29 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setAlarms(defaultAuthContext.alarms);
};

const markAlertAsRead = async (alertId: string) => {
const userId = localStorage.getItem('userId');
if (!userId) {
console.error('์ฝ์Œ ์ฒ˜๋ฆฌ ์‹คํŒจ: userId๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
return;
}
Comment on lines +85 to +88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ฌ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ๋‚จ๊ฒจ๋„ ๊ดœ์ฐฎ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์—๊ณ  ์ˆ˜์ •ํ–ˆ์–ด์•ผ๋๋Š”๋ฐ ๋จธ์ง€ํ•ด๋ฒ„๋ ธ๋„ค์š” ๋‚˜์ค‘์— ์ˆ˜์ •์‚ฌํ•ญ ์žˆ์„๋•Œ ํ•จ๊ป˜ ์ˆ˜์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค


setAlarms((prevAlarms) => ({
...prevAlarms,
items: prevAlarms.items.filter((alert) => alert.item.id !== alertId),
}));

try {
await putAlerts(userId, alertId);
} catch (error) {
console.error('API - ์•Œ๋ฆผ ์ฝ์Œ ์ฒ˜๋ฆฌ ์‹คํŒจ:', error);
}
};

return (
<AuthContext.Provider value={{ isLoggedIn, role, alarms, login, logout }}>
<AuthContext.Provider
value={{ isLoggedIn, role, alarms, login, logout, markAlertAsRead }}
>
{children}
</AuthContext.Provider>
);
Expand Down