diff --git a/src/Home.module.css b/src/Home.module.css index 13ac1cb..0a2c8dd 100644 --- a/src/Home.module.css +++ b/src/Home.module.css @@ -1,14 +1,29 @@ +.filterContainer { + position: sticky; + top: 0; + z-index: 100; + background-color: white; /* Ensure background is white to cover content when sticky */ + padding: 16px 30px 0px 30px; /* Add padding to match main content */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Optional: Add shadow for better visual separation */ +} + .main { display: flex; flex-direction: column; - padding: 16px 30px; + padding: 0 30px 16px 30px; /* Adjust padding for main content */ +} + +.contentWrapper { + display: flex; + flex-direction: column; + gap: 20px; } .accordion { background-color: #eee; color: #444; cursor: pointer; - padding: 16px 24px 16px 24px; + padding: 16px 24px; width: 100%; display: flex; flex-direction: row; @@ -16,7 +31,6 @@ outline: none; transition: 0.4s; justify-content: space-between; - font-size: 18px; font-weight: 700; } @@ -33,39 +47,57 @@ } ul.chips { - margin: 30px 0 50px; + margin: 0; list-style: none; display: flex; gap: 6px; flex-wrap: wrap; - padding: 0px 16px 16px 16px; + padding: 0; } -.chip { - background-color: rgb(255, 255, 255); - display: flex; - align-items: center; - gap: 4px; - margin-right: 6px; - font-size: 12px; - float: left; - border: 1px solid #f1f2f5; - border-radius: 14px; - padding: 16px 8px; - font-size: 14px; +.postList { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; /* Gap between cards */ } -.resetChip { - background-color: rgb(255, 255, 255); +.pagination { display: flex; + justify-content: center; align-items: center; - gap: 4px; - margin-right: 6px; - font-size: 12px; - float: left; - padding: 16px 8px; + gap: 5px; /* Smaller gap between page buttons */ + margin-top: 20px; +} + +.pagination button { + padding: 6px 10px; /* Smaller padding for buttons */ + border: 1px solid #ccc; + border-radius: 4px; + background-color: #f0f0f0; + cursor: pointer; + transition: background-color 0.2s, border-color 0.2s, color 0.2s; font-size: 14px; - border: none; + min-width: 32px; /* Ensure consistent button width */ + text-align: center; +} + +.pagination button:hover:not(:disabled) { + background-color: #e0e0e0; + border-color: #a0a0a0; +} + +.pagination button:disabled { + cursor: not-allowed; + opacity: 0.6; + background-color: #f8f8f8; + border-color: #e0e0e0; + color: #a0a0a0; +} + +.pagination button.activePage { + background-color: #007bff; + color: white; + border-color: #007bff; } /* MODALS */ diff --git a/src/components/Home.tsx b/src/components/Home.tsx index d81f655..c81aaaa 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -6,6 +6,8 @@ import styles from '../Home.module.css'; import { encodeQueryParams, usePosts } from '../contexts/PostContext'; import InternCard from './InternCard'; +type OpenDropdownType = 'rec' | 'dm' | 'new' | null; + const Home = () => { const DOMAINS: string[] = [ 'FINTECH', @@ -21,23 +23,19 @@ const Home = () => { const DEV: string[] = ['FRONT', 'APP', 'BACKEND', 'DATA', 'OTHERS']; - const { posts, isLoading, fetchPosts } = usePosts(); + const { posts, isLoading, fetchPosts, paginator } = usePosts(); const [panelExpanded, setPanelExpanded] = useState(false); const [roles, setRoles] = useState([]); - const [recExpanded, setRecExpanded] = useState(false); const [isActive, setIsActive] = useState(false); - // radio selection state (temporary) const [recStatus, setRecStatus] = useState<'all' | 'recruiting'>( isActive ? 'recruiting' : 'all' ); const [domains, setDomain] = useState(DOMAINS); const [dmStatus, setDmStatus] = useState(domains); - const [dmExpanded, setDmExpanded] = useState(false); const [order, setOrder] = useState(0); - const [newExpanded, setNewExpanded] = useState(false); const [orderStatus, setOrderStatus] = useState(0); const [query, setQuery] = useState(''); @@ -45,6 +43,25 @@ const Home = () => { const [isModalOpen, setIsModalOpen] = useState(false); const navigate = useNavigate(); + const [openDropdown, setOpenDropdown] = useState(null); + + // Pagination state + const [currentPage, setCurrentPage] = useState(1); + // const postsPerPage = 12; + + const currentPosts = posts; + const totalPages = paginator; + + const handlePageChange = (pageNumber: number) => { + if (pageNumber > 0 && pageNumber <= totalPages) { + setCurrentPage(pageNumber); + } + }; + + const handleDropdownToggle = (dropdown: OpenDropdownType) => { + setOpenDropdown((prev) => (prev === dropdown ? null : dropdown)); + }; + const handleLoginRequired = () => { setIsModalOpen(true); }; @@ -60,12 +77,13 @@ const Home = () => { params: { roles: roles.length === 0 ? null : roles, isActive: isActive, - domains: domains.length === DOMAINS.length ? null : domains, + domains: domains.length === DOMAINS.length ? null : domains, // if null -> all domains order: order, + page: currentPage, }, }); setQuery(q); - }, [roles, isActive, domains, order]); + }, [roles, isActive, domains, order, currentPage]); // role check const handleRoleCheck = (e: ChangeEvent) => { const { checked, value } = e.currentTarget; @@ -82,7 +100,11 @@ const Home = () => { const handleDomainCheck = (e: ChangeEvent) => { const { checked, value } = e.currentTarget; if (value === 'all') { - setDmStatus(DOMAINS); + if (checked) { + setDmStatus(DOMAINS); + } else { + setDmStatus([]); + } } else { setDmStatus((p) => { if (checked) return [...p, value]; @@ -103,415 +125,480 @@ const Home = () => { setOrderStatus(0); }; - return ( -
- - {panelExpanded && ( -
-
-

개발

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-

기획

-
- - -
-
- -
-

디자인

-
- - -
-
-
-

마케팅

-
- - -
-
-
- )} + const renderPageNumbers = () => { + const pageNumbers = []; + const pageBlockSize = 5; -
    - {/* 모집 상태 옵션 버튼 */} -
  • { - setRecExpanded((p) => !p); - }} + const currentBlock = Math.ceil(currentPage / pageBlockSize); + + const startPage = (currentBlock - 1) * pageBlockSize + 1; + const endPage = startPage + pageBlockSize - 1; + + for (let i = startPage; i <= endPage; i++) { + const isDisabled = i > totalPages; + + pageNumbers.push( + - - - - )} -
  • + {i} + + ); + } + return pageNumbers; + }; - {/* 업종 옵션 버튼 */} -
  • +
    + + {panelExpanded && ( +
    +
    +

    개발

    +
    + +
    -
    - - +
    + +
    -
    - )} -
  • +
    + + +
    +
    + + +
    +
    + + +
    + - {/* 순서 */} -
  • { - setNewExpanded((p) => !p); - }} - > - 최신순 {newExpanded ? : } - {/* 순서 선택 팝업 */} - {newExpanded && ( -
    e.stopPropagation()}> -
    - - +
    +

    기획

    +
    + +
    -
    - - +
    + +
    +

    디자인

    +
    + +
    -
    - )} -
  • -
  • - 초기화 -
  • -
