feat: remove unused survey sale API routes & add address popup at Survey BasicForm

This commit is contained in:
Dayoung 2025-05-09 15:08:55 +09:00
parent 555e6f3b4a
commit 95d971b198
25 changed files with 398 additions and 336 deletions

View File

@ -1,72 +0,0 @@
import { NextResponse } from 'next/server'
export async function POST(request: Request, context: { params: { id: string } }) {
const body = await request.json()
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
detail_info: {
create: body,
},
},
})
return NextResponse.json({ message: 'Survey detail created successfully' })
}
export async function GET(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.findUnique({
where: { id: Number(id) },
include: {
detail_info: true,
},
})
return NextResponse.json(survey)
}
export async function PUT(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
const body = await request.json()
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
...body,
detail_info: {
update: body.detail_info,
},
},
})
return NextResponse.json(survey)
}
export async function DELETE(request: Request, context: { params: { id: string; detail_id: string } }) {
const { id, detail_id } = await context.params
if (detail_id) {
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_DETAIL_INFO.delete({
where: { id: Number(detail_id) },
})
} else {
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.delete({
where: { id: Number(id) },
})
}
return NextResponse.json({ message: 'Survey deleted successfully' })
}
export async function PATCH(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
submission_status: true,
},
})
return NextResponse.json({ message: 'Survey confirmed successfully' })
}

View File

@ -1,33 +0,0 @@
import { NextResponse } from 'next/server'
import { prisma } from '@/libs/prisma'
export async function POST(request: Request) {
const body = await request.json()
// @ts-ignore
const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.create({
data: body,
})
return NextResponse.json(res)
}
export async function GET() {
try {
// @ts-ignore
const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany()
return NextResponse.json(res)
} catch (error) {
console.error(error)
}
}
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,
})
return NextResponse.json({ message: 'Survey sales updated successfully' })
}

View File

