Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type ExperienceType = ExperienceTypeCode;
export interface ExperienceUpsertBody {
title: string;

type: ExperienceType | null;
type: string | null;

startAt: string | null;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { EXPERIENCE_TYPE } from "@/shared/config/experience";
import {
EXPERIENCE_TYPE,
type ExperienceTypeCode,
} from "@/shared/config/experience";
import { parseYMD } from "@/shared/lib/format-date";
import { ModalBasic, Tooltip } from "@/shared/ui";
import { Button } from "@/shared/ui/button/button";
Expand Down Expand Up @@ -40,7 +43,10 @@ const ExperienceViewer = () => {
);
}

const typeLabel = current.type ? EXPERIENCE_TYPE[current.type] : "미지정";
const typeLabel =
current.type && current.type in EXPERIENCE_TYPE
? EXPERIENCE_TYPE[current.type as ExperienceTypeCode]
: "미지정";

return (
<main className={s.page}>
Expand Down
5 changes: 2 additions & 3 deletions src/features/experience/api/use-experience-list.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import { api } from "@/shared/api/axios-instance";
import { experienceQueryKey } from "@/shared/api/config/query-key";

import type { ExperienceList } from "../type/experience";
import type { ExperienceTypeCode } from "@/shared/config";

export const getExperienceList = async ({
type,
page,
}: {
type?: ExperienceTypeCode | undefined;
type?: string;
page: number;
}) => {
const response = await api.experiences.getSummaryExperienceList({
Expand All @@ -24,7 +23,7 @@ export const useGetExperienceList = ({
type,
page,
}: {
type: ExperienceTypeCode | null;
type?: string | null;
page: number;
}) => {
return useQuery({
Expand Down
33 changes: 21 additions & 12 deletions src/pages/experience/experience-page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useSearchParams, useNavigate } from "react-router-dom";

import { ROUTES } from "@/app/routes/paths";
import { useGetExperienceList } from "@/features/experience/api/use-experience-list.query";
Expand All @@ -9,24 +9,33 @@ import { ExperienceFilter } from "@/widgets";
import * as styles from "./experience-page.css";
import { ExperienceListContainer } from "./ui/experience-list-container";

import type { ExperienceTypeCode } from "@/shared/config/experience";

const ExperiencePage = () => {
const [filter, setFilter] = useState<ExperienceTypeCode | null>(null);
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();

const currentPage = Number(searchParams.get("page")) || 1;
Comment thread
hummingbbird marked this conversation as resolved.
Outdated
Comment thread
hummingbbird marked this conversation as resolved.
Outdated
const type = searchParams.get("type") || "";
Comment thread
hummingbbird marked this conversation as resolved.
Outdated

const [isExpTouched, setIsExpTouched] = useState(false);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

사소한 부분인데요..!
필터 값은 URL 에서 잘 복원되지만 isExpTouched는 로컬 state라 뒤로가기하면 다시 false로 초기화 될 것 같은데 혹시 의도한 동작인건지 궁금해서 리뷰 남깁니다 ! 👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

말씀주신 대로 현재는 isExpTouched가 state이기 때문에, 페이지 리렌더링 시 false로 초기화 되고 있습니다. 하지만 현재 로직상 type 파라미터가 비어있을 때 '전체' 데이터를 보여주고 있어서 필터 라벨이 기본값(경험 유형)으로 돌아가더라도 UX 측면에서 큰 지장이 없다고 생각해 지금과 같은 방식으로 구현했습니다 😀

허나 ‘전체’와 ‘경험 유형’은 엄연히 다른 상태이기 때문에 구분하는 것이 더 좋을 것이라고 생각해요! 우선 우선순위가 더 높은 쿼리스트링값의 유지를 우선적으로 구현하고 해당 부분은 추후 스프린트에 적용할까 하는데 정민님 생각은 어떠신가요??

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

넵 이해했습니다!

말씀해주신 것처럼 현재 PR에서는 쿼리스트링 기반으로 목록 상태를 유지하는 쪽이 우선순위가 더 높아 보여서 ! 지금 방향으로 가도 괜찮을 것 같아요 👍

전체 / 경험 유형 구분 부분은 추후 스프린트에서 보완되면 더 좋을 것 같습니다!! 설명 감사합니다 😄

const [currentPage, setCurrentPage] = useState(1);
const navigate = useNavigate();

const { data } = useGetExperienceList({
type: filter,
type,
page: currentPage,
});

const handleFilterChange = (value: ExperienceTypeCode | null) => {
const handleFilterChange = (value: string) => {
setIsExpTouched(true);
setFilter(value);
setCurrentPage(1);
setSearchParams({
type: value,
page: "1",
});
};

const handlePageChange = (page: number) => {
setSearchParams({
type: type ?? "",
page: String(page),
});
};
Comment thread
hummingbbird marked this conversation as resolved.

return (
Expand All @@ -53,15 +62,15 @@ const ExperiencePage = () => {
</button>

<ExperienceFilter
value={filter}
value={type}
onChange={handleFilterChange}
isTouched={isExpTouched}
hasTotal={true}
/>
</div>
</section>

<ExperienceListContainer data={data} onPageChange={setCurrentPage} />
<ExperienceListContainer data={data} onPageChange={handlePageChange} />
</div>
);
};
Expand Down
91 changes: 58 additions & 33 deletions src/pages/home/search-section/search-section.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { useSearchParams } from "react-router-dom";

import { useGetCompanies } from "@/features/home";
import { ScaleFilter, IndustryFilter } from "@/features/home/ui";
Expand All @@ -11,42 +12,66 @@ import * as styles from "./search-section.css";

import type { IndustryCode, ScaleCode } from "@/shared/config";

interface CompanySearchParamsType {
keyword?: string;
industry?: IndustryCode;
scale?: ScaleCode;
sort?: string;
page?: number;
isRecruited?: boolean;
}

const SearchSection = () => {
const [params, setParams] = useState<CompanySearchParamsType>({
page: 1,
isRecruited: true,
});
const [searchParams, setSearchParams] = useSearchParams();

const keyword = searchParams.get("keyword") || "";
const industry = (searchParams.get("industry") as IndustryCode) || undefined;
const scale = (searchParams.get("scale") as ScaleCode) || undefined;
Comment thread
hummingbbird marked this conversation as resolved.
Outdated
const page = Number(searchParams.get("page")) || 1;
const isRecruited = searchParams.get("isRecruited") !== "false";

const params = {
keyword,
industry,
scale,
page,
isRecruited,
};

const { data, isLoading, isPlaceholderData } = useGetCompanies(params);
const content = data?.content || [];
const hasResult = content.length > 0;
const [searchValue, setSearchValue] = useState("");
const currentPage = params.page ?? 1;

const [searchValue, setSearchValue] = useState(keyword);

const [isScaleTouched, setIsScaleTouched] = useState(false);
const [isIndustryTouched, setIsIndustryTouched] = useState(false);

const updateParams = (patch: Partial<CompanySearchParamsType>) => {
setParams((prev) => ({
...prev,
...patch,
}));
const updateSearchParams = (
patch: Record<string, string | number | boolean | undefined>
) => {
const newParams = new URLSearchParams(searchParams);

Object.entries(patch).forEach(([key, value]) => {
if (value === undefined || value === "") {
newParams.delete(key);
} else {
newParams.set(key, String(value));
}
});

if (!patch.page) {
newParams.set("page", "1");
}
Copy link
Copy Markdown
Collaborator

@u-zzn u-zzn Mar 16, 2026

Choose a reason for hiding this comment

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

패치된 값 기준으로 page를 자동으로 1로 돌려주는 방식 좋은 것 같습니다 👍
다만 현재는 !patch.page 조건이라 page: 0 같은 값도 동일하게 reset 대상으로 들어가게 되어 의미가 조금 모호해질 수 있다고 생각하는데, “이번 patch에 page가 포함되지 않은 경우”라는 의도를 더 명확히 드러내기 위해 !("page" in patch)처럼 표현하는 방식은 어떨까요? 🙂

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

좋은 지적 감사합니다! 유진님 말씀대로 조건문의 목적을 고려했을 때 제안해주신 방향으로 수정하는 게 더 적합한 것 같습니다! 반영하겠습니다 😀


setSearchParams(newParams);
};

const handlePageChange = (newPage: number) => {
if (isPlaceholderData) return;
updateParams({ page: newPage });
updateSearchParams({ page: newPage });
};

const handleSearch = (newKeyword: string) => {
updateSearchParams({ keyword: newKeyword });
};

// 검색값 유지
useEffect(() => {
setSearchValue(keyword);
}, [keyword]);

return (
<>
<section
Expand All @@ -68,7 +93,7 @@ const SearchSection = () => {
placeholder="지원하고 싶은 기업을 검색해보세요"
value={searchValue}
onChange={setSearchValue}
onSearch={(keyword) => updateParams({ keyword, page: 1 })}
onSearch={handleSearch}
/>
</div>
</div>
Expand All @@ -78,20 +103,20 @@ const SearchSection = () => {
<div className={styles.container}>
<div className={styles.filterWrapper}>
<IndustryFilter
value={params.industry ?? null}
value={industry ?? null}
isTouched={isIndustryTouched}
onChange={(industry) => {
onChange={(newIndustry) => {
setIsIndustryTouched(true);
updateParams({ industry, page: 1 });
updateSearchParams({ industry: newIndustry });
}}
/>

<ScaleFilter
value={params.scale}
value={scale}
isTouched={isScaleTouched}
onChange={(scale) => {
onChange={(newScale) => {
setIsScaleTouched(true);
updateParams({ scale, page: 1 });
updateSearchParams({ scale: newScale });
}}
/>

Expand All @@ -100,9 +125,9 @@ const SearchSection = () => {
</p>

<Toggle
checked={params.isRecruited ?? true}
onCheckedChange={(isRecruited) =>
updateParams({ isRecruited, page: 1 })
checked={isRecruited}
onCheckedChange={(checked) =>
updateSearchParams({ isRecruited: checked })
}
/>
</div>
Expand All @@ -122,7 +147,7 @@ const SearchSection = () => {
))}
</div>
<Pagination
currentPage={currentPage}
currentPage={page}
totalPage={data?.totalPage ?? 1}
onPageChange={handlePageChange}
/>
Expand Down
45 changes: 26 additions & 19 deletions src/pages/matching-list/matching-list-page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { useSearchParams } from "react-router-dom";

import { useGetAiReportList } from "@/features/matching-list/api/use-get-matching-list";
import { ICON_MATCH, ERROR } from "@/shared/assets/images";
Expand All @@ -7,39 +8,45 @@ import { Search } from "@/shared/ui";
import { ListSection } from "./list-section/list-section";
import * as styles from "./matching-list-page.css";

interface MatchingListParams {
keyword?: string;
page: number;
}
const MatchingListPage = () => {
const [params, setParams] = useState<MatchingListParams>({
keyword: "",
page: 1,
});
const [searchParams, setSearchParams] = useSearchParams();
const currentPage = Number(searchParams.get("page")) || 1;
Comment thread
hummingbbird marked this conversation as resolved.
Outdated
const keyword = searchParams.get("keyword") || "";

const { data, isLoading } = useGetAiReportList(params);
const { content = [], currentPage = 1, totalPage = 1 } = data ?? {};
const { data, isLoading } = useGetAiReportList({
page: currentPage,
keyword,
});
const { content = [], totalPage = 1 } = data ?? {};

const showEmptyState = !isLoading && content.length === 0;
const showList = content.length > 0;

const [searchValue, setSearchValue] = useState("");
const [searchValue, setSearchValue] = useState(keyword);

const handleSearch = (keyword: string) => {
setParams({ keyword, page: 1 });
const handleSearch = (newKeyword: string) => {
setSearchParams({
keyword: newKeyword,
page: "1",
});
};

const handlePageChange = (page: number) => {
setParams((prev) => ({
...prev,
page,
}));
setSearchParams({
keyword,
page: String(page),
});
Comment thread
hummingbbird marked this conversation as resolved.
};

const handleSearchChange = (keyword: string) => {
setSearchValue(keyword);
};

// 검색값 유지
useEffect(() => {
setSearchValue(keyword);
}, [keyword]);

return (
<main className={styles.container}>
{/* header 섹션 */}
Expand Down Expand Up @@ -84,7 +91,7 @@ const MatchingListPage = () => {
/>
<p className={styles.emptyTitle}>"검색 결과가 없습니다"</p>
<p className={styles.emptyDescription}>
{params.keyword
{keyword
? "다른 검색어로 다시 시도해보세요."
: "경험 등록하기 버튼을 눌러 경험을 등록해보세요."}
</p>
Expand Down
4 changes: 2 additions & 2 deletions src/shared/api/generate/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export interface ExperienceRequestDto {
*/
title: string;
/** @example "INTERNSHIP" */
type: "INTERNSHIP" | "PROJECT" | "EDUCATION" | "ETC";
type: string;
/**
Comment thread
hummingbbird marked this conversation as resolved.
* @format date
* @example "2025-12-23"
Expand Down Expand Up @@ -812,7 +812,7 @@ export class Api<
*/
getSummaryExperienceList: (
query?: {
type?: "INTERNSHIP" | "PROJECT" | "EDUCATION" | "ETC";
type?: string;
/**
* @format int32
* @default 1
Expand Down
Loading
Loading