Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/suitable

# Conflicts:
#	src/api/suitable.ts
This commit is contained in:
Daseul Kim 2025-05-08 15:53:53 +09:00
commit b1f81d7853
17 changed files with 324 additions and 298 deletions

View File

@ -1,3 +1,7 @@
# 모바일 디바이스로 로컬 서버 확인하려면 자신 IP 주소로 변경 # 모바일 디바이스로 로컬 서버 확인하려면 자신 IP 주소로 변경
# 다시 로컬에서 개발할때는 localhost로 변경 # 다시 로컬에서 개발할때는 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

View File

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

View File

@ -8,6 +8,10 @@ const nextConfig: NextConfig = {
}, },
async rewrites() { async rewrites() {
return [ return [
{
source: '/api/user/login',
destination: `${process.env.NEXT_PUBLIC_QSP_API_URL}/api/user/login`,
},
{ {
source: '/api/:path*', source: '/api/:path*',
destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`,

View File

@ -7,7 +7,6 @@ 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
@ -33,7 +32,7 @@ model MS_SUITABLE {
//금구형태(쇠붙이형) //금구형태(쇠붙이형)
shape String? @db.VarChar(200) shape String? @db.VarChar(200)
//지지 기와 //지지 기와
support_roof_tile String? @db.VarChar(1) support_roof_tile String? @db.VarChar(2)
//지지 기와 메모 //지지 기와 메모
support_roof_tile_memo String? @db.VarChar(500) support_roof_tile_memo String? @db.VarChar(500)
//지지 금구 //지지 금구
@ -92,114 +91,63 @@ model MS_SUITABLE {
updated_at DateTime @updatedAt updated_at DateTime @updatedAt
} }
// 조사 매물 기본 정보
model SD_SERVEY_SALES_BASIC_INFO { model SD_SERVEY_SALES_BASIC_INFO {
//일련번호
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
//담당자명
representative String @db.VarChar(200) representative String @db.VarChar(200)
//판매점
store String? @db.VarChar(200) store String? @db.VarChar(200)
//시공점
construction_point String? @db.VarChar(200) construction_point String? @db.VarChar(200)
//현재 조사일
investigation_date String? @db.VarChar(10) investigation_date String? @db.VarChar(10)
//건물명
building_name String? @db.VarChar(200) building_name String? @db.VarChar(200)
//고객명
customer_name String? @db.VarChar(200) customer_name String? @db.VarChar(200)
//우편번호
post_code String? @db.VarChar(10) post_code String? @db.VarChar(10)
//주소
address String? @db.VarChar(200) address String? @db.VarChar(200)
//상세주소
address_detail String? @db.VarChar(300) address_detail String? @db.VarChar(300)
//제출상태
submission_status Boolean @default(false) submission_status Boolean @default(false)
//제출일
submission_date DateTime? @db.Date submission_date DateTime? @db.Date
//상세정보
detail_info SD_SERVEY_SALES_DETAIL_INFO?
created_at DateTime @default(now()) created_at DateTime @default(now())
updated_at DateTime @updatedAt updated_at DateTime @updatedAt
detail_info SD_SERVEY_SALES_DETAIL_INFO?
} }
// 조사 매물 전기 지붕 정보
model SD_SERVEY_SALES_DETAIL_INFO { model SD_SERVEY_SALES_DETAIL_INFO {
//일련번호
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
//전기계약 용량
contract_capacity String? @db.VarChar(20) contract_capacity String? @db.VarChar(20)
//전기 소매 회사
retail_company String? @db.VarChar(100) retail_company String? @db.VarChar(100)
//전기 부대 설비 supplementary_facilities Int?
supplementary_facilities Int? @db.Int
//전기 부대 설비 기타
supplementary_facilities_etc String? @db.VarChar(200) supplementary_facilities_etc String? @db.VarChar(200)
//설치 희망 시스템 installation_system Int?
installation_system Int? @db.Int
//설치 희망 시스템 기타
installation_system_etc String? @db.VarChar(200) installation_system_etc String? @db.VarChar(200)
//건축 연수 construction_year Int?
construction_year Int? @db.Int
//건축 연수 기타
construction_year_etc String? @db.VarChar(200) construction_year_etc String? @db.VarChar(200)
//지붕재 roof_material Int?
roof_material Int? @db.Int
//지붕재 기타
roof_material_etc String? @db.VarChar(200) roof_material_etc String? @db.VarChar(200)
//지붕 모양 roof_shape Int?
roof_shape Int? @db.Int
//지붕 모양 기타
roof_shape_etc String? @db.VarChar(200) roof_shape_etc String? @db.VarChar(200)
//지붕 경사도
roof_slope String? @db.VarChar(5) roof_slope String? @db.VarChar(5)
//주택 구조 house_structure Int?
house_structure Int? @db.Int
//주택 구조 기타
house_structure_etc String? @db.VarChar(200) house_structure_etc String? @db.VarChar(200)
//서까래 재질 rafter_material Int?
rafter_material Int? @db.Int
//서까래 재질 기타
rafter_material_etc String? @db.VarChar(200) rafter_material_etc String? @db.VarChar(200)
//서까래 크기 rafter_size Int?
rafter_size Int? @db.Int
//서까래 크기 기타
rafter_size_etc String? @db.VarChar(200) rafter_size_etc String? @db.VarChar(200)
//서까래 피치 rafter_pitch Int?
rafter_pitch Int? @db.Int
//서까래 피치 기타
rafter_pitch_etc String? @db.VarChar(200) rafter_pitch_etc String? @db.VarChar(200)
//서까래 방향 rafter_direction Int?
rafter_direction Int? @db.Int open_field_plate_kind Int?
//노지판 종류
open_field_plate_kind Int? @db.Int
//노지판 종류 기타
open_field_plate_kind_etc String? @db.VarChar(200) open_field_plate_kind_etc String? @db.VarChar(200)
//노지판 두께
open_field_plate_thickness String? @db.VarChar(5) open_field_plate_thickness String? @db.VarChar(5)
//누수 흔적
leak_trace Boolean? @default(false) leak_trace Boolean? @default(false)
//방수재 종류 waterproof_material Int?
waterproof_material Int? @db.Int
//방수재 종류 기타
waterproof_material_etc String? @db.VarChar(200) waterproof_material_etc String? @db.VarChar(200)
//단열재 여부 insulation_presence Int?
insulation_presence Int? @db.Int
//단열재 여부 기타
insulation_presence_etc String? @db.VarChar(200) insulation_presence_etc String? @db.VarChar(200)
//지붕 구조 순서 structure_order Int?
structure_order Int? @db.Int
//지붕 구조 순서 기타
structure_order_etc String? @db.VarChar(200) structure_order_etc String? @db.VarChar(200)
//설치 가능 여부 installation_availability Int?
installation_availability Int? @db.Int
//설치 가능 여부 기타
installation_availability_etc String? @db.VarChar(200) installation_availability_etc String? @db.VarChar(200)
//메모
memo String? @db.VarChar(500) memo String? @db.VarChar(500)
created_at DateTime @default(now()) created_at DateTime @default(now())
updated_at DateTime @updatedAt updated_at DateTime @updatedAt
basic_info SD_SERVEY_SALES_BASIC_INFO @relation(fields: [basic_info_id], references: [id])
basic_info_id Int @unique basic_info_id Int @unique
basic_info SD_SERVEY_SALES_BASIC_INFO @relation(fields: [basic_info_id], references: [id])
} }

View File

@ -1,112 +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 (category?: string, keyword?: string): Promise<Suitable[]> => {
let condition: any = {}
if (category) {
condition['category'] = category
}
if (keyword) {
condition['keyword'] = {
contains: keyword,
}
}
console.log('🚀 ~ getList: ~ condition:', condition)
const response = await axiosInstance.get<Suitable[]>('/api/suitable/list', { params: condition })
console.log('🚀 ~ getList: ~ response:', response)
return response.data
},
getCategory: async (): Promise<Suitable[]> => {
const response = await axiosInstance.get<Suitable[]>('/api/suitable/category')
console.log('🚀 ~ getCategory: ~ response:', response)
return response.data
},
getDetails: async (roofMaterial: string): Promise<Suitable[]> => {
const response = await axiosInstance.get<Suitable[]>(`/api/suitable/details?roof-material=${roofMaterial}`)
console.log('🚀 ~ getDetails: ~ response:', response)
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
},
}

View File

@ -1,71 +0,0 @@
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
},
update: async (data: SurveySalesBasicInfo): Promise<SurveySalesBasicInfo> => {
const response = await axiosInstance.put<SurveySalesBasicInfo>(`/api/survey-sales`, data)
return response.data
},
}

View File

@ -1,5 +1,5 @@
import Header from '@/components/ui/common/Header' import Header from '@/components/ui/common/Header'
export default function page() { export default async function page() {
return <Header name={'調査物件一覧'} /> return <Header name={'調査物件一覧'} />
} }

View 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
View 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!!' })
}

View File

@ -1,6 +1,14 @@
import Main from '@/components/ui/Main' 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() { export default async function Home() {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
console.log('🚀 ~ Home ~ session:', session)
return ( return (
<> <>
<div className="container"> <div className="container">

View File

@ -1,16 +1,69 @@
'use client' '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() { 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 ( return (
<> <>
<div className="login-form-wrap"> <div className="login-form-wrap">
<div className="login-form mb15"> <div className="login-form mb15">
<div className="login-input id"> <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"> <button className="login-icon">
<i className="del-icon"></i> <i className="del-icon"></i>
</button> </button>
@ -18,7 +71,13 @@ export default function Login() {
</div> </div>
<div className="login-form"> <div className="login-form">
<div className="login-input pw"> <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)}> <button className={`login-icon ${pwShow ? 'act' : ''}`} onClick={() => setPwShow(!pwShow)}>
<i className="show-icon"></i> <i className="show-icon"></i>
</button> </button>
@ -26,19 +85,19 @@ export default function Login() {
</div> </div>
<div className="login-check-warp"> <div className="login-check-warp">
<div className="check-form-box"> <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> <label htmlFor="ch01">ID Save</label>
</div> </div>
<div className="toggle-form"> <div className="toggle-form">
<label className="toggle-btn"> <label className="toggle-btn">
<input type="checkbox" /> <input type="checkbox" checked={isPartners} onChange={() => setIsPartners(!isPartners)} />
<span className="slider"></span> <span className="slider"></span>
</label> </label>
<div className="toggle-name">Q.PARTNERS</div> <div className="toggle-name">Q.PARTNERS</div>
</div> </div>
</div> </div>
<div className="login-btn-wrap"> <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> <i className="btn-arr"></i>
</button> </button>
</div> </div>

View File

@ -1,16 +1,27 @@
'use client' 'use client'
import { useHeaderStore } from '@/store/header' 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' import { useEffect } from 'react'
export default function Main() { export default function Main() {
const router = useRouter() const router = useRouter()
const pathname = usePathname()
const { setBackBtn } = useHeaderStore() const { setBackBtn } = useHeaderStore()
const { reset } = useSideNavState()
/**
*
*
*/
useEffect(() => { useEffect(() => {
setBackBtn(false) if (pathname === '/') {
}, []) setBackBtn(false)
}
//사이드바 초기화
reset()
}, [pathname])
return ( return (
<> <>

View File

@ -1,16 +1,17 @@
'use client' 'use client'
import { useEffect, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import { Swiper, SwiperSlide } from 'swiper/react' import { Swiper, SwiperSlide } from 'swiper/react'
import { useSideNavState } from '@/store/sideNavState'
import { useHeaderStore } from '@/store/header'
import type { HeaderProps } from '@/types/Header' import type { HeaderProps } from '@/types/Header'
import 'swiper/css' import 'swiper/css'
import { useSideNavState } from '@/store/sideNavState' import { axiosInstance } from '@/libs/axios'
// type HeaderProps = { // type HeaderProps = {
// name: string //header 이름 // name: string //header 이름
@ -20,27 +21,26 @@ import { useSideNavState } from '@/store/sideNavState'
export default function Header({ name }: HeaderProps) { export default function Header({ name }: HeaderProps) {
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const { sideNavIsOpen, setSideNavIsOpen, reset } = useSideNavState() const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState()
const [isShowBackBtn, setIsShowBackBtn] = useState<boolean>(false) const { backBtn } = useHeaderStore()
if (pathname === '/login') { if (pathname === '/login') {
return null return null
} }
useEffect(() => { const handleLogout = async () => {
if (pathname !== '/') { const { data } = await axiosInstance(null).get('/api/auth/logout')
setIsShowBackBtn(true) if (data.code === 200) {
router.push('/login')
} }
//사이드바 초기화 }
reset()
}, [pathname])
return ( return (
<> <>
<div className="header-warp"> <div className="header-warp">
<header> <header>
<div className="header-inner"> <div className="header-inner">
{isShowBackBtn && ( {backBtn && (
<div className="back-button-wrap"> <div className="back-button-wrap">
<button className="back-button" onClick={() => router.back()}></button> <button className="back-button" onClick={() => router.back()}></button>
</div> </div>
@ -112,7 +112,9 @@ export default function Header({ name }: HeaderProps) {
<div className="side-nav-footer"> <div className="side-nav-footer">
<ul className="side-footer-list"> <ul className="side-footer-list">
<li className="side-footer-item"> <li className="side-footer-item">
<button className="bold">LOGOUT</button> <button className="bold" onClick={handleLogout}>
LOGOUT
</button>
</li> </li>
<li className="side-footer-item"> <li className="side-footer-item">
<button>Jynoadmin</button> <button>Jynoadmin</button>

View File

@ -1,16 +1,18 @@
import axios from 'axios' 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({ return axios.create({
baseURL, baseURL,
headers: { headers: {
'Content-Type': 'application/json', Accept: 'application/json',
}, },
}) })
}
// Request interceptor // Request interceptor
axiosInstance.interceptors.request.use( axios.interceptors.request.use(
(config) => { (config) => {
// 여기에 토큰 추가 등의 공통 로직을 넣을 수 있습니다 // 여기에 토큰 추가 등의 공통 로직을 넣을 수 있습니다
return config return config
@ -21,10 +23,44 @@ axiosInstance.interceptors.request.use(
) )
// Response interceptor // Response interceptor
axiosInstance.interceptors.response.use( axios.interceptors.response.use(
(response) => response, (response) => transferResponse(response),
(error) => { (error) => {
// 에러 처리 로직 // 에러 처리 로직
return Promise.reject(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('_', ''))
}

View File

@ -1,5 +1,7 @@
import type { SessionData } from '@/types/Auth'
import { getIronSession, SessionOptions } from 'iron-session' import { getIronSession, SessionOptions } from 'iron-session'
import { cookies } from 'next/headers' import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
export const sessionOptions: SessionOptions = { export const sessionOptions: SessionOptions = {
cookieName: 'onsitesurvey_session', 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 = { export const defaultSession: SessionData = {
username: '', langCd: null,
email: '', 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, isLoggedIn: false,
} }
@ -31,3 +52,11 @@ export const getSession = async () => {
const session = await getIronSession<SessionData>(cookieStore, sessionOptions) const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
return session return session
} }
export const logout = async () => {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
session.destroy()
return redirect('/login')
}

View File

@ -2,7 +2,8 @@ import { NextResponse } from 'next/server'
import { cookies } from 'next/headers' import { cookies } from 'next/headers'
import type { NextRequest } from 'next/server' import type { NextRequest } from 'next/server'
import { getIronSession } from 'iron-session' 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) { export async function middleware(request: NextRequest) {
const cookieStore = await cookies() const cookieStore = await cookies()

30
src/types/Auth.ts Normal file
View 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
}