feature/survey-sales #1

Merged
swyoo merged 3 commits from feature/survey-sales into main 2025-04-29 10:24:39 +09:00
15 changed files with 368 additions and 44 deletions

3
.env
View File

@ -4,7 +4,8 @@
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="sqlserver://3team.devgrr.kr:1433;database=onsitesurvey;user=sa;password=1q2w3e4r!;encrypt=true;trustServerCertificate=true;" # DATABASE_URL="sqlserver://3team.devgrr.kr:1433;database=onsitesurvey;user=sa;password=1q2w3e4r!;encrypt=true;trustServerCertificate=true;"
DATABASE_URL="sqlserver://172.30.1.35:1433;database=onsitesurvey;user=sa;password=1q2w3e4r!;encrypt=true;trustServerCertificate=true;"
# SESSION_PASSWORD="QWERASDFZXCV1234567890REWQFDSAVCXZ" # SESSION_PASSWORD="QWERASDFZXCV1234567890REWQFDSAVCXZ"
SESSION_PASSWORD="This application is for mobile field research" SESSION_PASSWORD="This application is for mobile field research"

View File

@ -0,0 +1,3 @@
# 모바일 디바이스로 로컬 서버 확인하려면 자신 IP 주소로 변경
# 다시 로컬에서 개발할때는 localhost로 변경
NEXT_PUBLIC_API_URL=http://localhost:3000

View File

View File

@ -0,0 +1 @@
NEXT_PUBLIC_API_URL=http://172.30.1.35:3000

View File

@ -6,6 +6,35 @@ const nextConfig: NextConfig = {
sassOptions: { sassOptions: {
includePaths: [path.join(__dirname, './src/styles')], includePaths: [path.join(__dirname, './src/styles')],
}, },
async rewrites() {
return [
{
source: '/:path*',
destination: `${process.env.NEXT_PUBLIC_API_URL}/:path*`,
},
]
},
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: '*',
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
]
},
} }
export default nextConfig export default nextConfig

View File

