Compare commits

..

No commits in common. "431512f5bf11ef696e4c178df5c00f20bee29ca0" and "bdbdf9997f897a896c9973916efefa18bb1498a6" have entirely different histories.

34 changed files with 125 additions and 584 deletions

View File

@ -1,4 +1,3 @@
NEXT_PUBLIC_RUN_MODE=development
# 모바일 디바이스로 로컬 서버 확인하려면 자신 IP 주소로 변경
# 다시 로컬에서 개발할때는 localhost로 변경
#route handler

View File

@ -1,18 +0,0 @@
NEXT_PUBLIC_RUN_MODE=local
# 모바일 디바이스로 로컬 서버 확인하려면 자신 IP 주소로 변경
# 다시 로컬에서 개발할때는 localhost로 변경
#route handler
NEXT_PUBLIC_API_URL=http://localhost:3000
#qsp 로그인 api
NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
#1:1문의 api
NEXT_PUBLIC_INQUIRY_API_URL=http://1.248.227.176:38080
#QPARTNER 로그인 api
DB_HOST=202.218.61.226
DB_USER=readonly
DB_PASSWORD=aAjmFW12iHKW84l1
DB_DATABASE=qpartners
DB_PORT=3306

View File

@ -1,6 +1,5 @@
NEXT_PUBLIC_RUN_MODE=production
#route handler
NEXT_PUBLIC_API_URL=http://1.248.227.176:3000
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

