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
61 changes: 58 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/api/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
TSignupValues,
TSocialLoginResponse,
TSocialLoginValues,
} from '@/types/auth';
} from '@/types/auth/auth';

import { axiosInstance } from '../axiosInstance';

Expand Down
2 changes: 1 addition & 1 deletion src/api/course/course.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TSearchRegionResponse, TSearchRegionValues } from '@/types/dateCourse';
import type { TSearchRegionResponse, TSearchRegionValues } from '@/types/dateCourse/dateCourse';

import { axiosInstance } from '../axiosInstance';

Expand Down
25 changes: 25 additions & 0 deletions src/api/notice/notice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { TFetchNoticeDetailResponse, TFetchNoticesResponse } from '@/types/notice/notice';

import { axiosInstance } from '../axiosInstance';

// 공지사항 전체 조회 API
export const fetchNotices = async ({
category,
page,
size,
}: {
category: 'SERVICE' | 'SYSTEM';
page: number;
size: number;
}): Promise<TFetchNoticesResponse> => {
const { data } = await axiosInstance.get('/api/v1/notices', {
params: { noticeCategory: category, page, size },
});
return data;
};

// 공지사항 상세 조회 API
export const fetchNoticeDetail = async (noticeId: number): Promise<TFetchNoticeDetailResponse> => {
const { data } = await axiosInstance.get(`/api/v1/notices/${noticeId}`);
return data;
};
5 changes: 5 additions & 0 deletions src/assets/icons/Burger_fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/Clear.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/dateCourse/dateCourseOptionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TDateCourseOptionButtonProps } from '@/types/dateCourse';
import type { TDateCourseOptionButtonProps } from '@/types/dateCourse/dateCourse';