@ -7,6 +7,7 @@ datasource db {
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }
// 사용자 정보
model User { model User {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
username String @unique username String @unique
@ -19,48 +20,186 @@ model User {
updated_at DateTime @updatedAt updated_at DateTime @updatedAt
} }
// 지붕재 적합성 정보
model MS_SUITABLE { model MS_SUITABLE {
//일련번호
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
product_name String //제품명
manufacturer String? product_name String @db.VarChar(200)
roof_material String? //제조업체명
shape String? manufacturer String? @db.VarChar(200)
support_roof_tile String? //지붕재
support_roof_tile_memo String? roof_material String? @db.VarChar(100)
support_roof_bracket String? //금구형태(쇠붙이형)
support_roof_bracket_memo String? shape String? @db.VarChar(200)
yg_anchor String? //지지 기와
yg_anchor_memo String? support_roof_tile String? @db.VarChar(1)
rg_roof_tile_part String? //지지 기와 메모
rg_roof_tile_part_memo String? support_roof_tile_memo String? @db.VarChar(500)
dido_hunt_support_tile_2 String? //지지 금구
dido_hunt_support_tile_2_memo String? support_roof_bracket String? @db.VarChar(200)
takashima_power_base String? //지지 금구 메모
takashima_power_base_memo String? support_roof_bracket_memo String? @db.VarChar(500)
takashima_tile_bracket String? //yg 앵커
takashima_tile_bracket_memo String? yg_anchor String? @db.VarChar(200)
slate_bracket_4 String? //yg 앵커 메모
slate_bracket_4_memo String? yg_anchor_memo String? @db.VarChar(500)
slate_single_metal_bracket String? //rg 지붕판
slate_single_metal_bracket_memo String? rg_roof_tile_part String? @db.VarChar(200)
dido_hunt_short_rack_4 String? //rg 지붕판 메모
dido_hunt_short_rack_4_memo String? rg_roof_tile_part_memo String? @db.VarChar(500)
takashima_slate_bracket_slate_single String? //다이도헌트 지지 기와2
takashima_slate_bracket_slate_single_memo String? dido_hunt_support_tile_2 String? @db.VarChar(200)
df_metal_bracket String? //다이도헌트 지지 기와2 메모
df_metal_bracket_memo String? dido_hunt_support_tile_2_memo String? @db.VarChar(500)
slate_metal_bracket String? //타카시마 파워 베이스
slate_metal_bracket_memo String? takashima_power_base String? @db.VarChar(200)
takashima_slate_bracket_metal_roof String? //타카시마 파워 베이스 메모
takashima_slate_bracket_metal_roof_memo String? takashima_power_base_memo String? @db.VarChar(500)
//타카시마용 금구
takashima_tile_bracket String? @db.VarChar(200)
//타카시마용 금구 메모
takashima_tile_bracket_memo String? @db.VarChar(500)
//슬레이트 금구4
slate_bracket_4 String? @db.VarChar(200)
//슬레이트 금구4 메모
slate_bracket_4_memo String? @db.VarChar(500)
//슬레이트 판금 금구(슬레이트, 싱글)
slate_single_metal_bracket String? @db.VarChar(200)
//슬레이트 판금 금구 메모(슬레이트, 싱글)
slate_single_metal_bracket_memo String? @db.VarChar(500)
//다이도헌트 짧은 트랙4
dido_hunt_short_rack_4 String? @db.VarChar(200)
//다이도헌트 짧은 트랙4 메모
dido_hunt_short_rack_4_memo String? @db.VarChar(500)
//타카시마 슬레이트 금구
takashima_slate_bracket_slate_single String? @db.VarChar(200)
//타카시마 슬레이트 금구 메모
takashima_slate_bracket_slate_single_memo String? @db.VarChar(500)
//df 판금 금구
df_metal_bracket String? @db.VarChar(200)
//df 판금 금구 메모
df_metal_bracket_memo String? @db.VarChar(500)
//슬레이트 판금 금구(금속 지붕)
slate_metal_bracket String? @db.VarChar(200)
//슬레이트 판금 금구(금속 지붕) 메모
slate_metal_bracket_memo String? @db.VarChar(500)
//타카시마 슬레이트 금구(금속 지붕)
takashima_slate_bracket_metal_roof String? @db.VarChar(200)
//타카시마 슬레이트 금구(금속 지붕) 메모
takashima_slate_bracket_metal_roof_memo String? @db.VarChar(500)
created_at DateTime @default(now()) created_at DateTime @default(now())
updated_at DateTime @updatedAt updated_at DateTime @updatedAt
} }
model SD_SERVEY_SALES { // 조사 매물 기본 정보
id Int @id @default(autoincrement()) model SD_SERVEY_SALES_BASIC_INFO {
title String @db.VarChar(200) //일련번호
content String? id Int @id @default(autoincrement())
created_at DateTime @default(now()) //담당자명
updated_at DateTime @updatedAt representative String @db.VarChar(200)
//판매점
store String? @db.VarChar(200)
//시공점
construction_point String? @db.VarChar(200)
//현재 조사일
investigation_date String? @db.VarChar(10)
//건물명
building_name String? @db.VarChar(200)
//고객명
customer_name String? @db.VarChar(200)
//우편번호
post_code String? @db.VarChar(10)
//주소
address String? @db.VarChar(200)
//상세주소
address_detail String? @db.VarChar(300)
//제출상태
submission_status Boolean @default(false)
//제출일
submission_date DateTime? @db.Date
//상세정보
detail_info SD_SERVEY_SALES_DETAIL_INFO?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}
// 조사 매물 전기 지붕 정보
model SD_SERVEY_SALES_DETAIL_INFO {
//일련번호
id Int @id @default(autoincrement())
//전기계약 용량
contract_capacity String? @db.VarChar(20)
//전기 소매 회사
retail_company String? @db.VarChar(100)
//전기 부대 설비
supplementary_facilities Int? @db.Int
//전기 부대 설비 기타
supplementary_facilities_etc String? @db.VarChar(200)
//설치 희망 시스템
installation_system Int? @db.Int
//설치 희망 시스템 기타
installation_system_etc String? @db.VarChar(200)
//건축 연수
construction_year Int? @db.Int
//건축 연수 기타
construction_year_etc String? @db.VarChar(200)
//지붕재
roof_material Int? @db.Int
//지붕재 기타
roof_material_etc String? @db.VarChar(200)
//지붕 모양
roof_shape Int? @db.Int
//지붕 모양 기타
roof_shape_etc String? @db.VarChar(200)
//지붕 경사도
roof_slope String? @db.VarChar(5)
//주택 구조
house_structure Int? @db.Int
//주택 구조 기타
house_structure_etc String? @db.VarChar(200)
//서까래 재질
rafter_material Int? @db.Int
//서까래 재질 기타
rafter_material_etc String? @db.VarChar(200)
//서까래 크기
rafter_size Int? @db.Int
//서까래 크기 기타
rafter_size_etc String? @db.VarChar(200)
//서까래 피치
rafter_pitch Int? @db.Int
//서까래 피치 기타
rafter_pitch_etc String? @db.VarChar(200)
//서까래 방향
rafter_direction Int? @db.Int
//노지판 종류
open_field_plate_kind Int? @db.Int
//노지판 종류 기타
open_field_plate_kind_etc String? @db.VarChar(200)
//노지판 두께
open_field_plate_thickness String? @db.VarChar(5)
//누수 흔적
leak_trace Boolean? @default(false)
//방수재 종류
waterproof_material Int? @db.Int
//방수재 종류 기타
waterproof_material_etc String? @db.VarChar(200)
//단열재 여부
insulation_presence Int? @db.Int
//단열재 여부 기타
insulation_presence_etc String? @db.VarChar(200)
//지붕 구조 순서
structure_order Int? @db.Int
//지붕 구조 순서 기타
structure_order_etc String? @db.VarChar(200)
//설치 가능 여부
installation_availability Int? @db.Int
//설치 가능 여부 기타
installation_availability_etc String? @db.VarChar(200)
//메모
memo String? @db.VarChar(500)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
basic_info SD_SERVEY_SALES_BASIC_INFO @relation(fields: [basic_info_id], references: [id])
basic_info_id Int @unique
} }

67
src/api/surveySales.ts Normal file
View File

@ -0,0 +1,67 @@
import { axiosInstance } from '@/libs/axios'
export interface SurveySalesBasicInfo {
id?: number
representative: String
store: String | null
construction_point: String | null
investigation_date: String | null
building_name: String | null
customer_name: String | null
post_code: String | null
address: String | null
address_detail: String | null
submission_status: Boolean
submission_date?: String | null
detail_info?: SurveySalesDetailInfo | null
}
export interface SurveySalesDetailInfo {
id?: number
contract_capacity: String | null
retail_company: String | null
supplementary_facilities: Number | null
supplementary_facilities_etc: String | null
installation_system: Number | null
installation_system_etc: String | null
construction_year: Number | null
construction_year_etc: String | null
roof_material: Number | null
roof_material_etc: String | null
roof_shape: Number | null
roof_shape_etc: String | null
roof_slope: String | null
house_structure: Number | null
house_structure_etc: String | null
rafter_material: Number | null
rafter_material_etc: String | null
rafter_size: Number | null
rafter_size_etc: String | null
rafter_pitch: Number | null
rafter_pitch_etc: String | null
rafter_direction: Number | null
open_field_plate_kind: Number | null
open_field_plate_kind_etc: String | null
open_field_plate_thickness: String | null
leak_trace: Boolean | null
waterproof_material: Number | null
waterproof_material_etc: String | null
insulation_presence: Number | null
insulation_presence_etc: String | null
structure_order: Number | null
structure_order_etc: String | null
installation_availability: Number | null
installation_availability_etc: String | null
memo: String | null
}
export const surveySalesApi = {
create: async (data: SurveySalesBasicInfo): Promise<SurveySalesBasicInfo> => {
const response = await axiosInstance.post<SurveySalesBasicInfo>('/api/survey-sales', data)
return response.data
},
getList: async (): Promise<SurveySalesBasicInfo[]> => {
const response = await axiosInstance.get<SurveySalesBasicInfo[]>('/api/survey-sales')
return response.data
},
}

View File

@ -0,0 +1,13 @@
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({ message: 'Survey sales created successfully' })
}

