From dfd5ba419b3bfb3f958aee11f2d3c8ca1ee2b755 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Thu, 8 May 2025 16:58:14 +0900 Subject: [PATCH] feat: implement SurveyFilteringOptions to global MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 조사매물 검색 조건 zustand 통해 전역으로 관리하도록 구현 --- src/app/api/survey-sales/route.ts | 60 +++++++++--- .../survey-sale/detail/DataTable.tsx | 11 ++- src/components/survey-sale/list/ListTable.tsx | 88 +++++++---------- .../survey-sale/list/SearchForm.tsx | 79 ++++++++++++--- src/hooks/useSurvey.ts | 58 ++++++----- src/store/surveyFilterStore.ts | 98 +++++++++++++++---- 6 files changed, 260 insertions(+), 134 deletions(-) diff --git a/src/app/api/survey-sales/route.ts b/src/app/api/survey-sales/route.ts index b93ff5e..ee6dbe7 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -10,10 +10,54 @@ export async function POST(request: Request) { return NextResponse.json(res) } -export async function GET() { +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + const keyword = searchParams.get('keyword') + const searchOption = searchParams.get('searchOption') + const isMySurvey = searchParams.get('isMySurvey') + const sort = searchParams.get('sort') + + const searchOptions = ['building_name', 'representative', 'store', 'construction_point', 'customer_name', 'post_code', 'address', 'address_detail'] try { + const where: any = {} + + if (isMySurvey !== null && isMySurvey !== undefined) { + where.representative = isMySurvey + } + + if (keyword && keyword.trim() !== '' && searchOption) { + if (searchOption === 'all') { + where.OR = []; + if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) { + where.OR.push({ + id: { + equals: Number(keyword), + }, + }) + } + where.OR.push( + ...searchOptions.map((field) => ({ + [field]: { + contains: keyword, + }, + })) + ) + } else if (searchOptions.includes(searchOption)) { + where[searchOption] = { + contains: keyword, + } + } else if (searchOption === 'id') { + where[searchOption] = { + equals: Number(keyword), + } + } + } + // @ts-ignore - const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany() + const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany({ + where, + orderBy: sort === 'created' ? { created_at: 'desc' } : { updated_at: 'desc' }, + }) return NextResponse.json(res) } catch (error) { console.error(error) @@ -21,21 +65,9 @@ export async function GET() { } } -// export async function GET(request: Request) { -// // @ts-ignore -// const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany({ -// include: { -// detail_info: true, -// }, -// }) -// return NextResponse.json(res) -// } - export async function PUT(request: Request) { const body = await request.json() - console.log('🚀 ~ PUT ~ body:', body) const detailInfo = { ...body.detail_info, basic_info_id: body.id } - console.log('🚀 ~ PUT ~ detailInfo:', detailInfo) // @ts-ignore const res = await prisma.SD_SERVEY_SALES_DETAIL_INFO.create({ data: detailInfo, diff --git a/src/components/survey-sale/detail/DataTable.tsx b/src/components/survey-sale/detail/DataTable.tsx index 21873c7..978c321 100644 --- a/src/components/survey-sale/detail/DataTable.tsx +++ b/src/components/survey-sale/detail/DataTable.tsx @@ -52,9 +52,14 @@ export default function DataTable() { 提出可否 - {surveyDetail?.submission_status && surveyDetail?.submission_date - ? new Date(surveyDetail.submission_date).toLocaleString() - : '-'} + {surveyDetail?.submission_status && surveyDetail?.submission_date ? ( + <> +
{new Date(surveyDetail.submission_date).toLocaleString()}
+
제출한 판매점 ID 추가 필요
+ + ) : ( + '-' + )} diff --git a/src/components/survey-sale/list/ListTable.tsx b/src/components/survey-sale/list/ListTable.tsx index c39b00c..d0f599c 100644 --- a/src/components/survey-sale/list/ListTable.tsx +++ b/src/components/survey-sale/list/ListTable.tsx @@ -2,7 +2,7 @@ import LoadMoreButton from '@/components/LoadMoreButton' import { useServey } from '@/hooks/useSurvey' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import SearchForm from './SearchForm' @@ -10,33 +10,17 @@ export default function ListTable() { const router = useRouter() const { surveyList, isLoadingSurveyList } = useServey() - const [visibleItems, setVisibleItems] = useState(5) - const [search, setSearch] = useState('') - const [isMyPostsOnly, setIsMyPostsOnly] = useState(false) + const [visibleItems, setVisibleItems] = useState(10) const [hasMore, setHasMore] = useState(false) - // TODO: 로그인 구현 이후 USERNAME 변경 - const username = 'test' - - const filteredSurveyList = useMemo(() => { - let filtered = surveyList - if (search.trim().length > 0) { - filtered = filtered.filter((survey) => survey.building_name?.includes(search)) - } - if (isMyPostsOnly) { - filtered = filtered.filter((survey) => survey.representative === username) - } - return filtered - }, [surveyList, search, isMyPostsOnly, username]) - useEffect(() => { - setHasMore(filteredSurveyList.length > visibleItems) - }, [filteredSurveyList, visibleItems]) + setHasMore(surveyList.length > visibleItems) + }, [surveyList, visibleItems]) const handleLoadMore = () => { - const newVisibleItems = Math.min(visibleItems + 5, filteredSurveyList.length) + const newVisibleItems = Math.min(visibleItems + 10, surveyList.length) setVisibleItems(newVisibleItems) - setHasMore(newVisibleItems < filteredSurveyList.length) + setHasMore(newVisibleItems < surveyList.length) } const handleScrollToTop = () => { @@ -47,46 +31,42 @@ export default function ListTable() { router.push(`/survey-sale/${id}`) } - const handleSearchChange = (e: React.ChangeEvent) => { - setSearch(e.target.value) - setVisibleItems(5) - } - - const handleMyPostsToggle = () => { - setIsMyPostsOnly((prev) => !prev) - setVisibleItems(5) - } - if (isLoadingSurveyList) { return
Loading...
} return ( <> - -
-
    - {filteredSurveyList.slice(0, visibleItems).map((survey) => ( -
  • handleDetailClick(survey.id)}> -
    -
    -
    {survey.id}
    -
    {survey.investigation_date}
    + setVisibleItems(10)} /> + {surveyList.length > 0 ? ( +
    +
      + {surveyList.slice(0, visibleItems).map((survey) => ( +
    • handleDetailClick(survey.id)}> +
      +
      +
      {survey.id}
      +
      {survey.investigation_date}
      +
      +
      {survey.building_name}
      +
      {survey.customer_name}
      +
      +
      {survey.representative}
      +
      {new Date(survey.updated_at).toLocaleString()}
      +
      -
      {survey.building_name}
      -
      {survey.customer_name}
      -
      -
      {survey.representative}
      -
      {new Date(survey.updated_at).toLocaleString()}
      -
      -
    -
  • - ))} -
-
- + + ))} + +
+ +
-
+ ) : ( +
+

作成された物件はありません。

+
+ )} ) } diff --git a/src/components/survey-sale/list/SearchForm.tsx b/src/components/survey-sale/list/SearchForm.tsx index 8942836..b9cdb21 100644 --- a/src/components/survey-sale/list/SearchForm.tsx +++ b/src/components/survey-sale/list/SearchForm.tsx @@ -1,9 +1,25 @@ 'use client' +import { SEARCH_OPTIONS, SEARCH_OPTIONS_ENUM, useSurveyFilterStore } from '@/store/surveyFilterStore' import { useRouter } from 'next/navigation' +import { useState } from 'react' -export default function SearchForm({ handleSearch, handleMyPosts }: { handleSearch: (e: React.ChangeEvent) => void, handleMyPosts: () => void }) { +export default function SearchForm({ onItemsInit }: { onItemsInit: () => void }) { const router = useRouter() + const { setSearchOption, setSort, setIsMySurvey, setKeyword, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore() + const [searchKeyword, setSearchKeyword] = useState(keyword) + + const username = 'test' + + const handleSearch = () => { + if (searchKeyword.trim().length < 2) { + alert('2文字以上入力してください') + return + } + setKeyword(searchKeyword) + onItemsInit() + } + return (
@@ -12,33 +28,64 @@ export default function SearchForm({ handleSearch, handleMyPosts }: { handleSear
- setSearchOption(e.target.value as SEARCH_OPTIONS_ENUM)} + > + {SEARCH_OPTIONS.map((option) => ( + + ))}
- - + setSearchKeyword(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleSearch() + } + }} + /> +
- + { + setIsMySurvey(isMySurvey === username ? null : username) + onItemsInit() + }} + />
- { + setSort(e.target.value as 'created' | 'updated') + onItemsInit() + }} + > + +
diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index 00517b7..f4d08a7 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -1,6 +1,7 @@ -import { axiosInstance } from '@/libs/axios' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import type { SurveyBasicInfo, SurveyBasicRequest, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey' +import type { SurveyBasicInfo, SurveyBasicRequest, SurveyDetailInfo, SurveyDetailRequest, SurveyDetailCoverRequest } from '@/types/Survey' +import { axiosInstance } from '@/libs/axios' +import { useSurveyFilterStore } from '@/store/surveyFilterStore' export function useServey(id?: number): { surveyList: SurveyBasicInfo[] | [] @@ -11,18 +12,22 @@ export function useServey(id?: number): { isUpdatingSurvey: boolean isDeletingSurvey: boolean createSurvey: (survey: SurveyBasicRequest) => Promise - createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailRequest }) => void + createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => void updateSurvey: (survey: SurveyBasicRequest) => void deleteSurvey: () => Promise submitSurvey: () => void validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string } { const queryClient = useQueryClient() + const { keyword, searchOption, isMySurvey, sort } = useSurveyFilterStore() const { data: surveyList, isLoading: isLoadingSurveyList } = useQuery({ - queryKey: ['survey', 'list'], + queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort], queryFn: async () => { - const resp = await axiosInstance.get('/api/survey-sales') + console.log('keyword, searchOption, isMySurvey, sort:: ', keyword, searchOption, isMySurvey, sort) + const resp = await axiosInstance(null).get('/api/survey-sales', { + params: { keyword, searchOption, isMySurvey, sort }, + }) return resp.data }, }) @@ -32,7 +37,7 @@ export function useServey(id?: number): { queryFn: async () => { if (id === undefined) throw new Error('id is required') if (id === null) return null - const resp = await axiosInstance.get(`/api/survey-sales/${id}`) + const resp = await axiosInstance(null).get(`/api/survey-sales/${id}`) return resp.data }, enabled: id !== undefined, @@ -40,7 +45,7 @@ export function useServey(id?: number): { const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({ mutationFn: async (survey: SurveyBasicRequest) => { - const resp = await axiosInstance.post('/api/survey-sales', survey) + const resp = await axiosInstance(null).post('/api/survey-sales', survey) return resp.data.id ?? 0 }, onSuccess: (data) => { @@ -54,7 +59,7 @@ export function useServey(id?: number): { mutationFn: async (survey: SurveyBasicRequest) => { console.log('updateSurvey:: ', survey) if (id === undefined) throw new Error('id is required') - const resp = await axiosInstance.put(`/api/survey-sales/${id}`, survey) + const resp = await axiosInstance(null).put(`/api/survey-sales/${id}`, survey) return resp.data }, onSuccess: () => { @@ -66,7 +71,7 @@ export function useServey(id?: number): { const { mutateAsync: deleteSurvey, isPending: isDeletingSurvey } = useMutation({ mutationFn: async () => { if (id === null) throw new Error('id is required') - const resp = await axiosInstance.delete(`/api/survey-sales/${id}`) + const resp = await axiosInstance(null).delete(`/api/survey-sales/${id}`) return resp.data }, onSuccess: () => { @@ -76,8 +81,8 @@ export function useServey(id?: number): { }) const { mutateAsync: createSurveyDetail, isPending: isCreatingSurveyDetail } = useMutation({ - mutationFn: async ({ surveyId, surveyDetail }: { surveyId: number; surveyDetail: SurveyDetailRequest }) => { - const resp = await axiosInstance.post(`/api/survey-sales/${surveyId}`, surveyDetail) + mutationFn: async ({ surveyId, surveyDetail }: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => { + const resp = await axiosInstance(null).patch(`/api/survey-sales/${surveyId}`, surveyDetail) return resp.data }, onSuccess: () => { @@ -89,7 +94,9 @@ export function useServey(id?: number): { const { mutateAsync: submitSurvey } = useMutation({ mutationFn: async () => { if (id === undefined) throw new Error('id is required') - const resp = await axiosInstance.patch(`/api/survey-sales/${id}`) + const resp = await axiosInstance(null).patch(`/api/survey-sales/${id}`, { + submit: true, + }) return resp.data }, onSuccess: () => { @@ -108,27 +115,24 @@ export function useServey(id?: number): { 'waterproof_material', 'insulation_presence', 'structure_order', - ] as const; + ] as const - const etcFields = [ - 'installation_system', - 'construction_year', - 'rafter_size', - 'rafter_pitch', - 'waterproof_material', - 'structure_order', - ] as const; + const etcFields = ['installation_system', 'construction_year', 'rafter_size', 'rafter_pitch', 'waterproof_material', 'structure_order'] as const const emptyField = requiredFields.find((field) => { if (etcFields.includes(field as (typeof etcFields)[number])) { - return surveyDetail[field as keyof SurveyDetailRequest] === null && - surveyDetail[`${field}_etc` as keyof SurveyDetailRequest] === null; + return surveyDetail[field as keyof SurveyDetailRequest] === null && surveyDetail[`${field}_etc` as keyof SurveyDetailRequest] === null } - return surveyDetail[field as keyof SurveyDetailRequest] === null; - }); + return surveyDetail[field as keyof SurveyDetailRequest] === null + }) - return emptyField || ''; - }; + const contractCapacity = surveyDetail.contract_capacity + if (contractCapacity && contractCapacity.trim() !== '' && contractCapacity.split(' ')?.length === 1) { + return 'contract_capacity_unit' + } + + return emptyField || '' + } return { surveyList: surveyList || [], diff --git a/src/store/surveyFilterStore.ts b/src/store/surveyFilterStore.ts index ae1ce9d..c5f0e07 100644 --- a/src/store/surveyFilterStore.ts +++ b/src/store/surveyFilterStore.ts @@ -1,27 +1,85 @@ import { create } from 'zustand' +export type SEARCH_OPTIONS_ENUM = 'all' | 'id' | 'building_name' | 'representative' | 'store' | 'construction_point' +// | 'store_id' | 'construction_id' +export type SEARCH_OPTIONS_PARTNERS_ENUM = 'all' | 'id' | 'building_name' | 'representative' + +export type SORT_OPTIONS_ENUM = 'created' | 'updated' + +export const SEARCH_OPTIONS = [ + { + id: 'all', + label: '全体', + }, + { + id: 'id', + label: '登録番号', + }, + { + id: 'building_name', + label: '建物名', + }, + { + id: 'representative', + label: '作成者', + }, + { + id: 'store', + label: '販売店名', + }, +// { +// id: 'store_id', +// label: '販売店ID', +// }, + { + id: 'construction_point', + label: '施工店名', + }, +// { +// id: 'construction_id', +// label: '施工店ID', +// }, +] + +export const SEARCH_OPTIONS_PARTNERS = [ + { + id: 'all', + label: '全体', + }, + { + id: 'id', + label: '登録番号', + }, + { + id: 'building_name', + label: '建物名', + }, + { + id: 'representative', + label: '作成者', + }, +] + type SurveyFilterState = { - keyword: string; - searchOption: string; - isMySurvey: boolean; - sort: 'recent' | 'updated'; - setKeyword: (keyword: string) => void; - setSearchOption: (searchOption: string) => void; - setIsMySurvey: (isMySurvey: boolean) => void; - setSort: (sort: 'recent' | 'updated') => void; - reset: () => void; + keyword: string + searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM + isMySurvey: string | null + sort: SORT_OPTIONS_ENUM + setKeyword: (keyword: string) => void + setSearchOption: (searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM) => void + setIsMySurvey: (isMySurvey: string | null) => void + setSort: (sort: SORT_OPTIONS_ENUM) => void + reset: () => void } export const useSurveyFilterStore = create((set) => ({ - keyword: '', - searchOption: '', - isMySurvey: false, - sort: 'recent', - setKeyword: (keyword: string) => set({ keyword }), - setSearchOption: (searchOption: string) => set({ searchOption }), - setIsMySurvey: (isMySurvey: boolean) => set({ isMySurvey }), - setSort: (sort: 'recent' | 'updated') => set({ sort }), - reset: () => set({ keyword: '', searchOption: '', isMySurvey: false, sort: 'recent' }), + keyword: '', + searchOption: 'all', + isMySurvey: null, + sort: 'created', + setKeyword: (keyword: string) => set({ keyword }), + setSearchOption: (searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM) => set({ searchOption }), + setIsMySurvey: (isMySurvey: string | null) => set({ isMySurvey }), + setSort: (sort: SORT_OPTIONS_ENUM) => set({ sort }), + reset: () => set({ keyword: '', searchOption: 'all', isMySurvey: null, sort: 'created' }), })) - -