Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
55 changes: 39 additions & 16 deletions src/entities/search/api/getFilteredProjectLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { getDocs, getCountFromServer } from "firebase/firestore";
import type { QueryDocumentSnapshot, DocumentData } from "firebase/firestore";

import { SearchFilterBuilder } from "@entities/search/model/searchFilterBuilder";
import type { ProjectSearchFilterOption } from "@entities/search/types";

import type { ProjectListRes } from "@shared/types/project";
import type { ProjectSearchFilterOption } from "@shared/types/search";

interface GetFilteredProjectListsOptions {
cursor?: QueryDocumentSnapshot<DocumentData> | null;
Expand All @@ -23,28 +23,39 @@ export const getFilteredProjectCount = async (
filter: ProjectSearchFilterOption
): Promise<number> => {
const queryBuilder = new SearchFilterBuilder(collectionName)
.setTitle(filter.title || undefined)
.setCategory(filter.category === "all" ? undefined : filter.category)
.setStatus(filter.status === "all" ? undefined : filter.status)
.setWorkflow(filter.workflow === "all" ? undefined : filter.workflow);

const query = queryBuilder.build();

const snapshot = await getCountFromServer(query);

if (filter.position && filter.position !== "all") {
if (filter.title || (filter.position && filter.position !== "all")) {
const allDocs = await getDocs(query);

const filteredProjects = allDocs.docs.filter((doc) => {
const data = doc.data() as ProjectListRes;
return data.positions.some(
(position) => position.position === filter.position
let projects = allDocs.docs.map((doc) => ({
id: doc.id,
...doc.data(),
})) as ProjectListRes[];

if (filter.title && filter.title.trim()) {
const searchTitle = filter.title.toLowerCase().trim();
projects = projects.filter((project) =>
project.title.toLowerCase().includes(searchTitle)
);
});
}

return filteredProjects.length;
if (filter.position && filter.position !== "all") {
projects = projects.filter((project) =>
project.positions.some(
(position) => position.position === filter.position
)
);
}

return projects.length;
}

const snapshot = await getCountFromServer(query);
return snapshot.data().count;
};

Expand All @@ -55,28 +66,34 @@ export const getFilteredProjectsByPage = async (
pageSize: number = 6
): Promise<ProjectListRes[]> => {
const queryBuilder = new SearchFilterBuilder(collectionName)
.setTitle(filter.title || undefined)
.setCategory(filter.category === "all" ? undefined : filter.category)
.setStatus(filter.status === "all" ? undefined : filter.status)
.setWorkflow(filter.workflow === "all" ? undefined : filter.workflow)
.setSortBy(filter.sortBy || "latest");

if (filter.position && filter.position !== "all") {
queryBuilder.addLimit(pageSize * 5);
if (filter.title || (filter.position && filter.position !== "all")) {
queryBuilder.addLimit(pageSize * 10);
} else {
const offset = (page - 1) * pageSize;
queryBuilder.addLimit(offset + pageSize * 2);
}

const query = queryBuilder.build();

const snapshot = await getDocs(query);

let projects = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
})) as ProjectListRes[];

if (filter.title && filter.title.trim()) {
const searchTitle = filter.title.toLowerCase().trim();

projects = projects.filter((project) =>
project.title.toLowerCase().includes(searchTitle)
);
}

if (filter.position && filter.position !== "all") {
projects = projects.filter((project) =>
project.positions.some(
Expand All @@ -102,7 +119,6 @@ const getFilteredProjectLists = async (
const { cursor, pageSize = 6 } = options;

let queryBuilder = new SearchFilterBuilder(collectionName)
.setTitle(filter.title || undefined)
.setCategory(filter.category === "all" ? undefined : filter.category)
.setStatus(filter.status === "all" ? undefined : filter.status)
.setWorkflow(filter.workflow === "all" ? undefined : filter.workflow)
Expand All @@ -122,6 +138,13 @@ const getFilteredProjectLists = async (
...doc.data(),
})) as ProjectListRes[];

if (filter.title && filter.title.trim()) {
const searchTitle = filter.title.toLowerCase().trim();
projects = projects.filter((project) =>
project.title.toLowerCase().includes(searchTitle)
);
}

if (filter.position && filter.position !== "all") {
projects = projects.filter((project) =>
project.positions.some(
Expand Down
34 changes: 18 additions & 16 deletions src/entities/search/hooks/useFilteredProjects.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useState } from "react";

import type { ProjectSearchFilterOption, SortBy } from "@entities/search/types";
import { useState, useMemo, useCallback } from "react";

import {
RecruitmentStatus,
type ProjectCategory,
type Workflow,
} from "@shared/types/project";
import type { ProjectSearchFilterOption, SortBy } from "@shared/types/search";
import type { UserRole } from "@shared/types/user";

interface UseFilteredProjects {
Expand All @@ -25,7 +24,7 @@ interface UseFilteredProjects {
updateSortBy: (newSortBy: SortBy | "latest") => void;
resetFilters: () => void;
getActiveFiltersCount: () => number;
getCleanFilter: () => ProjectSearchFilterOption;
getFilterStatus: () => ProjectSearchFilterOption;
}

const useFilteredProjects = (): UseFilteredProjects => {
Expand Down Expand Up @@ -79,7 +78,7 @@ const useFilteredProjects = (): UseFilteredProjects => {
return count;
};

const getCleanFilter = (): ProjectSearchFilterOption => {
const getFilterStatus = useCallback((): ProjectSearchFilterOption => {
const cleanFilter: ProjectSearchFilterOption = {
title: title || "",
category: category === "all" ? undefined : category,
Expand All @@ -92,16 +91,19 @@ const useFilteredProjects = (): UseFilteredProjects => {
return Object.fromEntries(
Object.entries(cleanFilter).filter(([_, value]) => value !== undefined)
) as ProjectSearchFilterOption;
};

const filterState = {
title,
category,
position,
status,
workflow,
sortBy,
};
}, [title, category, position, status, workflow, sortBy]);

const filterState = useMemo(
() => ({
title,
category,
position,
status,
workflow,
sortBy,
}),
[title, category, position, status, workflow, sortBy]
);

return {
filterState,
Expand All @@ -119,7 +121,7 @@ const useFilteredProjects = (): UseFilteredProjects => {
updateSortBy,
resetFilters,
getActiveFiltersCount,
getCleanFilter,
getFilterStatus,
};
};

Expand Down
28 changes: 22 additions & 6 deletions src/entities/search/hooks/useProjectListPage.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useState } from "react";
import { useState, useCallback, useEffect } from "react";

import {
useGetFilteredProjectsByPage,
useGetFilteredProjectsCount,
} from "@entities/search/queries/useGetFilteredProjectLists";
import type { ProjectSearchFilterOption } from "@entities/search/types";

import { usePaginationWithState } from "@shared/hooks/usePagination";
import type { ProjectListRes } from "@shared/types/project";
import type { ProjectSearchFilterOption } from "@shared/types/search";

const ITEMS_PER_PAGE = 6;

Expand Down Expand Up @@ -35,6 +35,7 @@ const useProjectListPage = (): UseProjectListPageReturn => {
data: totalCount = 0,
isLoading: isCountLoading,
isError: isCountError,
refetch: refetchCount,
} = useGetFilteredProjectsCount(currentFilter);

const { currentPage, totalPages, setPage, goToReset } =
Expand All @@ -47,15 +48,30 @@ const useProjectListPage = (): UseProjectListPageReturn => {
data: projects = [],
isLoading: isProjectsLoading,
isError: isProjectsError,
refetch: refetchProjects,
} = useGetFilteredProjectsByPage(currentFilter, currentPage, ITEMS_PER_PAGE);

const isLoading = isProjectsLoading || isCountLoading;
const isError = isProjectsError || isCountError;

const handleSearch = (filter: ProjectSearchFilterOption): void => {
setCurrentFilter(filter);
goToReset();
};
// 필터가 변경되면 즉시 refetch
useEffect(() => {
if (Object.keys(currentFilter).length > 0) {
refetchProjects();
refetchCount();
}
}, [currentFilter, refetchProjects, refetchCount]);

const handleSearch = useCallback(
(filter: ProjectSearchFilterOption): void => {
console.log("검색 필터 받음:", filter);

// 상태 업데이트만 하면 useEffect가 자동으로 refetch
setCurrentFilter(filter);
goToReset();
},
[goToReset]
);

const handlePageChange = (page: number): void => {
if (page === currentPage || isLoading) return;
Expand Down
65 changes: 65 additions & 0 deletions src/entities/search/hooks/useSearchHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useCallback } from "react";

import {
useSearchHistory as useSearchHistoryStore,
useSearchHistoryActions,
useIsHistoryEnabled,
} from "@shared/stores/searchStore";

interface UseSearchHistoryReturn {
searchHistory: string[];
isHistoryEnabled: boolean;
handleRemoveHistoryItem: (historyItem: string, e: React.MouseEvent) => void;
handleClearAllHistory: () => void;
handleToggleHistory: () => void;
handleHistoryItemClick: (
historyItem: string,
onItemClick: (item: string) => void,
onClose: () => void
) => void;
}

export const useSearchHistory = (): UseSearchHistoryReturn => {
const searchHistory = useSearchHistoryStore();
const isHistoryEnabled = useIsHistoryEnabled();
const { clearHistory, removeFromHistory, toggleHistoryEnabled } =
useSearchHistoryActions();

const handleRemoveHistoryItem = useCallback(
(historyItem: string, e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
removeFromHistory(historyItem);
},
[removeFromHistory]
);

const handleClearAllHistory = useCallback(() => {
clearHistory();
}, [clearHistory]);

const handleToggleHistory = useCallback(() => {
toggleHistoryEnabled();
}, [toggleHistoryEnabled]);

const handleHistoryItemClick = useCallback(
(
historyItem: string,
onItemClick: (item: string) => void,
onClose: () => void
) => {
onItemClick(historyItem);
onClose();
},
[]
);

return {
searchHistory,
isHistoryEnabled,
handleRemoveHistoryItem,
handleClearAllHistory,
handleToggleHistory,
handleHistoryItemClick,
};
};
Loading