View File

@ -24,12 +24,14 @@ export async function POST(request: Request) {
const cookieStore = await cookies() const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions) const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
console.log('start session edit!')
session.username = user.username! session.username = user.username!
session.email = user.email! session.email = user.email!
session.isLoggedIn = true session.isLoggedIn = true
console.log('end session edit!')
await session.save() await session.save()
console.log('🚀 ~ POST ~ session:', session)
// return NextResponse.redirect(new URL(process.env.NEXT_PUBLIC_URL!, request.url))
return NextResponse.json(user) return NextResponse.json(user)
} }

View File

@ -26,7 +26,7 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}> <body>
<ReactQueryProviders>{children}</ReactQueryProviders> <ReactQueryProviders>{children}</ReactQueryProviders>
<Footer /> <Footer />
</body> </body>

View File

@ -31,6 +31,12 @@ export default async function Home() {
</Link> </Link>
</div> </div>
<div className="p-4">
<Link href="/survey-sales">
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> </button>
</Link>
</div>
<User /> <User />
</div> </div>
</> </>

View File

@ -1,5 +1,4 @@
import Suitable from '@/components/Suitable' import Suitable from '@/components/Suitable'
import SuitableCreateBtn from '@/components/SuitableCreateBtn'
import SuitableSearch from '@/components/SuitableSearch' import SuitableSearch from '@/components/SuitableSearch'
export default function suitablePage() { export default function suitablePage() {
@ -8,7 +7,7 @@ export default function suitablePage() {
<SuitableSearch /> <SuitableSearch />
<Suitable /> <Suitable />
{/* 최초 한번 밀어넣음 */} {/* 최초 한번 밀어넣음 */}
<SuitableCreateBtn /> {/* <SuitableCreateBtn /> */}
</> </>
) )
} }

