feat: implement SurveyFilteringOptions to global

- 조사매물 검색 조건 zustand 통해 전역으로 관리하도록 구현
This commit is contained in:
Dayoung 2025-05-08 16:58:14 +09:00
parent 68408eb3c9
commit dfd5ba419b
6 changed files with 260 additions and 134 deletions

View File

@ -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,

View File

@ -52,9 +52,14 @@ export default function DataTable() {
<tr>
<th></th>
<td>
{surveyDetail?.submission_status && surveyDetail?.submission_date
? new Date(surveyDetail.submission_date).toLocaleString()
: '-'}
{surveyDetail?.submission_status && surveyDetail?.submission_date ? (
<>
<div>{new Date(surveyDetail.submission_date).toLocaleString()}</div>
<div> ID </div>
</>
) : (
'-'
)}
</td>
</tr>
<tr>

View File

@ -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<HTMLInputElement>) => {
setSearch(e.target.value)
setVisibleItems(5)
}
const handleMyPostsToggle = () => {
setIsMyPostsOnly((prev) => !prev)
setVisibleItems(5)
}
if (isLoadingSurveyList) {
return <div>Loading...</div>
}
return (
<>
<SearchForm handleSearch={handleSearchChange} handleMyPosts={handleMyPostsToggle} />
<div className="sale-frame">
<ul className="sale-list-wrap">
{filteredSurveyList.slice(0, visibleItems).map((survey) => (
<li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetailClick(survey.id)}>
<div className="sale-item-bx">
<div className="sale-item-date-bx">
<div className="sale-item-num">{survey.id}</div>
<div className="sale-item-date">{survey.investigation_date}</div>
<SearchForm onItemsInit={() => setVisibleItems(10)} />
{surveyList.length > 0 ? (
<div className="sale-frame">
<ul className="sale-list-wrap">
{surveyList.slice(0, visibleItems).map((survey) => (
<li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetailClick(survey.id)}>
<div className="sale-item-bx">
<div className="sale-item-date-bx">
<div className="sale-item-num">{survey.id}</div>
<div className="sale-item-date">{survey.investigation_date}</div>
</div>
<div className="sale-item-tit">{survey.building_name}</div>
<div className="sale-item-customer">{survey.customer_name}</div>
<div className="sale-item-update-bx">
<div className="sale-item-name">{survey.representative}</div>
<div className="sale-item-update">{new Date(survey.updated_at).toLocaleString()}</div>
</div>
</div>
<div className="sale-item-tit">{survey.building_name}</div>
<div className="sale-item-customer">{survey.customer_name}</div>
<div className="sale-item-update-bx">
<div className="sale-item-name">{survey.representative}</div>
<div className="sale-item-update">{new Date(survey.updated_at).toLocaleString()}</div>
</div>
</div>
</li>
))}
</ul>
<div className="sale-edit-btn">
<LoadMoreButton hasMore={hasMore} onLoadMore={handleLoadMore} onScrollToTop={handleScrollToTop} />
</li>
))}
</ul>
<div className="sale-edit-btn">
<LoadMoreButton hasMore={hasMore} onLoadMore={handleLoadMore} onScrollToTop={handleScrollToTop} />
</div>
</div>
</div>
) : (
<div>
<p></p>
</div>
)}
</>
)
}

View File

@ -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<HTMLInputElement>) => 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 (
<div className="sale-frame">
<div className="sale-form-bx">
@ -12,33 +28,64 @@ export default function SearchForm({ handleSearch, handleMyPosts }: { handleSear
</button>
</div>
<div className="sale-form-bx">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<select
className="select-form"
name="search-option"
id="search-option"
value={searchOption}
onChange={(e) => setSearchOption(e.target.value as SEARCH_OPTIONS_ENUM)}
>
{SEARCH_OPTIONS.map((option) => (
<option key={option.id} value={option.id}>
{option.label}
</option>
))}
</select>
</div>
<div className="sale-form-bx">
<div className="search-input">
<input type="text" className="search-frame" placeholder="タイトルを入力してください. (2文字以上)" onChange={handleSearch} />
<button className="search-icon"></button>
<input
type="text"
className="search-frame"
value={searchKeyword}
placeholder="タイトルを入力してください. (2文字以上)"
onChange={(e) => setSearchKeyword(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSearch()
}
}}
/>
<button className="search-icon" onClick={handleSearch}></button>
</div>
</div>
<div className="sale-form-bx">
<div className="check-form-box">
<input type="checkbox" id="ch01" onClick={handleMyPosts} />
<input
type="checkbox"
id="ch01"
checked={isMySurvey === username}
onChange={() => {
setIsMySurvey(isMySurvey === username ? null : username)
onItemsInit()
}}
/>
<label htmlFor="ch01"></label>
</div>
</div>
<div className="sale-form-bx">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<select
className="select-form"
name="sort-option"
id="sort-option"
value={sort}
onChange={(e) => {
setSort(e.target.value as 'created' | 'updated')
onItemsInit()
}}
>
<option value="created"></option>
<option value="updated"></option>
</select>
</div>
</div>

View File

@ -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<number>
createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailRequest }) => void
createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => void
updateSurvey: (survey: SurveyBasicRequest) => void
deleteSurvey: () => Promise<boolean>
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<SurveyBasicInfo[]>('/api/survey-sales')
console.log('keyword, searchOption, isMySurvey, sort:: ', keyword, searchOption, isMySurvey, sort)
const resp = await axiosInstance(null).get<SurveyBasicInfo[]>('/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<SurveyBasicInfo>(`/api/survey-sales/${id}`)
const resp = await axiosInstance(null).get<SurveyBasicInfo>(`/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<SurveyBasicInfo>('/api/survey-sales', survey)
const resp = await axiosInstance(null).post<SurveyBasicInfo>('/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<SurveyBasicInfo>(`/api/survey-sales/${id}`, survey)
const resp = await axiosInstance(null).put<SurveyBasicInfo>(`/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<boolean>(`/api/survey-sales/${id}`)
const resp = await axiosInstance(null).delete<boolean>(`/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<SurveyDetailInfo>(`/api/survey-sales/${surveyId}`, surveyDetail)
mutationFn: async ({ surveyId, surveyDetail }: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => {
const resp = await axiosInstance(null).patch<SurveyDetailInfo>(`/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<boolean>(`/api/survey-sales/${id}`)
const resp = await axiosInstance(null).patch<boolean>(`/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 || [],

View File

@ -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<SurveyFilterState>((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' }),
}))