diff --git a/public/assets/images/common/btn_arr_up.svg b/public/assets/images/common/btn_arr_up.svg deleted file mode 100644 index b90389f..0000000 --- a/public/assets/images/common/btn_arr_up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/app/api/survey-sales/[id]/route.ts b/src/app/api/survey-sales/[id]/route.ts index 415d1e6..db5c9df 100644 --- a/src/app/api/survey-sales/[id]/route.ts +++ b/src/app/api/survey-sales/[id]/route.ts @@ -19,34 +19,55 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ } } +const getNewSrlNo = async (srlNo: string, storeId: string) => { + let newSrlNo = srlNo + console.log('srlNo:: ', srlNo) + if (srlNo.startsWith('一時保存')) { + //@ts-ignore + const lastSurvey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({ + where: { + SRL_NO: { + startsWith: storeId, + }, + }, + orderBy: { + ID: 'desc', + }, + }) + const lastNo = lastSurvey ? parseInt(lastSurvey.SRL_NO.slice(-3)) : 0 + newSrlNo = + storeId + + new Date().getFullYear().toString().slice(-2) + + (new Date().getMonth() + 1).toString().padStart(2, '0') + + new Date().getDate().toString().padStart(2, '0') + + (lastNo + 1).toString().padStart(3, '0') + } + return newSrlNo +} + export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params const body = await request.json() - const { DETAIL_INFO, ...basicInfo } = body + const { detailInfo, ...basicInfo } = body.survey - console.log('body:: ', body) + // PUT 요청 시 임시저장 여부 확인 후 임시저장 시 기존 SRL_NO 사용, 기본 저장 시 새로운 SRL_NO 생성 + const newSrlNo = body.isTemporary ? body.survey.srlNo : await getNewSrlNo(body.survey.srlNo, body.storeId) // @ts-ignore const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ where: { ID: Number(id) }, data: { ...convertToSnakeCase(basicInfo), + SRL_NO: newSrlNo, UPT_DT: new Date(), - DETAIL_INFO: DETAIL_INFO ? { - upsert: { - create: convertToSnakeCase(DETAIL_INFO), - update: convertToSnakeCase(DETAIL_INFO), - where: { - BASIC_INFO_ID: Number(id) - } - } - } : undefined + DETAIL_INFO: { + update: convertToSnakeCase(detailInfo), + }, }, include: { - DETAIL_INFO: true - } + DETAIL_INFO: true, + }, }) - console.log('survey:: ', survey) return NextResponse.json(survey) } catch (error) { console.error('Error updating survey:', error) @@ -92,49 +113,24 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< const { id } = await params const body = await request.json() - if (body.submit) { + // 제출 시 기존 SRL_NO 확인 후 '임시저장'으로 시작하면 새로운 SRL_NO 생성 + const newSrlNo = await getNewSrlNo(body.srlNo, body.storeId) + + if (body.targetId) { // @ts-ignore const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ where: { ID: Number(id) }, data: { SUBMISSION_STATUS: true, SUBMISSION_DATE: new Date(), + SUBMISSION_TARGET_ID: body.targetId, UPT_DT: new Date(), + SRL_NO: newSrlNo, }, }) + console.log(survey) return NextResponse.json({ message: 'Survey confirmed successfully' }) } - // } else { - // // @ts-ignore - // const hasDetails = await prisma.SD_SURVEY_SALES_DETAIL_INFO.findUnique({ - // where: { BASIC_INFO_ID: Number(id) }, - // }) - - // if (hasDetails) { - // //@ts-ignore - // const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ - // where: { ID: Number(id) }, - // data: { - // UPT_DT: new Date(), - // DETAIL_INFO: { - // update: convertToSnakeCase(body.DETAIL_INFO), - // }, - // }, - // }) - // return NextResponse.json(result) - // } else { - // // @ts-ignore - // const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ - // where: { ID: Number(id) }, - // data: { - // DETAIL_INFO: { - // create: convertToSnakeCase(body.DETAIL_INFO), - // }, - // }, - // }) - // return NextResponse.json({ message: 'Survey detail created successfully' }) - // } - // } } catch (error) { console.error('Error updating survey:', error) return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 }) diff --git a/src/app/api/survey-sales/route.ts b/src/app/api/survey-sales/route.ts index 3298f5d..7d51802 100644 --- a/src/app/api/survey-sales/route.ts +++ b/src/app/api/survey-sales/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' import { convertToSnakeCase } from '@/utils/common-utils' +import { equal } from 'assert' /** * 검색 파라미터 */ @@ -87,13 +88,14 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { switch (params.role) { case 'Admin': // 1차점 - // 같은 판매점에서 작성된 매물 + 2차점에서 제출받은 매물 where.OR = [ { + // 같은 판매점에서 작성한 제출/제출되지 않은 매물 AND: [{ STORE: { equals: params.store } }], }, { - AND: [{ STORE: { startsWith: params.store } }, { SUBMISSION_STATUS: { equals: true } }], + // MUSUBI (시공권한 X) 가 ORDER 에 제출한 매물 + AND: [{ SUBMISSION_TARGET_ID: { equals: params.store } }, { SUBMISSION_STATUS: { equals: true } }], }, ] break @@ -101,6 +103,7 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { case 'Admin_Sub': // 2차점 where.OR = [ { + // MUSUBI (시공권한 X) 같은 판매점에서 작성한 제출/제출되지 않은 매물 AND: [ { STORE: { equals: params.store } }, { @@ -109,8 +112,9 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { ], }, { + // MUSUBI (시공권한 O) 가 MUSUBI 에 제출한 매물 + PARTNER 가 제출한 매물 AND: [ - { STORE: { equals: params.store } }, + { SUBMISSION_TARGET_ID: { equals: params.store } }, { CONSTRUCTION_POINT: { not: null } }, { CONSTRUCTION_POINT: { not: '' } }, { SUBMISSION_STATUS: { equals: true } }, @@ -119,8 +123,8 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { ] break - case 'Builder': // 2차점 시공권한 - case 'Partner': // Partner + case 'Builder': // MUSUBI (시공권한 O) + case 'Partner': // PARTNER // 같은 시공ID에서 작성된 매물 where.AND?.push({ CONSTRUCTION_POINT: { equals: params.builderNo }, @@ -128,6 +132,21 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { break case 'T01': + where.OR = [ + { + NOT: { + SRL_NO: { + startsWith: '一時保存', + }, + }, + }, + { + STORE: { + equals: params.store, + }, + }, + ] + break case 'User': // 모든 매물 조회 가능 (추가 조건 없음) break @@ -219,22 +238,47 @@ export async function PUT(request: Request) { } } -export async function POST(request: Request) { +export async function POST(request: Request) { try { const body = await request.json() - console.log('body:: ', body) - const { detailInfo, ...basicInfo } = body + // 임시 저장 시 임시저장 + 000 으로 저장 + // 기본 저장 시 판매점ID + yyMMdd + 000 으로 저장 + const baseSrlNo = + body.survey.srlNo ?? + body.storeId + + new Date().getFullYear().toString().slice(-2) + + (new Date().getMonth() + 1).toString().padStart(2, '0') + + new Date().getDate().toString().padStart(2, '0') - // 기본 정보 생성 - //@ts-ignore + // @ts-ignore + const lastSurvey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({ + where: { + SRL_NO: { + startsWith: body.storeId, + }, + }, + orderBy: { + SRL_NO: 'desc', + }, + }) + + // 마지막 번호 추출 + const lastNumber = lastSurvey ? parseInt(lastSurvey.SRL_NO.slice(-3)) : 0 + + // 새로운 srlNo 생성 - 임시저장일 경우 '임시저장' 으로 저장 + const newSrlNo = baseSrlNo.startsWith('一時保存') ? baseSrlNo : baseSrlNo + (lastNumber + 1).toString().padStart(3, '0') + + const { detailInfo, ...basicInfo } = body.survey + // @ts-ignore const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({ data: { ...convertToSnakeCase(basicInfo), + SRL_NO: newSrlNo, DETAIL_INFO: { - create: convertToSnakeCase(detailInfo) - } - } + create: convertToSnakeCase(detailInfo), + }, + }, }) return NextResponse.json(result) } catch (error) { diff --git a/src/components/survey-sale/detail/BasicForm.tsx b/src/components/survey-sale/detail/BasicForm.tsx index 0942abb..b984101 100644 --- a/src/components/survey-sale/detail/BasicForm.tsx +++ b/src/components/survey-sale/detail/BasicForm.tsx @@ -25,7 +25,7 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas setBasicInfo({ ...basicInfo, representative: session.userNm ?? '', - store: session.storeNm ?? null, + store: session.role === 'Partner' ? null : session.storeNm ?? null, constructionPoint: session.builderNo ?? null, }) } diff --git a/src/components/survey-sale/detail/ButtonForm.tsx b/src/components/survey-sale/detail/ButtonForm.tsx index a22ac5c..8f8e3dd 100644 --- a/src/components/survey-sale/detail/ButtonForm.tsx +++ b/src/components/survey-sale/detail/ButtonForm.tsx @@ -22,8 +22,15 @@ export default function ButtonForm(props: { const params = useParams() const routeId = params.id - const [isSubmitProcess, setIsSubmitProcess] = useState(false) // ------------------------------------------------------------ + const [saveData, setSaveData] = useState({ + ...props.data.basic, + detailInfo: props.data.roof, + }) + + // !!!!!!!!!! + const [tempTargetId, setTempTargetId] = useState('') + // -------------------------------------------------------------- // 권한 // 제출권한 ㅇ @@ -34,39 +41,63 @@ export default function ButtonForm(props: { useEffect(() => { if (session?.isLoggedIn) { - setIsSubmiter(session.storeNm === props.data.basic.store && session.builderNo === props.data.basic.constructionPoint) + switch (session?.role) { + // T01 제출권한 없음 + case 'T01': + setIsSubmiter(false) + break + // 1차 판매점(Order) + 2차 판매점(Musubi) => 같은 판매점 제출권한 + case 'Admin': + case 'Admin_Sub': + setIsSubmiter(session.storeNm === props.data.basic.store && session.builderNo === props.data.basic.constructionPoint) + break + // 시공권한 User(Musubi) + Partner => 같은 시공ID 제출권한 + case 'Builder': + case 'Partner': + setIsSubmiter(session.builderNo === props.data.basic.constructionPoint) + break + default: + setIsSubmiter(false) + break + } + setIsWriter(session.userNm === props.data.basic.representative) } + setSaveData({ + ...props.data.basic, + detailInfo: props.data.roof, + }) }, [session, props.data]) // ------------------------------------------------------------ // 저장/임시저장/수정 + const id = Number(routeId) ? Number(routeId) : Number(idParam) - const id = routeId ? Number(routeId) : Number(idParam) const { deleteSurvey, submitSurvey, updateSurvey } = useServey(Number(id)) const { validateSurveyDetail, createSurvey } = useServey() - let saveData = { - ...props.data.basic, - detailInfo: props.data.roof, - } - const handleSave = (isTemporary: boolean) => { + const handleSave = (isTemporary: boolean, isSubmitProcess = false) => { const emptyField = validateSurveyDetail(props.data.roof) - console.log('handleSave, emptyField:: ', emptyField) + const hasEmptyField = emptyField?.trim() !== '' + if (isTemporary) { - tempSaveProcess() + hasEmptyField ? tempSaveProcess() : saveProcess(emptyField, false) } else { - saveProcess(emptyField) + saveProcess(emptyField, isSubmitProcess) } } const tempSaveProcess = async () => { if (idParam) { - await updateSurvey(saveData) - router.push(`/survey-sale/detail?id=${idParam}&isTemporary=true`) + await updateSurvey({ survey: saveData, isTemporary: true }) + router.push(`/survey-sale/${idParam}`) } else { - const id = await createSurvey(saveData) - router.push(`/survey-sale/detail?id=${id}&isTemporary=true`) + const updatedData = { + ...saveData, + srlNo: '一時保存', + } + const id = await createSurvey(updatedData) + router.push(`/survey-sale/${id}`) } alert('一時保存されました。') } @@ -78,30 +109,40 @@ export default function ButtonForm(props: { } } - const saveProcess = async (emptyField: string) => { - if (emptyField.trim() === '') { + const saveProcess = async (emptyField: string | null, isSubmitProcess?: boolean) => { + if (emptyField?.trim() === '') { if (idParam) { - // 수정 페이지에서 작성 후 제출 if (isSubmitProcess) { - saveData = { + const updatedData = { ...saveData, submissionStatus: true, submissionDate: new Date().toISOString(), + submissionTargetId: tempTargetId, } + await updateSurvey({ survey: updatedData, isTemporary: false, storeId: session.storeId ?? '' }) + router.push(`/survey-sale/${idParam}`) + } else { + await updateSurvey({ survey: saveData, isTemporary: false, storeId: session.storeId ?? '' }) + router.push(`/survey-sale/${idParam}`) } - await updateSurvey(saveData) - router.push(`/survey-sale/${idParam}`) } else { - const id = await createSurvey(saveData) if (isSubmitProcess) { + const updatedData = { + ...saveData, + submissionStatus: true, + submissionDate: new Date().toISOString(), + submissionTargetId: tempTargetId, + } + const id = await createSurvey(updatedData) submitProcess(id) - return + } else { + const id = await createSurvey(saveData) + router.push(`/survey-sale/${id}`) } - router.push(`/survey-sale/${id}`) } alert('保存されました。') } else { - if (emptyField.includes('Unit')) { + if (emptyField?.includes('Unit')) { alert('電気契約容量の単位を入力してください。') focusInput(emptyField as keyof SurveyDetailInfo) } else { @@ -123,17 +164,25 @@ export default function ButtonForm(props: { } const handleSubmit = async () => { + if (props.data.basic.srlNo?.startsWith('一時保存')) { + alert('一時保存されたデータは提出できません。') + return + } + if (tempTargetId.trim() === '') { + alert('提出対象店舗を入力してください。') + return + } window.neoConfirm('提出しますか?', async () => { - setIsSubmitProcess(true) - if (routeId) { + if (Number(routeId)) { submitProcess() } else { - handleSave(false) + handleSave(false, true) } }) } + const submitProcess = async (saveId?: number) => { - await submitSurvey(saveId) + await submitSurvey({ saveId: saveId, targetId: tempTargetId, storeId: session.storeId ?? '', srlNo: '一時保存' }) alert('提出されました。') router.push('/survey-sale') } @@ -159,7 +208,7 @@ export default function ButtonForm(props: { {(isWriter || !isSubmiter) && } - {!isSubmit && isSubmiter && } + {!isSubmit && isSubmiter && } )} @@ -170,7 +219,7 @@ export default function ButtonForm(props: { - + {session?.role !== 'T01' && }{' '} )} @@ -210,15 +259,20 @@ function EditButton(props: { setMode: (mode: Mode) => void; id: string; mode: Mo ) } -function SubmitButton(props: { handleSubmit: () => void }) { - const { handleSubmit } = props +function SubmitButton(props: { handleSubmit: () => void; setTempTargetId: (targetId: string) => void }) { + const { handleSubmit, setTempTargetId } = props return ( -
- {/* 제출 */} - -
+ <> +
+ {/* 제출 */} + +
+
+ setTempTargetId(e.target.value)} /> +
+ ) } @@ -256,7 +310,6 @@ function TempButton(props: { setMode: (mode: Mode) => void; handleSave: (isTempo +
調査物件登録
diff --git a/src/components/ui/common/Spinner.tsx b/src/components/ui/common/Spinner.tsx new file mode 100644 index 0000000..4a2aa38 --- /dev/null +++ b/src/components/ui/common/Spinner.tsx @@ -0,0 +1,7 @@ +export default function Spinner() { + return ( +
+ +
+ ) +} diff --git a/src/hooks/useAxios.ts b/src/hooks/useAxios.ts new file mode 100644 index 0000000..ac59166 --- /dev/null +++ b/src/hooks/useAxios.ts @@ -0,0 +1,93 @@ +import axios from 'axios' +import { useSpinnerStore } from '@/store/spinnerStore' + +export const useAxios = () => { + const { setIsShow } = useSpinnerStore() + + const axiosInstance = (url: string | null | undefined) => { + const baseURL = url || process.env.NEXT_PUBLIC_API_URL + const instance = axios.create({ + baseURL, + headers: { + Accept: 'application/json', + }, + }) + + instance.interceptors.request.use( + (config) => { + // console.log('🚀 ~ config:', config) + setIsShow(true) + return config + }, + (error) => { + return Promise.reject(error) + }, + ) + + instance.interceptors.response.use( + (response) => { + response.data = transferResponse(response) + setIsShow(false) + return response + }, + (error) => { + // 에러 처리 로직 + return Promise.reject(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, + } +} diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index 043cfce..6610cea 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -1,11 +1,10 @@ -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 { queryStringFormatter } from '@/utils/common-utils' -import { useSessionStore } from '@/store/session' import { useMemo } from 'react' -import { AxiosResponse } from 'axios' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { useSurveyFilterStore } from '@/store/surveyFilterStore' +import { useSessionStore } from '@/store/session' +import { useAxios } from './useAxios' +import { queryStringFormatter } from '@/utils/common-utils' export const requiredFields = [ { @@ -65,9 +64,9 @@ export function useServey(id?: number): { isDeletingSurvey: boolean createSurvey: (survey: SurveyRegistRequest) => Promise createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => void - updateSurvey: (survey: SurveyRegistRequest) => void + updateSurvey: ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string }) => void deleteSurvey: () => Promise - submitSurvey: (saveId?: number) => void + submitSurvey: (params: { saveId?: number; targetId?: string; storeId?: string; srlNo?: string }) => void validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string getZipCode: (zipCode: string) => Promise refetchSurveyList: () => void @@ -75,6 +74,7 @@ export function useServey(id?: number): { const queryClient = useQueryClient() const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore() const { session } = useSessionStore() + const { axiosInstance } = useAxios() const { data: surveyListData, @@ -119,7 +119,7 @@ export function useServey(id?: number): { const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({ mutationFn: async (survey: SurveyRegistRequest) => { - const resp = await axiosInstance(null).post('/api/survey-sales', survey) + const resp = await axiosInstance(null).post('/api/survey-sales', { survey: survey, storeId: session?.storeId ?? null }) return resp.data.id ?? 0 }, onSuccess: (data) => { @@ -130,10 +130,14 @@ export function useServey(id?: number): { }) const { mutate: updateSurvey, isPending: isUpdatingSurvey } = useMutation({ - mutationFn: async (survey: SurveyRegistRequest) => { + mutationFn: async ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string }) => { console.log('updateSurvey, survey:: ', survey) if (id === undefined) throw new Error('id is required') - const resp = await axiosInstance(null).put(`/api/survey-sales/${id}`, survey) + const resp = await axiosInstance(null).put(`/api/survey-sales/${id}`, { + survey: survey, + isTemporary: isTemporary, + storeId: storeId, + }) return resp.data }, onSuccess: () => { @@ -166,11 +170,13 @@ export function useServey(id?: number): { }) const { mutateAsync: submitSurvey } = useMutation({ - mutationFn: async (saveId?: number) => { + mutationFn: async ({ saveId, targetId, storeId, srlNo }: { saveId?: number; targetId?: string; storeId?: string; srlNo?: string }) => { const submitId = saveId ?? id if (!submitId) throw new Error('id is required') const resp = await axiosInstance(null).patch(`/api/survey-sales/${submitId}`, { - submit: true, + targetId, + storeId, + srlNo, }) return resp.data }, diff --git a/src/libs/axios.ts b/src/libs/axios.ts index 0abc6ab..6007c47 100644 --- a/src/libs/axios.ts +++ b/src/libs/axios.ts @@ -1,3 +1,4 @@ +import { useSpinnerStore } from '@/store/spinnerStore' import axios from 'axios' export const axiosInstance = (url: string | null | undefined) => { diff --git a/src/providers/EdgeProvider.tsx b/src/providers/EdgeProvider.tsx index 77b8edd..e04f0e5 100644 --- a/src/providers/EdgeProvider.tsx +++ b/src/providers/EdgeProvider.tsx @@ -8,6 +8,8 @@ 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 { @@ -28,6 +30,7 @@ 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() /** * 사용자 이벤트 트래킹 처리 @@ -110,5 +113,10 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp handlePageEvent(pathname) }, [pathname]) - return <>{children} + return ( + <> + {children} + {isShow && } + + ) } diff --git a/src/store/spinnerStore.ts b/src/store/spinnerStore.ts new file mode 100644 index 0000000..976382e --- /dev/null +++ b/src/store/spinnerStore.ts @@ -0,0 +1,21 @@ +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((set) => ({ + ...initialState, + setIsShow: (isShow: boolean) => set({ isShow }), + resetCount: () => set(initialState), +})) diff --git a/src/styles/base/_button.scss b/src/styles/base/_button.scss index b4d61a4..c6d11ad 100644 --- a/src/styles/base/_button.scss +++ b/src/styles/base/_button.scss @@ -49,14 +49,6 @@ 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; diff --git a/src/styles/base/_check-radio.scss b/src/styles/base/_check-radio.scss index 47c50cb..f6ff573 100644 --- a/src/styles/base/_check-radio.scss +++ b/src/styles/base/_check-radio.scss @@ -201,7 +201,7 @@ } } input:checked + .slider { - background-color: #A8B6C7; + background-color: #0081b5; &:after { content: ''; left: 10px; diff --git a/src/styles/components/_index.scss b/src/styles/components/_index.scss index 4ebdd02..3ae03f1 100644 --- a/src/styles/components/_index.scss +++ b/src/styles/components/_index.scss @@ -1,4 +1,6 @@ @forward 'main'; @forward 'login'; @forward 'pop-contents'; -@forward 'sub'; \ No newline at end of file +@forward 'sub'; +@forward 'pdfview'; +@forward 'spinner'; \ No newline at end of file diff --git a/src/styles/components/_pdfview.scss b/src/styles/components/_pdfview.scss new file mode 100644 index 0000000..8db6cf7 --- /dev/null +++ b/src/styles/components/_pdfview.scss @@ -0,0 +1,57 @@ +@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; +} \ No newline at end of file diff --git a/src/styles/components/_pop-contents.scss b/src/styles/components/_pop-contents.scss index f0f18d5..b598397 100644 --- a/src/styles/components/_pop-contents.scss +++ b/src/styles/components/_pop-contents.scss @@ -103,16 +103,12 @@ @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-right: 1px solid #2E3A59; + border-bottom: 1px solid #2E3A59; } .pop-data-table-footer-data{ - flex: none; - width: 104px; padding: 10px; @include defaultFont($font-s-13, $font-w-400, $font-c); } diff --git a/src/styles/components/_spinner.scss b/src/styles/components/_spinner.scss new file mode 100644 index 0000000..f37f12d --- /dev/null +++ b/src/styles/components/_spinner.scss @@ -0,0 +1,37 @@ +.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; + } +} + \ No newline at end of file diff --git a/src/types/Survey.ts b/src/types/Survey.ts index 36e7aa5..8066836 100644 --- a/src/types/Survey.ts +++ b/src/types/Survey.ts @@ -14,6 +14,8 @@ export type SurveyBasicInfo = { detailInfo: SurveyDetailInfo | null regDt: Date uptDt: Date + submissionTargetId: string | null + srlNo: string | null //판매점IDyyMMdd000 } export type SurveyDetailInfo = { @@ -70,6 +72,8 @@ export type SurveyBasicRequest = { addressDetail: string | null submissionStatus: boolean submissionDate: string | null + submissionTargetId: string | null + srlNo: string | null //판매점IDyyMMdd000 } export type SurveyDetailRequest = { @@ -127,6 +131,8 @@ export type SurveyRegistRequest = { submissionStatus: boolean submissionDate: string | null detailInfo: SurveyDetailRequest | null + submissionTargetId: string | null + srlNo: string | null //판매점IDyyMMdd000 } export type Mode = 'CREATE' | 'EDIT' | 'READ' | 'TEMP' // 등록 | 수정 | 상세 | 임시저장