View File

@ -0,0 +1,10 @@
import SurveySales from '@/components/SurveySales'
export default function page() {
return (
<>
<h1 className="text-2xl font-bold my-4 flex justify-center"> </h1>
<SurveySales />
</>
)
}

View File

@ -0,0 +1,53 @@
'use client'
import { surveySalesApi, SurveySalesBasicInfo } from '@/api/surveySales'
import { useMutation, useQueryClient } from '@tanstack/react-query'
export default function SurveySales() {
const queryClient = useQueryClient()
const {
mutate: createSurveySales,
isPending,
error,
} = useMutation({
mutationFn: surveySalesApi.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['survey-sales', 'list'] })
},
})
const handleSurveySales = () => {
const data: SurveySalesBasicInfo = {
representative: 'keyy1315',
store: 'HWJ(T01)',
construction_point: 'HWJ(T01)',
investigation_date: '2025-04-28',
building_name: 'Hanwha Japan Building',
customer_name: 'Hong Gil Dong',
post_code: '1050013',
address: 'Tokyo, Japan',
address_detail: '1-1-1',
submission_status: false,
}
createSurveySales(data)
}
return (
<>
<div className="flex items-center justify-center">
<div className="flex gap-2">
<button className="bg-blue-500 text-white px-4 py-2 rounded-md" onClick={handleSurveySales}>
</button>
<button className="bg-blue-500 text-white px-4 py-2 rounded-md"></button>
</div>
</div>
{/* <div className="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4 m-4" role="alert">
<p className="font-bold">Be Warned</p>
<p> .</p>
</div> */}
</>
)
}

View File

@ -7,6 +7,7 @@ import { SessionData, sessionOptions } from './libs/session'
export async function middleware(request: NextRequest) { export async function middleware(request: NextRequest) {
const cookieStore = await cookies() const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions) const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
console.log('🚀 ~ middleware ~ session:', session)
if (!session.isLoggedIn) { if (!session.isLoggedIn) {
return NextResponse.redirect(new URL('/login', request.url)) return NextResponse.redirect(new URL('/login', request.url))
} }