Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] QFEED-64 알림페이지 레이아웃 #27

Merged
merged 5 commits into from
Nov 28, 2024
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
25 changes: 15 additions & 10 deletions src/components/common/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import { IoSearch } from 'react-icons/io5';
import { HiOutlineBell } from 'react-icons/hi';
import styled from '@emotion/styled';
import theme from '@/styles/theme';
import Logo from '@/assets/qfeed-logo.svg?react';
import { IoSearch } from "react-icons/io5";
import { HiOutlineBell } from "react-icons/hi";
import styled from "@emotion/styled";
import { useNavigate } from "react-router-dom"; // React Router의 useNavigate 가져오기
import theme from "@/styles/theme";
import Logo from "@/assets/qfeed-logo.svg?react";

interface HeaderProps {
onSearchClick?: () => void;
onNotificationClick?: () => void;
onProfileClick?: () => void;
onLogoClick?: () => void;
profileImage?: string;
className?: string;
}

const Header = ({ onSearchClick, onNotificationClick, onLogoClick, className }: HeaderProps) => {
const Header = ({ onSearchClick, onLogoClick, className }: HeaderProps) => {
const navigate = useNavigate();

const handleNotificationClick = () => {
navigate("/alarm");
};

return (
<StyledHeader className={className}>
<LogoWrapper onClick={onLogoClick}>
<Logo />
</LogoWrapper>
<RightSection>
<IconButton onClick={onSearchClick} aria-label='검색'>
<IconButton onClick={onSearchClick} aria-label="검색">
<IoSearch size={24} />
</IconButton>
<IconButton onClick={onNotificationClick} aria-label='알림'>
<IconButton onClick={handleNotificationClick} aria-label="알림">
<HiOutlineBell size={28} />
</IconButton>
</RightSection>
Expand Down
201 changes: 201 additions & 0 deletions src/pages/Alarm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { IoChevronBack } from "react-icons/io5";
import ProfileImage from "@/components/ui/ProfileImageCon/ProfileImageCon";
interface NotificationItem {
id: number;
type: string;
message: string;
time: string;
}

const NotificationPage: React.FC = () => {
const navigate = useNavigate();
const notifications: NotificationItem[] = [
{
id: 1,
type: "알림 유형(큐스페이스, 이벤트 등)",
message: "큐피드님이 나의 글에 '댓글'을 남겼습니다.",
time: "방금",
},
{
id: 2,
type: "알림 유형(큐스페이스, 이벤트 등)",
message: "큐피드님이 나의 글에 '댓글'을 남겼습니다.",
time: "1시간 전",
},
{
id: 3,
type: "알림 유형(큐스페이스, 이벤트 등)",
message: "큐피드님이 나의 글에 '댓글'을 남겼습니다.",
time: "1시간 전",
},
{
id: 4,
type: "알림 유형(큐스페이스, 이벤트 등)",
message: "큐피드님이 나의 글에 '댓글'을 남겼습니다.",
time: "방금",
},
{
id: 5,
type: "알림 유형(큐스페이스, 이벤트 등)",
message: "큐피드님이 나의 글에 '댓글'을 남겼습니다.",
time: "1시간 전",
},
{
id: 6,
type: "알림 유형(큐스페이스, 이벤트 등)",
message: "큐피드님이 나의 글에 '댓글'을 남겼습니다.",
time: "1시간 전",
},
];

const [readItems, setReadItems] = useState<number[]>([]); // 읽음 처리된 알림 ID 저장

const handleItemClick = (id: number) => {
setReadItems((prev) => (prev.includes(id) ? prev : [...prev, id]));
};

const markAllAsRead = () => {
setReadItems(notifications.map((notification) => notification.id));
};

return (
<div css={containerStyle}>
{/* Header */}
<div css={headerStyle}>
<IoChevronBack css={backIconStyle} onClick={() => navigate(-1)} />
<span css={headerTitleStyle}>알림</span>
</div>

{/* Unread count */}
<div css={readWrap}>
<div css={unreadCountStyle}>
안읽은 알림 {notifications.length - readItems.length}개
</div>
<span css={markAllAsReadStyle} onClick={markAllAsRead}>
모두 읽음 표시
</span>
</div>

{/* Notification List */}
<div css={listStyle}>
{notifications.map((notification) => (
<div
key={notification.id}
css={[
listCon,
readItems.includes(notification.id) && listConRead, // 읽음 처리된 항목 스타일
]}
onClick={() => handleItemClick(notification.id)}
>
<ProfileImage src="" size={40} />
<div css={notificationContentStyle}>
<span css={notificationTypeStyle}>{notification.type}</span>
<p css={notificationMessageStyle}>{notification.message}</p>
</div>
<span css={timeStyle}>{notification.time}</span>
</div>
))}
</div>
</div>
);
};

export default NotificationPage;

// 스타일 정의
const containerStyle = css`
display: flex;
flex-direction: column;
height: 773px;
background-color: #f9f4ef;
`;

const headerStyle = css`
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 10px 15px;
background-color: #ffffff;
border-bottom: 1px solid #ccc;
`;
const readWrap = css`
display: flex;
text-align: center;
align-items: center;
justify-content: space-between;
padding-right: 12px;
`;

const backIconStyle = css`
position: absolute;
left: 15px;
font-size: 24px;
cursor: pointer;
`;

const headerTitleStyle = css`
font-size: 18px;
font-weight: bold;
`;

const markAllAsReadStyle = css`
font-size: 14px;
border-radius: 16px;
padding: 6px;
color: #b9a298;
background-color: #f3ebe1;
cursor: pointer;
`;

const unreadCountStyle = css`
padding: 10px 15px;
font-size: 14px;
color: #666;
`;

const listStyle = css`
flex: 1;
overflow-y: auto;
background-color: #f9f3ec;
`;

const listCon = css`
background-color: #f3ebe1;
padding: 8px;
height: 80px;
border-bottom: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
`;

const listConRead = css`
background-color: #f9f4ef; /* 읽음 처리된 배경 색 */
`;

const notificationContentStyle = css`
flex: 1;
margin-left: 8px;
`;

const notificationTypeStyle = css`
font-size: 12px;
color: #999;
`;

const notificationMessageStyle = css`
margin-top: 4px;
font-size: 14px;
color: #333;
`;

const timeStyle = css`
font-size: 12px;
color: #999;
`;
3 changes: 1 addition & 2 deletions src/pages/Main/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Header from '@/components/common/Header';

import Header from "@/components/common/Header";
export default function Main() {
return (
<div>
Expand Down
34 changes: 20 additions & 14 deletions src/router/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
import RootLayout from '@/components/RootLayout';
import Login from '@/pages/Login';
import ChatList from '@/pages/ChatList';
import Main from '@/pages/Main';
import MyPage from '@/pages/MyPage';
import QSpaceMainPage from '@/pages/QSpaceMain';
import CategorySelectPage from '@/pages/QSpacePost/CategorySelectPage';
import { createBrowserRouter } from 'react-router-dom';
import RootLayout from "@/components/RootLayout";
import Login from "@/pages/Login";
import ChatList from "@/pages/ChatList";
import Main from "@/pages/Main";
import MyPage from "@/pages/MyPage";
import QSpaceMainPage from "@/pages/QSpaceMain";
import CategorySelectPage from "@/pages/QSpacePost/CategorySelectPage";
import NotificationPage from "@/pages/Alarm";
import PostGroupPage from '@/pages/QSpacePost/PostGroupPage';
import { createBrowserRouter } from "react-router-dom";


const router = createBrowserRouter([
{
path: '/',
path: "/",
element: <RootLayout />,
children: [
{
path: '/',
path: "",
element: <Main />,
},
{
path: 'chat', // 채팅 목록
path: "/chat", // 채팅 목록
element: <ChatList />,
},
{
path: '/mypage', // 마이페이지
path: "/mypage", // 마이페이지
element: <MyPage />,
},
{
path: '/qspace',
path: "/qspace",
element: <QSpaceMainPage />,
},
{
path: '/qspace/category',
path: "/qspace/category",
element: <CategorySelectPage />,
},
{
Expand All @@ -41,6 +43,10 @@ const router = createBrowserRouter([
path: '/login',
element: <Login />,
},
{
path: "/Alarm",
element: <NotificationPage />,
},
],
},
]);
Expand Down
Loading