Compare commits

..

No commits in common. "4ed8a7819298e8b3f72bc4ee90a170fba467d205" and "7fbaf4a1e0d0426a18307dc75730e0800c38b03f" have entirely different histories.

19 changed files with 54 additions and 132 deletions

View File

@ -5,8 +5,8 @@ NEXT_PUBLIC_RUN_MODE=development
NEXT_PUBLIC_API_URL=http://localhost:3000
#qsp 로그인 api
# NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com
NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
# NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com
#1:1문의 api
NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com

View File

@ -5,8 +5,8 @@ NEXT_PUBLIC_RUN_MODE=local
NEXT_PUBLIC_API_URL=http://localhost:3000
#qsp 로그인 api
# NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com
NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
# NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com
#1:1문의 api
NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com

View File

@ -123,7 +123,7 @@ session에 있는 role 키로 구분한다
session.role === 'Builder'
- teshg44 / 1234 -> 시공사\
session.role === 'Builder'
- isogai@yanegiken.co.jp / password -> Q.Partners 계정\
- partners -> Q.Partners 계정\
session.role === 'Partner'
- 이외의 경우 -> 굳이 체크할 필요 없어보임\
session.role === 'User'

View File

@ -1,71 +0,0 @@
# 루트 레이아웃 구조
이 문서는 애플리케이션의 모든 페이지에서 공통으로 사용되는 루트 레이아웃(`src/app/layout.tsx`)의 구조를 설명합니다.
## 컴포넌트 계층 구조
```mermaid
graph TD
A[RootLayout] --> B[ReactQueryProviders]
B --> C[EdgeProvider]
C --> D[HTML Structure]
D --> E[Header]
D --> F[Children/Main Content]
D --> G[Footer]
D --> H[Float Button]
D --> I[PopupController]
J[Session Management] --> |Session Data| C
subgraph Providers
B
C
end
subgraph Layout Components
E
F
G
H
I
end
subgraph Session
J
end
```
## 주요 컴포넌트
### 프로바이더
- **ReactQueryProviders**: React Query 상태 관리를 위한 최상위 프로바이더
- **EdgeProvider**: 세션 데이터를 관리하고 애플리케이션에 제공
### 레이아웃 컴포넌트
- **Header**: 공통 헤더 컴포넌트
- **Children**: 메인 콘텐츠 영역 (페이지별 콘텐츠)
- **Footer**: 공통 푸터 컴포넌트
- **Float Button**: 플로팅 액션 버튼
- **PopupController**: 팝업/모달 상태 관리
### 세션 관리
- `iron-session`을 사용한 서버 사이드 세션 관리
- 세션 데이터는 EdgeProvider를 통해 전역적으로 접근 가능
## 데이터 흐름
1. 세션 데이터는 서버 사이드에서 관리됨
2. 세션 데이터는 EdgeProvider로 전달됨
3. 모든 레이아웃 컴포넌트는 세션 컨텍스트에 접근 가능
4. React Query는 데이터 페칭과 캐싱 기능을 제공
## 구현 세부사항
- 서버 컴포넌트로 구현
- Next.js App Router 구조 사용
- 서버 사이드 렌더링 지원
- 하이드레이션 억제를 통한 성능 최적화

View File