export default function DateCourseOptionButton({ option, isSelected, onClick }: TDateCourseOptionButtonProps) {
return (
Expand Down
26 changes: 11 additions & 15 deletions src/components/dateCourse/dateCourseSearchFilterOption.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useEffect, useRef, useState } from 'react';

import type { TDateCourseSearchFilterOption } from '@/types/dateCourse';
import type { TDateCourseSearchFilterOption, TRegion } from '@/types/dateCourse/dateCourse';
import DATE_KEYWORD from '@/constants/dateKeywords';

import { useSearchRegion } from '@/hooks/course/useSearchRegion';
import useDebounce from '@/hooks/useDebounce';

import DateCourseOptionButton from './dateCourseOptionButton';
import DateKeyword from './dateKeyword';
Expand All @@ -24,9 +23,6 @@ export default function DateCourseSearchFilterOption({ options, type, value, onC
const [date, setDate] = useState(defaultDate);
const [time, setTime] = useState(defaultTime);
const [inputValue, setInputValue] = useState('');
const [showSearchResults, setShowSearchResults] = useState(false);

const debouncedInputValue = useDebounce(inputValue, 3000);

useEffect(() => {
onChange(`${date} ${time}`);
Expand All @@ -50,11 +46,12 @@ export default function DateCourseSearchFilterOption({ options, type, value, onC
setInputValue(e.target.value);
};

const { data: regionList } = useSearchRegion({ keyword: debouncedInputValue });
const { data: regionList, refetch } = useSearchRegion({ keyword: inputValue }, { enabled: false });

const handleSearch = () => {
if (!inputValue.trim()) return;
setShowSearchResults(true);
const keyword = inputValue.trim();
if (!keyword) return;
refetch();
};

return (
Expand Down Expand Up @@ -96,22 +93,21 @@ export default function DateCourseSearchFilterOption({ options, type, value, onC
onChange={handleInputChange}
/>
</div>
{showSearchResults && regionList && regionList.result.regions.length > 0 && (
{regionList && regionList.result.regions.length > 0 && (
<ul className="mt-2 w-full border border-primary-500 rounding-16 shadow-default bg-white max-h-[200px] overflow-auto">
{regionList.result.regions.map((region: string, idx: number) => (
{regionList.result.regions.map((region: TRegion, idx: number) => (
<li
key={idx}
className="p-2 cursor-pointer hover:bg-gray-100 text-sm text-default-gray-800"
className="px-4 py-2 cursor-pointer hover:bg-gray-100 text-sm text-default-gray-800"
onClick={() => {
const current = Array.isArray(value) ? value : [];
if (!current.includes(region)) {
onChange([...current, region]);
if (!current.includes(region.name)) {
onChange([...current, region.name]);
}
setInputValue('');
setShowSearchResults(false);
}}
>
{region}
{region.name}
</li>
))}
</ul>
Expand Down
2 changes: 1 addition & 1 deletion src/components/dateCourse/dateKeyword.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TDateKeyword } from '@/types/dateCourse';
import type { TDateKeyword } from '@/types/dateCourse/dateCourse';

import KeywordButton from './keywordButton';

Expand Down
2 changes: 1 addition & 1 deletion src/components/dateCourse/info.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TInfo } from '@/types/dateCourse';
import type { TInfo } from '@/types/dateCourse/dateCourse';

import InfoElement from './infoElement';

Expand Down
2 changes: 1 addition & 1 deletion src/components/dateCourse/keywordButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TKeywordButtonProps } from '@/types/dateCourse';
import type { TKeywordButtonProps } from '@/types/dateCourse/dateCourse';

export default function KeywordButton({ tag, selected = false, onClick, isButton }: TKeywordButtonProps) {
return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/dateCourse/timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from 'react';

import type { TTimeline } from '@/types/dateCourse';
import type { TTimeline } from '@/types/dateCourse/dateCourse';

import KeywordButton from './keywordButton';

Expand Down
48 changes: 34 additions & 14 deletions src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';

import MobileMenu from './MobileMenu';
import SettingsModal from '../modal/SettingModal';

import BurgerIcon from '@/assets/icons/Burger_fill.svg?react';
import ClearIcon from '@/assets/icons/Clear.svg?react';
import NotificationsIcon from '@/assets/icons/notifications_Blank.svg?react';
import SettingsIcon from '@/assets/icons/settings_Blank.svg?react';
import NavbarLogo from '@/assets/withTimeLogo/navbarLogo.svg?react';

// Header 컴포넌트 props
interface IHeaderProps {
mode?: 'full' | 'minimal'; // full: nav + border | minimal: 로고만
}

export default function Header({ mode = 'full' }: IHeaderProps) {
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false); //설정 모달
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); // 모바일 메뉴

const showNav = mode === 'full';
const showBorder = mode === 'full';

return (
<header className={`w-full ${showBorder ? 'border-b border-gray-200' : ''}`}>
{/* 최상단 네브바 */}
<div className="max-w-7xl mx-auto flex items-center justify-between px-4 lg:px-8 py-4">
{/* 로고 */}
<div className="flex items-center space-x-2">
<Link to="/home">
<NavbarLogo className="w-40 h-auto" />
</Link>
</div>
<Link to="/home">
<NavbarLogo className="w-40 h-auto" />
</Link>

{/* Nav 그룹 */}
{/* 데스크탑 메뉴 */}
{showNav && (
<div className="flex items-center gap-x-10 text-default-gray-00">
{/* 메뉴 */}
<div className="hidden lg:flex items-center gap-x-10 text-default-gray-00">
{/* 네비게이션 링크들 */}
<nav>
<ul className="flex space-x-5 sm:space-x-10 text-sm font-medium">
<ul className="flex space-x-5 sm:space-x-10">
<li>
<Link to="/home" className="font-body1">
메인
Expand All @@ -52,7 +54,7 @@ export default function Header({ mode = 'full' }: IHeaderProps) {
</ul>
</nav>

{/* 아이콘 */}
{/* 아이콘 버튼 */}
<div className="hidden lg:flex items-center space-x-5">
<Link to="/">
<NotificationsIcon className="w-5 h-5" fill="none" stroke="#000000" />
Expand All @@ -61,12 +63,30 @@ export default function Header({ mode = 'full' }: IHeaderProps) {
<SettingsIcon className="w-5 h-5" fill="none" stroke="#000000" />
</button>
</div>
</div>
)}

{/* 설정 모달 */}
{isSettingsOpen && <SettingsModal onClose={() => setIsSettingsOpen(false)} />}
{/* 모바일 메뉴 토글 버튼 */}
{showNav && (
<div className="lg:hidden">
{!isMobileMenuOpen ? (
<button onClick={() => setIsMobileMenuOpen(true)}>
<BurgerIcon className="w-6 h-6 text-default-gray-800" />
</button>
) : (
<button onClick={() => setIsMobileMenuOpen(false)}>
<ClearIcon className="w-6 h-6 text-default-gray-800" />
</button>
)}
</div>
)}
</div>

{/* 모바일 메뉴 */}
{isMobileMenuOpen && <MobileMenu onClose={() => setIsMobileMenuOpen(false)} onOpenSettings={() => setIsSettingsOpen(true)} />}

{/* 설정 모달 */}
{isSettingsOpen && <SettingsModal onClose={() => setIsSettingsOpen(false)} />}
</header>
);
}
Loading