@ -3,15 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "env-cmd -f .env.localhost next dev --turbopack",
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"build:local": "env-cmd -f .env.localhost next build",
"build:dev": "env-cmd -f .env.development next build",
"build:prod": "env-cmd -f .env.production next build",
"start:local": "env-cmd -f .env.localhost next start",
"start:dev": "env-cmd -f .env.development next start",
"start:prod": "env-cmd -f .env.production next start",
"lint": "next lint"
},
"dependencies": {
@ -19,7 +13,6 @@
"@tanstack/react-query": "^5.71.0",
"@tanstack/react-query-devtools": "^5.71.0",
"axios": "^1.8.4",
"env-cmd": "^10.1.0",
"iron-session": "^8.0.4",
"lucide": "^0.503.0",
"mssql": "^11.0.1",

63
pnpm-lock.yaml generated
View File

@ -20,9 +20,6 @@ importers:
axios:
specifier: ^1.8.4
version: 1.8.4
env-cmd:
specifier: ^10.1.0
version: 10.1.0
iron-session:
specifier: ^8.0.4
version: 8.0.4
@ -804,10 +801,6 @@ packages:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
@ -815,10 +808,6 @@ packages:
core-js@3.41.0:
resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
@ -877,11 +866,6 @@ packages:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
env-cmd@10.1.0:
resolution: {integrity: sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==}
engines: {node: '>=8.0.0'}
hasBin: true
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@ -1037,9 +1021,6 @@ packages:
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
engines: {node: '>=16'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
@ -1236,10 +1217,6 @@ packages:
resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==}
engines: {node: '>=18'}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
@ -1347,14 +1324,6 @@ packages:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
@ -1454,11 +1423,6 @@ packages:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
zustand@5.0.3:
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
engines: {node: '>=12.20.0'}
@ -2106,19 +2070,11 @@ snapshots:
commander@11.1.0: {}
commander@4.1.1: {}
cookie@0.7.2: {}
core-js@3.41.0:
optional: true
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
@ -2167,11 +2123,6 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.2.1
env-cmd@10.1.0:
dependencies:
commander: 4.1.1
cross-spawn: 7.0.6
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@ -2346,8 +2297,6 @@ snapshots:
dependencies:
is-inside-container: 1.0.0
isexe@2.0.0: {}
jiti@2.4.2: {}
js-md4@0.3.2: {}
@ -2551,8 +2500,6 @@ snapshots:
is-inside-container: 1.0.0
is-wsl: 3.1.0
path-key@3.1.1: {}
performance-now@2.1.0:
optional: true
@ -2675,12 +2622,6 @@ snapshots:
'@img/sharp-win32-x64': 0.33.5
optional: true
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
simple-swizzle@0.2.2:
dependencies:
is-arrayish: 0.3.2
@ -2762,10 +2703,6 @@ snapshots:
uuid@8.3.2: {}
which@2.0.2:
dependencies:
isexe: 2.0.0
zustand@5.0.3(@types/react@19.0.12)(react@19.1.0):
optionalDependencies:
'@types/react': 19.0.12

View File

@ -0,0 +1,3 @@
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 5L5 1L9 5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 207 B

View File

@ -15,9 +15,7 @@ export async function POST(request: Request) {
})
console.log('🚀 ~ result ~ result:', result.data)
let finalResult = {}
if (result.data.result.resultCode === 'S') {
if (result.data.result.code === 200) {
tracking({
url: `/api/auth/login`,
data: JSON.stringify({
@ -74,64 +72,52 @@ export async function POST(request: Request) {
console.log('end session edit!')
await session.save()
const resultForSession = {
LANG_CD: result.data.data.langCd,
CURR_PAGE: result.data.data.currPage,
ROW_COUNT: result.data.data.rowCount,
START_ROW: result.data.data.startRow,
END_ROW: result.data.data.endRow,
COMP_CD: result.data.data.compCd,
AGENCY_STORE_ID: result.data.data.agencyStoreId,
STORE_ID: result.data.data.storeId,
STORE_NM: result.data.data.storeNm,
USER_ID: result.data.data.userId,
CATEGORY: result.data.data.category,
USER_NM: result.data.data.userNm,
USER_NM_KANA: result.data.data.userNmKana,
TEL_NO: result.data.data.telNo,
FAX: result.data.data.fax,
EMAIL: result.data.data.email,
LAST_EDIT_USER: result.data.data.lastEditUser,
STORE_GUBUN: result.data.data.storeGubun,
PW_CURR: result.data.data.pwCurr,
PWD_INIT_YN: result.data.data.pwdInitYn,
APPR_STAT_CD: result.data.data.apprStatCd,
LOGIN_FAIL_CNT: result.data.data.loginFailCnt,
LOGIN_FAIL_MIN_YN: result.data.data.loginFailMinYn,
PRICE_VIEW_STAT_CD: result.data.data.priceViewStatCd,
GROUP_ID: result.data.data.groupId,
STORE_LVL: result.data.data.storeLvl,
CUST_CD: result.data.data.custCd,
BUILDER_NO: result.data.data.builderNo,
IS_LOGGED_IN: true,
ROLE: '',
}
if (result.data.data.userId === 'T01') {
resultForSession.ROLE = 'T01'
} else if (result.data.data.groupId === '60000') {
resultForSession.ROLE = 'Admin'
} else if (result.data.data.groupId === '70000' && result.data.data.builderNo === null) {
resultForSession.ROLE = 'Admin_Sub'
} else if (result.data.data.groupId === '70000' && result.data.data.builderNo !== null) {
resultForSession.ROLE = 'Builder'
} else {
resultForSession.ROLE = 'User'
}
finalResult = {
code: 200,
message: 'Login is Succecss!!',
result: resultForSession,
}
} else {
finalResult = {
code: 400,
message: 'Login is Failed!!',
result: {},
}
}
return NextResponse.json(finalResult)
const resultForSession = {
LANG_CD: result.data.data.langCd,
CURR_PAGE: result.data.data.currPage,
ROW_COUNT: result.data.data.rowCount,
START_ROW: result.data.data.startRow,
END_ROW: result.data.data.endRow,
COMP_CD: result.data.data.compCd,
AGENCY_STORE_ID: result.data.data.agencyStoreId,
STORE_ID: result.data.data.storeId,
STORE_NM: result.data.data.storeNm,
USER_ID: result.data.data.userId,
CATEGORY: result.data.data.category,
USER_NM: result.data.data.userNm,
USER_NM_KANA: result.data.data.userNmKana,
TEL_NO: result.data.data.telNo,
FAX: result.data.data.fax,
EMAIL: result.data.data.email,
LAST_EDIT_USER: result.data.data.lastEditUser,
STORE_GUBUN: result.data.data.storeGubun,
PW_CURR: result.data.data.pwCurr,
PWD_INIT_YN: result.data.data.pwdInitYn,
APPR_STAT_CD: result.data.data.apprStatCd,
LOGIN_FAIL_CNT: result.data.data.loginFailCnt,
LOGIN_FAIL_MIN_YN: result.data.data.loginFailMinYn,
PRICE_VIEW_STAT_CD: result.data.data.priceViewStatCd,
GROUP_ID: result.data.data.groupId,
STORE_LVL: result.data.data.storeLvl,
CUST_CD: result.data.data.custCd,
BUILDER_NO: result.data.data.builderNo,
IS_LOGGED_IN: true,
ROLE: '',
}
if (result.data.data.userId === 'T01') {
resultForSession.ROLE = 'T01'
} else if (result.data.data.groupId === '60000') {
resultForSession.ROLE = 'Admin'
} else if (result.data.data.groupId === '70000' && result.data.data.builderNo === null) {
resultForSession.ROLE = 'Admin_Sub'
} else if (result.data.data.groupId === '70000' && result.data.data.builderNo !== null) {
resultForSession.ROLE = 'Builder'
} else {
resultForSession.ROLE = 'User'
}
return NextResponse.json({ code: 200, message: 'Login is Succecss!!', result: resultForSession })
}

View File

@ -32,7 +32,6 @@ const SEARCH_OPTIONS = [
'POST_CODE', // 우편번호
'ADDRESS', // 주소
'ADDRESS_DETAIL', // 상세주소
'SRL_NO', // 등록번호
] as const
// 페이지당 항목 수
@ -51,6 +50,13 @@ const createKeywordSearchCondition = (keyword: string, searchOption: string): Wh
// 모든 필드 검색 시 OR 조건 사용
where.OR = []
// ID가 숫자인 경우 ID 검색 조건 추가
if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) {
where.OR.push({
ID: { equals: Number(keyword) },
})
}
where.OR.push(
...SEARCH_OPTIONS.map((field) => ({
[field]: { contains: keyword },
@ -59,6 +65,15 @@ const createKeywordSearchCondition = (keyword: string, searchOption: string): Wh
} else if (SEARCH_OPTIONS.includes(searchOption.toUpperCase() as any)) {
// 특정 필드 검색
where[searchOption.toUpperCase()] = { contains: keyword }
} else if (searchOption === 'id') {
// ID 검색 (숫자 변환 필요)
const number = Number(keyword)
if (!isNaN(number)) {
where.ID = { equals: number }
} else {
// 유효하지 않은 ID 검색 시 빈 결과 반환
where.ID = { equals: null }
}
}
return where
}

View File

@ -113,10 +113,6 @@ export default function page() {
<input type="checkbox" id="ch06" disabled />
<label htmlFor="ch06">Check Box</label>
</div>
<div className="check-form-box space">
<input type="checkbox" id="ch07" defaultChecked />
<label htmlFor="ch07">Check Box</label>
</div>
</div>
</div>
<div className="design-box">

View File

@ -5,8 +5,9 @@ import { useEffect, useReducer, useState } from 'react'
import { useRouter } from 'next/navigation'
import { useLocalStorage } from 'usehooks-ts'
import { useQuery } from '@tanstack/react-query'
import { axiosInstance } from '@/libs/axios'
import { useSessionStore } from '@/store/session'
import { useAxios } from '@/hooks/useAxios'
interface AccountState {
loginId: string
pwd: string
@ -23,8 +24,6 @@ export default function Login() {
//로그인 상태
const [isLogin, setIsLogin] = useState(false)
const { axiosInstance } = useAxios()
const { session, setSession } = useSessionStore()
const [value, setValue, removeValue] = useLocalStorage<{ indivisualData: string }>('hanasysIndivisualState', { indivisualData: '' })
@ -39,26 +38,6 @@ export default function Login() {
return emailRegex.test(email)
}
const handleLogin = () => {
if (validateLogin()) {
setIsLogin(true)
}
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleLogin()
}
}
const validateLogin = () => {
if (account.loginId === '' || account.pwd === '') {
alert('Please enter your ID and password.')
return false
}
return true
}
interface LoginData {
code: number
message: string | null
@ -105,9 +84,6 @@ export default function Login() {
...loginData?.result,
})
router.push('/')
} else if (loginData?.code === 400) {
alert(loginData?.message)
setAccount({ loginId: '', pwd: '' })
}
}, [loginData])
@ -129,7 +105,6 @@ export default function Login() {
className="login-frame"
placeholder="Input Frame ID"
value={account.loginId}
onKeyDown={(e) => handleKeyDown(e)}
onChange={(e) => setAccount({ loginId: e.target.value })}
/>
<button className="login-icon" onClick={() => setAccount({ loginId: '' })}>
@ -144,7 +119,6 @@ export default function Login() {
className="login-frame"
placeholder="Input Frame PW"
value={account.pwd}
onKeyDown={(e) => handleKeyDown(e)}
onChange={(e) => setAccount({ pwd: e.target.value })}
/>
<button className={`login-icon ${pwShow ? 'act' : ''}`} onClick={() => setPwShow(!pwShow)}>
@ -166,7 +140,7 @@ export default function Login() {
</div>
</div>
<div className="login-btn-wrap">
<button className="btn-frame icon login" onClick={handleLogin}>
<button className="btn-frame icon login" onClick={() => setIsLogin(true)}>
<i className="btn-arr"></i>
</button>
</div>

View File

@ -27,7 +27,7 @@ export default function ListTable() {
}, [pathname])
useEffect(() => {
if (!session.isLoggedIn || isLoadingSurveyList) return
if (!session.isLoggedIn || !('data' in surveyList)) return
if ('count' in surveyList && surveyList.count > 0) {
if (offset > 0) {
setHeldSurveyList((prev) => [...prev, ...surveyList.data])
@ -45,12 +45,14 @@ export default function ListTable() {
router.push(`/survey-sale/${id}`)
}
// TODO: 로딩 처리 필요
return (
<>
<SearchForm memberRole={session?.role ?? ''} userNm={session?.userNm ?? ''} />
<div className="sale-frame">
{heldSurveyList.length > 0 ? (
<ul className="sale-list-wrap">
<div className="sale-frame">
{heldSurveyList.length > 0 ? (
<ul className="sale-list-wrap">
{heldSurveyList.map((survey) => (
<li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetailClick(survey.id)}>
<div className="sale-item-bx">
@ -65,18 +67,18 @@ export default function ListTable() {
<div className="sale-item-update">{new Date(survey.uptDt).toLocaleString()}</div>
</div>
</div>
</li>
))}
</ul>
) : (
<div className="compliace-nosearch">
<span className="mb10"></span>
</li>
))}
</ul>
) : (
<div className="compliace-nosearch">
<span className="mb10"></span>
</div>
)}
<div className="sale-edit-btn">
<LoadMoreButton hasMore={hasMore} onLoadMore={() => setOffset(offset + 10)} />
</div>
)}
<div className="sale-edit-btn">
<LoadMoreButton hasMore={hasMore} onLoadMore={() => setOffset(offset + 10)} />
</div>
</div>
</>
)
}

View File

@ -6,7 +6,7 @@ import { useState } from 'react'
export default function SearchForm({ memberRole, userNm }: { memberRole: string; userNm: string }) {
const router = useRouter()
const { setSearchOption, setSort, setIsMySurvey, setKeyword, reset, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore()
const { setSearchOption, setSort, setIsMySurvey, setKeyword, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore()
const [searchKeyword, setSearchKeyword] = useState(keyword)
const [option, setOption] = useState(searchOption)
@ -15,7 +15,6 @@ export default function SearchForm({ memberRole, userNm }: { memberRole: string;
alert('2文字以上入力してください')
return
}
reset()
setKeyword(searchKeyword)
setSearchOption(option)
}

View File

@ -1,7 +1,6 @@
'use client'
import Link from 'next/link'
import Config from '@/config/config.export'
export default function Footer() {
return (
@ -12,9 +11,6 @@ export default function Footer() {
<span>
<Link href="/pdf">PDF</Link>
</span>
<span>{Config().mode}</span>
<span>{Config().baseUrl}</span>
<span>{process.env.NEXT_PUBLIC_API_URL}</span>
</div>
</footer>
</>

View File

@ -13,13 +13,14 @@ import { useSessionStore } from '@/store/session'
import { usePopupController } from '@/store/popupController'
import { useTitle } from '@/hooks/useTitle'
import { useAxios } from '@/hooks/useAxios'
import { axiosInstance } from '@/libs/axios'
import 'swiper/css'
export default function Header() {
const router = useRouter()
const pathname = usePathname()
const { axiosInstance } = useAxios()
const [value, setValue, removeValue] = useLocalStorage<{ indivisualData: string }>('hanasysIndivisualState', { indivisualData: '' })
const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState()
const { backBtn } = useHeaderStore()

View File

@ -1,7 +0,0 @@
export default function Spinner() {
return (
<div className="spinner-wrap">
<span className="loader"></span>
</div>
)
}

View File

@ -1,20 +0,0 @@
export declare namespace ICommonConfig {
export type Mode = 'local' | 'development' | 'production'
export interface Params {
baseUrl: string
mode: Mode
}
}
// local, development, production 과 관계없이 동일한 값으로 반환되는 부분은 해당 함수의 return 되는 부분만 수정하면 됩니다. (달라져야 하는 값이 아닌, 같은 값에 대해서는 local, development, production 파일을 모두 수정할 필요가 없어지게 됩니다.)
export default function getConfigs(params: ICommonConfig.Params) {
// local, development, production 마다 달라지는 값
const { baseUrl, mode } = params
// 공통으로 반환되는 구조
return {
baseUrl,
mode,
}
}

View File

@ -1,13 +0,0 @@
import getConfigs from '@/config/config.common'
// 환경마다 달라져야 할 변수, 값들을 정의합니다. (여기는 development 환경에 맞는 값을 지정합니다.)
const baseUrl = 'http://1.248.227.176:3000'
const mode = 'development'
// 환경마다 달라져야 할 값들을 getConfig 함수에 전달합니다.
const configDevelopment = getConfigs({
baseUrl,
mode,
})
export default configDevelopment

View File

@ -1,19 +0,0 @@
import configDevelopment from './config.development'
import configLocal from './config.local'
import configProduction from './config.production'
// 클라이언트에서는 이 함수를 사용하여 config 값을 참조합니다.
const Config = () => {
switch (process.env.NEXT_PUBLIC_RUN_MODE) {
case 'local':
return configLocal
case 'development':
return configDevelopment
case 'production':
return configProduction
default:
return configLocal
}
}
export default Config

View File

@ -1,13 +0,0 @@
import getConfigs from '@/config/config.common'
// 환경마다 달라져야 할 변수, 값들을 정의합니다. (여기는 local 환경에 맞는 값을 지정합니다.)
const baseUrl = 'http://localhost:3000'
const mode = 'local'
// 환경마다 달라져야 할 값들을 getConfig 함수에 전달합니다.
const configLocal = getConfigs({
baseUrl,
mode,
})
export default configLocal

View File

@ -1,13 +0,0 @@
import getConfigs from '@/config/config.common'
// 환경마다 달라져야 할 변수, 값들을 정의합니다. (여기는 production 환경에 맞는 값을 지정합니다.)
const baseUrl = 'http://localhost.prod:3000'
const mode = 'production'
// 환경마다 달라져야 할 값들을 getConfig 함수에 전달합니다.
const configProduction = getConfigs({
baseUrl,
mode,
})
export default configProduction

View File

@ -1,119 +0,0 @@
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import Config from '@/config/config.export'
import { useSpinnerStore } from '@/store/spinnerStore'
export function useAxios() {
// const { setIsShow } = useSpinnerStore()
const requestHandler = (config: InternalAxiosRequestConfig) => {
// setIsShow(true)
return config
}
const responseHandler = (response: AxiosResponse) => {
// setIsShow(false)
response.data = transferResponse(response)
return response
}
const errorHandler = (error: any) => {
// setIsShow(false)
return Promise.reject(error)
}
const createAxiosInstance = (url: string | null | undefined) => {
const baseURL = url || Config().baseUrl
return axios.create({
baseURL,
headers: {
Accept: 'application/json',
},
})
}
const axiosInstance = (url: string | null | undefined) => {
const instance = axios.create({
baseURL: url || Config().baseUrl,
headers: {
Accept: 'application/json',
},
})
instance.interceptors.request.use(
// (config) => {
// return config
// },
// (error) => {
// return Promise.reject(error)
// },
(config) => requestHandler(config),
(error) => errorHandler(error),
)
instance.interceptors.response.use(
// (response) => {
// response.data = transferResponse(response)
// return response
// },
// (error) => {
// return Promise.reject(error)
// },
(response) => responseHandler(response),
(error) => errorHandler(error),
)
return instance
}
// 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) => {
let transformedKey = key
// Handle uppercase snake_case (e.g., USER_NAME -> userName)
// Handle lowercase snake_case (e.g., user_name -> userName)
if (/^[A-Z_]+$/.test(key) || /^[a-z_]+$/.test(key)) {
transformedKey = snakeToCamel(key)
}
// Handle single uppercase word (e.g., ROLE -> role)
else if (/^[A-Z]+$/.test(key)) {
transformedKey = key.toLowerCase()
}
// Preserve existing camelCase
acc[transformedKey] = transformObjectKeys(obj[key])
return acc
}, {})
}
return obj
}
const snakeToCamel = (str: string): string => {
return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''))
}
return {
axiosInstance,
transferResponse,
transformObjectKeys,
}
}

View File

@ -1,10 +1,11 @@
import type { SurveyBasicInfo, SurveyDetailInfo, SurveyDetailRequest, SurveyDetailCoverRequest, SurveyRegistRequest } from '@/types/Survey'
import { useMemo } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { SurveyBasicInfo, SurveyDetailInfo, SurveyDetailRequest, SurveyDetailCoverRequest, SurveyRegistRequest } from '@/types/Survey'
import { axiosInstance } from '@/libs/axios'
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
import { useSessionStore } from '@/store/session'
import { useAxios } from './useAxios'
import { queryStringFormatter } from '@/utils/common-utils'
import { useSessionStore } from '@/store/session'
import { useMemo } from 'react'
import { AxiosResponse } from 'axios'
export const requiredFields = [
{
@ -74,7 +75,6 @@ export function useServey(id?: number): {
const queryClient = useQueryClient()
const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore()
const { session } = useSessionStore()
const { axiosInstance } = useAxios()
const {
data: surveyListData,

View File

@ -1,4 +1,3 @@
import { useSpinnerStore } from '@/store/spinnerStore'
import axios from 'axios'
export const axiosInstance = (url: string | null | undefined) => {

View File

@ -1,8 +1,7 @@
import { useAxios } from '@/hooks/useAxios'
import { axiosInstance } from './axios'
export const tracking = async (params: { url: string; data: string }) => {
const { url, data } = params
const { axiosInstance } = useAxios()
const result = await axiosInstance(null).post('/api/tracking', {
url,
data,

View File

@ -10,9 +10,9 @@ export async function middleware(request: NextRequest) {
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
// todo: 로그인 기능 추가 시 주석 해제
// if (!session.isLoggedIn) {
// return NextResponse.redirect(new URL('/login', request.url))
// }
if (!session.isLoggedIn) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
@ -22,5 +22,5 @@ export async function middleware(request: NextRequest) {
// 2. /admin/* (exclude all routes under /admin)
// 3. /_next/* (exclude Next.js static and image assets)
export const config = {
matcher: ['/((?!login|assets).*)', '/((?!_next/static|_next/image|favicon.ico).*)'],
matcher: ['/((?!dashboard|login|admin|api|_next/static|_next/image|favicon.ico).*)'],
}

View File

@ -8,8 +8,6 @@ import { usePopupController } from '@/store/popupController'
import { useSideNavState } from '@/store/sideNavState'
import { useSessionStore } from '@/store/session'
import { tracking } from '@/libs/tracking'
import Spinner from '@/components/ui/common/Spinner'
import { useSpinnerStore } from '@/store/spinnerStore'
declare global {
interface Window {
@ -30,7 +28,12 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp
const { reset } = useSideNavState()
const { setAlertMsg, setAlertBtn, setAlert, setAlert2, setAlert2BtnYes, setAlert2BtnNo } = usePopupController()
const { session, setSession } = useSessionStore()
const { isShow, setIsShow } = useSpinnerStore()
if (pathname === '/login') {
if (session?.isLoggedIn) {
router.push('/')
}
}
/**
*
@ -69,17 +72,6 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp
}
useEffect(() => {
if (pathname === '/login') {
if (session?.isLoggedIn) {
router.push('/')
}
}
if (pathname === '/') {
if (!session?.isLoggedIn) {
router.push('/login')
}
}
//alert 함수 변경해서 바인딩
window.alert = function (msg, alertBtn = () => setAlert(false)) {
alertFunc(msg, alertBtn)
@ -119,10 +111,5 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp
handlePageEvent(pathname)
}, [pathname])
return (
<>
{children}
{isShow && <Spinner />}
</>
)
return <>{children}</>
}

View File

@ -1,21 +0,0 @@
import { create } from 'zustand'
type SpinnerState = {
isShow: boolean
setIsShow: (isShow: boolean) => void
resetCount: () => void
}
type InitialState = {
isShow: boolean
}
const initialState: InitialState = {
isShow: false,
}
export const useSpinnerStore = create<SpinnerState>((set) => ({
...initialState,
setIsShow: (isShow: boolean) => set({ isShow }),
resetCount: () => set(initialState),
}))

View File

@ -6,7 +6,7 @@ export const SEARCH_OPTIONS = [
label: '全体',
},
{
id: 'srl_no',
id: 'id',
label: '登録番号',
},
{
@ -41,7 +41,7 @@ export const SEARCH_OPTIONS_PARTNERS = [
label: '全体',
},
{
id: 'srl_no',
id: 'id',
label: '登録番号',
},
{

View File

@ -49,6 +49,14 @@
background-size: cover;
margin-left: 12px;
}
.btn-arr-up{
display: block;
width: 10px;
height: 6px;
background: url(/assets/images/common/btn_arr_up.svg)no-repeat center;
background-size: cover;
margin-left: 12px;
}
.btn-edit{
display: block;
width: 10px;

View File

@ -98,23 +98,6 @@
color: #8595A7;
}
}
&.space{
label{
&::after{
top: 8px;
left: 0px;
width: 10px;
height: 2px;
border: none;
background-color: transparent;
transform: translate(50%, 50%);
-ms-transform: none;
}
}
input[type="checkbox"]:checked + label::after{
background-color: #fff;
}
}
}
// radio box
@ -218,7 +201,7 @@
}
}
input:checked + .slider {
background-color: #0081b5;
background-color: #A8B6C7;
&:after {
content: '';
left: 10px;

View File

@ -1,6 +1,4 @@
@forward 'main';
@forward 'login';
@forward 'pop-contents';
@forward 'sub';
@forward 'pdfview';
@forward 'spinner';
@forward 'sub';

View File

@ -1,57 +0,0 @@
@use "../abstracts" as *;
.pdf-contents{
padding: 0 20px;
border-top: 1px solid #ececec;
}
.pdf-cont-head{
align-items: center;
padding: 24px 0 15px;
border-bottom: 2px solid $black-1010;
.pdf-cont-head-tit{
@include defaultFont($font-s-16, $font-w-600, $black-1010);
margin-bottom: 10px;
}
}
.pdf-cont-head-data-wrap{
@include flex(20px);
align-items: center;
.pdf-cont-head-data-tit{
@include defaultFont($font-s-13, $font-w-500, $black-1010);
}
.pdf-cont-head-data{
@include defaultFont($font-s-13, $font-w-400, #FF5656);
}
}
.pdf-cont-body{
padding: 24px 0 0;
}
.pdf-data-tit{
@include defaultFont($font-s-13, $font-w-500, $black-1010);
margin-bottom: 5px;
}
.pdf-table{
margin-bottom: 24px;
table{
width: 100%;
table-layout: fixed;
border-collapse: collapse;
th{
padding: 9.5px;
@include defaultFont($font-s-11, $font-w-500, $black-1010);
border: 1px solid #2E3A59;
background-color: #F5F6FA;
}
td{
padding: 9.5px;
@include defaultFont($font-s-11, $font-w-400, #FF5656);
border: 1px solid #2E3A59;
}
}
}
.pdf-textarea-data{
padding: 10px;
@include defaultFont($font-s-11, $font-w-400, #FF5656);
border: 1px solid $black-1010;
min-height: 150px;
}

View File

@ -103,12 +103,16 @@
@include defaultFont($font-s-13, $font-w-400, $font-c);
}
.pop-data-table-footer{
@include flex(0px);
.pop-data-table-footer-unit{
flex: 1;
padding: 10px;
@include defaultFont($font-s-13, $font-w-500, $font-c);
border-bottom: 1px solid #2E3A59;
border-right: 1px solid #2E3A59;
}
.pop-data-table-footer-data{
flex: none;
width: 104px;
padding: 10px;
@include defaultFont($font-s-13, $font-w-400, $font-c);
}

View File

@ -1,37 +0,0 @@
.spinner-wrap{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba($color: #101010, $alpha: 0.5);
z-index: 2000000;
}
.loader {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #fff;
box-shadow: 32px 0 #fff, -32px 0 #fff;
position: relative;
animation: flash 0.5s ease-out infinite alternate;
}
@keyframes flash {
0% {
background-color: #FFF2;
box-shadow: 32px 0 #FFF2, -32px 0 #FFF;
}
50% {
background-color: #FFF;
box-shadow: 32px 0 #FFF2, -32px 0 #FFF2;
}
100% {
background-color: #FFF2;
box-shadow: 32px 0 #FFF, -32px 0 #FFF2;
}
}