- -
- {isLoading ? ( -

Loading...

- ) : ( - posts?.map((post) => ( - - )) +
+
+

마케팅

+
+ + +
+
+ )} - + - {isModalOpen && ( -
setIsModalOpen(false)} - > -
e.stopPropagation()} - > -

로그인이 필요한 기능입니다.

-
- + +
+
+ )} + + + {/* 업종 옵션 버튼 */} +
  • handleDropdownToggle('dm')} + > + 업종{' '} + {openDropdown === 'dm' ? : } + {/* 업종 선택 팝업 */} + {openDropdown === 'dm' && ( +
    e.stopPropagation()} + > +
    +
    + + dmStatus.includes(d)) + } + onChange={handleDomainCheck} + /> +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + )} +
  • + + {/* 순서 */} +
  • handleDropdownToggle('new')} + > + 최신순{' '} + {openDropdown === 'new' ? : } + {/* 순서 선택 팝업 */} + {openDropdown === 'new' && ( +
    e.stopPropagation()} + > +
    + + +
    +
    + + +
    +
    + )} +
  • +
  • + 초기화 +
  • + + +
    + {isLoading ? ( +

    Loading...

    + ) : ( + //test + currentPosts?.map((post) => ( + + )) + )} +
    + +
    + -
    - )} -
    + {isModalOpen && ( +
    setIsModalOpen(false)} + > +
    e.stopPropagation()} + > +

    로그인이 필요한 기능입니다.

    +
    + + +
    +
    +
    + )} + + ); }; diff --git a/src/contexts/PostContext.tsx b/src/contexts/PostContext.tsx index d80f71e..2498f62 100644 --- a/src/contexts/PostContext.tsx +++ b/src/contexts/PostContext.tsx @@ -108,7 +108,7 @@ export const PostProvider = ({ children }: PostProviderProps) => { try { const url = `/api/post?${query}`; const response = await apiClient.get<{ - posts: ApiPost[]; + posts: ApiPost[]; // posts stored in array paginator: { lastpage: number }; }>(url); setPaginator(response.data.paginator.lastpage);