@ -8,27 +8,25 @@ datasource db {
}
model SD_SURVEY_SALES_BASIC_INFO {
ID Int @id @default(autoincrement())
SRL_NO String @db.NVarChar(20)
REPRESENTATIVE String @db.NVarChar(200)
STORE String? @db.NVarChar(200)
CONSTRUCTION_POINT String? @db.NVarChar(200)
CONSTRUCTION_POINT_ID String? @db.NVarChar(200)
INVESTIGATION_DATE String? @db.NVarChar(10)
BUILDING_NAME String? @db.NVarChar(200)
CUSTOMER_NAME String? @db.NVarChar(200)
POST_CODE String? @db.NVarChar(10)
ADDRESS String? @db.NVarChar(200)
ADDRESS_DETAIL String? @db.NVarChar(300)
SUBMISSION_STATUS Boolean @default(false)
SUBMISSION_DATE DateTime? @db.Date
SUBMISSION_TARGET_ID String? @db.NVarChar(200)
SUBMISSION_TARGET_NM String? @db.NVarChar(200)
REG_DT DateTime @default(now())
UPT_DT DateTime @updatedAt
REPRESENTATIVE_ID String? @db.NVarChar(100)
STORE_ID String? @db.NVarChar(100)
DETAIL_INFO SD_SURVEY_SALES_DETAIL_INFO?
ID Int @id @default(autoincrement())
SRL_NO String @db.NVarChar(20)
REPRESENTATIVE String @db.NVarChar(200)
STORE String? @db.NVarChar(200)
CONSTRUCTION_POINT String? @db.NVarChar(200)
INVESTIGATION_DATE String? @db.NVarChar(10)
BUILDING_NAME String? @db.NVarChar(200)
CUSTOMER_NAME String? @db.NVarChar(200)
POST_CODE String? @db.NVarChar(10)
ADDRESS String? @db.NVarChar(200)
ADDRESS_DETAIL String? @db.NVarChar(300)
SUBMISSION_STATUS Boolean @default(false)
SUBMISSION_DATE DateTime? @db.Date
SUBMISSION_TARGET_ID String? @db.NVarChar(200)
REG_DT DateTime @default(now())
UPT_DT DateTime @updatedAt
REPRESENTATIVE_ID String? @db.NVarChar(100)
STORE_ID String? @db.NVarChar(100)
DETAIL_INFO SD_SURVEY_SALES_DETAIL_INFO?
}
model SD_SURVEY_SALES_DETAIL_INFO {

View File

@ -57,7 +57,6 @@ export async function POST(request: Request) {
session.storeLvl = result.data.data.storeLvl
session.custCd = result.data.data.custCd
session.builderNo = result.data.data.builderNo
session.builderNm = ''
session.isLoggedIn = true
if (result.data.data.userId === 'T01') {
@ -105,7 +104,6 @@ export async function POST(request: Request) {
STORE_LVL: result.data.data.storeLvl,
CUST_CD: result.data.data.custCd,
BUILDER_NO: result.data.data.builderNo,
BUILDER_NM: '',
IS_LOGGED_IN: true,
ROLE: '',
}

View File

@ -85,7 +85,6 @@ export async function POST(request: Request) {
session.storeLvl = null
session.custCd = null
session.builderNo = data[0].user_seko_id
session.builderNm = data[0].supplier_name
session.isLoggedIn = true
session.role = 'Partner'
@ -124,7 +123,6 @@ export async function POST(request: Request) {
STORE_LVL: null,
CUST_CD: null,
BUILDER_NO: data[0].user_seko_id,
BUILDER_NM: data[0].supplier_name,
IS_LOGGED_IN: true,
ROLE: 'Partner',
}

View File

@ -124,7 +124,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
SUBMISSION_STATUS: true,
SUBMISSION_DATE: new Date(),
SUBMISSION_TARGET_ID: body.targetId,
SUBMISSION_TARGET_NM: body.targetNm,
UPT_DT: new Date(),
},
})

View File

@ -112,7 +112,7 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
// 시공점이 있고 STORE_ID 가 시공ID와 같은 매물
where.AND?.push({
// CONSTRUCTION_POINT: { not: null },
CONSTRUCTION_POINT_ID: { equals: params.builderNo },
CONSTRUCTION_POINT: { equals: params.builderNo },
})
break

View File

@ -11,8 +11,7 @@ import { useSpinnerStore } from '@/store/spinnerStore'
interface SubmitFormData {
saleBase: string | null
targetId: string
targetNm: string
store: string
sender: string
receiver: string[] | string
reference: string | null
@ -38,8 +37,7 @@ export default function SurveySaleSubmitPopup() {
const [submitData, setSubmitData] = useState<SubmitFormData>({
saleBase: null,
targetId: session?.role === 'Builder' ? surveyDetail?.storeId ?? '' : '',
targetNm: session?.role === 'Builder' ? surveyDetail?.store ?? '' : '',
store: '',
sender: session?.email ?? '',
receiver: [],
reference: null,
@ -65,7 +63,7 @@ export default function SurveySaleSubmitPopup() {
const FORM_FIELDS: FormField[] = [
{ id: 'sender', name: '発送者', required: true },
{ id: 'saleBase', name: '提出地点選択', required: session?.role === 'Admin' },
{ id: 'targetNm', name: '提出販売店', required: session?.role !== 'Admin' },
{ id: 'store', name: '提出販売店', required: true },
{ id: 'receiver', name: '受信者', required: true },
{ id: 'reference', name: '参考', required: false },
{ id: 'title', name: 'タイトル', required: true },
@ -105,7 +103,7 @@ export default function SurveySaleSubmitPopup() {
})
.then(() => {
if (!isSubmittingSurvey) {
submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm })
submitSurvey({ targetId: submitData.store })
alert('提出が完了しました。')
popupController.setSurveySaleSubmitPopup(false)
}
@ -116,6 +114,7 @@ export default function SurveySaleSubmitPopup() {
})
.finally(() => {
setIsShow(false)
submitSurvey({ targetId: submitData.store })
popupController.setSurveySaleSubmitPopup(false)
})
})
@ -134,9 +133,6 @@ export default function SurveySaleSubmitPopup() {
if (field.id === 'saleBase' && session?.role !== 'Admin') {
return null
}
if (field.id === 'targetNm' && session?.role === 'Admin') {
return null
}
return (
<div className="data-input-form-bx" key={field.id}>
<div className="data-input-form-tit">

View File

@ -20,16 +20,17 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
setBasicInfoSelected()
}, [])
// 시공권한 user(Builder), Partner 계정은 조사매물 등록 할 때 STORE_ID에 시공점ID가 들어감
// 권한 별 목록 필터링 시 시공권한 user(Builder), Partner는 시공점ID가 같은 것들만 조회
useEffect(() => {
if (session?.isLoggedIn) {
setBasicInfo({
...basicInfo,
representative: session.userNm ?? '',
representativeId: session.userId ?? null,
store: session.storeNm ?? null,
storeId: session.storeId ?? null,
constructionPoint: session.builderNm ?? null,
constructionPointId: session.builderNo ?? null,
store: session.role === 'Partner' ? null : session.storeNm ?? null,
storeId: session.role === 'Partner' ? session.builderNo : session.storeId ?? null,
constructionPoint: session.builderNo ?? null,
})
}
if (addressData) {

View File

@ -6,6 +6,7 @@ import { useEffect, useState } from 'react'
import { useParams, useRouter, useSearchParams } from 'next/navigation'
import { requiredFields, useSurvey } from '@/hooks/useSurvey'
import { usePopupController } from '@/store/popupController'
import { useSpinnerStore } from '@/store/spinnerStore'
export default function ButtonForm(props: {
mode: Mode
@ -30,6 +31,7 @@ export default function ButtonForm(props: {
detailInfo: props.data.roof,
})
const { setIsShow } = useSpinnerStore()
// --------------------------------------------------------------
// 권한
@ -76,6 +78,14 @@ export default function ButtonForm(props: {
const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey } = useSurvey(Number(id))
const { validateSurveyDetail, createSurvey, isCreatingSurvey } = useSurvey()
useEffect(() => {
if (isCreatingSurvey || isUpdatingSurvey || isDeletingSurvey) {
setIsShow(true)
}
if (!isCreatingSurvey && !isUpdatingSurvey && !isDeletingSurvey) {
setIsShow(false)
}
}, [isCreatingSurvey, isUpdatingSurvey, isDeletingSurvey])
const handleSave = (isTemporary: boolean, isSubmitProcess: boolean) => {
const emptyField = validateSurveyDetail(props.data.roof)
@ -129,7 +139,7 @@ export default function ButtonForm(props: {
}
if (isSubmitProcess) {
if (!isCreatingSurvey && !isUpdatingSurvey) {
await popupController.setSurveySaleSubmitPopup(true)
popupController.setSurveySaleSubmitPopup(true)
}
} else {
alert('保存されました。')

View File

@ -57,7 +57,7 @@ export default function DataTable() {
<>
<div>{new Date(surveyDetail.submissionDate).toLocaleString()}</div>
<div>
({surveyDetail.submissionTargetNm} - {surveyDetail.submissionTargetId})
({surveyDetail.submissionTargetId} - {surveyDetail.submissionTargetId})
</div>
</>
) : (

View File

@ -52,7 +52,6 @@ const basicInfoForm: SurveyBasicRequest = {
store: null,
storeId: null,
constructionPoint: null,
constructionPointId: null,
investigationDate: new Date().toLocaleDateString('en-CA'),
buildingName: null,
customerName: null,
@ -62,7 +61,6 @@ const basicInfoForm: SurveyBasicRequest = {
submissionStatus: false,
submissionDate: null,
submissionTargetId: null,
submissionTargetNm: null,
srlNo: null,
}

View File

@ -3,19 +3,21 @@ import Config from '@/config/config.export'
import { useSpinnerStore } from '@/store/spinnerStore'
export function useAxios() {
// const { setIsShow } = useSpinnerStore()
const requestHandler = (config: InternalAxiosRequestConfig) => {
useSpinnerStore.getState().setIsShow(true)
// setIsShow(true)
return config
}
const responseHandler = (response: AxiosResponse) => {
useSpinnerStore.getState().setIsShow(false)
// setIsShow(false)
response.data = transferResponse(response)
return response
}
const errorHandler = (error: any) => {
useSpinnerStore.getState().setIsShow(false)
// setIsShow(false)
return Promise.reject(error)
}

View File

@ -66,7 +66,7 @@ export function useSurvey(id?: number): {
createSurvey: (survey: SurveyRegistRequest) => Promise<number>
updateSurvey: ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string }) => void
deleteSurvey: () => Promise<boolean>
submitSurvey: (params: { saveId?: number; targetId?: string; targetNm?: string; storeId?: string; srlNo?: string }) => void
submitSurvey: (params: { saveId?: number; targetId?: string; storeId?: string; srlNo?: string }) => void
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
getZipCode: (zipCode: string) => Promise<ZipCode[] | null>
refetchSurveyList: () => void
@ -163,11 +163,10 @@ export function useSurvey(id?: number): {
})
const { mutateAsync: submitSurvey, isPending: isSubmittingSurvey } = useMutation({
mutationFn: async ({ targetId, targetNm, storeId, srlNo }: { targetId?: string; targetNm?: string; storeId?: string; srlNo?: string }) => {
mutationFn: async ({ targetId, storeId, srlNo }: { targetId?: string; storeId?: string; srlNo?: string }) => {
if (!id) throw new Error('id is required')
const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, {
targetId,
targetNm,
storeId,
srlNo,
role: session?.role ?? null,

View File

@ -11,7 +11,6 @@ export default function ReactQueryProviders({ children }: React.PropsWithChildre
defaultOptions: {
queries: {
staleTime: 60 * 1000,
retry: false,
},
},
}),

View File

@ -27,7 +27,6 @@ export interface SessionData {
storeLvl: null
custCd: null
builderNo: null
builderNm: null | string
isLoggedIn: boolean
role: string | null
}

View File

@ -5,7 +5,6 @@ export type SurveyBasicInfo = {
store: string | null
storeId: string | null
constructionPoint: string | null
constructionPointId: string | null
investigationDate: string | null
buildingName: string | null
customerName: string | null
@ -18,7 +17,6 @@ export type SurveyBasicInfo = {
regDt: Date
uptDt: Date
submissionTargetId: string | null
submissionTargetNm: string | null
srlNo: string | null //판매점IDyyMMdd000
}
@ -70,7 +68,6 @@ export type SurveyBasicRequest = {
store: string | null
storeId: string | null
constructionPoint: string | null
constructionPointId: string | null
investigationDate: string | null
buildingName: string | null
customerName: string | null
@ -80,7 +77,6 @@ export type SurveyBasicRequest = {
submissionStatus: boolean
submissionDate: string | null
submissionTargetId: string | null
submissionTargetNm: string | null
srlNo: string | null //판매점IDyyMMdd000
}