Compare commits

..

2 Commits

Author SHA1 Message Date
junh_eee(이준희) 3269298ba3 기초정보/기체관리 toolkit 적용중 8 months ago
junh_eee(이준희) 33cb319d96 redux-toolkit 초기셋팅 8 months ago
  1. 2388
      package-lock.json
  2. 10
      package.json
  3. 9
      src/app/page.tsx
  4. 31
      src/app/test/drone.tsx
  5. 11
      src/app/test/page.tsx
  6. 26
      src/layout/components/footer.tsx
  7. 109
      src/layout/components/layout-wrapper.tsx
  8. 25
      src/redux/features/basis/drone/droneSlice.ts
  9. 71
      src/redux/features/basis/drone/droneState.ts
  10. 146
      src/redux/features/basis/drone/droneThunk.ts
  11. 7
      src/redux/hooks.ts
  12. 10
      src/redux/provider.tsx
  13. 11
      src/redux/rootReducer.ts
  14. 9
      src/redux/store.ts
  15. 27
      src/utils/axiosInterceptor.ts
  16. 20
      src/utils/cookie.ts
  17. 51
      src/utils/jwtToken.ts

2388
package-lock.json generated

File diff suppressed because it is too large Load Diff

10
package.json

@ -9,15 +9,19 @@
"lint": "next lint"
},
"dependencies": {
"animate.css": "4.1.1",
"classnames": "2.5.1",
"@reduxjs/toolkit": "^1.9.7",
"axios": "^1.6.0",
"js-cookie": "^3.0.0",
"jwt-decode": "^3.1.2",
"next": "13.5.6",
"qs": "^6.11.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-feather": "2.0.10"
"react-redux": "^8.1.3"
},
"devDependencies": {
"@types/node": "^20",
"@types/qs": "^6.9.11",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",

9
src/app/page.tsx

@ -1,10 +1,3 @@
import LayoutWrapper from '../layout/components/layout-wrapper';
import Footer from '../layout/components/footer';
export default function Home() {
return (
<LayoutWrapper layout='VerticalLayout' transition='fadeIn'>
<div>main</div>
</LayoutWrapper>
);
return <div>main</div>;
}

31
src/app/test/drone.tsx

@ -0,0 +1,31 @@
import { getDroneList } from '@/redux/features/basis/drone/droneThunk';
import { useAppSelector } from '@/redux/hooks';
import { AppDispatch } from '@/redux/store';
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
export default function Drone() {
const dispatch = useDispatch<AppDispatch>();
const droneList = useAppSelector(state => state.drone.list);
useEffect(() => {
console.log(droneList, '---list');
}, [droneList]);
const handler = () => {
dispatch(
getDroneList({
groupId: 'C807F9',
page: 1
})
);
};
return (
<div>
main
<div style={{ background: '#ff00ff', width: '100px' }} onClick={handler}>
Redux-toolkit
</div>
</div>
);
}

11
src/app/test/page.tsx

@ -0,0 +1,11 @@
'use client';
import Drone from './drone';
export default function Home() {
return (
<>
<Drone />
</>
);
}

26
src/layout/components/footer.tsx

@ -1,26 +0,0 @@
// ** Icons Import
import { Heart } from 'react-feather';
function Footer() {
return (
<p className='clearfix mb-0'>
<span className='float-md-left d-block d-md-inline-block mt-25'>
COPYRIGHT © {new Date().getFullYear()}{' '}
<a
href='https://1.envato.market/pixinvent_portfolio'
target='_blank'
rel='noopener noreferrer'
>
Pixinvent
</a>
<span className='d-none d-sm-inline-block'>, All rights Reserved</span>
</span>
<span className='float-md-right d-none d-md-block'>
Hand-crafted & Made with
<Heart size={14} />
</span>
</p>
);
}
export default Footer;

109
src/layout/components/layout-wrapper.tsx

@ -1,109 +0,0 @@
// ** React Imports
import { Fragment, useEffect, ReactNode } from 'react';
// ** Third Party Components
import classnames from 'classnames';
// ** Store & Actions
// import { useSelector, useDispatch } from 'react-redux'
// import { handleContentWidth, handleMenuCollapsed, handleMenuHidden } from '@store/actions/layout'
// ** Styles
import 'animate.css/animate.css';
interface Props {
children: ReactNode;
layout: string;
transition: string;
wrapperClass?: string;
appLayout?: string;
routeMeta?: {
authRoute: boolean;
naveLink: string;
};
}
function LayoutWrapper(props: Props) {
// ** Props
const {
layout,
children,
appLayout = '',
wrapperClass = '',
transition,
routeMeta
} = props;
// ** Store Vars
// const dispatch = useDispatch()
// const store = useSelector(state => state)
// const navbarStore = store.navbar
// const contentWidth = store.layout.contentWidth
//** Vars
const Tag = layout === 'HorizontalLayout' && !appLayout ? 'div' : Fragment;
// ** Clean Up Function
// const cleanUp = () => {
// if (routeMeta) {
// if (routeMeta.contentWidth) {
// dispatch(handleContentWidth('full'))
// }
// if (routeMeta.menuCollapsed) {
// dispatch(handleMenuCollapsed(!routeMeta.menuCollapsed))
// }
// if (routeMeta.menuHidden) {
// dispatch(handleMenuHidden(!routeMeta.menuHidden))
// }
// }
// }
// ** ComponentDidMount
// useEffect(() => {
// if (routeMeta) {
// if (routeMeta.contentWidth) {
// dispatch(handleContentWidth(routeMeta.contentWidth))
// }
// if (routeMeta.menuCollapsed) {
// dispatch(handleMenuCollapsed(routeMeta.menuCollapsed))
// }
// if (routeMeta.menuHidden) {
// dispatch(handleMenuHidden(routeMeta.menuHidden))
// }
// }
// return () => cleanUp()
// }, [])
return (
<div
className={classnames('app-content content overflow-hidden', {
[wrapperClass]: wrapperClass
// 'show-overlay': navbarStore.query.length
})}
>
<div className='content-overlay'></div>
<div className='header-navbar-shadow' />
<div
className={classnames({
'content-wrapper': !appLayout,
'content-area-wrapper': appLayout,
// 'container p-0': contentWidth === 'boxed',
[`animate__animated animate__${transition}`]:
transition !== 'none' && transition.length
})}
>
<Tag
/*eslint-disable */
{...(layout === 'HorizontalLayout' && !appLayout
? { className: classnames({ 'content-body': !appLayout }) }
: {})}
/*eslint-enable */
>
{children}
</Tag>
</div>
</div>
);
}
export default LayoutWrapper;

25
src/redux/features/basis/drone/droneSlice.ts

@ -0,0 +1,25 @@
import { createSlice } from '@reduxjs/toolkit';
import { InitDroneState } from './droneState';
import { getDroneList, getIdntfList } from './droneThunk';
const droneSlice = createSlice({
name: 'droneSlice',
initialState: InitDroneState,
reducers: {},
extraReducers: builder => {
builder.addCase(getDroneList.fulfilled, (state, action) => {
const { data, count } = action.payload;
state.list = data.items;
state.total = data.total;
state.count = count;
});
builder.addCase(getIdntfList.fulfilled, (state, action) => {
const { data, count } = action.payload;
state.listIdntf = data;
state.idntfCount = count;
state.isRefreshIdntf = false;
});
}
});
export const droneReducer = droneSlice.reducer;

71
src/redux/features/basis/drone/droneState.ts

@ -0,0 +1,71 @@
export const InitDroneState = {
list: undefined,
count: 0,
detail: undefined,
searchParams: '',
selectData: undefined,
listIdntf: undefined,
idntfCount: 0,
isRefreshIdntf: false,
page: 1,
total: 0
};
export interface DroneState {
list: DroneData[] | undefined;
count: number | 0;
detail: DroneData | undefined;
searchParams: string | '';
selectData: SelectData | undefined;
listIdntf: IdntfData[] | undefined;
idntfCount: number | 0;
isRefreshIdntf: boolean | false;
page: number | 1;
total: number | 0;
}
export interface SelectData {
groupId: string;
groupNm: string;
}
export interface DroneData {
groupId: string;
arcrftSno: number;
arcrftHght: number;
arcrftLngth: number;
arcrftModelNm: string;
arcrftTypeCd: string;
arcrftWdth: number;
arcrftWght: number;
cameraYn: string;
createDt: Date;
createUserId: string;
insrncYn: string;
prdctCmpnNm: string;
prdctDate: Date;
prdctNum: string;
takeoffWght: number;
updateDt: Date;
updateUserId: string;
imageUrl: string;
wghtTypeCd: string;
idntfNum: string;
newIdntfNum: string;
idntfTypeCd: string;
ownerSno: number;
ownerNm: string;
hpno: string;
telno: string;
useYn: string;
file: FileList;
}
export interface IdntfData {
idntfNum: string;
newIdntfNum: string;
isSave: boolean;
updateDt: Date;
createDt: Date;
cstmrSno: number;
}

146
src/redux/features/basis/drone/droneThunk.ts

@ -0,0 +1,146 @@
import axios from '../../../../utils/axiosInterceptor';
import qs from 'qs';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { DroneData } from './droneState';
// 드론 목록 조회
export const getDroneList = createAsyncThunk(
'basis/getDroneList',
async (data: any) => {
try {
const queryString = qs.stringify(data, {
addQueryPrefix: true,
arrayFormat: 'repeat'
});
const res = await axios.get(`api/bas/dron/list${queryString}`);
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 드론 상세 조회
export const getDroneDetail = createAsyncThunk(
'basis/getDroneDetail',
async (id: number) => {
try {
const res = await axios.get(`api/bas/dron/detail/${id}`);
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 드론 생성
export const createDrone = createAsyncThunk(
'basis/createDrone',
async (data: DroneData) => {
try {
const res = await axios.post('api/bas/dron/create', data);
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 드론 수정
export const updateDrone = createAsyncThunk(
'basis/updateDrone',
async (data: DroneData) => {
try {
const res = await axios.put('api/bas/dron/update', data);
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 드론 삭제
export const deleteDrone = createAsyncThunk(
'basis/deleteDrone',
async (id: number) => {
try {
const res = await axios.delete(`api/bas/dron/delete/${id}`);
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 식별장치 목록 조회
export const getIdntfList = createAsyncThunk(
'basis/getIdntfList',
async (id: number) => {
try {
const res = await axios.get(`api/bas/dron/idntf/list/${id}`);
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 식별장치 수정
export const updateIdntf = createAsyncThunk(
'basis/updateIdntf',
async (item: { id: number; data: any[] }) => {
try {
const res = await axios.put(`/api/bas/dron/idntf/update/${item.id}`, {
idntfNum: item.data[0].idntfNum,
ownerNm: item.data[0].ownerNm,
hpno: item.data[0].hpno
});
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 식별장치 삭제
export const deleteIdntf = createAsyncThunk(
'basis/deleteIdntf',
async (id: string) => {
try {
const res = await axios.delete(`api/bas/dron/idntf/delete/${id}`);
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 식별장치 생성
export const createIdntf = createAsyncThunk(
'basis/createIdntf',
async (item: { data: any[]; arcrftSno: number }) => {
try {
const res = await axios.post('api/bas/dron/idntf/create', item);
return res.data;
} catch (error) {
console.error(error);
}
}
);
// 파일 업로드
export const fileUpload = createAsyncThunk(
'basis/fileUpload',
async (file: any) => {
try {
let form = new FormData();
form.append('file', file);
const res = await axios.post('api/file/upload', form);
return res.data;
} catch (error) {
console.error(error);
}
}
);

7
src/redux/hooks.ts

@ -0,0 +1,7 @@
import { AppDispatch, store } from './store';
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
type RootState = ReturnType<typeof store.getState>;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

10
src/redux/provider.tsx

@ -0,0 +1,10 @@
'use client';
import { store } from './store';
import { Provider } from 'react-redux';
type Props = {
children: React.ReactNode;
};
export default function ReduxProvider({ children }: Props) {
return <Provider store={store}>{children}</Provider>;
}

11
src/redux/rootReducer.ts

@ -0,0 +1,11 @@
import { combineReducers } from '@reduxjs/toolkit';
import { droneReducer } from './features/basis/drone/droneSlice';
const rootReducer = (state: any, action: any) => {
const combineReducer = combineReducers({
drone: droneReducer
});
return combineReducer(state, action);
};
export default rootReducer;

9
src/redux/store.ts

@ -0,0 +1,9 @@
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';
export const store = configureStore({
reducer: rootReducer
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

27
src/utils/axiosInterceptor.ts

@ -0,0 +1,27 @@
import axios, { AxiosInstance } from 'axios';
import { getAccessToken } from './jwtToken';
import { store } from '@/redux/store';
const axiosInstance: AxiosInstance = axios.create({
baseURL: 'http://121.190.193.50:6081/'
});
axiosInstance.interceptors.request.use(
async config => {
config.headers['Authorization'] =
'palnet eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJwYWxuZXQiLCJjc3RtclNubyI6MiwiZXhwIjoxNzA4MzA2Nzg4LCJ1c2VySWQiOiJwYWxuZXQiLCJpYXQiOjE3MDY0OTIzODh9.X5S5oF5ZWcTK6FwjpVAfRwffuwLeCHUssn1OEJbsSFJDzi-Vp_DdnfgIvWAM1ry97VmNfFsT9bpX8XhE4dRnWg';
// let accessToken = await getAccessToken();
// config.headers['Authorization'] = accessToken;
// // alert(JSON.stringify(config.headers));
// // store.dispatch({
// // // type: GLOBAL_LOADING
// // });
return config;
},
error => {
return Promise.reject(error);
}
);
export default axiosInstance;

20
src/utils/cookie.ts

@ -0,0 +1,20 @@
import Cookies, { CookieAttributes, CookiesStatic } from 'js-cookie';
const CookieStorageBuilder = (cookies: CookiesStatic) => ({
setCookie: (key: string, value: string, options?: CookieAttributes) =>
cookies.set(key, value, {
path: '/',
secure: process.env.NODE_ENV === 'production' ? false : false,
sameSite: 'Lax',
...options
}),
getCookie: (key: string) => cookies.get(key),
removeCookie: (key: string) => cookies.remove(key)
});
export const cookieStorage = CookieStorageBuilder(Cookies);
const COOKIE_BASE_NAME = 'palnet';
export const COOKIE_ACCESS_TOKEN = `${COOKIE_BASE_NAME}_accs`;
export const COOKIE_REFRESH_TOKEN = `${COOKIE_BASE_NAME}_rfrs`;

51
src/utils/jwtToken.ts

@ -0,0 +1,51 @@
import decode from 'jwt-decode';
import {
cookieStorage,
COOKIE_ACCESS_TOKEN,
COOKIE_REFRESH_TOKEN
} from './cookie';
const JWT_FLEFIX = 'palnet ';
export const checkTokenExpired = (token?: string) => {
return true;
// const decodedToken = decode<LoginData>(token as string);
// if (parseInt(decodedToken.exp) < dayjs().unix()) {
// return true;
// }
// return false;
};
export const getAccessToken = async () => {
let token = cookieStorage.getCookie(COOKIE_ACCESS_TOKEN);
if (!token || token == undefined || token == 'undefined') {
return '';
}
// if (checkTokenExpired(token)) {
// //freshtoken 으로 재발급 진행.
// const refreshToken = getRefreshToken();
// //const isRefresh = store.getState()?.authState?.isRefresh;
// if (refreshToken) {
// // if (!store.getState()?.authState?.isRefresh) {
// const decodeToken = decode<LoginData>(token as string);
// const cstmrSno: number = decodeToken.cstmrSno;
// const { data, errorCode } = await refreshTokenAPI(cstmrSno, refreshToken);
// if (errorCode === '-101') {
// cookieStorage.setCookie(COOKIE_REFRESH_TOKEN, '');
// }
// //REFHRESTH 토큰 넣기
// cookieStorage.setCookie(COOKIE_ACCESS_TOKEN, data.accessToken);
// cookieStorage.setCookie(COOKIE_REFRESH_TOKEN, data.refreshToken);
// token = data.refreshToken;
// // }
// }
// }
return JWT_FLEFIX + token;
};
Loading…
Cancel
Save