diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a58b277..f2a45c6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,7 +3,7 @@ generator client { } datasource db { - provider = "sqlserver" + provider = "mysql" url = env("DATABASE_URL") } diff --git a/src/api/survey.ts b/src/api/survey.ts deleted file mode 100644 index e004930..0000000 --- a/src/api/survey.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { axiosInstance } from '@/libs/axios' - -export interface Survey { - id: number - title: string - content: string - created_at: string - updated_at: string - checked: string[] - otherText: string -} - -export const surveyApi = { - create: async (data: Survey): Promise => { - try { - const response = await axiosInstance.post('/api/survey', data) - return response.data - } catch (error) { - console.error('Error creating survey:', error) - throw error - } - }, - - getList: async (): Promise => { - try { - const response = await axiosInstance.get('/api/survey') - return response.data - } catch (error) { - console.error('Error fetching survey list:', error) - return [] - } - }, - - getDetail: async (id?: number): Promise => { - try { - if (id) { - const response = await axiosInstance.get(`/api/survey/${id}`) - return response.data - } else { - return {} as Survey - } - } catch (error) { - console.error('Error fetching survey detail:', error) - throw error - } - }, - - update: async (id: number, data: Survey): Promise => { - try { - const response = await axiosInstance.put(`/api/survey/${id}`, data) - return response.data - } catch (error) { - console.error('Error updating survey:', error) - throw error - } - }, - - delete: async (id: number): Promise => { - try { - await axiosInstance.delete(`/api/survey/${id}`) - } catch (error) { - console.error('Error deleting survey:', error) - throw error - } - }, -} diff --git a/src/api/surveySales.ts b/src/api/surveySales.ts index 3d9d727..4ecff63 100644 --- a/src/api/surveySales.ts +++ b/src/api/surveySales.ts @@ -2,66 +2,122 @@ import { axiosInstance } from '@/libs/axios' export interface SurveySalesBasicInfo { id?: number - representative: String - store: String | null - construction_point: String | null - investigation_date: String | null - building_name: String | null - customer_name: String | null - post_code: String | null - address: String | null - address_detail: String | null - submission_status: Boolean - submission_date?: String | null + representative: string + store: string | null + construction_point: string | null + investigation_date: string | null + building_name: string | null + customer_name: string | null + post_code: string | null + address: string | null + address_detail: string | null + submission_status: boolean + submission_date?: string | null detail_info?: SurveySalesDetailInfo | null } export interface SurveySalesDetailInfo { id?: number - contract_capacity: String | null - retail_company: String | null - supplementary_facilities: Number | null - supplementary_facilities_etc: String | null - installation_system: Number | null - installation_system_etc: String | null - construction_year: Number | null - construction_year_etc: String | null - roof_material: Number | null - roof_material_etc: String | null - roof_shape: Number | null - roof_shape_etc: String | null - roof_slope: String | null - house_structure: Number | null - house_structure_etc: String | null - rafter_material: Number | null - rafter_material_etc: String | null - rafter_size: Number | null - rafter_size_etc: String | null - rafter_pitch: Number | null - rafter_pitch_etc: String | null - rafter_direction: Number | null - open_field_plate_kind: Number | null - open_field_plate_kind_etc: String | null - open_field_plate_thickness: String | null - leak_trace: Boolean | null - waterproof_material: Number | null - waterproof_material_etc: String | null - insulation_presence: Number | null - insulation_presence_etc: String | null - structure_order: Number | null - structure_order_etc: String | null - installation_availability: Number | null - installation_availability_etc: String | null - memo: String | null + contract_capacity: string | null + retail_company: string | null + supplementary_facilities: number | null + supplementary_facilities_etc: string | null + installation_system: number | null + installation_system_etc: string | null + construction_year: number | null + construction_year_etc: string | null + roof_material: number | null + roof_material_etc: string | null + roof_shape: number | null + roof_shape_etc: string | null + roof_slope: string | null + house_structure: number | null + house_structure_etc: string | null + rafter_material: number | null + rafter_material_etc: string | null + rafter_size: number | null + rafter_size_etc: string | null + rafter_pitch: number | null + rafter_pitch_etc: string | null + rafter_direction: number | null + open_field_plate_kind: number | null + open_field_plate_kind_etc: string | null + open_field_plate_thickness: string | null + leak_trace: boolean | null + waterproof_material: number | null + waterproof_material_etc: string | null + insulation_presence: number | null + insulation_presence_etc: string | null + structure_order: number | null + structure_order_etc: string | null + installation_availability: number | null + installation_availability_etc: string | null + memo: string | null } export const surveySalesApi = { - create: async (data: SurveySalesBasicInfo): Promise => { - const response = await axiosInstance.post('/api/survey-sales', data) - return response.data + create: async (data: SurveySalesBasicInfo): Promise => { + try { + const response = await axiosInstance.post('/api/survey-sales', data) + return response.data.id ?? 0 + } catch (error) { + console.error(error) + return 0 + } }, - getList: async (): Promise => { - const response = await axiosInstance.get('/api/survey-sales') - return response.data + getList: async (): Promise => { + try { + const response = await axiosInstance.get('/api/survey-sales') + return response.data + } catch (error) { + console.error(error) + return [] + } + }, + getDetail: async (id: number): Promise => { + try { + const response = await axiosInstance.get(`/api/survey-sales/${id}`) + return response.data + } catch (error) { + console.error(error) + return null + } + }, + update: async (id: number, data: SurveySalesBasicInfo): Promise => { + try { + const response = await axiosInstance.put(`/api/survey-sales/${id}`, data) + return response.data + } catch (error) { + console.error(error) + return null + } + }, + delete: async (id: number, isDetail: boolean = false): Promise => { + try { + await axiosInstance.delete(`/api/survey-sales/${id}`, { + params: { + detail_id: isDetail ? id : undefined, + }, + }) + return true + } catch (error) { + throw error + } + }, + createDetail: async (surveyId: number, data: SurveySalesDetailInfo): Promise => { + try { + await axiosInstance.post(`/api/survey-sales/${surveyId}`, data) + return true + } catch (error) { + throw error + } + }, + confirm: async (id: number): Promise => { + try { + await axiosInstance.patch(`/api/survey-sales/${id}`) + return true + } catch (error) { + throw error + } }, } diff --git a/src/app/api/survey-sales/[id]/route.ts b/src/app/api/survey-sales/[id]/route.ts new file mode 100644 index 0000000..bf5afe4 --- /dev/null +++ b/src/app/api/survey-sales/[id]/route.ts @@ -0,0 +1,72 @@ +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' }) +} diff --git a/src/app/api/survey-sales/route.ts b/src/app/api/survey-sales/route.ts index decd3a3..aebff14 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -3,11 +3,19 @@ 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({ message: 'Survey sales created successfully' }) + 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) + } } diff --git a/src/app/api/survey/[id]/route.ts b/src/app/api/survey/[id]/route.ts deleted file mode 100644 index b8a989a..0000000 --- a/src/app/api/survey/[id]/route.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NextResponse } from 'next/server' - -export async function GET(request: Request, { params }: { params: { id: string } }) { - const { id } = params - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES.findUnique({ - where: { id: parseInt(id) }, - }) - return NextResponse.json(survey) -} - -export async function PUT(request: Request, { params }: { params: { id: string } }) { - const { id } = params - const body = await request.json() - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES.update({ - where: { id: parseInt(id) }, - data: body, - }) - return NextResponse.json(survey) -} - -export async function DELETE(request: Request, { params }: { params: { id: string } }) { - const { id } = params - // @ts-ignore - await prisma.SD_SERVEY_SALES.delete({ where: { id: parseInt(id) } }) - return NextResponse.json({ message: 'Survey deleted successfully' }) -} diff --git a/src/app/api/survey/route.ts b/src/app/api/survey/route.ts deleted file mode 100644 index a8b6b7e..0000000 --- a/src/app/api/survey/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextResponse } from 'next/server' - -export async function GET() { - // @ts-ignore - const surveys = await prisma.SD_SERVEY_SALES.findMany() - return NextResponse.json(surveys) -} - -export async function POST(request: Request) { - const body = await request.json() - // @ts-ignore - const survey = await prisma.SD_SERVEY_SALES.create({ - data: body, - }) - return NextResponse.json(survey) -} diff --git a/src/app/survey-sales/[id]/page.tsx b/src/app/survey-sales/[id]/page.tsx new file mode 100644 index 0000000..eb6685c --- /dev/null +++ b/src/app/survey-sales/[id]/page.tsx @@ -0,0 +1,6 @@ +import SurveyDetail from '@/components/survey-sales/SurveyDetail' + +export default function SurveySalesDetailPage() { + return +} + diff --git a/src/app/survey-sales/page.tsx b/src/app/survey-sales/page.tsx index 6671ce4..b04c6c1 100644 --- a/src/app/survey-sales/page.tsx +++ b/src/app/survey-sales/page.tsx @@ -1,10 +1,12 @@ import SurveySales from '@/components/SurveySales' +import SurveySaleList from '@/components/survey-sales/SurveySaleList' export default function page() { return ( <>

조사 매물 정보

- + {/* */} + ) } diff --git a/src/app/survey-sales/write/page.tsx b/src/app/survey-sales/write/page.tsx new file mode 100644 index 0000000..5d7b38f --- /dev/null +++ b/src/app/survey-sales/write/page.tsx @@ -0,0 +1,9 @@ +import MainSurveyForm from '@/components/survey-sales/write-survey-sales/MainSurveyForm' + +export default function SurveyWritePage() { + return ( +
+ +
+ ) +} diff --git a/src/app/survey/[id]/page.tsx b/src/app/survey/[id]/page.tsx deleted file mode 100644 index 234ee63..0000000 --- a/src/app/survey/[id]/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import SurveySaleDetail from '@/components/survey/SurveySaleDetail' - -export default function SurveyDetailPage() { - return ( -
- -
- ) -} diff --git a/src/app/survey/page.tsx b/src/app/survey/page.tsx deleted file mode 100644 index d716d96..0000000 --- a/src/app/survey/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import SurveySaleList from '@/components/survey/SurveySaleList' - -export default function SurveyPage() { - return ( -
- -
- ) -} - diff --git a/src/app/survey/write/page.tsx b/src/app/survey/write/page.tsx deleted file mode 100644 index 925b622..0000000 --- a/src/app/survey/write/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import SurveySaleWriteForm from '@/components/survey/SurveySaleWriteForm' - -export default function SurveyWritePage() { - return ( -
- -
- ) -} diff --git a/src/components/survey-sales/EtcCheckbox.tsx b/src/components/survey-sales/EtcCheckbox.tsx new file mode 100644 index 0000000..6e4a866 --- /dev/null +++ b/src/components/survey-sales/EtcCheckbox.tsx @@ -0,0 +1,68 @@ +'use client' +import { useState } from "react" +import { SurveySalesDetailInfo } from "@/api/surveySales" + +interface EtcCheckboxProps { + formName: keyof SurveySalesDetailInfo + label: string + detailInfoForm: SurveySalesDetailInfo + setDetailInfoForm: (form: SurveySalesDetailInfo) => void +} + +export default function EtcCheckbox({ formName, label, detailInfoForm, setDetailInfoForm }: EtcCheckboxProps) { + const [showEtcInput, setShowEtcInput] = useState(false) + const etcFieldName = `${formName}_etc` as keyof SurveySalesDetailInfo + + return ( +
+ +
+
+ setDetailInfoForm({ + ...detailInfoForm, + [formName]: e.target.checked ? 1 : 0 + })} + /> + +
+ +
+ { + setShowEtcInput(e.target.checked) + if (!e.target.checked) { + setDetailInfoForm({ + ...detailInfoForm, + [etcFieldName]: '' + }) + } + }} + /> + + + {showEtcInput && ( + setDetailInfoForm({ + ...detailInfoForm, + [etcFieldName]: e.target.value + })} + className="border rounded px-2 py-1 ml-2" + /> + )} +
+
+
+ ) +} + diff --git a/src/components/survey-sales/SurveyDetail.tsx b/src/components/survey-sales/SurveyDetail.tsx new file mode 100644 index 0000000..20145a3 --- /dev/null +++ b/src/components/survey-sales/SurveyDetail.tsx @@ -0,0 +1,61 @@ +'use client' + +import { useServey } from '@/hooks/useSurvey' +import { SurveySalesBasicInfo } from '@/api/surveySales' +import { useParams, useRouter } from 'next/navigation' + +export default function SurveyDetail() { + const params = useParams() + const id = params.id + const router = useRouter() + + const { surveyDetail, deleteSurvey, isDeletingSurvey, confirmSurvey } = useServey(Number(id)) + + console.log('surveyDetail:: ', surveyDetail) + + const handleDelete = async () => { + if (confirm('delete?')) { + if (surveyDetail?.representative) { + if (surveyDetail.detail_info?.id) { + await deleteSurvey({ id: Number(surveyDetail.detail_info.id), isDetail: true }) + } + await deleteSurvey({ id: Number(id), isDetail: false }) + } + alert('delete success') + router.push('/survey-sales') + } + } + const handleSubmit = () => { + if (confirm('submit?')) { + confirmSurvey(Number(id)) + } + alert('submit success') + router.push('/survey-sales') + } + + if (isDeletingSurvey) { + return
Deleting...
+ } + + return ( +
+

SurveyDetail

+

{id}

+

{surveyDetail?.representative}

+
+ + + + +
+
+ ) +} diff --git a/src/components/survey/SurveyFilter.tsx b/src/components/survey-sales/SurveyFilter.tsx similarity index 74% rename from src/components/survey/SurveyFilter.tsx rename to src/components/survey-sales/SurveyFilter.tsx index 8b03add..2249186 100644 --- a/src/components/survey/SurveyFilter.tsx +++ b/src/components/survey-sales/SurveyFilter.tsx @@ -5,7 +5,7 @@ export default function SurveyFilter({ handleSearch, handleMyPosts }: { handleSe const router = useRouter() return (
- +
- +
) diff --git a/src/components/survey-sales/SurveySaleList.tsx b/src/components/survey-sales/SurveySaleList.tsx new file mode 100644 index 0000000..92b9ece --- /dev/null +++ b/src/components/survey-sales/SurveySaleList.tsx @@ -0,0 +1,90 @@ +'use client' + +import { useServey } from '@/hooks/useSurvey' +import LoadMoreButton from '@/components/LoadMoreButton' +import { useState } from 'react' +import SurveyFilter from './SurveyFilter' +import { useRouter } from 'next/navigation' + +export default function SurveySaleList() { + const { surveyList, isLoadingSurveyList } = useServey() + const [search, setSearch] = useState('') + const [isMyPostsOnly, setIsMyPostsOnly] = useState(false) + const [hasMore, setHasMore] = useState(surveyList.length > 5) + const [visibleItems, setVisibleItems] = useState(5) + + const router = useRouter() + + // TEMP USERNAME + const username = 'test' + + const surveyData = () => { + if (search.trim().length > 0) { + return surveyList.filter((survey) => survey.building_name?.includes(search)) + } + if (isMyPostsOnly) { + return surveyList.filter((survey) => survey.representative === username) + } + return surveyList + } + + const handleLoadMore = () => { + const newVisibleItems = Math.min(visibleItems + 5, surveyData().length) + setVisibleItems(newVisibleItems) + setHasMore(newVisibleItems < surveyData().length) + } + + const handleScrollToTop = () => { + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + const handleSearch = (e: React.ChangeEvent) => { + setSearch(e.target.value) + } + + const handleDetail = (id: number | undefined) => { + if (id === undefined) throw new Error('id is required') + router.push(`/survey-sales/${id}`) + } + + if (isLoadingSurveyList) { + return
Loading...
+ } + + return ( +
+ setIsMyPostsOnly(!isMyPostsOnly)} /> + +
+ {surveyData().slice(0, visibleItems).map((survey) => ( +
handleDetail(survey.id)} + className="bg-white rounded-lg shadow p-4 hover:shadow-md transition-shadow cursor-pointer border border-gray-200" + > +
+
+

{survey.id}

+
+

담당자: {survey.representative || '-'}

+

판매점: {survey.store || '-'}

+
+
+ + {survey.submission_status ? '제출' : '미제출'} + +
+
+ ))} +
+ +
+ +
+
+ ) +} diff --git a/src/components/survey-sales/write-survey-sales/BasicWriteForm.tsx b/src/components/survey-sales/write-survey-sales/BasicWriteForm.tsx new file mode 100644 index 0000000..bf73338 --- /dev/null +++ b/src/components/survey-sales/write-survey-sales/BasicWriteForm.tsx @@ -0,0 +1,86 @@ +'use client' + +import { SurveySalesBasicInfo } from '@/api/surveySales' + +export default function BasicWriteForm({ + basicInfoData, + setBasicInfoData, +}: { + basicInfoData: SurveySalesBasicInfo + setBasicInfoData: (basicInfoData: SurveySalesBasicInfo) => void +}) { + const handleChange = (key: keyof SurveySalesBasicInfo, value: string) => { + setBasicInfoData({ ...basicInfoData, [key]: value.toString() }) + } + + return ( +
+
+ + handleChange('representative', e.target.value)} + /> +
+
+ + handleChange('store', e.target.value)} /> +
+
+ + handleChange('construction_point', e.target.value)} + /> +
+
+ + handleChange('investigation_date', e.target.value)} + /> +
+
+ + handleChange('building_name', e.target.value)} + /> +
+
+ + handleChange('customer_name', e.target.value)} + /> +
+
+ + handleChange('post_code', e.target.value)} /> +
+
+ + handleChange('address', e.target.value)} /> +
+
+ + handleChange('address_detail', e.target.value)} + /> +
+
+ ) +} diff --git a/src/components/survey-sales/write-survey-sales/DetailWriteForm.tsx b/src/components/survey-sales/write-survey-sales/DetailWriteForm.tsx new file mode 100644 index 0000000..488f046 --- /dev/null +++ b/src/components/survey-sales/write-survey-sales/DetailWriteForm.tsx @@ -0,0 +1,157 @@ +'use client' +import React from 'react' +import EtcCheckbox from '../EtcCheckbox' +import { SurveySalesDetailInfo } from '@/api/surveySales' + +interface DetailWriteFormProps { + detailInfoForm: SurveySalesDetailInfo + setDetailInfoForm: (form: SurveySalesDetailInfo) => void +} + +export default function DetailWriteForm({ detailInfoForm, setDetailInfoForm }: DetailWriteFormProps) { + const handleNumberInput = (field: keyof SurveySalesDetailInfo, value: string) => { + const numberValue = value === '' ? null : Number(value) + setDetailInfoForm({ ...detailInfoForm, [field]: numberValue }) + } + + const handleTextInput = (field: keyof SurveySalesDetailInfo, value: string) => { + setDetailInfoForm({ ...detailInfoForm, [field]: value || null }) + } + + const handleBooleanInput = (field: keyof SurveySalesDetailInfo, checked: boolean) => { + setDetailInfoForm({ ...detailInfoForm, [field]: checked }) + } + + return ( +
+
+ + handleTextInput('contract_capacity', e.target.value)} + /> +
+ +
+ + handleTextInput('retail_company', e.target.value)} + /> +
+ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + handleTextInput('roof_slope', e.target.value)} + /> +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + handleTextInput('rafter_direction', e.target.value)} + /> +
+ +
+ +
+ +
+ + handleTextInput('open_field_plate_thickness', e.target.value)} + /> +
+ +
+ + handleBooleanInput('leak_trace', e.target.checked)} + /> +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +