Compare commits
13 Commits
8153dd9d18
...
68408eb3c9
| Author | SHA1 | Date | |
|---|---|---|---|
| 68408eb3c9 | |||
| 115ffb8a74 | |||
| fd27bfe7d0 | |||
| 692d2b7d84 | |||
| 05f4fe4e94 | |||
| 5048fe1c08 | |||
| c9378dfccd | |||
|
|
2166b7836e | ||
| d340e3d976 | |||
| 82e198836a | |||
| daf9f31733 | |||
| 3cf55e1a9e | |||
| 3937179279 |
@ -1,3 +1,7 @@
|
||||
# 모바일 디바이스로 로컬 서버 확인하려면 자신 IP 주소로 변경
|
||||
# 다시 로컬에서 개발할때는 localhost로 변경
|
||||
NEXT_PUBLIC_API_URL=http://localhost:3000
|
||||
#route handler
|
||||
NEXT_PUBLIC_API_URL=http://localhost:3000
|
||||
|
||||
#qsp 로그인 api
|
||||
NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
|
||||
@ -1 +1,5 @@
|
||||
NEXT_PUBLIC_API_URL=http://172.30.1.35:3000
|
||||
#route handler
|
||||
NEXT_PUBLIC_API_URL=http://172.30.1.35:3000
|
||||
|
||||
#qsp 로그인 api
|
||||
NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
|
||||
@ -6,14 +6,18 @@ const nextConfig: NextConfig = {
|
||||
sassOptions: {
|
||||
includePaths: [path.join(__dirname, './src/styles')],
|
||||
},
|
||||
// async rewrites() {
|
||||
// return [
|
||||
// {
|
||||
// source: '/api/:path*',
|
||||
// destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`,
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/api/user/login',
|
||||
destination: `${process.env.NEXT_PUBLIC_QSP_API_URL}/api/user/login`,
|
||||
},
|
||||
{
|
||||
source: '/api/:path*',
|
||||
destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
||||
@ -133,7 +133,7 @@ model SD_SERVEY_SALES_DETAIL_INFO {
|
||||
//전기 소매 회사
|
||||
retail_company String? @db.VarChar(100)
|
||||
//전기 부대 설비
|
||||
supplementary_facilities Int? @db.Int
|
||||
supplementary_facilities String? @db.VarChar(20)
|
||||
//전기 부대 설비 기타
|
||||
supplementary_facilities_etc String? @db.VarChar(200)
|
||||
//설치 희망 시스템
|
||||
@ -145,7 +145,7 @@ model SD_SERVEY_SALES_DETAIL_INFO {
|
||||
//건축 연수 기타
|
||||
construction_year_etc String? @db.VarChar(200)
|
||||
//지붕재
|
||||
roof_material Int? @db.Int
|
||||
roof_material String? @db.VarChar(20)
|
||||
//지붕재 기타
|
||||
roof_material_etc String? @db.VarChar(200)
|
||||
//지붕 모양
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
import { database } from '@/data'
|
||||
import { axiosInstance } from '@/libs/axios'
|
||||
|
||||
export interface Suitable {
|
||||
id?: number
|
||||
product_name: string
|
||||
manufacturer: string
|
||||
roof_material: string
|
||||
shape: string
|
||||
support_roof_tile: string
|
||||
support_roof_tile_memo: string
|
||||
support_roof_bracket: string
|
||||
support_roof_bracket_memo: string
|
||||
yg_anchor: string
|
||||
yg_anchor_memo: string
|
||||
rg_roof_tile_part: string
|
||||
rg_roof_tile_part_memo: string
|
||||
dido_hunt_support_tile_2: string
|
||||
dido_hunt_support_tile_2_memo: string
|
||||
takashima_power_base: string
|
||||
takashima_power_base_memo: string
|
||||
takashima_tile_bracket: string
|
||||
takashima_tile_bracket_memo: string
|
||||
slate_bracket_4: string
|
||||
slate_bracket_4_memo: string
|
||||
slate_single_metal_bracket: string
|
||||
slate_single_metal_bracket_memo: string
|
||||
dido_hunt_short_rack_4: string
|
||||
dido_hunt_short_rack_4_memo: string
|
||||
takashima_slate_bracket_slate_single: string
|
||||
takashima_slate_bracket_slate_single_memo: string
|
||||
df_metal_bracket: string
|
||||
df_metal_bracket_memo: string
|
||||
slate_metal_bracket: string
|
||||
slate_metal_bracket_memo: string
|
||||
takashima_slate_bracket_metal_roof: string
|
||||
takashima_slate_bracket_metal_roof_memo: string
|
||||
}
|
||||
|
||||
export const suitableApi = {
|
||||
getList: async (): Promise<Suitable[]> => {
|
||||
const response = await axiosInstance.get<Suitable[]>('/api/suitable/list')
|
||||
console.log('🚀 ~ getList: ~ response:', response)
|
||||
return response.data
|
||||
},
|
||||
|
||||
getDetails: async (roofMaterial: string): Promise<Suitable[]> => {
|
||||
const response = await axiosInstance.get<Suitable[]>(`/api/suitable/details?roof-material=${roofMaterial}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
create: async () => {
|
||||
const suitableData: Suitable[] = []
|
||||
|
||||
database.forEach((item) => {
|
||||
suitableData.push({
|
||||
product_name: item[0],
|
||||
manufacturer: item[1],
|
||||
roof_material: item[2],
|
||||
shape: item[3],
|
||||
support_roof_tile: item[4],
|
||||
support_roof_tile_memo: item[5],
|
||||
support_roof_bracket: item[6],
|
||||
support_roof_bracket_memo: item[7],
|
||||
yg_anchor: item[8],
|
||||
yg_anchor_memo: item[9],
|
||||
rg_roof_tile_part: item[10],
|
||||
rg_roof_tile_part_memo: item[11],
|
||||
dido_hunt_support_tile_2: item[12],
|
||||
dido_hunt_support_tile_2_memo: item[13],
|
||||
takashima_power_base: item[14],
|
||||
takashima_power_base_memo: item[15],
|
||||
takashima_tile_bracket: item[16],
|
||||
takashima_tile_bracket_memo: item[17],
|
||||
slate_bracket_4: item[18],
|
||||
slate_bracket_4_memo: item[19],
|
||||
slate_single_metal_bracket: item[20],
|
||||
slate_single_metal_bracket_memo: item[21],
|
||||
dido_hunt_short_rack_4: item[22],
|
||||
dido_hunt_short_rack_4_memo: item[23],
|
||||
takashima_slate_bracket_slate_single: item[24],
|
||||
takashima_slate_bracket_slate_single_memo: item[25],
|
||||
df_metal_bracket: item[26],
|
||||
df_metal_bracket_memo: item[27],
|
||||
slate_metal_bracket: item[28],
|
||||
slate_metal_bracket_memo: item[29],
|
||||
takashima_slate_bracket_metal_roof: item[30],
|
||||
takashima_slate_bracket_metal_roof_memo: item[31],
|
||||
})
|
||||
})
|
||||
|
||||
const response = await axiosInstance.post<Suitable[]>('/api/suitable', suitableData)
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import Header from '@/components/ui/common/Header'
|
||||
|
||||
export default function page() {
|
||||
export default async function page() {
|
||||
return <Header name={'調査物件一覧'} />
|
||||
}
|
||||
|
||||
15
src/app/api/auth/logout/route.ts
Normal file
15
src/app/api/auth/logout/route.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { sessionOptions } from '@/libs/session'
|
||||
import { SessionData } from '@/types/Auth'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { cookies } from 'next/headers'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const cookieStore = await cookies()
|
||||
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
|
||||
|
||||
session.destroy()
|
||||
// return redirect('/login')
|
||||
|
||||
return NextResponse.json({ code: 200, message: 'Logout successful' })
|
||||
}
|
||||
58
src/app/api/auth/route.ts
Normal file
58
src/app/api/auth/route.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { cookies } from 'next/headers'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { axiosInstance } from '@/libs/axios'
|
||||
import { sessionOptions } from '@/libs/session'
|
||||
|
||||
import type { SessionData } from '@/types/Auth'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { loginId, pwd } = await request.json()
|
||||
|
||||
const result = await axiosInstance(`${process.env.NEXT_PUBLIC_QSP_API_URL}`).post(`/api/user/login`, {
|
||||
loginId,
|
||||
pwd,
|
||||
})
|
||||
console.log('🚀 ~ result ~ result:', result)
|
||||
|
||||
if (result.data.result.code === 200) {
|
||||
const cookieStore = await cookies()
|
||||
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
|
||||
|
||||
console.log('start session edit!')
|
||||
session.langCd = result.data.data.langCd
|
||||
session.currPage = result.data.data.currPage
|
||||
session.rowCount = result.data.data.rowCount
|
||||
session.startRow = result.data.data.startRow
|
||||
session.endRow = result.data.data.endRow
|
||||
session.compCd = result.data.data.compCd
|
||||
session.agencyStoreId = result.data.data.agencyStoreId
|
||||
session.storeId = result.data.data.storeId
|
||||
session.userId = result.data.data.userId
|
||||
session.category = result.data.data.category
|
||||
session.userNm = result.data.data.userNm
|
||||
session.userNmKana = result.data.data.userNmKana
|
||||
session.telNo = result.data.data.telNo
|
||||
session.fax = result.data.data.fax
|
||||
session.email = result.data.data.email
|
||||
session.lastEditUser = result.data.data.lastEditUser
|
||||
session.storeGubun = result.data.data.storeGubun
|
||||
session.pwCurr = result.data.data.pwCurr
|
||||
session.pwdInitYn = result.data.data.pwdInitYn
|
||||
session.apprStatCd = result.data.data.apprStatCd
|
||||
session.loginFailCnt = result.data.data.loginFailCnt
|
||||
session.loginFailMinYn = result.data.data.loginFailMinYn
|
||||
session.priceViewStatCd = result.data.data.priceViewStatCd
|
||||
session.groupId = result.data.data.groupId
|
||||
session.storeLvl = result.data.data.storeLvl
|
||||
session.custCd = result.data.data.custCd
|
||||
session.builderNo = result.data.data.builderNo
|
||||
session.isLoggedIn = true
|
||||
console.log('end session edit!')
|
||||
|
||||
await session.save()
|
||||
}
|
||||
|
||||
return NextResponse.json({ code: 200, message: 'Login is Succecss!!' })
|
||||
}
|
||||
16
src/app/api/suitable/category/route.ts
Normal file
16
src/app/api/suitable/category/route.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/libs/prisma'
|
||||
|
||||
export async function GET() {
|
||||
// @ts-ignore
|
||||
const roofMaterialCategory = await prisma.MS_SUITABLE.findMany({
|
||||
select: {
|
||||
roof_material: true,
|
||||
},
|
||||
distinct: ['roof_material'],
|
||||
orderBy: {
|
||||
roof_material: 'asc',
|
||||
},
|
||||
})
|
||||
return NextResponse.json(roofMaterialCategory)
|
||||
}
|
||||
17
src/app/api/suitable/detail/route.ts
Normal file
17
src/app/api/suitable/detail/route.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/libs/prisma'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const roofMaterial = searchParams.get('roof-material')
|
||||
console.log('🚀 ~ GET ~ roof-material:', roofMaterial)
|
||||
|
||||
// @ts-ignore
|
||||
const suitables = await prisma.MS_SUITABLE.findMany({
|
||||
where: {
|
||||
roof_material: roofMaterial,
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json(suitables)
|
||||
}
|
||||
34
src/app/api/suitable/list/route.ts
Normal file
34
src/app/api/suitable/list/route.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/libs/prisma'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const searchParams = request.nextUrl.searchParams
|
||||
const category = searchParams.get('category')
|
||||
const keyword = searchParams.get('keyword')
|
||||
|
||||
let whereCondition: any = {}
|
||||
if (category) {
|
||||
whereCondition['roof_material'] = category
|
||||
}
|
||||
if (keyword) {
|
||||
whereCondition['product_name'] = {
|
||||
contains: keyword,
|
||||
}
|
||||
}
|
||||
console.log('🚀 ~ /api/suitable/list: ~ prisma where condition:', whereCondition)
|
||||
|
||||
// @ts-ignore
|
||||
const suitables = await prisma.MS_SUITABLE.findMany({
|
||||
where: whereCondition,
|
||||
orderBy: {
|
||||
product_name: 'asc',
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json(suitables)
|
||||
} catch (error) {
|
||||
console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
|
||||
return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
12
src/app/api/suitable/route.ts
Normal file
12
src/app/api/suitable/route.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/libs/prisma'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json()
|
||||
// @ts-ignore
|
||||
const suitables = await prisma.MS_SUITABLE.createMany({
|
||||
data: body,
|
||||
})
|
||||
|
||||
return NextResponse.json({ message: 'Suitable created successfully' })
|
||||
}
|
||||
72
src/app/api/survey-sale/[id]/route.ts
Normal file
72
src/app/api/survey-sale/[id]/route.ts
Normal file
@ -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' })
|
||||
}
|
||||
33
src/app/api/survey-sale/route.ts
Normal file
33
src/app/api/survey-sale/route.ts
Normal file
@ -0,0 +1,33 @@
|
||||
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' })
|
||||
}
|
||||
23
src/app/api/user/create/route.ts
Normal file
23
src/app/api/user/create/route.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/libs/prisma'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { username, email, password } = body
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
updated_at: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json(user)
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error)
|
||||
return NextResponse.json({ error: 'Error creating user' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
7
src/app/api/user/list/route.ts
Normal file
7
src/app/api/user/list/route.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/libs/prisma'
|
||||
|
||||
export const GET = async () => {
|
||||
const users = await prisma.user.findMany()
|
||||
return NextResponse.json(users)
|
||||
}
|
||||
37
src/app/api/user/route.ts
Normal file
37
src/app/api/user/route.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/libs/prisma'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { cookies } from 'next/headers'
|
||||
import { SessionData, sessionOptions } from '@/libs/session'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { username, password } = await request.json()
|
||||
|
||||
console.log('🚀 ~ POST ~ username:', username)
|
||||
console.log('🚀 ~ POST ~ password:', password)
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
})
|
||||
console.log('🚀 ~ POST ~ user:', user)
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const cookieStore = await cookies()
|
||||
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
|
||||
console.log('start session edit!')
|
||||
session.username = user.username!
|
||||
session.email = user.email!
|
||||
session.isLoggedIn = true
|
||||
console.log('end session edit!')
|
||||
await session.save()
|
||||
console.log('🚀 ~ POST ~ session:', session)
|
||||
|
||||
// return NextResponse.redirect(new URL(process.env.NEXT_PUBLIC_URL!, request.url))
|
||||
return NextResponse.json(user)
|
||||
}
|
||||
@ -1,6 +1,14 @@
|
||||
import Main from '@/components/ui/Main'
|
||||
import { sessionOptions } from '@/libs/session'
|
||||
import type { SessionData } from '@/types/Auth'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Home() {
|
||||
const cookieStore = await cookies()
|
||||
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
|
||||
console.log('🚀 ~ Home ~ session:', session)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import BasicForm from '@/components/survey-sale/detail/BasicForm'
|
||||
import BasicForm from '@/components/survey-sale/detail/form/BasicForm'
|
||||
|
||||
export default function page() {
|
||||
return (
|
||||
|
||||
@ -4,7 +4,6 @@ import SearchForm from '@/components/survey-sale/list/SearchForm'
|
||||
export default function page() {
|
||||
return (
|
||||
<>
|
||||
<SearchForm />
|
||||
<ListTable />
|
||||
</>
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import RoofInfoForm from '@/components/survey-sale/detail/RoofInfoForm'
|
||||
import RoofInfoForm from '@/components/survey-sale/detail/form/RoofInfoForm'
|
||||
|
||||
export default function page() {
|
||||
return (
|
||||
|
||||
@ -1,16 +1,69 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useReducer, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { axiosInstance } from '@/libs/axios'
|
||||
|
||||
interface AccountState {
|
||||
loginId: string
|
||||
pwd: string
|
||||
}
|
||||
|
||||
export default function Login() {
|
||||
const [pwShow, setPwShow] = useState(false) //비밀번호 보이기 숨기기
|
||||
const router = useRouter()
|
||||
//비밀번호 보이기 숨기기
|
||||
const [pwShow, setPwShow] = useState(false)
|
||||
//ID 저장 체크박스
|
||||
const [idSave, setIdSave] = useState(false)
|
||||
//Q.PARTNERS 체크박스
|
||||
const [isPartners, setIsPartners] = useState(false)
|
||||
//로그인 상태
|
||||
const [isLogin, setIsLogin] = useState(false)
|
||||
|
||||
const reducer = (state: AccountState, newState: Partial<AccountState>) => ({ ...state, ...newState })
|
||||
const [account, setAccount] = useReducer(reducer, {
|
||||
loginId: '',
|
||||
pwd: '',
|
||||
})
|
||||
|
||||
interface LoginData {
|
||||
code: number
|
||||
message: string | null
|
||||
}
|
||||
|
||||
const {
|
||||
data: loginData,
|
||||
isPending,
|
||||
error,
|
||||
} = useQuery<LoginData, Error>({
|
||||
queryKey: ['login', 'account'],
|
||||
queryFn: async () => {
|
||||
const { data } = await axiosInstance('').post<LoginData>(`/api/auth`, {
|
||||
loginId: account.loginId,
|
||||
pwd: account.pwd,
|
||||
})
|
||||
router.push('/')
|
||||
|
||||
return data
|
||||
},
|
||||
enabled: isLogin,
|
||||
staleTime: 0,
|
||||
retry: false,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="login-form-wrap">
|
||||
<div className="login-form mb15">
|
||||
<div className="login-input id">
|
||||
<input type="text" className="login-frame" placeholder="Input Frame ID" />
|
||||
<input
|
||||
type="text"
|
||||
className="login-frame"
|
||||
placeholder="Input Frame ID"
|
||||
value={account.loginId}
|
||||
onChange={(e) => setAccount({ loginId: e.target.value })}
|
||||
/>
|
||||
<button className="login-icon">
|
||||
<i className="del-icon"></i>
|
||||
</button>
|
||||
@ -18,7 +71,13 @@ export default function Login() {
|
||||
</div>
|
||||
<div className="login-form">
|
||||
<div className="login-input pw">
|
||||
<input type={`${pwShow ? 'text' : 'password'}`} className="login-frame" placeholder="Input Frame PW" />
|
||||
<input
|
||||
type={`${pwShow ? 'text' : 'password'}`}
|
||||
className="login-frame"
|
||||
placeholder="Input Frame PW"
|
||||
value={account.pwd}
|
||||
onChange={(e) => setAccount({ pwd: e.target.value })}
|
||||
/>
|
||||
<button className={`login-icon ${pwShow ? 'act' : ''}`} onClick={() => setPwShow(!pwShow)}>
|
||||
<i className="show-icon"></i>
|
||||
</button>
|
||||
@ -26,19 +85,19 @@ export default function Login() {
|
||||
</div>
|
||||
<div className="login-check-warp">
|
||||
<div className="check-form-box">
|
||||
<input type="checkbox" id="ch01" />
|
||||
<input type="checkbox" id="ch01" checked={idSave} onChange={() => setIdSave(!idSave)} />
|
||||
<label htmlFor="ch01">ID Save</label>
|
||||
</div>
|
||||
<div className="toggle-form">
|
||||
<label className="toggle-btn">
|
||||
<input type="checkbox" />
|
||||
<input type="checkbox" checked={isPartners} onChange={() => setIsPartners(!isPartners)} />
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
<div className="toggle-name">Q.PARTNERS</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-btn-wrap">
|
||||
<button className="btn-frame icon login">
|
||||
<button className="btn-frame icon login" onClick={() => setIsLogin(true)}>
|
||||
お問い合わせ <i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
'use client'
|
||||
|
||||
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { usePathname, useRouter, useSearchParams, useParams } from 'next/navigation'
|
||||
import { useEffect } from 'react'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
|
||||
export default function NavTab() {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
if (pathname === '/survey-sale') {
|
||||
return null
|
||||
}
|
||||
const searchParams = useSearchParams()
|
||||
const id = searchParams.get('id')
|
||||
|
||||
const params = useParams()
|
||||
const detailId = params.id
|
||||
|
||||
const { basicInfoSelected, roofInfoSelected, reset } = useSurveySaleTabState()
|
||||
|
||||
@ -18,14 +21,37 @@ export default function NavTab() {
|
||||
return () => {
|
||||
reset()
|
||||
}
|
||||
}, [])
|
||||
}, [reset])
|
||||
|
||||
if (pathname === '/survey-sale') {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleBasicInfoClick = () => {
|
||||
router.push('/survey-sale/basic-info')
|
||||
if (id) {
|
||||
router.push(`/survey-sale/basic-info?id=${id}`)
|
||||
return
|
||||
}
|
||||
if (detailId) {
|
||||
router.push(`/survey-sale/${detailId}?tab=basic-info`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const handleRoofInfoClick = () => {
|
||||
router.push('/survey-sale/roof-info')
|
||||
if (id) {
|
||||
router.push(`/survey-sale/roof-info?id=${id}`)
|
||||
return
|
||||
}
|
||||
if (detailId) {
|
||||
router.push(`/survey-sale/${detailId}?tab=roof-info`)
|
||||
return
|
||||
}
|
||||
if (pathname === '/survey-sale/basic-info') {
|
||||
// TODO: 팝업 추가
|
||||
alert('Save essential information first')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -2,12 +2,21 @@
|
||||
|
||||
import { useServey } from '@/hooks/useSurvey'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function DataTable() {
|
||||
const params = useParams()
|
||||
const id = params.id
|
||||
|
||||
const { surveyDetail, isLoadingSurveyDetail } = useServey(Number(id))
|
||||
const [isTemporary, setIsTemporary] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!surveyDetail?.representative || !surveyDetail?.store || !surveyDetail?.construction_point) {
|
||||
setIsTemporary(true)
|
||||
}
|
||||
}, [surveyDetail])
|
||||
|
||||
if (isLoadingSurveyDetail) {
|
||||
return <div>Loading...</div>
|
||||
@ -24,7 +33,13 @@ export default function DataTable() {
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>登録番号</th>
|
||||
<td>{surveyDetail?.id}</td>
|
||||
{isTemporary ? (
|
||||
<td>
|
||||
<span className="text-red-500">仮保存</span>
|
||||
</td>
|
||||
) : (
|
||||
<td>{surveyDetail?.id}</td>
|
||||
)}
|
||||
</tr>
|
||||
<tr>
|
||||
<th>登録日</th>
|
||||
@ -39,7 +54,7 @@ export default function DataTable() {
|
||||
<td>
|
||||
{surveyDetail?.submission_status && surveyDetail?.submission_date
|
||||
? new Date(surveyDetail.submission_date).toLocaleString()
|
||||
: '未提出'}
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@ -1,15 +1,37 @@
|
||||
'use client'
|
||||
|
||||
import { useServey } from '@/hooks/useSurvey'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function DetailForm() {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const id = params.id
|
||||
|
||||
const { surveyDetail, deleteSurvey, submitSurvey, isLoadingSurveyDetail } = useServey(Number(id))
|
||||
const searchParams = useSearchParams()
|
||||
const tab = searchParams.get('tab')
|
||||
|
||||
const { surveyDetail, deleteSurvey, submitSurvey, isLoadingSurveyDetail } = useServey(Number(id))
|
||||
const { setBasicInfoSelected, setRoofInfoSelected } = useSurveySaleTabState()
|
||||
const [isTemporary, setIsTemporary] = useState(true)
|
||||
|
||||
// TODO: 조사매물 지붕정보 퍼블 구현되면 탭 화면 변경 추가
|
||||
useEffect(() => {
|
||||
if (tab === 'basic-info') {
|
||||
setBasicInfoSelected()
|
||||
}
|
||||
if (tab === 'roof-info') {
|
||||
setRoofInfoSelected()
|
||||
}
|
||||
if (surveyDetail?.representative && surveyDetail?.store && surveyDetail?.construction_point) {
|
||||
setIsTemporary(false)
|
||||
}
|
||||
}, [tab, setBasicInfoSelected, setRoofInfoSelected, surveyDetail])
|
||||
|
||||
console.log('isTemporary', isTemporary)
|
||||
console.log('surveyDetail', surveyDetail)
|
||||
if (isLoadingSurveyDetail) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
@ -68,16 +90,22 @@ export default function DetailForm() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn-flex-wrap">
|
||||
<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>
|
||||
{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>
|
||||
|
||||
@ -1,415 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
|
||||
|
||||
import { useServey } from '@/hooks/useSurvey'
|
||||
import { SurveyDetailRequest } from '@/types/Survey'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useEffect, useState } from 'react'
|
||||
import MultiCheckbox from './form/MultiCheckbox'
|
||||
const defaultDetailInfoForm: SurveyDetailRequest = {
|
||||
contract_capacity: null,
|
||||
retail_company: null,
|
||||
supplementary_facilities: null,
|
||||
supplementary_facilities_etc: null,
|
||||
installation_system: null,
|
||||
installation_system_etc: null,
|
||||
construction_year: null,
|
||||
construction_year_etc: null,
|
||||
roof_material: null,
|
||||
roof_material_etc: null,
|
||||
roof_shape: null,
|
||||
roof_shape_etc: null,
|
||||
roof_slope: null,
|
||||
house_structure: null,
|
||||
house_structure_etc: null,
|
||||
rafter_material: null,
|
||||
rafter_material_etc: null,
|
||||
rafter_size: null,
|
||||
rafter_size_etc: null,
|
||||
rafter_pitch: null,
|
||||
rafter_pitch_etc: null,
|
||||
rafter_direction: null,
|
||||
open_field_plate_kind: null,
|
||||
open_field_plate_kind_etc: null,
|
||||
open_field_plate_thickness: null,
|
||||
leak_trace: null,
|
||||
waterproof_material: null,
|
||||
waterproof_material_etc: null,
|
||||
insulation_presence: null,
|
||||
insulation_presence_etc: null,
|
||||
structure_order: null,
|
||||
structure_order_etc: null,
|
||||
installation_availability: null,
|
||||
installation_availability_etc: null,
|
||||
memo: null,
|
||||
}
|
||||
|
||||
export default function RoofInfoForm() {
|
||||
const { setRoofInfoSelected } = useSurveySaleTabState()
|
||||
|
||||
useEffect(() => {
|
||||
setRoofInfoSelected()
|
||||
}, [])
|
||||
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const id = searchParams.get('id')
|
||||
|
||||
const { updateSurvey, isUpdatingSurvey, surveyDetail, createSurveyDetail } = useServey(Number(id))
|
||||
|
||||
const [detailInfoData, setDetailInfoData] = useState<SurveyDetailRequest>(defaultDetailInfoForm)
|
||||
|
||||
useEffect(() => {
|
||||
if (surveyDetail?.detail_info) {
|
||||
const { id, updated_at, created_at, ...rest } = surveyDetail.detail_info
|
||||
setDetailInfoData(rest)
|
||||
}
|
||||
}, [surveyDetail])
|
||||
|
||||
const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => {
|
||||
if (typeof value === 'string') {
|
||||
const numberValue = value === '' ? null : Number(value)
|
||||
setDetailInfoData({ ...detailInfoData, [key]: numberValue })
|
||||
} else {
|
||||
setDetailInfoData({ ...detailInfoData, [key]: value })
|
||||
}
|
||||
}
|
||||
|
||||
const handleTextInput = (key: keyof SurveyDetailRequest, value: string) => {
|
||||
setDetailInfoData({ ...detailInfoData, [key]: value || null })
|
||||
}
|
||||
|
||||
const handleBooleanInput = (key: keyof SurveyDetailRequest, checked: boolean) => {
|
||||
setDetailInfoData({ ...detailInfoData, [key]: checked })
|
||||
}
|
||||
|
||||
const handleUnitInput = (value: string) => {
|
||||
const capacity = detailInfoData.contract_capacity
|
||||
setDetailInfoData({ ...detailInfoData, contract_capacity: `${capacity} ${value}` })
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
if (id) {
|
||||
console.log('detailInfoData:: ', detailInfoData)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="sale-frame">
|
||||
<div className="sale-roof-title">電気関係</div>
|
||||
<div className="data-form-wrap">
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">電気契約容量</div>
|
||||
<div className="data-input mb5">
|
||||
<input
|
||||
type="text"
|
||||
className="input-frame"
|
||||
value={detailInfoData.contract_capacity ?? ''}
|
||||
onChange={(e) => handleTextInput('contract_capacity', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<select
|
||||
className="select-form"
|
||||
name="contract_capacity_unit"
|
||||
id="contract_capacity_unit"
|
||||
onChange={(e) => handleUnitInput(e.target.value)}
|
||||
>
|
||||
<option value="kVA">kVA</option>
|
||||
<option value="">kVA</option>
|
||||
<option value="">kVA</option>
|
||||
<option value="">kVA</option>
|
||||
<option value="">kVA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">電気小売会社</div>
|
||||
<input
|
||||
type="text"
|
||||
className="input-frame"
|
||||
value={detailInfoData.retail_company ?? ''}
|
||||
onChange={(e) => handleTextInput('retail_company', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<MultiCheckbox column={'supplementary_facilities'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData}/>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">設置希望システム</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">太陽光発電</option>
|
||||
<option value="">太陽光発電</option>
|
||||
<option value="">太陽光発電</option>
|
||||
<option value="">太陽光発電</option>
|
||||
<option value="">太陽光発電</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sale-frame">
|
||||
<div className="sale-roof-title">屋根関係</div>
|
||||
<div className="data-form-wrap">
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">建築研修</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">新築</option>
|
||||
<option value="">新築</option>
|
||||
<option value="">新築</option>
|
||||
<option value="">新築</option>
|
||||
<option value="">新築</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input flex">
|
||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
||||
<span>年</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<MultiCheckbox column={'roof_material'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">建築研修</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">太陽光発電</option>
|
||||
<option value="">太陽光発電</option>
|
||||
<option value="">太陽光発電</option>
|
||||
<option value="">太陽光発電</option>
|
||||
<option value="">太陽光発電</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">屋根の斜面</div>
|
||||
<div className="data-input flex">
|
||||
<input type="text" className="input-frame" defaultValue={'4'} />
|
||||
<span>寸</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">住宅構造</div>
|
||||
<div className="radio-form-box mb10">
|
||||
<input type="radio" name="radio01" id="ra01" />
|
||||
<label htmlFor="ra01">木製</label>
|
||||
</div>
|
||||
<div className="radio-form-box mb10">
|
||||
<input type="radio" name="radio01" id="ra02" />
|
||||
<label htmlFor="ra02">その他 (直接入力)</label>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" disabled defaultValue={''} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">垂木材質</div>
|
||||
<div className="data-check-wrap">
|
||||
<div className="radio-form-box">
|
||||
<input type="radio" name="radio02" id="ra03" />
|
||||
<label htmlFor="ra03">木製</label>
|
||||
</div>
|
||||
<div className="radio-form-box">
|
||||
<input type="radio" name="radio02" id="ra04" />
|
||||
<label htmlFor="ra04">強制</label>
|
||||
</div>
|
||||
<div className="radio-form-box">
|
||||
<input type="radio" name="radio02" id="ra05" />
|
||||
<label htmlFor="ra05">その他 (直接入力)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" disabled defaultValue={''} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">垂木サイズ</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">幅35mm以上×高さ48mm以上</option>
|
||||
<option value="">幅35mm以上×高さ48mm以上</option>
|
||||
<option value="">幅35mm以上×高さ48mm以上</option>
|
||||
<option value="">幅35mm以上×高さ48mm以上</option>
|
||||
<option value="">幅35mm以上×高さ48mm以上</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">垂木サイズ</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">455mm以下</option>
|
||||
<option value="">455mm以下</option>
|
||||
<option value="">455mm以下</option>
|
||||
<option value="">455mm以下</option>
|
||||
<option value="">455mm以下</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">垂木の方向</div>
|
||||
<div className="data-check-wrap mb0">
|
||||
<div className="radio-form-box">
|
||||
<input type="radio" name="radio03" id="ra06" />
|
||||
<label htmlFor="ra06">垂直垂木</label>
|
||||
</div>
|
||||
<div className="radio-form-box">
|
||||
<input type="radio" name="radio03" id="ra07" />
|
||||
<label htmlFor="ra07">水平垂木</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">路地板の種類</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">構造用合板</option>
|
||||
<option value="">構造用合板</option>
|
||||
<option value="">構造用合板</option>
|
||||
<option value="">構造用合板</option>
|
||||
<option value="">構造用合板</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">
|
||||
路地板厚<span>※小幅板を選択した場合, 厚さ. 小幅板間の間隔寸法を記載</span>
|
||||
</div>
|
||||
<div className="data-input flex">
|
||||
<input type="text" className="input-frame" defaultValue={'150'} />
|
||||
<span>mm</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit ">垂木の方向</div>
|
||||
<div className="data-check-wrap mb0">
|
||||
<div className="radio-form-box">
|
||||
<input type="radio" name="radio04" id="ra08" />
|
||||
<label htmlFor="ra08">あり</label>
|
||||
</div>
|
||||
<div className="radio-form-box">
|
||||
<input type="radio" name="radio04" id="ra09" />
|
||||
<label htmlFor="ra09">なし</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">住宅構造</div>
|
||||
<div className="radio-form-box mb10">
|
||||
<input type="radio" name="radio05" id="ra10" />
|
||||
<label htmlFor="ra10">アスファルト屋根940(22kg以上)</label>
|
||||
</div>
|
||||
<div className="radio-form-box mb10">
|
||||
<input type="radio" name="radio05" id="ra11" />
|
||||
<label htmlFor="ra11">その他 (直接入力)</label>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" disabled defaultValue={''} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">断熱材の有無</div>
|
||||
<div className="radio-form-box mb10">
|
||||
<input type="radio" name="radio06" id="ra12" />
|
||||
<label htmlFor="ra12">あり</label>
|
||||
</div>
|
||||
<div className="data-input mb10">
|
||||
<input type="text" className="input-frame" defaultValue={''} />
|
||||
</div>
|
||||
<div className="radio-form-box">
|
||||
<input type="radio" name="radio06" id="ra13" />
|
||||
<label htmlFor="ra13">なし</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">路地板の種類</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">材/防水材/屋根基礎/垂木</option>
|
||||
<option value="">材/防水材/屋根基礎/垂木</option>
|
||||
<option value="">材/防水材/屋根基礎/垂木</option>
|
||||
<option value="">材/防水材/屋根基礎/垂木</option>
|
||||
<option value="">材/防水材/屋根基礎/垂木</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">屋根製品名 設置可否確認</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" defaultValue={'高島'} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">メモ</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<textarea
|
||||
className="textarea-form"
|
||||
name=""
|
||||
id=""
|
||||
defaultValue={'漏れの兆候があるため、正確な点検が必要です.'}
|
||||
placeholder="TextArea Filed"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn-flex-wrap">
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={handleSave}>
|
||||
一時保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame red icon" onClick={handleSave}>
|
||||
保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
|
||||
リスト<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -20,10 +20,14 @@ const defaultBasicInfoForm: SurveyBasicRequest = {
|
||||
submission_date: null,
|
||||
}
|
||||
|
||||
const REQUIRED_FIELDS: (keyof SurveyBasicRequest)[] = ['representative', 'store', 'construction_point']
|
||||
|
||||
export default function BasicForm() {
|
||||
const searchParams = useSearchParams()
|
||||
const id = searchParams.get('id')
|
||||
const router = useRouter()
|
||||
|
||||
const { setBasicInfoSelected } = useSurveySaleTabState()
|
||||
const { surveyDetail, createSurvey, isCreatingSurvey, updateSurvey, isUpdatingSurvey } = useServey(Number(id))
|
||||
|
||||
const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(defaultBasicInfoForm)
|
||||
@ -35,31 +39,53 @@ export default function BasicForm() {
|
||||
}
|
||||
}, [surveyDetail])
|
||||
|
||||
useEffect(() => {
|
||||
setBasicInfoSelected()
|
||||
}, [])
|
||||
|
||||
const focusInput = (input: keyof SurveyBasicRequest) => {
|
||||
const inputElement = document.getElementById(input)
|
||||
if (inputElement) {
|
||||
inputElement.focus()
|
||||
}
|
||||
}
|
||||
|
||||
const validateSurvey = (basicInfoData: SurveyBasicRequest) => {
|
||||
const emptyField = REQUIRED_FIELDS.find((field) => !basicInfoData[field])
|
||||
|
||||
if (emptyField) {
|
||||
focusInput(emptyField)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const handleChange = (key: keyof SurveyBasicRequest, value: string) => {
|
||||
setBasicInfoData({ ...basicInfoData, [key]: value })
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const handleSave = () => {
|
||||
const handleSave = async (isTemporary: boolean) => {
|
||||
if (id) {
|
||||
console.log('basicInfoData:: ', basicInfoData)
|
||||
updateSurvey(basicInfoData)
|
||||
} else {
|
||||
createSurvey(basicInfoData)
|
||||
router.push(`/survey-sale/${id}?tab=basic-info`)
|
||||
}
|
||||
if (isTemporary) {
|
||||
const saveId = await createSurvey(basicInfoData)
|
||||
alert('save success temporary id: ' + saveId)
|
||||
router.push(`/survey-sale/${saveId}?tab=basic-info`)
|
||||
} else {
|
||||
if (validateSurvey(basicInfoData)) {
|
||||
const saveId = await createSurvey(basicInfoData)
|
||||
alert('save success id: ' + saveId)
|
||||
router.push(`/survey-sale/${saveId}?tab=basic-info`)
|
||||
}
|
||||
}
|
||||
router.push('/survey-sale')
|
||||
}
|
||||
|
||||
if (isCreatingSurvey || isUpdatingSurvey) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
const { setBasicInfoSelected } = useSurveySaleTabState()
|
||||
|
||||
useEffect(() => {
|
||||
setBasicInfoSelected()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sale-frame">
|
||||
@ -72,6 +98,7 @@ export default function BasicForm() {
|
||||
id="representative"
|
||||
value={basicInfoData.representative}
|
||||
onChange={(e) => handleChange('representative', e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
@ -82,6 +109,7 @@ export default function BasicForm() {
|
||||
id="store"
|
||||
value={basicInfoData.store ?? ''}
|
||||
onChange={(e) => handleChange('store', e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="data-input-form-bx">
|
||||
@ -92,6 +120,7 @@ export default function BasicForm() {
|
||||
id="construction_point"
|
||||
value={basicInfoData.construction_point ?? ''}
|
||||
onChange={(e) => handleChange('construction_point', e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,7 +130,6 @@ export default function BasicForm() {
|
||||
<div className="data-form-wrap">
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">現地調査日</div>
|
||||
{/* TODO: 달력 라이브러리 추가 ?? */}
|
||||
<div className="date-input">
|
||||
<button className="date-btn">
|
||||
<i className="date-icon"></i>
|
||||
@ -110,7 +138,7 @@ export default function BasicForm() {
|
||||
type="date"
|
||||
className="date-frame"
|
||||
id="investigation_date"
|
||||
value={basicInfoData.investigation_date ?? ''}
|
||||
value={basicInfoData.investigation_date ?? new Date().toISOString().split('T')[0]}
|
||||
onChange={(e) => handleChange('investigation_date', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@ -182,12 +210,12 @@ export default function BasicForm() {
|
||||
</div>
|
||||
<div className="btn-flex-wrap">
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={handleSave}>
|
||||
<button className="btn-frame n-blue icon" onClick={() => handleSave(true)}>
|
||||
一時保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame red icon">
|
||||
<button className="btn-frame red icon" onClick={() => handleSave(false)}>
|
||||
保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
141
src/components/survey-sale/detail/form/MultiCheckEtc.tsx
Normal file
141
src/components/survey-sale/detail/form/MultiCheckEtc.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
import { SurveyDetailRequest } from '@/types/Survey'
|
||||
import { useState } from 'react'
|
||||
|
||||
const supplementary_facilities = [
|
||||
{ id: 1, name: 'エコキュート' }, //에코큐트
|
||||
{ id: 2, name: 'エネパーム' }, //에네팜
|
||||
{ id: 3, name: '蓄電池システム' }, //축전지시스템
|
||||
{ id: 4, name: '太陽光発電' }, //태양광발전
|
||||
]
|
||||
|
||||
const roof_material = [
|
||||
{ id: 1, name: 'スレート' }, //슬레이트
|
||||
{ id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글
|
||||
{ id: 3, name: '瓦' }, //기와
|
||||
{ id: 4, name: '金属屋根' }, //금속지붕
|
||||
]
|
||||
|
||||
export default function MultiCheckbox({
|
||||
column,
|
||||
setDetailInfoData,
|
||||
detailInfoData,
|
||||
}: {
|
||||
column: string
|
||||
setDetailInfoData: (data: any) => void
|
||||
detailInfoData: SurveyDetailRequest
|
||||
}) {
|
||||
const selectList = column === 'supplementary_facilities' ? supplementary_facilities : roof_material
|
||||
|
||||
const [isOtherChecked, setIsOtherChecked] = useState(false)
|
||||
const [otherValue, setOtherValue] = useState('')
|
||||
|
||||
const handleCheckbox = (dataIndex: number) => {
|
||||
const value = String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.filter((v) => v.length > 0)
|
||||
|
||||
let newValue: string[]
|
||||
if (value.includes(String(dataIndex))) {
|
||||
// 체크 해제
|
||||
newValue = value.filter((v) => v !== String(dataIndex))
|
||||
} else {
|
||||
// 체크
|
||||
if (column === 'roof_material') {
|
||||
// 기타가 체크되어 있는지 확인
|
||||
const isOtherSelected = isOtherChecked
|
||||
// 현재 선택된 항목 수 + 기타 선택 여부
|
||||
const totalSelected = value.length + (isOtherSelected ? 1 : 0)
|
||||
|
||||
if (totalSelected >= 2) {
|
||||
alert('屋根材は最大2個まで選択可能です。')
|
||||
return
|
||||
}
|
||||
}
|
||||
newValue = [...value, String(dataIndex)]
|
||||
}
|
||||
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[column]: newValue.join(', '),
|
||||
})
|
||||
}
|
||||
|
||||
const handleOtherCheckbox = () => {
|
||||
if (column === 'roof_material') {
|
||||
const value = String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.filter((v) => v.length > 0)
|
||||
|
||||
// 현재 선택된 항목 수
|
||||
const currentSelected = value.length
|
||||
|
||||
if (!isOtherChecked && currentSelected >= 2) {
|
||||
alert('Up to two roofing materials can be selected.')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setIsOtherChecked(!isOtherChecked)
|
||||
if (!isOtherChecked) {
|
||||
setOtherValue('')
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[`${column}_etc`]: null,
|
||||
})
|
||||
} else {
|
||||
setOtherValue('')
|
||||
}
|
||||
}
|
||||
|
||||
const handleOtherInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
setOtherValue(value)
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[`${column}_etc`]: value,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{column === 'supplementary_facilities' ? (
|
||||
<>
|
||||
<div className="data-input-form-tit">
|
||||
電気袋設備<span>※複数選択可能</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="data-input-form-tit">
|
||||
屋根材<span>※最大2個まで選択可能</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="data-check-wrap">
|
||||
{selectList.map((item) => (
|
||||
<div className="check-form-box" key={item.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`${column}_ch${item.id}`}
|
||||
checked={String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.includes(String(item.id))}
|
||||
onChange={() => handleCheckbox(item.id)}
|
||||
/>
|
||||
<label htmlFor={`${column}_ch${item.id}`}>{item.name}</label>
|
||||
</div>
|
||||
))}
|
||||
<div className="check-form-box">
|
||||
<input type="checkbox" id={`${column}_ch05`} checked={isOtherChecked} onChange={handleOtherCheckbox} />
|
||||
<label htmlFor={`${column}_ch05`}>その他 (直接入力)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" disabled={!isOtherChecked} value={otherValue} onChange={handleOtherInputChange} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
import { SurveyDetailRequest } from '@/types/Survey'
|
||||
import { useState } from 'react'
|
||||
|
||||
const supplementary_facilities = [
|
||||
{ id: 1, name: 'エコキュート' },
|
||||
{ id: 2, name: 'エネパーム' },
|
||||
{ id: 3, name: '蓄電池システム' },
|
||||
{ id: 4, name: '太陽光発電' },
|
||||
]
|
||||
|
||||
const roof_material = [
|
||||
{ id: 1, name: 'スレート' },
|
||||
{ id: 2, name: 'アスファルトシングル' },
|
||||
{ id: 3, name: '瓦' },
|
||||
{ id: 4, name: '金属屋根' },
|
||||
]
|
||||
|
||||
export default function MultiCheckbox({
|
||||
column,
|
||||
setDetailInfoData,
|
||||
detailInfoData,
|
||||
}: {
|
||||
column: string
|
||||
setDetailInfoData: (data: any) => void
|
||||
detailInfoData: SurveyDetailRequest
|
||||
}) {
|
||||
const selectList = column === 'supplementary_facilities' ? supplementary_facilities : roof_material
|
||||
|
||||
const [isOtherChecked, setIsOtherChecked] = useState(false)
|
||||
|
||||
const handleCheckbox = (dataName: string) => {
|
||||
const value = column === 'supplementary_facilities' ? detailInfoData.supplementary_facilities : detailInfoData.roof_material
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[column]: `${value}, ${dataName}`,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{column === 'supplementary_facilities' ? (
|
||||
<>
|
||||
<div className="data-input-form-tit">
|
||||
電気袋設備<span>※複数選択可能</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="data-input-form-tit">
|
||||
屋根材<span>※最大2個まで選択可能</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="data-check-wrap">
|
||||
{selectList.map((item) => (
|
||||
<div className="check-form-box" key={item.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`ch${item.id}`}
|
||||
checked={
|
||||
String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.includes(item.name)
|
||||
}
|
||||
onChange={() => handleCheckbox(item.name)}
|
||||
/>
|
||||
<label htmlFor={`ch${item.id}`}>{item.name}</label>
|
||||
</div>
|
||||
))}
|
||||
<div className="check-form-box">
|
||||
<input type="checkbox" id="ch05" checked={isOtherChecked} />
|
||||
<label htmlFor="ch05">その他 (直接入力)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input type="text" className="input-frame" disabled defaultValue={''} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
128
src/components/survey-sale/detail/form/RadioEtc.tsx
Normal file
128
src/components/survey-sale/detail/form/RadioEtc.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import { SurveyDetailRequest } from '@/types/Survey'
|
||||
|
||||
type RadioEtcKeys = 'house_structure' | 'rafter_material' | 'waterproof_material' | 'insulation_presence'
|
||||
|
||||
const translateJapanese: Record<RadioEtcKeys, string> = {
|
||||
house_structure: '住宅構造',
|
||||
rafter_material: '垂木材質',
|
||||
waterproof_material: '防水材の種類',
|
||||
insulation_presence: '断熱材の有無',
|
||||
}
|
||||
|
||||
const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]> = {
|
||||
house_structure: [
|
||||
{
|
||||
id: 1,
|
||||
label: '木製',
|
||||
},
|
||||
],
|
||||
rafter_material: [
|
||||
{
|
||||
id: 1,
|
||||
label: '木製',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '強制',
|
||||
},
|
||||
],
|
||||
waterproof_material: [
|
||||
{
|
||||
id: 1,
|
||||
label: 'アスファルト屋根940(22kg以上)',
|
||||
},
|
||||
],
|
||||
insulation_presence: [
|
||||
{
|
||||
id: 1,
|
||||
label: 'なし',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: 'あり',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default function RadioEtc({
|
||||
column,
|
||||
setDetailInfoData,
|
||||
detailInfoData,
|
||||
}: {
|
||||
column: RadioEtcKeys
|
||||
setDetailInfoData: (data: any) => void
|
||||
detailInfoData: SurveyDetailRequest
|
||||
}) {
|
||||
const [isEtcSelected, setIsEtcSelected] = useState(false)
|
||||
const [etcValue, setEtcValue] = useState('')
|
||||
|
||||
const handleRadioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
if (column === 'insulation_presence') {
|
||||
setIsEtcSelected(value === '2')
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[column]: Number(value),
|
||||
})
|
||||
} else if (value === 'etc') {
|
||||
setIsEtcSelected(true)
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[column]: null,
|
||||
})
|
||||
} else {
|
||||
setIsEtcSelected(false)
|
||||
setEtcValue('')
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[column]: Number(value),
|
||||
[`${column}_etc`]: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleEtcInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
setEtcValue(value)
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[`${column}_etc`]: value,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">{translateJapanese[column]}</div>
|
||||
{radioEtcData[column].map((item) => (
|
||||
<div className="radio-form-box mb10" key={item.id}>
|
||||
<input
|
||||
type="radio"
|
||||
name={column}
|
||||
id={`${column}_${item.id}`}
|
||||
value={item.id}
|
||||
onChange={handleRadioChange}
|
||||
checked={detailInfoData[column] === item.id}
|
||||
/>
|
||||
<label htmlFor={`${column}_${item.id}`}>{item.label}</label>
|
||||
</div>
|
||||
))}
|
||||
{column !== 'insulation_presence' && (
|
||||
<div className="radio-form-box mb10">
|
||||
<input type="radio" name={column} id={`${column}`} value="etc" onChange={handleRadioChange} />
|
||||
<label htmlFor={`${column}_etc`}>その他 (直接入力)</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="data-input">
|
||||
<input
|
||||
type="text"
|
||||
className="input-frame"
|
||||
disabled={column === 'insulation_presence' ? !isEtcSelected : !isEtcSelected}
|
||||
value={etcValue}
|
||||
onChange={handleEtcInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
321
src/components/survey-sale/detail/form/RoofInfoForm.tsx
Normal file
321
src/components/survey-sale/detail/form/RoofInfoForm.tsx
Normal file
@ -0,0 +1,321 @@
|
||||
'use client'
|
||||
|
||||
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
|
||||
|
||||
import { useServey } from '@/hooks/useSurvey'
|
||||
import { SurveyDetailRequest } from '@/types/Survey'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useEffect, useState } from 'react'
|
||||
import MultiCheckEtc from './MultiCheckEtc'
|
||||
import SelectBoxEtc from './SelectBoxEtc'
|
||||
import RadioEtc from './RadioEtc'
|
||||
|
||||
const defaultDetailInfoForm: SurveyDetailRequest = {
|
||||
contract_capacity: null,
|
||||
retail_company: null,
|
||||
supplementary_facilities: null,
|
||||
supplementary_facilities_etc: null,
|
||||
installation_system: null,
|
||||
installation_system_etc: null,
|
||||
construction_year: null,
|
||||
construction_year_etc: null,
|
||||
roof_material: null,
|
||||
roof_material_etc: null,
|
||||
roof_shape: null,
|
||||
roof_shape_etc: null,
|
||||
roof_slope: null,
|
||||
house_structure: 1,
|
||||
house_structure_etc: null,
|
||||
rafter_material: 1,
|
||||
rafter_material_etc: null,
|
||||
rafter_size: null,
|
||||
rafter_size_etc: null,
|
||||
rafter_pitch: null,
|
||||
rafter_pitch_etc: null,
|
||||
rafter_direction: 1,
|
||||
open_field_plate_kind: null,
|
||||
open_field_plate_kind_etc: null,
|
||||
open_field_plate_thickness: null,
|
||||
leak_trace: false,
|
||||
waterproof_material: null,
|
||||
waterproof_material_etc: null,
|
||||
insulation_presence: 1,
|
||||
insulation_presence_etc: null,
|
||||
structure_order: null,
|
||||
structure_order_etc: null,
|
||||
installation_availability: null,
|
||||
installation_availability_etc: null,
|
||||
memo: null,
|
||||
}
|
||||
|
||||
export default function RoofInfoForm() {
|
||||
const { setRoofInfoSelected } = useSurveySaleTabState()
|
||||
|
||||
useEffect(() => {
|
||||
setRoofInfoSelected()
|
||||
}, [])
|
||||
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const id = searchParams.get('id')
|
||||
|
||||
const { surveyDetail, createSurveyDetail, validateSurveyDetail } = useServey(Number(id))
|
||||
|
||||
const [detailInfoData, setDetailInfoData] = useState<SurveyDetailRequest>(defaultDetailInfoForm)
|
||||
|
||||
useEffect(() => {
|
||||
if (surveyDetail?.detail_info) {
|
||||
const { id, updated_at, created_at, ...rest } = surveyDetail.detail_info
|
||||
setDetailInfoData(rest)
|
||||
}
|
||||
}, [surveyDetail])
|
||||
|
||||
const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => {
|
||||
if (typeof value === 'string') {
|
||||
const numberValue = value === '' ? null : Number(value)
|
||||
setDetailInfoData({ ...detailInfoData, [key]: numberValue })
|
||||
} else {
|
||||
setDetailInfoData({ ...detailInfoData, [key]: value })
|
||||
}
|
||||
}
|
||||
|
||||
const handleTextInput = (key: keyof SurveyDetailRequest, value: string) => {
|
||||
setDetailInfoData({ ...detailInfoData, [key]: value || null })
|
||||
}
|
||||
|
||||
const handleBooleanInput = (key: keyof SurveyDetailRequest, value: boolean) => {
|
||||
setDetailInfoData({ ...detailInfoData, [key]: value })
|
||||
}
|
||||
|
||||
const handleUnitInput = (value: string) => {
|
||||
const numericValue = detailInfoData.contract_capacity?.replace(/[^0-9.]/g, '') || ''
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
contract_capacity: numericValue ? `${numericValue} ${value}` : value,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: 조사매물 저장 요구사항 정립 이후 수정 필요
|
||||
const handleSave = async () => {
|
||||
if (id) {
|
||||
const emptyField = validateSurveyDetail(detailInfoData)
|
||||
if (emptyField.trim() === '') {
|
||||
createSurveyDetail({ surveyId: Number(id), surveyDetail: detailInfoData })
|
||||
router.push(`/survey-sale`)
|
||||
} else {
|
||||
alert(emptyField + ' is required')
|
||||
focusOnInput(emptyField)
|
||||
}
|
||||
} else {
|
||||
alert('save essential information first')
|
||||
}
|
||||
}
|
||||
const focusOnInput = (field: string) => {
|
||||
const input = document.getElementById(field)
|
||||
if (input) {
|
||||
input.focus()
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="sale-frame">
|
||||
<div className="sale-roof-title">電気関係</div>
|
||||
<div className="data-form-wrap">
|
||||
<div className="data-input-form-bx">
|
||||
{/* 전기계약 용량 - contract_capacity */}
|
||||
<div className="data-input-form-tit">電気契約容量</div>
|
||||
<div className="data-input mb5">
|
||||
<input
|
||||
type="text"
|
||||
className="input-frame"
|
||||
value={detailInfoData.contract_capacity?.split(' ')[0] ?? ''}
|
||||
onChange={(e) => handleTextInput('contract_capacity', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<select
|
||||
className="select-form"
|
||||
name="contract_capacity_unit"
|
||||
id="contract_capacity_unit"
|
||||
onChange={(e) => handleUnitInput(e.target.value)}
|
||||
>
|
||||
<option value="kVA">kVA</option>
|
||||
<option value="A">A</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/* 전기 소매 회사 - retail_company */}
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">電気小売会社</div>
|
||||
<input
|
||||
type="text"
|
||||
className="input-frame"
|
||||
value={detailInfoData.retail_company ?? ''}
|
||||
onChange={(e) => handleTextInput('retail_company', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{/* 전기 부대 설비 - supplementary_facilities */}
|
||||
<div className="data-input-form-bx">
|
||||
<MultiCheckEtc column={'supplementary_facilities'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
</div>
|
||||
{/* 설치 희망 시스템 - installation_system */}
|
||||
<SelectBoxEtc column={'installation_system'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sale-frame">
|
||||
<div className="sale-roof-title">屋根関係</div>
|
||||
<div className="data-form-wrap">
|
||||
{/* 건축 연수 - construction_year */}
|
||||
<SelectBoxEtc column={'construction_year'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 지붕재 - roof_material */}
|
||||
<div className="data-input-form-bx">
|
||||
<MultiCheckEtc column={'roof_material'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
</div>
|
||||
{/* 지붕 모양 - roof_shape */}
|
||||
<SelectBoxEtc column={'roof_shape'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 지붕 경사도 - roof_slope */}
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">屋根の斜面</div>
|
||||
<div className="data-input flex">
|
||||
<input
|
||||
type="text"
|
||||
className="input-frame"
|
||||
value={detailInfoData.roof_slope ?? ''}
|
||||
onChange={(e) => handleTextInput('roof_slope', e.target.value)}
|
||||
/>
|
||||
<span>寸</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* 주택 구조 - house_structure */}
|
||||
<RadioEtc column={'house_structure'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 서까래 재질 - rafter_material */}
|
||||
<RadioEtc column={'rafter_material'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 서까래 크기 - rafter_size */}
|
||||
<SelectBoxEtc column={'rafter_size'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 서까래 피치 - rafter_pitch */}
|
||||
<SelectBoxEtc column={'rafter_pitch'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 서까래 방향 - rafter_direction */}
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit red-f">垂木の方向</div>
|
||||
<div className="data-check-wrap mb0" id="rafter_direction">
|
||||
<div className="radio-form-box">
|
||||
<input
|
||||
type="radio"
|
||||
name="rafter_direction"
|
||||
id="rafter_direction_1"
|
||||
value={1}
|
||||
onChange={(e) => handleNumberInput('rafter_direction', e.target.value)}
|
||||
checked={detailInfoData.rafter_direction === 1}
|
||||
/>
|
||||
<label htmlFor="rafter_direction_1">垂直垂木</label>
|
||||
</div>
|
||||
<div className="radio-form-box">
|
||||
<input
|
||||
type="radio"
|
||||
name="rafter_direction"
|
||||
id="rafter_direction_2"
|
||||
value={2}
|
||||
onChange={(e) => handleNumberInput('rafter_direction', e.target.value)}
|
||||
checked={detailInfoData.rafter_direction === 2}
|
||||
/>
|
||||
<label htmlFor="rafter_direction_2">水平垂木</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 노지판 종류 - open_field_plate_kind */}
|
||||
<SelectBoxEtc column={'open_field_plate_kind'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 노지판 두께 - open_field_plate_thickness */}
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">
|
||||
路地板厚<span>※小幅板を選択した場合, 厚さ. 小幅板間の間隔寸法を記載</span>
|
||||
</div>
|
||||
<div className="data-input flex">
|
||||
<input
|
||||
type="text"
|
||||
className="input-frame"
|
||||
value={detailInfoData.open_field_plate_thickness ?? ''}
|
||||
onChange={(e) => handleTextInput('open_field_plate_thickness', e.target.value)}
|
||||
/>
|
||||
<span>mm</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* 누수 흔적 - leak_trace */}
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit ">水漏れの痕跡</div>
|
||||
<div className="data-check-wrap mb0">
|
||||
<div className="radio-form-box">
|
||||
<input
|
||||
type="radio"
|
||||
name="leak_trace"
|
||||
id="leak_trace_1"
|
||||
checked={detailInfoData.leak_trace === true}
|
||||
onChange={(e) => handleBooleanInput('leak_trace', true)}
|
||||
/>
|
||||
<label htmlFor="leak_trace_1">あり</label>
|
||||
</div>
|
||||
<div className="radio-form-box">
|
||||
<input
|
||||
type="radio"
|
||||
name="leak_trace"
|
||||
id="leak_trace_2"
|
||||
checked={detailInfoData.leak_trace === false}
|
||||
onChange={(e) => handleBooleanInput('leak_trace', false)}
|
||||
/>
|
||||
<label htmlFor="leak_trace_2">なし</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 방수재 종류 - waterproof_material */}
|
||||
<RadioEtc column={'waterproof_material'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 단열재 유무 - insulation_presence */}
|
||||
<RadioEtc column={'insulation_presence'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 노지판 종류 - open_field_plate_kind */}
|
||||
<SelectBoxEtc column={'structure_order'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 설치 가능 여부 - installation_availability */}
|
||||
<SelectBoxEtc column={'installation_availability'} setDetailInfoData={setDetailInfoData} detailInfoData={detailInfoData} />
|
||||
{/* 메모 - memo */}
|
||||
<div className="data-input-form-bx">
|
||||
<div className="data-input-form-tit">メモ</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name="" id="">
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
<option value="">確認済み</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<textarea
|
||||
className="textarea-form"
|
||||
name="memo"
|
||||
id="memo"
|
||||
value={detailInfoData.memo ?? ''}
|
||||
onChange={(e) => handleTextInput('memo', e.target.value)}
|
||||
placeholder="例: 漏れの兆候があるため、正確な点検が必要です."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn-flex-wrap">
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={handleSave}>
|
||||
一時保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame red icon" onClick={handleSave}>
|
||||
保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
|
||||
リスト<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
234
src/components/survey-sale/detail/form/SelectBoxEtc.tsx
Normal file
234
src/components/survey-sale/detail/form/SelectBoxEtc.tsx
Normal file
@ -0,0 +1,234 @@
|
||||
import type { SurveyDetailRequest } from '@/types/Survey'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
type SelectBoxKeys =
|
||||
| 'installation_system'
|
||||
| 'construction_year'
|
||||
| 'roof_shape'
|
||||
| 'rafter_pitch'
|
||||
| 'rafter_size'
|
||||
| 'open_field_plate_kind'
|
||||
| 'structure_order'
|
||||
| 'installation_availability'
|
||||
|
||||
const font: Record<SelectBoxKeys, string> = {
|
||||
installation_system: 'data-input-form-tit red-f',
|
||||
construction_year: 'data-input-form-tit red-f',
|
||||
roof_shape: 'data-input-form-tit',
|
||||
rafter_pitch: 'data-input-form-tit red-f',
|
||||
rafter_size: 'data-input-form-tit red-f',
|
||||
open_field_plate_kind: 'data-input-form-tit',
|
||||
structure_order: 'data-input-form-tit red-f',
|
||||
installation_availability: 'data-input-form-tit',
|
||||
}
|
||||
|
||||
const translateJapanese: Record<SelectBoxKeys, string> = {
|
||||
installation_system: '設置希望システム',
|
||||
construction_year: '建築年数',
|
||||
roof_shape: '屋根の形状',
|
||||
rafter_pitch: '垂木傾斜',
|
||||
rafter_size: '垂木サイズ',
|
||||
open_field_plate_kind: '路地板の種類',
|
||||
structure_order: '屋根構造の順序',
|
||||
installation_availability: '屋根製品名 設置可否確認',
|
||||
}
|
||||
|
||||
const selectBoxOptions: Record<SelectBoxKeys, { id: number; name: string }[]> = {
|
||||
installation_system: [
|
||||
{
|
||||
id: 1,
|
||||
name: '太陽光発電', //태양광발전
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'ハイブリッド蓄電システム', //하이브리드축전지시스템
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '蓄電池システム', //축전지시스템
|
||||
},
|
||||
],
|
||||
construction_year: [
|
||||
{
|
||||
id: 1,
|
||||
name: '新築', //신축
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '既築', //기존
|
||||
},
|
||||
],
|
||||
roof_shape: [
|
||||
{
|
||||
id: 1,
|
||||
name: '切妻', //박공지붕
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '寄棟', //기동
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '片流れ', //한쪽흐름
|
||||
},
|
||||
],
|
||||
rafter_size: [
|
||||
{
|
||||
id: 1,
|
||||
name: '幅35mm以上×高さ48mm以上',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '幅36mm以上×高さ46mm以上',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '幅37mm以上×高さ43mm以上',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '幅38mm以上×高さ40mm以上',
|
||||
},
|
||||
],
|
||||
rafter_pitch: [
|
||||
{
|
||||
id: 1,
|
||||
name: '(455mm以下',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '500mm以下',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '606mm以下',
|
||||
},
|
||||
],
|
||||
open_field_plate_kind: [
|
||||
{
|
||||
id: 1,
|
||||
name: '構造用合板', //구조용합판
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'OSB', //OSB
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'パーティクルボード', //파티클보드
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '小幅板', //소판
|
||||
},
|
||||
],
|
||||
structure_order: [
|
||||
{
|
||||
id: 1,
|
||||
name: '屋根材', //지붕재
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '防水材', //방수재
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '屋根の基礎', //지붕의기초
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '垂木', //서까래
|
||||
},
|
||||
],
|
||||
installation_availability: [
|
||||
{
|
||||
id: 1,
|
||||
name: '確認済み', //확인완료
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '未確認', //미확인
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default function SelectBoxForm({
|
||||
column,
|
||||
setDetailInfoData,
|
||||
detailInfoData,
|
||||
}: {
|
||||
column: SelectBoxKeys
|
||||
setDetailInfoData: (data: any) => void
|
||||
detailInfoData: SurveyDetailRequest
|
||||
}) {
|
||||
const [isEtcSelected, setIsEtcSelected] = useState(false)
|
||||
const [etcValue, setEtcValue] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setEtcValue(detailInfoData[`${column}_etc`] ?? '')
|
||||
}, [detailInfoData[`${column}_etc`]])
|
||||
|
||||
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.target.value
|
||||
if (column === 'installation_availability' || column === 'construction_year') {
|
||||
setIsEtcSelected(value === '2') // 既築(2) 또는 未確認(2) 선택 시 input 활성화
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[column]: Number(value),
|
||||
})
|
||||
} else if (value === 'etc') {
|
||||
setIsEtcSelected(true)
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[column]: null,
|
||||
})
|
||||
} else {
|
||||
setIsEtcSelected(false)
|
||||
setEtcValue('')
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[`${column}_etc`]: null,
|
||||
[column]: Number(value),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleEtcInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
setEtcValue(value)
|
||||
setDetailInfoData({
|
||||
...detailInfoData,
|
||||
[`${column}_etc`]: value,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="data-input-form-bx">
|
||||
<div className={font[column]}>{translateJapanese[column]}</div>
|
||||
<div className="data-input mb5">
|
||||
<select className="select-form" name={column} id={column} onChange={handleSelectChange} value={detailInfoData[column] ?? ''}>
|
||||
<option value="" hidden>
|
||||
선택해주세요
|
||||
</option>
|
||||
{selectBoxOptions[column].map((option) => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
{column !== 'installation_availability' && column !== 'construction_year' && <option value="etc">その他 (直接入力)</option>}
|
||||
</select>
|
||||
</div>
|
||||
<div className="data-input">
|
||||
<input
|
||||
type="text"
|
||||
className="input-frame"
|
||||
value={etcValue ?? ''}
|
||||
onChange={handleEtcInputChange}
|
||||
disabled={column === 'installation_availability' || column === 'construction_year' ? !Boolean(detailInfoData[column]) : !isEtcSelected}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -2,39 +2,72 @@
|
||||
|
||||
import LoadMoreButton from '@/components/LoadMoreButton'
|
||||
import { useServey } from '@/hooks/useSurvey'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import SearchForm from './SearchForm'
|
||||
|
||||
export default function ListTable() {
|
||||
const router = useRouter()
|
||||
const { surveyList, isLoadingSurveyList } = useServey()
|
||||
|
||||
const [hasMore, setHasMore] = useState(surveyList.length > 5)
|
||||
const [visibleItems, setVisibleItems] = useState(5)
|
||||
const [search, setSearch] = useState('')
|
||||
const [isMyPostsOnly, setIsMyPostsOnly] = useState(false)
|
||||
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])
|
||||
|
||||
const handleLoadMore = () => {
|
||||
const newVisibleItems = Math.min(visibleItems + 5, surveyList.length)
|
||||
const newVisibleItems = Math.min(visibleItems + 5, filteredSurveyList.length)
|
||||
setVisibleItems(newVisibleItems)
|
||||
setHasMore(newVisibleItems < surveyList.length)
|
||||
setHasMore(newVisibleItems < filteredSurveyList.length)
|
||||
}
|
||||
|
||||
const handleScrollToTop = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
const handleDetail = (id: number) => {
|
||||
const handleDetailClick = (id: number) => {
|
||||
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">
|
||||
{surveyList.slice(0, visibleItems).map((survey) => (
|
||||
<li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetail(survey.id)}>
|
||||
{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>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function SearchForm() {
|
||||
export default function SearchForm({ handleSearch, handleMyPosts }: { handleSearch: (e: React.ChangeEvent<HTMLInputElement>) => void, handleMyPosts: () => void }) {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div className="sale-frame">
|
||||
@ -22,13 +22,13 @@ export default function SearchForm() {
|
||||
</div>
|
||||
<div className="sale-form-bx">
|
||||
<div className="search-input">
|
||||
<input type="text" className="search-frame" placeholder="タイトルを入力してください. (2文字以上)" />
|
||||
<input type="text" className="search-frame" placeholder="タイトルを入力してください. (2文字以上)" onChange={handleSearch} />
|
||||
<button className="search-icon"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sale-form-bx">
|
||||
<div className="check-form-box">
|
||||
<input type="checkbox" id="ch01" />
|
||||
<input type="checkbox" id="ch01" onClick={handleMyPosts} />
|
||||
<label htmlFor="ch01">私が書いた物件</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
'use client'
|
||||
import { useState } from "react"
|
||||
import { SurveyDetailRequest } from "@/types/Survey"
|
||||
|
||||
interface EtcCheckboxProps {
|
||||
formName: keyof SurveyDetailRequest
|
||||
label: string
|
||||
detailInfoForm: SurveyDetailRequest
|
||||
setDetailInfoForm: (form: SurveyDetailRequest) => void
|
||||
}
|
||||
|
||||
export default function EtcCheckbox({ formName, label, detailInfoForm, setDetailInfoForm }: EtcCheckboxProps) {
|
||||
const [showEtcInput, setShowEtcInput] = useState(false)
|
||||
const etcFieldName = `${formName}_etc` as keyof SurveyDetailRequest
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<label htmlFor={formName} className="block font-medium">{label}</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={formName}
|
||||
checked={detailInfoForm[formName] === 1}
|
||||
onChange={(e) => setDetailInfoForm({
|
||||
...detailInfoForm,
|
||||
[formName]: e.target.checked ? 1 : 0
|
||||
})}
|
||||
/>
|
||||
<label htmlFor={formName}>있음</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`${formName}_etc_check`}
|
||||
checked={showEtcInput}
|
||||
onChange={(e) => {
|
||||
setShowEtcInput(e.target.checked)
|
||||
if (!e.target.checked) {
|
||||
setDetailInfoForm({
|
||||
...detailInfoForm,
|
||||
[etcFieldName]: ''
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={`${formName}_etc_check`}>기타</label>
|
||||
|
||||
{showEtcInput && (
|
||||
<input
|
||||
type="text"
|
||||
id={`${formName}_etc`}
|
||||
value={(detailInfoForm[etcFieldName] as string | null) ?? ''}
|
||||
placeholder="기타 사항을 입력하세요"
|
||||
onChange={(e) => setDetailInfoForm({
|
||||
...detailInfoForm,
|
||||
[etcFieldName]: e.target.value
|
||||
})}
|
||||
className="border rounded px-2 py-1 ml-2"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
// 'use client'
|
||||
|
||||
// import { useServey } from '@/hooks/useSurvey'
|
||||
// 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 <div>Deleting...</div>
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div>
|
||||
// <h1>SurveyDetail</h1>
|
||||
// <p>{id}</p>
|
||||
// <p>{surveyDetail?.representative}</p>
|
||||
// <div className="flex gap-3">
|
||||
// <button className="bg-blue-500 text-white px-2 py-1 rounded-md cursor-pointer" onClick={() => router.push('/survey-sales/write?id=' + id)}>
|
||||
// edit
|
||||
// </button>
|
||||
// <button className="bg-blue-500 text-white px-2 py-1 rounded-md cursor-pointer" onClick={handleSubmit}>
|
||||
// submit
|
||||
// </button>
|
||||
// <button className="bg-red-500 text-white px-2 py-1 rounded-md cursor-pointer" onClick={handleDelete}>
|
||||
// delete
|
||||
// </button>
|
||||
// <button className="bg-gray-500 text-white px-2 py-1 rounded-md cursor-pointer" onClick={() => router.back()}>
|
||||
// back
|
||||
// </button>
|
||||
// </div>
|
||||
// <input type="text" className="input-frame" disabled defaultValue={surveyDetail?.store ?? ''} />
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
@ -1,22 +0,0 @@
|
||||
import { Search } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function SurveyFilter({ handleSearch, handleMyPosts }: { handleSearch: (e: React.ChangeEvent<HTMLInputElement>) => void, handleMyPosts: () => void }) {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => router.push('/survey-sales/write')}>write survey {'>'}</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="text" placeholder="Search" onChange={handleSearch} />
|
||||
<button>
|
||||
<Search />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={handleMyPosts} className="cursor-pointer">
|
||||
my posts
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
'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<HTMLInputElement>) => {
|
||||
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 <div>Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-4">
|
||||
<SurveyFilter handleSearch={handleSearch} handleMyPosts={() => setIsMyPostsOnly(!isMyPostsOnly)} />
|
||||
|
||||
<div className="grid gap-4 mt-6">
|
||||
{surveyData().slice(0, visibleItems).map((survey) => (
|
||||
<div
|
||||
key={survey.id}
|
||||
onClick={() => handleDetail(survey.id)}
|
||||
className="bg-white rounded-lg shadow p-4 hover:shadow-md transition-shadow cursor-pointer border border-gray-200"
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-lg font-semibold text-gray-900">{survey.id}</h2>
|
||||
<div className="space-y-1 text-sm text-gray-600">
|
||||
<p>담당자: {survey.representative || '-'}</p>
|
||||
<p>판매점: {survey.store || '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-sm ${
|
||||
survey.submission_status ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
|
||||
}`}
|
||||
>
|
||||
{survey.submission_status ? '제출' : '미제출'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<LoadMoreButton hasMore={hasMore} onLoadMore={handleLoadMore} onScrollToTop={handleScrollToTop} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { SurveyBasicRequest } from '@/types/Survey'
|
||||
|
||||
export default function BasicWriteForm({
|
||||
basicInfoData,
|
||||
setBasicInfoData,
|
||||
}: {
|
||||
basicInfoData: SurveyBasicRequest
|
||||
setBasicInfoData: (basicInfoData: SurveyBasicRequest) => void
|
||||
}) {
|
||||
const handleChange = (key: keyof SurveyBasicRequest, value: string) => {
|
||||
setBasicInfoData({ ...basicInfoData, [key]: value.toString() })
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<label htmlFor="representative">담당자명</label>
|
||||
<input
|
||||
type="text"
|
||||
id="representative"
|
||||
value={basicInfoData.representative}
|
||||
onChange={(e) => handleChange('representative', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="store">판매점</label>
|
||||
<input type="text" id="store" value={basicInfoData.store ?? ''} onChange={(e) => handleChange('store', e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="construction_point">시공점</label>
|
||||
<input
|
||||
type="text"
|
||||
id="construction_point"
|
||||
value={basicInfoData.construction_point ?? ''}
|
||||
onChange={(e) => handleChange('construction_point', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="investigation_date">현재 조사일</label>
|
||||
<input
|
||||
type="date"
|
||||
id="investigation_date"
|
||||
value={basicInfoData.investigation_date ?? ''}
|
||||
onChange={(e) => handleChange('investigation_date', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="building_name">건물명</label>
|
||||
<input
|
||||
type="text"
|
||||
id="building_name"
|
||||
value={basicInfoData.building_name ?? ''}
|
||||
onChange={(e) => handleChange('building_name', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="customer_name">고객명</label>
|
||||
<input
|
||||
type="text"
|
||||
id="customer_name"
|
||||
value={basicInfoData.customer_name ?? ''}
|
||||
onChange={(e) => handleChange('customer_name', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="post_code">우편번호</label>
|
||||
<input type="text" id="post_code" value={basicInfoData.post_code ?? ''} onChange={(e) => handleChange('post_code', e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="address">주소</label>
|
||||
<input type="text" id="address" value={basicInfoData.address ?? ''} onChange={(e) => handleChange('address', e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="address_detail">상세주소</label>
|
||||
<input
|
||||
type="text"
|
||||
id="address_detail"
|
||||
value={basicInfoData.address_detail ?? ''}
|
||||
onChange={(e) => handleChange('address_detail', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import EtcCheckbox from '../EtcCheckbox'
|
||||
import { SurveyDetailRequest } from '@/types/Survey'
|
||||
|
||||
interface DetailWriteFormProps {
|
||||
detailInfoForm: SurveyDetailRequest
|
||||
setDetailInfoForm: (form: SurveyDetailRequest) => void
|
||||
}
|
||||
|
||||
export default function DetailWriteForm({ detailInfoForm, setDetailInfoForm }: DetailWriteFormProps) {
|
||||
const handleNumberInput = (field: keyof SurveyDetailRequest, value: string) => {
|
||||
const numberValue = value === '' ? null : Number(value)
|
||||
setDetailInfoForm({ ...detailInfoForm, [field]: numberValue })
|
||||
}
|
||||
|
||||
const handleTextInput = (field: keyof SurveyDetailRequest, value: string) => {
|
||||
setDetailInfoForm({ ...detailInfoForm, [field]: value || null })
|
||||
}
|
||||
|
||||
const handleBooleanInput = (field: keyof SurveyDetailRequest, checked: boolean) => {
|
||||
setDetailInfoForm({ ...detailInfoForm, [field]: checked })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="contract_capacity">계약용량</label>
|
||||
<input
|
||||
type="text"
|
||||
id="contract_capacity"
|
||||
value={detailInfoForm.contract_capacity ?? ''}
|
||||
onChange={(e) => handleTextInput('contract_capacity', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="retail_company">소매회사</label>
|
||||
<input
|
||||
type="text"
|
||||
id="retail_company"
|
||||
value={detailInfoForm.retail_company ?? ''}
|
||||
onChange={(e) => handleTextInput('retail_company', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<EtcCheckbox
|
||||
formName="supplementary_facilities"
|
||||
label="부대설비"
|
||||
detailInfoForm={detailInfoForm}
|
||||
setDetailInfoForm={setDetailInfoForm}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="installation_system" label="설치시스템" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="construction_year" label="건축년도" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="roof_material" label="지붕재료" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="roof_shape" label="지붕형태" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="roof_slope">지붕 경사도</label>
|
||||
<input
|
||||
type="text"
|
||||
id="roof_slope"
|
||||
value={detailInfoForm.roof_slope ?? ''}
|
||||
onChange={(e) => handleTextInput('roof_slope', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="house_structure" label="주택 구조" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="rafter_material" label="주탑재료" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="rafter_size" label="주탑 크기" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="rafter_pitch" label="주탑 경사도" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="rafter_direction">오픈필드 판형</label>
|
||||
<input
|
||||
type="text"
|
||||
id="rafter_direction"
|
||||
value={detailInfoForm.rafter_direction ?? ''}
|
||||
onChange={(e) => handleTextInput('rafter_direction', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="open_field_plate_kind" label="노지판 형태" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="open_field_plate_thickness">노지판 두께</label>
|
||||
<input
|
||||
type="text"
|
||||
id="open_field_plate_thickness"
|
||||
value={detailInfoForm.open_field_plate_thickness ?? ''}
|
||||
onChange={(e) => handleTextInput('open_field_plate_thickness', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="leak_trace">누수 흔적</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="leak_trace"
|
||||
checked={detailInfoForm.leak_trace ?? false}
|
||||
onChange={(e) => handleBooleanInput('leak_trace', e.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="waterproof_material" label="방수재료" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="insulation_presence" label="보증금 존재" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="structure_order" label="구조체계" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EtcCheckbox formName="installation_availability" label="설치가능" detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="memo">메모</label>
|
||||
<textarea
|
||||
id="memo"
|
||||
value={detailInfoForm.memo ?? ''}
|
||||
onChange={(e) => handleTextInput('memo', e.target.value)}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,168 +0,0 @@
|
||||
// 'use client'
|
||||
|
||||
// import { useState, useEffect } from 'react'
|
||||
// import BasicWriteForm from './BasicWriteForm'
|
||||
// import DetailWriteForm from './DetailWriteForm'
|
||||
// import { SurveySalesBasicInfo, SurveySalesDetailInfo } from '@/api/surveySales'
|
||||
// import { useRouter, useSearchParams } from 'next/navigation'
|
||||
// import { useServey } from '@/hooks/useSurvey'
|
||||
// import { SurveyBasicRequest, SurveyDetailRequest } from '@/types/Survey'
|
||||
|
||||
// type TabType = 'basic' | 'detail'
|
||||
|
||||
// const defaultDetailInfoForm: SurveyDetailRequest = {
|
||||
// basic_info_id: 0,
|
||||
// contract_capacity: null,
|
||||
// retail_company: null,
|
||||
// supplementary_facilities: null,
|
||||
// supplementary_facilities_etc: null,
|
||||
// installation_system: null,
|
||||
// installation_system_etc: null,
|
||||
// construction_year: null,
|
||||
// construction_year_etc: null,
|
||||
// roof_material: null,
|
||||
// roof_material_etc: null,
|
||||
// roof_shape: null,
|
||||
// roof_shape_etc: null,
|
||||
// roof_slope: null,
|
||||
// house_structure: null,
|
||||
// house_structure_etc: null,
|
||||
// rafter_material: null,
|
||||
// rafter_material_etc: null,
|
||||
// rafter_size: null,
|
||||
// rafter_size_etc: null,
|
||||
// rafter_pitch: null,
|
||||
// rafter_pitch_etc: null,
|
||||
// rafter_direction: null,
|
||||
// open_field_plate_kind: null,
|
||||
// open_field_plate_kind_etc: null,
|
||||
// open_field_plate_thickness: null,
|
||||
// leak_trace: false,
|
||||
// waterproof_material: null,
|
||||
// waterproof_material_etc: null,
|
||||
// structure_order: null,
|
||||
// structure_order_etc: null,
|
||||
// insulation_presence: null,
|
||||
// insulation_presence_etc: null,
|
||||
// installation_availability: null,
|
||||
// installation_availability_etc: null,
|
||||
// memo: null,
|
||||
// }
|
||||
|
||||
// const defaultBasicInfoForm: SurveyBasicRequest = {
|
||||
// representative: '',
|
||||
// store: null,
|
||||
// construction_point: null,
|
||||
// investigation_date: null,
|
||||
// building_name: null,
|
||||
// customer_name: null,
|
||||
// post_code: null,
|
||||
// address: null,
|
||||
// address_detail: null,
|
||||
// submission_status: false,
|
||||
// submission_date: null,
|
||||
// }
|
||||
|
||||
// export default function MainSurveyForm() {
|
||||
// const searchParams = useSearchParams()
|
||||
|
||||
// const id = searchParams.get('id')
|
||||
|
||||
// const [activeTab, setActiveTab] = useState<TabType>('basic')
|
||||
|
||||
// const handleTabClick = (tab: TabType) => {
|
||||
// setActiveTab(tab)
|
||||
// }
|
||||
|
||||
// const router = useRouter()
|
||||
// const { createSurvey, isCreatingSurvey, createSurveyDetail, surveyDetail, updateSurvey } = useServey(Number(id))
|
||||
|
||||
// const [detailInfoForm, setDetailInfoForm] = useState<SurveyDetailRequest>(defaultDetailInfoForm)
|
||||
// const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(defaultBasicInfoForm)
|
||||
|
||||
// useEffect(() => {
|
||||
// if (surveyDetail) {
|
||||
// setBasicInfoData({
|
||||
// ...defaultBasicInfoForm,
|
||||
// ...(({ id, ...rest }) => rest)(surveyDetail),
|
||||
// })
|
||||
// setDetailInfoForm({
|
||||
// ...defaultDetailInfoForm,
|
||||
// ...(surveyDetail.detail_info ? (({ id, basic_info_id, updated_at, ...rest }) => rest)(surveyDetail.detail_info as any) : {}),
|
||||
// })
|
||||
// }
|
||||
// }, [surveyDetail])
|
||||
|
||||
// const handleSave = async (isSubmit: boolean = false) => {
|
||||
// if (id) {
|
||||
// updateSurvey({
|
||||
// ...basicInfoData,
|
||||
// submission_status: isSubmit,
|
||||
// submission_date: isSubmit ? new Date().toISOString() : null,
|
||||
// })
|
||||
// router.push('/survey-sales')
|
||||
// return
|
||||
// }
|
||||
|
||||
// const surveyId = await createSurvey(basicInfoData)
|
||||
// if (surveyId && surveyId !== 0) {
|
||||
// createSurveyDetail({
|
||||
// surveyId,
|
||||
// surveyDetail: detailInfoForm,
|
||||
// })
|
||||
// router.push('/survey-sales')
|
||||
// return
|
||||
// }
|
||||
// throw new Error('‼️Survey creation failed')
|
||||
// }
|
||||
// if (isCreatingSurvey) {
|
||||
// return <div>Loading...</div>
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div>
|
||||
// {/* TAB BUTTONS */}
|
||||
// <div>
|
||||
// <button
|
||||
// onClick={() => handleTabClick('basic')}
|
||||
// className={`flex-1 px-4 py-2 text-center focus:outline-none focus:ring-2 focus:ring-blue-500
|
||||
// ${activeTab === 'basic' ? 'border-b-2 border-blue-500 font-semibold text-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||
// aria-selected={activeTab === 'basic'}
|
||||
// role="tab"
|
||||
// >
|
||||
// Basic Info
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={() => handleTabClick('detail')}
|
||||
// className={`flex-1 px-4 py-2 text-center focus:outline-none focus:ring-2 focus:ring-blue-500
|
||||
// ${activeTab === 'detail' ? 'border-b-2 border-blue-500 font-semibold text-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
|
||||
// aria-selected={activeTab === 'detail'}
|
||||
// role="tab"
|
||||
// >
|
||||
// Detail Info
|
||||
// </button>
|
||||
// </div>
|
||||
|
||||
// {/* Tab Content */}
|
||||
// <div className="mt-6">
|
||||
// {activeTab === 'basic' && (
|
||||
// <div className="rounded-lg border p-4">
|
||||
// <h2 className="text-lg font-semibold">Basic Information</h2>
|
||||
// <BasicWriteForm basicInfoData={basicInfoData} setBasicInfoData={setBasicInfoData} />
|
||||
// </div>
|
||||
// )}
|
||||
// {activeTab === 'detail' && (
|
||||
// <div className="rounded-lg border p-4">
|
||||
// <h2 className="text-lg font-semibold">Detail Information</h2>
|
||||
// <DetailWriteForm detailInfoForm={detailInfoForm} setDetailInfoForm={setDetailInfoForm} />
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// <div className="flex justify-start gap-4">
|
||||
// <button onClick={() => handleSave(false)}>save</button>
|
||||
// <button onClick={() => handleSave(true)}>submit</button>
|
||||
// <button onClick={() => router.push('/survey-sales')}>cancel</button>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
@ -1,16 +1,27 @@
|
||||
'use client'
|
||||
|
||||
import { useHeaderStore } from '@/store/header'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useSideNavState } from '@/store/sideNavState'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function Main() {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { setBackBtn } = useHeaderStore()
|
||||
const { reset } = useSideNavState()
|
||||
|
||||
/**
|
||||
* 헤더 뒤로가기 버튼 컨트롤
|
||||
* 사이드바 초기화 컨트롤
|
||||
*/
|
||||
useEffect(() => {
|
||||
setBackBtn(false)
|
||||
}, [])
|
||||
if (pathname === '/') {
|
||||
setBackBtn(false)
|
||||
}
|
||||
//사이드바 초기화
|
||||
reset()
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
|
||||
import { useSideNavState } from '@/store/sideNavState'
|
||||
import { useHeaderStore } from '@/store/header'
|
||||
|
||||
import type { HeaderProps } from '@/types/Header'
|
||||
|
||||
import 'swiper/css'
|
||||
import { useSideNavState } from '@/store/sideNavState'
|
||||
import { axiosInstance } from '@/libs/axios'
|
||||
|
||||
// type HeaderProps = {
|
||||
// name: string //header 이름
|
||||
@ -20,27 +21,26 @@ import { useSideNavState } from '@/store/sideNavState'
|
||||
export default function Header({ name }: HeaderProps) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { sideNavIsOpen, setSideNavIsOpen, reset } = useSideNavState()
|
||||
const [isShowBackBtn, setIsShowBackBtn] = useState<boolean>(false)
|
||||
const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState()
|
||||
const { backBtn } = useHeaderStore()
|
||||
|
||||
if (pathname === '/login') {
|
||||
return null
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname !== '/') {
|
||||
setIsShowBackBtn(true)
|
||||
const handleLogout = async () => {
|
||||
const { data } = await axiosInstance(null).get('/api/auth/logout')
|
||||
if (data.code === 200) {
|
||||
router.push('/login')
|
||||
}
|
||||
//사이드바 초기화
|
||||
reset()
|
||||
}, [pathname])
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="header-warp">
|
||||
<header>
|
||||
<div className="header-inner">
|
||||
{isShowBackBtn && (
|
||||
{backBtn && (
|
||||
<div className="back-button-wrap">
|
||||
<button className="back-button" onClick={() => router.back()}></button>
|
||||
</div>
|
||||
@ -112,7 +112,9 @@ export default function Header({ name }: HeaderProps) {
|
||||
<div className="side-nav-footer">
|
||||
<ul className="side-footer-list">
|
||||
<li className="side-footer-item">
|
||||
<button className="bold">LOGOUT</button>
|
||||
<button className="bold" onClick={handleLogout}>
|
||||
LOGOUT
|
||||
</button>
|
||||
</li>
|
||||
<li className="side-footer-item">
|
||||
<button>Jynoadmin</button>
|
||||
|
||||
@ -15,6 +15,7 @@ export function useServey(id?: number): {
|
||||
updateSurvey: (survey: SurveyBasicRequest) => void
|
||||
deleteSurvey: () => Promise<boolean>
|
||||
submitSurvey: () => void
|
||||
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
|
||||
} {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@ -97,6 +98,38 @@ export function useServey(id?: number): {
|
||||
},
|
||||
})
|
||||
|
||||
const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => {
|
||||
const requiredFields = [
|
||||
'installation_system',
|
||||
'construction_year',
|
||||
'rafter_size',
|
||||
'rafter_pitch',
|
||||
'rafter_direction',
|
||||
'waterproof_material',
|
||||
'insulation_presence',
|
||||
'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;
|
||||
});
|
||||
|
||||
return emptyField || '';
|
||||
};
|
||||
|
||||
return {
|
||||
surveyList: surveyList || [],
|
||||
surveyDetail: surveyDetail || null,
|
||||
@ -110,5 +143,6 @@ export function useServey(id?: number): {
|
||||
deleteSurvey,
|
||||
createSurveyDetail,
|
||||
submitSurvey,
|
||||
validateSurveyDetail,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'
|
||||
export const axiosInstance = (url: string | null | undefined) => {
|
||||
const baseURL = url || process.env.NEXT_PUBLIC_API_URL
|
||||
|
||||
export const axiosInstance = axios.create({
|
||||
baseURL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
return axios.create({
|
||||
baseURL,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Request interceptor
|
||||
axiosInstance.interceptors.request.use(
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
// 여기에 토큰 추가 등의 공통 로직을 넣을 수 있습니다
|
||||
return config
|
||||
@ -21,10 +23,44 @@ axiosInstance.interceptors.request.use(
|
||||
)
|
||||
|
||||
// Response interceptor
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
axios.interceptors.response.use(
|
||||
(response) => transferResponse(response),
|
||||
(error) => {
|
||||
// 에러 처리 로직
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
// response데이터가 array, object에 따라 분기하여 키 변환
|
||||
const transferResponse = (response: any) => {
|
||||
if (!response.data) return response.data
|
||||
|
||||
// 배열인 경우 각 객체의 키를 변환
|
||||
if (Array.isArray(response.data)) {
|
||||
return response.data.map((item: any) => transformObjectKeys(item))
|
||||
}
|
||||
|
||||
// 단일 객체인 경우
|
||||
return transformObjectKeys(response.data)
|
||||
}
|
||||
|
||||
// camel case object 반환
|
||||
const transformObjectKeys = (obj: any): any => {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(transformObjectKeys)
|
||||
}
|
||||
|
||||
if (obj !== null && typeof obj === 'object') {
|
||||
return Object.keys(obj).reduce((acc: any, key: string) => {
|
||||
const camelKey = snakeToCamel(key)
|
||||
acc[camelKey] = transformObjectKeys(obj[key])
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
// snake case to camel case
|
||||
const snakeToCamel = (str: string): string => {
|
||||
return str.replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''))
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import type { SessionData } from '@/types/Auth'
|
||||
import { getIronSession, SessionOptions } from 'iron-session'
|
||||
import { cookies } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export const sessionOptions: SessionOptions = {
|
||||
cookieName: 'onsitesurvey_session',
|
||||
@ -14,15 +16,34 @@ export const sessionOptions: SessionOptions = {
|
||||
},
|
||||
}
|
||||
|
||||
export interface SessionData {
|
||||
username: string | null
|
||||
email: string | null
|
||||
isLoggedIn: boolean
|
||||
}
|
||||
|
||||
export const defaultSession: SessionData = {
|
||||
username: '',
|
||||
email: '',
|
||||
langCd: null,
|
||||
currPage: 0,
|
||||
rowCount: 0,
|
||||
startRow: 0,
|
||||
endRow: 0,
|
||||
compCd: null,
|
||||
agencyStoreId: null,
|
||||
storeId: null,
|
||||
userId: null,
|
||||
category: null,
|
||||
userNm: null,
|
||||
userNmKana: null,
|
||||
telNo: null,
|
||||
fax: null,
|
||||
email: null,
|
||||
lastEditUser: null,
|
||||
storeGubun: null,
|
||||
pwCurr: null,
|
||||
pwdInitYn: null,
|
||||
apprStatCd: null,
|
||||
loginFailCnt: null,
|
||||
loginFailMinYn: null,
|
||||
priceViewStatCd: null,
|
||||
groupId: null,
|
||||
storeLvl: null,
|
||||
custCd: null,
|
||||
builderNo: null,
|
||||
isLoggedIn: false,
|
||||
}
|
||||
|
||||
@ -31,3 +52,11 @@ export const getSession = async () => {
|
||||
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
|
||||
return session
|
||||
}
|
||||
|
||||
export const logout = async () => {
|
||||
const cookieStore = await cookies()
|
||||
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
|
||||
|
||||
session.destroy()
|
||||
return redirect('/login')
|
||||
}
|
||||
|
||||
@ -2,7 +2,8 @@ import { NextResponse } from 'next/server'
|
||||
import { cookies } from 'next/headers'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { SessionData, sessionOptions } from './libs/session'
|
||||
import { sessionOptions } from './libs/session'
|
||||
import type { SessionData } from './types/Auth'
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
27
src/store/surveyFilterStore.ts
Normal file
27
src/store/surveyFilterStore.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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' }),
|
||||
}))
|
||||
|
||||
|
||||
30
src/types/Auth.ts
Normal file
30
src/types/Auth.ts
Normal file
@ -0,0 +1,30 @@
|
||||
export interface SessionData {
|
||||
langCd: null
|
||||
currPage: Number | null
|
||||
rowCount: Number | null
|
||||
startRow: Number | null
|
||||
endRow: Number | null
|
||||
compCd: null
|
||||
agencyStoreId: null
|
||||
storeId: null
|
||||
userId: null
|
||||
category: null
|
||||
userNm: null
|
||||
userNmKana: null
|
||||
telNo: null
|
||||
fax: null
|
||||
email: null
|
||||
lastEditUser: null
|
||||
storeGubun: null
|
||||
pwCurr: null
|
||||
pwdInitYn: null
|
||||
apprStatCd: null
|
||||
loginFailCnt: Number | null
|
||||
loginFailMinYn: null
|
||||
priceViewStatCd: null
|
||||
groupId: null
|
||||
storeLvl: null
|
||||
custCd: null
|
||||
builderNo: null
|
||||
isLoggedIn: boolean
|
||||
}
|
||||
@ -20,13 +20,13 @@ export type SurveyDetailInfo = {
|
||||
id: number
|
||||
contract_capacity: string | null
|
||||
retail_company: string | null
|
||||
supplementary_facilities: number | null
|
||||
supplementary_facilities: string | null // number 배열
|
||||
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: string | null // number 배열
|
||||
roof_material_etc: string | null
|
||||
roof_shape: number | null
|
||||
roof_shape_etc: string | null
|
||||
@ -74,13 +74,13 @@ export type SurveyBasicRequest = {
|
||||
export type SurveyDetailRequest = {
|
||||
contract_capacity: string | null
|
||||
retail_company: string | null
|
||||
supplementary_facilities: number | null
|
||||
supplementary_facilities: string | null // number 배열
|
||||
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: string | null // number 배열
|
||||
roof_material_etc: string | null
|
||||
roof_shape: number | null
|
||||
roof_shape_etc: string | null
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user