@ -1,21 +1,5 @@
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
// export async function POST(request: Request, context: { params: { id: string } }) {
// const body = await request.json()
// const { id } = await context.params
// // @ts-ignore
// const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
// where: { id: Number(id) },
// data: {
// detail_info: {
// create: body,
// },
// },
// })
// return NextResponse.json({ message: 'Survey detail created successfully' })
// }
export async function GET(request: Request, context: { params: { id: string } }) { export async function GET(request: Request, context: { params: { id: string } }) {
const { id } = await context.params const { id } = await context.params
// @ts-ignore // @ts-ignore

View File

@ -16,6 +16,7 @@ export async function GET(request: Request) {
const searchOption = searchParams.get('searchOption') const searchOption = searchParams.get('searchOption')
const isMySurvey = searchParams.get('isMySurvey') const isMySurvey = searchParams.get('isMySurvey')
const sort = searchParams.get('sort') const sort = searchParams.get('sort')
const offset = searchParams.get('offset')
const searchOptions = ['building_name', 'representative', 'store', 'construction_point', 'customer_name', 'post_code', 'address', 'address_detail'] const searchOptions = ['building_name', 'representative', 'store', 'construction_point', 'customer_name', 'post_code', 'address', 'address_detail']
try { try {
@ -27,7 +28,7 @@ export async function GET(request: Request) {
if (keyword && keyword.trim() !== '' && searchOption) { if (keyword && keyword.trim() !== '' && searchOption) {
if (searchOption === 'all') { if (searchOption === 'all') {
where.OR = []; where.OR = []
if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) { if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) {
where.OR.push({ where.OR.push({
id: { id: {
@ -40,7 +41,7 @@ export async function GET(request: Request) {
[field]: { [field]: {
contains: keyword, contains: keyword,
}, },
})) })),
) )
} else if (searchOptions.includes(searchOption)) { } else if (searchOptions.includes(searchOption)) {
where[searchOption] = { where[searchOption] = {
@ -53,12 +54,22 @@ export async function GET(request: Request) {
} }
} }
// @ts-ignore if (offset) {
const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany({ // @ts-ignore
where, const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany({
orderBy: sort === 'created' ? { created_at: 'desc' } : { updated_at: 'desc' }, where,
}) orderBy: sort === 'created' ? { created_at: 'desc' } : { updated_at: 'desc' },
return NextResponse.json(res) skip: Number(offset),
take: 10,
})
return NextResponse.json(res)
} else {
// @ts-ignore
const count = await prisma.SD_SERVEY_SALES_BASIC_INFO.count({
where,
})
return NextResponse.json(count)
}
} catch (error) { } catch (error) {
console.error(error) console.error(error)
throw error throw error

View File

@ -2,7 +2,8 @@ import { NextResponse } from 'next/server'
import { prisma } from '@/libs/prisma' import { prisma } from '@/libs/prisma'
import { getIronSession } from 'iron-session' import { getIronSession } from 'iron-session'
import { cookies } from 'next/headers' import { cookies } from 'next/headers'
import { SessionData, sessionOptions } from '@/libs/session' import { sessionOptions } from '@/libs/session'
import type { SessionData } from '@/types/Auth'
export async function POST(request: Request) { export async function POST(request: Request) {
const { username, password } = await request.json() const { username, password } = await request.json()
@ -25,8 +26,8 @@ export async function POST(request: Request) {
const cookieStore = await cookies() const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions) const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
console.log('start session edit!') console.log('start session edit!')
session.username = user.username! session.userNm = user.username!
session.email = user.email! // session.email = user.email!
session.isLoggedIn = true session.isLoggedIn = true
console.log('end session edit!') console.log('end session edit!')
await session.save() await session.save()

View File

@ -1,9 +0,0 @@
import InquiryWriteForm from '@/components/inquiry/InquiryWriteForm'
export default function InquiryWrite() {
return (
<div>
<InquiryWriteForm />
</div>
)
}

View File

@ -22,7 +22,8 @@ interface RootLayoutProps {
export default async function RootLayout({ children, header, footer, floatBtn }: RootLayoutProps): Promise<ReactNode> { export default async function RootLayout({ children, header, footer, floatBtn }: RootLayoutProps): Promise<ReactNode> {
return ( return (
<ReactQueryProviders> <ReactQueryProviders>
<html lang="en"> {/* 일본어로 렌더링 */}
<html lang="ja" suppressHydrationWarning>
<body> <body>
<div className="wrap"> <div className="wrap">
{header} {header}

View File

@ -5,7 +5,7 @@ export default function page() {
return ( return (
<> <>
<DataTable /> <DataTable />
<DetailForm /> {/* <DetailForm /> */}
</> </>
) )
} }

View File

@ -1,5 +1,4 @@
import ListTable from '@/components/survey-sale/list/ListTable' import ListTable from '@/components/survey-sale/list/ListTable'
import SearchForm from '@/components/survey-sale/list/SearchForm'
export default function page() { export default function page() {
return ( return (

View File

@ -3,10 +3,9 @@
interface LoadMoreButtonProps { interface LoadMoreButtonProps {
hasMore: boolean hasMore: boolean
onLoadMore: () => void onLoadMore: () => void
onScrollToTop: () => void
} }
export default function LoadMoreButton({ hasMore, onLoadMore, onScrollToTop }: LoadMoreButtonProps) { export default function LoadMoreButton({ hasMore, onLoadMore }: LoadMoreButtonProps) {
return ( return (
<> <>
{hasMore ? ( {hasMore ? (
@ -15,10 +14,7 @@ export default function LoadMoreButton({ hasMore, onLoadMore, onScrollToTop }: L
<i className="btn-edit"></i> <i className="btn-edit"></i>
</button> </button>
) : ( ) : (
<button onClick={onScrollToTop} className="btn-frame n-blue icon"> <></>
<i className="btn-arr-up"></i>
</button>
)} )}
</> </>
) )

View File

@ -23,11 +23,6 @@ export default function InquiryWriteForm() {
const file = Array.from(e.target.files || []) const file = Array.from(e.target.files || [])
setFormData({ ...formData, file: [...formData.file, ...file] }) setFormData({ ...formData, file: [...formData.file, ...file] })
} }
const handleFileDelete = (fileToDelete: File) => {
setFormData({ ...formData, file: formData.file.filter((f) => f !== fileToDelete) })
}
const handleSubmit = () => { const handleSubmit = () => {
console.log('submit: ', formData) console.log('submit: ', formData)
// router.push(`/inquiry`) // router.push(`/inquiry`)
@ -60,7 +55,7 @@ export default function InquiryWriteForm() {
<div key={f.name}> <div key={f.name}>
<div>{f.name}</div> <div>{f.name}</div>
<div> <div>
<button onClick={() => handleFileDelete(f)}>delete</button> <button>delete</button>
</div> </div>
</div> </div>
))} ))}

View File

@ -1,10 +1,13 @@
'use client' 'use client'
import { useRouter } from 'next/navigation'
export default function ListForm() { export default function ListForm() {
const router = useRouter()
return ( return (
<> <>
<div className="sale-frame"> <div className="sale-frame">
<div className="sale-form-bx"> <div className="sale-form-bx">
<button className="btn-frame n-blue icon"> <button className="btn-frame n-blue icon" onClick={() => router.push('/inquiry/regist')}>
<i className="btn-arr"></i> <i className="btn-arr"></i>
</button> </button>
</div> </div>

View File

@ -1,5 +1,52 @@
'use client' 'use client'
import { use, useEffect, useState } from 'react'
import LoadMoreButton from '../LoadMoreButton'
const inquiryDummy = [
{ id: 1, category: '屋根', title: '屋根材適合性確認依頼', date: '2025.04.02', status: 'completed' },
{ id: 2, category: '外壁', title: '外壁仕上げ材確認', date: '2025.04.03', status: 'completed' },
{ id: 3, category: '設備', title: '換気システム図面確認', date: '2025.04.04', status: 'completed' },
{ id: 4, category: '基礎', title: '基礎配筋検査依頼', date: '2025.04.05', status: 'completed' },
{ id: 5, category: '内装', title: 'クロス仕様確認', date: '2025.04.06', status: 'waiting' },
{ id: 6, category: '構造', title: '耐震壁位置変更申請', date: '2025.04.07', status: 'completed' },
{ id: 7, category: '屋根', title: '雨樋取付方法確認', date: '2025.04.08', status: 'completed' },
{ id: 8, category: '外構', title: 'フェンス高さ変更相談', date: '2025.04.09', status: 'completed' },
{ id: 9, category: '設備', title: '給湯器設置位置確認', date: '2025.04.10', status: 'completed' },
{ id: 10, category: '外壁', title: 'タイル割付案確認依頼', date: '2025.04.11', status: 'waiting' },
{ id: 11, category: '内装', title: '照明配置図面確認', date: '2025.04.12', status: 'completed' },
{ id: 12, category: '構造', title: '梁補強案確認', date: '2025.04.13', status: 'completed' },
{ id: 13, category: '基礎', title: '杭長設計確認依頼', date: '2025.04.14', status: 'completed' },
{ id: 14, category: '屋根', title: '断熱材施工方法確認', date: '2025.04.15', status: 'completed' },
{ id: 15, category: '外構', title: '駐車場勾配図確認', date: '2025.04.16', status: 'completed' },
]
const badgeStyle = [
{
id: 'completed',
label: '回答完了',
color: 'blue',
},
{
id: 'waiting',
label: '回答待ち',
color: 'orange',
},
]
export default function ListTable() { export default function ListTable() {
const [offset, setOffset] = useState(0)
const [hasMore, setHasMore] = useState(true)
const inquiryList = inquiryDummy.slice(0, offset + 10)
useEffect(() => {
if (inquiryDummy.length > offset + 10) {
setHasMore(true)
} else {
setHasMore(false)
}
}, [inquiryList])
return ( return (
<> <>
<div className="sale-frame"> <div className="sale-frame">
@ -13,10 +60,8 @@ export default function ListTable() {
<div className="filter-select"> <div className="filter-select">
<select className="select-form" name="" id=""> <select className="select-form" name="" id="">
<option value=""></option> <option value=""></option>
<option value=""></option> <option value=""></option>
<option value=""></option> <option value=""></option>
<option value=""></option>
<option value=""></option>
</select> </select>
</div> </div>
</div> </div>
@ -25,43 +70,21 @@ export default function ListTable() {
<span>98</span> <span>98</span>
</div> </div>
<ul className="inquiry-list"> <ul className="inquiry-list">
<li className="inquiry-item"> {inquiryList.map((inquiry) => (
<div className="inquiry-item-bx"> <li className="inquiry-item" key={inquiry.id}>
<div className="inquiry-item-category"></div> <div className="inquiry-item-bx">
<div className="inquiry-item-tit"></div> <div className="inquiry-item-category">{inquiry.category}</div>
<div className="inquiry-item-date">2025.04.02</div> <div className="inquiry-item-tit">{inquiry.title}</div>
<div className="inquiry-badge badge blue"></div> <div className="inquiry-item-date">{inquiry.date}</div>
</div> <div className={`inquiry-badge badge ${badgeStyle.find((badge) => badge.id === inquiry.status)?.color}`}>
</li> {badgeStyle.find((badge) => badge.id === inquiry.status)?.label}
<li className="inquiry-item"> </div>
<div className="inquiry-item-bx"> </div>
<div className="inquiry-item-category"></div> </li>
<div className="inquiry-item-tit"></div> ))}
<div className="inquiry-item-date">2025.04.02</div>
<div className="inquiry-badge badge orange"></div>
</div>
</li>
<li className="inquiry-item">
<div className="inquiry-item-bx">
<div className="inquiry-item-category"></div>
<div className="inquiry-item-tit"></div>
<div className="inquiry-item-date">2025.04.02</div>
<div className="inquiry-badge badge blue"></div>
</div>
</li>
<li className="inquiry-item">
<div className="inquiry-item-bx">
<div className="inquiry-item-category"></div>
<div className="inquiry-item-tit"></div>
<div className="inquiry-item-date">2025.04.02</div>
<div className="inquiry-badge badge orange"></div>
</div>
</li>
</ul> </ul>
<div className="sale-edit-btn"> <div className="sale-edit-btn">
<button className="btn-frame n-blue icon"> <LoadMoreButton hasMore={hasMore} onLoadMore={() => setOffset(offset + 10)} />
<i className="btn-edit"></i>
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,18 +1,51 @@
'use client' 'use client'
import { useAddressStore } from '@/store/addressStore'
import { usePopupController } from '@/store/popupController' import { usePopupController } from '@/store/popupController'
import { useState } from 'react' import { useState } from 'react'
const dummyData = [
{
post_code: '123-567',
pref: '東京都',
city: '千代田区',
detail: '永田町ハイツ101号室',
},
{
post_code: '987-654',
pref: '大阪府',
city: '北区',
detail: '梅田スカイビル23階',
},
{
post_code: '456-789',
pref: '福岡県',
city: '博多区',
detail: '中洲マンション305号',
},
]
export default function ZipCodePopup() { export default function ZipCodePopup() {
const [searchValue, setSearchValue] = useState('') //search 데이터 유무 const [searchValue, setSearchValue] = useState('') //search 데이터 유무
const [selected, setSelected] = useState('')
const { setAddressData } = useAddressStore()
//search 데이터 value 추가 //search 데이터 value 추가
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value) setSearchValue(e.target.value)
} }
const popupController = usePopupController() const popupController = usePopupController()
const handleApply = () => {
const addressData = dummyData.find((item) => item.post_code === selected)
setAddressData({
post_code: addressData?.post_code || '',
address: addressData?.pref || '',
address_detail: addressData?.city + ' ' + addressData?.detail || '',
})
popupController.setZipCodePopup(false)
}
return ( return (
<> <>
<div className="modal-popup"> <div className="modal-popup">
@ -50,31 +83,23 @@ export default function ZipCodePopup() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> {dummyData.map((item, index) => (
<td></td> <tr key={`${item.post_code}-${index}`} onClick={() => setSelected(item.post_code)}>
<td></td> <td>{item.pref}</td>
<td></td> <td>{item.city}</td>
</tr> <td>{item.detail}</td>
<tr> </tr>
<td></td> ))}
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</tbody> </tbody>
</table> </table>
<div className="btn-flex-wrap"> <div className="btn-flex-wrap">
<div className="btn-bx"> <div className="btn-bx">
<button className="btn-frame red icon"> <button className="btn-frame red icon" onClick={handleApply}>
<i className="btn-arr"></i> <i className="btn-arr"></i>
</button> </button>
</div> </div>
<div className="btn-bx"> <div className="btn-bx">
<button className="btn-frame n-blue icon"> <button className="btn-frame n-blue icon" onClick={() => popupController.setZipCodePopup(false)}>
<i className="btn-arr"></i> <i className="btn-arr"></i>
</button> </button>
</div> </div>

View File

@ -33,7 +33,7 @@ export default function NavTab() {
return return
} }
if (detailId) { if (detailId) {
router.push(`/survey-sale/${detailId}?tab=basic-info`) router.push(`/survey-sale/${detailId}`)
return return
} }
} }

View File

@ -1,22 +1,34 @@
'use client' 'use client'
import { useServey } from '@/hooks/useSurvey' import { useServey } from '@/hooks/useSurvey'
import { useParams } from 'next/navigation' import { useParams, useSearchParams } from 'next/navigation'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useState } from 'react' import { useState } from 'react'
import DetailForm from './DetailForm'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import RoofDetailForm from './RoofDetailForm'
export default function DataTable() { export default function DataTable() {
const params = useParams() const params = useParams()
const id = params.id const id = params.id
const searchParams = useSearchParams()
const tab = searchParams.get('tab')
const { surveyDetail, isLoadingSurveyDetail } = useServey(Number(id)) const { surveyDetail, isLoadingSurveyDetail } = useServey(Number(id))
const [isTemporary, setIsTemporary] = useState(false) const [isTemporary, setIsTemporary] = useState(false)
const { setBasicInfoSelected, setRoofInfoSelected } = useSurveySaleTabState()
useEffect(() => { useEffect(() => {
if (!surveyDetail?.representative || !surveyDetail?.store || !surveyDetail?.construction_point) { if (!surveyDetail?.representative || !surveyDetail?.store || !surveyDetail?.construction_point) {
setIsTemporary(true) setIsTemporary(true)
} }
}, [surveyDetail]) if (tab === 'roof-info') {
setRoofInfoSelected()
} else {
setBasicInfoSelected()
}
}, [surveyDetail, tab, setBasicInfoSelected, setRoofInfoSelected])
if (isLoadingSurveyDetail) { if (isLoadingSurveyDetail) {
return <div>Loading...</div> return <div>Loading...</div>
@ -73,6 +85,11 @@ export default function DataTable() {
</tbody> </tbody>
</table> </table>
</div> </div>
{tab === 'roof-info' ? (
<RoofDetailForm surveyDetail={surveyDetail} isLoadingSurveyDetail={isLoadingSurveyDetail} />
) : (
<DetailForm surveyDetail={surveyDetail} isLoadingSurveyDetail={isLoadingSurveyDetail} />
)}
</> </>
) )
} }

View File

@ -0,0 +1,65 @@
'use client'
import { useRouter } from 'next/navigation'
import { useServey } from '@/hooks/useSurvey'
export default function DetailButton({ isTemporary, surveyId }: { isTemporary: boolean, surveyId: number }) {
const router = useRouter()
const { submitSurvey, deleteSurvey } = useServey(surveyId)
const handleSubmit = async () => {
if (isTemporary) {
alert('SAVE FIRST')
return
}
if (confirm('submit?')) {
if (surveyId) {
// TODO: 제출 페이지 추가
alert('SUBMIT POPUP')
await submitSurvey()
}
}
}
const handleUpdate = () => {
router.push(`/survey-sale/basic-info?id=${surveyId}`)
}
const handleDelete = async () => {
if (confirm('delete?')) {
if (surveyId) {
await deleteSurvey()
router.push('/survey-sale')
}
}
}
return (
<div className="btn-flex-wrap">
{isTemporary ? (
<></>
) : (
<>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon" onClick={handleSubmit}>
<i className="btn-arr"></i>
</button>
</div>
</>
)}
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleUpdate}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleDelete}>
<i className="btn-arr"></i>
</button>
</div>
</div>
)
}

View File

@ -1,64 +1,27 @@
'use client' 'use client'
import { useServey } from '@/hooks/useSurvey'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useParams, useRouter, useSearchParams } from 'next/navigation' import DetailButton from './DetailButton'
import { SurveyBasicInfo } from '@/types/Survey'
export default function DetailForm() { export default function DetailForm({
const router = useRouter() surveyDetail,
const params = useParams() isLoadingSurveyDetail,
const id = params.id }: {
surveyDetail: SurveyBasicInfo | null
const searchParams = useSearchParams() isLoadingSurveyDetail: boolean
const tab = searchParams.get('tab') }) {
const { surveyDetail, deleteSurvey, submitSurvey, isLoadingSurveyDetail } = useServey(Number(id))
const { setBasicInfoSelected, setRoofInfoSelected } = useSurveySaleTabState()
const [isTemporary, setIsTemporary] = useState(true) const [isTemporary, setIsTemporary] = useState(true)
// TODO: 조사매물 지붕정보 퍼블 구현되면 탭 화면 변경 추가
useEffect(() => { useEffect(() => {
if (tab === 'basic-info') {
setBasicInfoSelected()
}
if (tab === 'roof-info') {
setRoofInfoSelected()
}
if (surveyDetail?.representative && surveyDetail?.store && surveyDetail?.construction_point) { if (surveyDetail?.representative && surveyDetail?.store && surveyDetail?.construction_point) {
setIsTemporary(false) setIsTemporary(false)
} }
}, [tab, setBasicInfoSelected, setRoofInfoSelected, surveyDetail]) }, [surveyDetail])
if (isLoadingSurveyDetail) { if (isLoadingSurveyDetail) {
return <div>Loading...</div> return <div>Loading...</div>
} }
const handleSubmit = async () => {
if (isTemporary) {
alert('SAVE FIRST')
return
}
if (confirm('submit?')) {
if (surveyDetail?.id) {
// TODO: 제출 페이지 추가
alert('SUBMIT POPUP')
await submitSurvey()
}
}
}
const handleUpdate = () => {
router.push(`/survey-sale/basic-info?id=${id}`)
}
const handleDelete = async () => {
if (confirm('delete?')) {
if (surveyDetail?.id) {
await deleteSurvey()
router.push('/survey-sale')
}
}
}
return ( return (
<> <>
<div className="sale-frame"> <div className="sale-frame">
@ -93,34 +56,7 @@ export default function DetailForm() {
<input type="text" className="input-frame" disabled defaultValue={surveyDetail?.customer_name ?? ''} /> <input type="text" className="input-frame" disabled defaultValue={surveyDetail?.customer_name ?? ''} />
</div> </div>
</div> </div>
<div className="btn-flex-wrap"> <DetailButton isTemporary={isTemporary} surveyId={Number(surveyDetail?.id)} />
{isTemporary ? (
<></>
) : (
<>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon" onClick={handleSubmit}>
<i className="btn-arr"></i>
</button>
</div>
</>
)}
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleUpdate}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleDelete}>
<i className="btn-arr"></i>
</button>
</div>
</div>
</div> </div>
</> </>
) )

View File

@ -0,0 +1,26 @@
import { SurveyBasicInfo } from '@/types/Survey'
import DetailButton from './DetailButton'
export default function RoofDetailForm({
surveyDetail,
isLoadingSurveyDetail,
}: {
surveyDetail: SurveyBasicInfo | null
isLoadingSurveyDetail: boolean
}) {
if (isLoadingSurveyDetail) {
return <div>Loading...</div>
}
return (
<>
<div className="sale-frame">
<div className="data-form-wrap">
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
</div>
</div>
<DetailButton isTemporary={false} surveyId={Number(surveyDetail?.id)} />
</div>
</>
)
}

View File

@ -5,6 +5,8 @@ import { SurveyBasicRequest } from '@/types/Survey'
import { useRouter, useSearchParams } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useSurveySaleTabState } from '@/store/surveySaleTabState' import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { usePopupController } from '@/store/popupController'
import { useAddressStore } from '@/store/addressStore'
const defaultBasicInfoForm: SurveyBasicRequest = { const defaultBasicInfoForm: SurveyBasicRequest = {
representative: '', representative: '',
@ -32,16 +34,25 @@ export default function BasicForm() {
const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(defaultBasicInfoForm) const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(defaultBasicInfoForm)
const { addressData } = useAddressStore()
const popupController = usePopupController()
useEffect(() => { useEffect(() => {
if (surveyDetail) { if (surveyDetail) {
const { id, updated_at, created_at, detail_info, ...rest } = surveyDetail const { id, updated_at, created_at, detail_info, ...rest } = surveyDetail
setBasicInfoData(rest) setBasicInfoData(rest)
} }
}, [surveyDetail]) if (addressData) {
setBasicInfoData({
useEffect(() => { ...basicInfoData,
post_code: addressData.post_code,
address: addressData.address,
address_detail: addressData.address_detail,
})
}
setBasicInfoSelected() setBasicInfoSelected()
}, []) }, [surveyDetail, addressData])
const focusInput = (input: keyof SurveyBasicRequest) => { const focusInput = (input: keyof SurveyBasicRequest) => {
const inputElement = document.getElementById(input) const inputElement = document.getElementById(input)
@ -192,7 +203,7 @@ export default function BasicForm() {
</div> </div>
</div> </div>
<div className="form-btn"> <div className="form-btn">
<button className="btn-frame n-blue icon"> <button className="btn-frame n-blue icon" onClick={() => popupController.setZipCodePopup(true)}>
便<i className="btn-arr"></i> 便<i className="btn-arr"></i>
</button> </button>
</div> </div>

View File

@ -2,46 +2,50 @@
import LoadMoreButton from '@/components/LoadMoreButton' import LoadMoreButton from '@/components/LoadMoreButton'
import { useServey } from '@/hooks/useSurvey' import { useServey } from '@/hooks/useSurvey'
import { useEffect, useState } from 'react' import { useEffect, useState, useRef } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import SearchForm from './SearchForm' import SearchForm from './SearchForm'
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
export default function ListTable() { export default function ListTable() {
const router = useRouter() const router = useRouter()
const { surveyList, isLoadingSurveyList } = useServey() const { surveyList, isLoadingSurveyList, surveyListCount } = useServey()
const { offset, setOffset } = useSurveyFilterStore()
const [visibleItems, setVisibleItems] = useState(10) const [heldSurveyList, setHeldSurveyList] = useState<typeof surveyList>([])
const [hasMore, setHasMore] = useState(false) const [hasMore, setHasMore] = useState(false)
useEffect(() => { useEffect(() => {
setHasMore(surveyList.length > visibleItems) if (surveyList && surveyList.length > 0) {
}, [surveyList, visibleItems]) if (offset === 0) {
setHeldSurveyList(surveyList)
const handleLoadMore = () => { } else {
const newVisibleItems = Math.min(visibleItems + 10, surveyList.length) const remainingList = heldSurveyList.slice(offset, offset + 10)
setVisibleItems(newVisibleItems) if (JSON.stringify(remainingList) !== JSON.stringify(surveyList)) {
setHasMore(newVisibleItems < surveyList.length) setHeldSurveyList((prev) => [...prev, ...surveyList])
} }
}
const handleScrollToTop = () => { setHasMore(surveyListCount > offset + 10)
window.scrollTo({ top: 0, behavior: 'smooth' }) }
} }, [surveyList, surveyListCount, offset])
const handleDetailClick = (id: number) => { const handleDetailClick = (id: number) => {
router.push(`/survey-sale/${id}`) router.push(`/survey-sale/${id}`)
} }
const handleItemsInit = () => {
if (isLoadingSurveyList) { setHeldSurveyList([])
return <div>Loading...</div> setOffset(0)
} }
// TODO: 로딩 처리 필요
return ( return (
<> <>
<SearchForm onItemsInit={() => setVisibleItems(10)} /> <SearchForm onItemsInit={handleItemsInit} />
{surveyList.length > 0 ? ( {heldSurveyList.length > 0 ? (
<div className="sale-frame"> <div className="sale-frame">
<ul className="sale-list-wrap"> <ul className="sale-list-wrap">
{surveyList.slice(0, visibleItems).map((survey) => ( {heldSurveyList.map((survey) => (
<li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetailClick(survey.id)}> <li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetailClick(survey.id)}>
<div className="sale-item-bx"> <div className="sale-item-bx">
<div className="sale-item-date-bx"> <div className="sale-item-date-bx">
@ -59,7 +63,7 @@ export default function ListTable() {
))} ))}
</ul> </ul>
<div className="sale-edit-btn"> <div className="sale-edit-btn">
<LoadMoreButton hasMore={hasMore} onLoadMore={handleLoadMore} onScrollToTop={handleScrollToTop} /> <LoadMoreButton hasMore={hasMore} onLoadMore={() => setOffset(offset + 10)} />
</div> </div>
</div> </div>
) : ( ) : (

View File

@ -6,6 +6,7 @@ import { useSurveyFilterStore } from '@/store/surveyFilterStore'
export function useServey(id?: number): { export function useServey(id?: number): {
surveyList: SurveyBasicInfo[] | [] surveyList: SurveyBasicInfo[] | []
surveyDetail: SurveyBasicInfo | null surveyDetail: SurveyBasicInfo | null
surveyListCount: number
isLoadingSurveyList: boolean isLoadingSurveyList: boolean
isLoadingSurveyDetail: boolean isLoadingSurveyDetail: boolean
isCreatingSurvey: boolean isCreatingSurvey: boolean
@ -19,14 +20,13 @@ export function useServey(id?: number): {
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
} { } {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const { keyword, searchOption, isMySurvey, sort } = useSurveyFilterStore() const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore()
const { data: surveyList, isLoading: isLoadingSurveyList } = useQuery({ const { data: surveyList, isLoading: isLoadingSurveyList } = useQuery({
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort], queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset],
queryFn: async () => { queryFn: async () => {
console.log('keyword, searchOption, isMySurvey, sort:: ', keyword, searchOption, isMySurvey, sort)
const resp = await axiosInstance(null).get<SurveyBasicInfo[]>('/api/survey-sales', { const resp = await axiosInstance(null).get<SurveyBasicInfo[]>('/api/survey-sales', {
params: { keyword, searchOption, isMySurvey, sort }, params: { keyword, searchOption, isMySurvey, sort, offset },
}) })
return resp.data return resp.data
}, },
@ -43,6 +43,16 @@ export function useServey(id?: number): {
enabled: id !== undefined, enabled: id !== undefined,
}) })
const { data: surveyListCount } = useQuery({
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort],
queryFn: async () => {
const resp = await axiosInstance(null).get<number>('/api/survey-sales', {
params: { keyword, searchOption, isMySurvey, sort },
})
return resp.data
},
})
const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({ const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({
mutationFn: async (survey: SurveyBasicRequest) => { mutationFn: async (survey: SurveyBasicRequest) => {
const resp = await axiosInstance(null).post<SurveyBasicInfo>('/api/survey-sales', survey) const resp = await axiosInstance(null).post<SurveyBasicInfo>('/api/survey-sales', survey)
@ -80,7 +90,7 @@ export function useServey(id?: number): {
}, },
}) })
const { mutateAsync: createSurveyDetail, isPending: isCreatingSurveyDetail } = useMutation({ const { mutateAsync: createSurveyDetail } = useMutation({
mutationFn: async ({ surveyId, surveyDetail }: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => { mutationFn: async ({ surveyId, surveyDetail }: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => {
const resp = await axiosInstance(null).patch<SurveyDetailInfo>(`/api/survey-sales/${surveyId}`, surveyDetail) const resp = await axiosInstance(null).patch<SurveyDetailInfo>(`/api/survey-sales/${surveyId}`, surveyDetail)
return resp.data return resp.data
@ -137,6 +147,7 @@ export function useServey(id?: number): {
return { return {
surveyList: surveyList || [], surveyList: surveyList || [],
surveyDetail: surveyDetail || null, surveyDetail: surveyDetail || null,
surveyListCount: surveyListCount || 0,
isLoadingSurveyList, isLoadingSurveyList,
isLoadingSurveyDetail, isLoadingSurveyDetail,
isCreatingSurvey, isCreatingSurvey,

19
src/store/addressStore.ts Normal file
View File

@ -0,0 +1,19 @@
import { create } from 'zustand'
type AddressData = {
post_code: string
address: string
address_detail: string
}
interface AddressState {
addressData: AddressData | null
setAddressData: (address: AddressData) => void
resetAddressData: () => void
}
export const useAddressStore = create<AddressState>((set) => ({
addressData: null,
setAddressData: (address) => set({ addressData: address }),
resetAddressData: () => set({ addressData: null }),
}))

View File

@ -0,0 +1,41 @@
import { create } from 'zustand'
export const FILTER_OPTIONS = [
{
id: 'all',
label: '全体',
},
{
id: 'completed',
label: '回答完了',
},
{
id: 'waiting',
label: '回答待ち',
},
]
export type FILTER_OPTIONS_ENUM = (typeof FILTER_OPTIONS)[number]['id']
type InquiryFilterState = {
keyword: string
filter: FILTER_OPTIONS_ENUM
isMySurvey: string | null
offset: number
setKeyword: (keyword: string) => void
setFilter: (filter: FILTER_OPTIONS_ENUM) => void
setIsMySurvey: (isMySurvey: string | null) => void
setOffset: (offset: number) => void
reset: () => void
}
export const useInquiryFilterStore = create<InquiryFilterState>((set) => ({
keyword: '',
filter: 'all',
isMySurvey: null,
offset: 0,
setKeyword: (keyword) => set({ keyword }),
setFilter: (filter) => set({ filter }),
setIsMySurvey: (isMySurvey) => set({ isMySurvey }),
setOffset: (offset) => set({ offset }),
reset: () => set({ keyword: '', filter: 'all', isMySurvey: null, offset: 0 }),
}))

View File

@ -1,10 +1,10 @@
import { create } from 'zustand' import { create } from 'zustand'
export type SEARCH_OPTIONS_ENUM = 'all' | 'id' | 'building_name' | 'representative' | 'store' | 'construction_point' // export type SEARCH_OPTIONS_ENUM = 'all' | 'id' | 'building_name' | 'representative' | 'store' | 'construction_point'
// | 'store_id' | 'construction_id' // | 'store_id' | 'construction_id'
export type SEARCH_OPTIONS_PARTNERS_ENUM = 'all' | 'id' | 'building_name' | 'representative' // export type SEARCH_OPTIONS_PARTNERS_ENUM = 'all' | 'id' | 'building_name' | 'representative'
export type SORT_OPTIONS_ENUM = 'created' | 'updated' // export type SORT_OPTIONS_ENUM = 'created' | 'updated'
export const SEARCH_OPTIONS = [ export const SEARCH_OPTIONS = [
{ {
@ -27,18 +27,18 @@ export const SEARCH_OPTIONS = [
id: 'store', id: 'store',
label: '販売店名', label: '販売店名',
}, },
// { // {
// id: 'store_id', // id: 'store_id',
// label: '販売店ID', // label: '販売店ID',
// }, // },
{ {
id: 'construction_point', id: 'construction_point',
label: '施工店名', label: '施工店名',
}, },
// { // {
// id: 'construction_id', // id: 'construction_id',
// label: '施工店ID', // label: '施工店ID',
// }, // },
] ]
export const SEARCH_OPTIONS_PARTNERS = [ export const SEARCH_OPTIONS_PARTNERS = [
@ -60,15 +60,21 @@ export const SEARCH_OPTIONS_PARTNERS = [
}, },
] ]
export type SEARCH_OPTIONS_ENUM = (typeof SEARCH_OPTIONS)[number]['id']
export type SEARCH_OPTIONS_PARTNERS_ENUM = (typeof SEARCH_OPTIONS_PARTNERS)[number]['id']
export type SORT_OPTIONS_ENUM = 'created' | 'updated'
type SurveyFilterState = { type SurveyFilterState = {
keyword: string keyword: string
searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM
isMySurvey: string | null isMySurvey: string | null
sort: SORT_OPTIONS_ENUM sort: SORT_OPTIONS_ENUM
offset: number
setKeyword: (keyword: string) => void setKeyword: (keyword: string) => void
setSearchOption: (searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM) => void setSearchOption: (searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM) => void
setIsMySurvey: (isMySurvey: string | null) => void setIsMySurvey: (isMySurvey: string | null) => void
setSort: (sort: SORT_OPTIONS_ENUM) => void setSort: (sort: SORT_OPTIONS_ENUM) => void
setOffset: (offset: number) => void
reset: () => void reset: () => void
} }
@ -77,9 +83,11 @@ export const useSurveyFilterStore = create<SurveyFilterState>((set) => ({
searchOption: 'all', searchOption: 'all',
isMySurvey: null, isMySurvey: null,
sort: 'created', sort: 'created',
offset: 0,
setKeyword: (keyword: string) => set({ keyword }), setKeyword: (keyword: string) => set({ keyword }),
setSearchOption: (searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM) => set({ searchOption }), setSearchOption: (searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM) => set({ searchOption }),
setIsMySurvey: (isMySurvey: string | null) => set({ isMySurvey }), setIsMySurvey: (isMySurvey: string | null) => set({ isMySurvey }),
setSort: (sort: SORT_OPTIONS_ENUM) => set({ sort }), setSort: (sort: SORT_OPTIONS_ENUM) => set({ sort }),
reset: () => set({ keyword: '', searchOption: 'all', isMySurvey: null, sort: 'created' }), setOffset: (offset: number) => set({ offset }),
reset: () => set({ keyword: '', searchOption: 'all', isMySurvey: null, sort: 'created', offset: 0 }),
})) }))