스마트서비스 최종완료보고 버전
This commit is contained in:
@@ -0,0 +1,426 @@
|
||||
// App Global functions
|
||||
"use client";
|
||||
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
|
||||
import { create } from "zustand"; //JWT
|
||||
import { jwtDecode } from "jwt-decode";//JWT
|
||||
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
|
||||
timeout: 7000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
withCredentials: true, //JWT for access token
|
||||
});
|
||||
|
||||
export default api;
|
||||
|
||||
|
||||
export const api2 = axios.create({
|
||||
baseURL: "http://localhost:17800",
|
||||
timeout: 7000,
|
||||
headers: {
|
||||
'Content-Type': 'text/html; charset=UTF-8',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
//JWT access token part
|
||||
export const SIGN_IN_PAGE_URI = "/signin";
|
||||
export const TOK_REFRESH_URI = "/api/sval2.do"; // security value 2(token refresh)
|
||||
|
||||
type JwtPayload = {
|
||||
sub: string; // gid
|
||||
user_id: string;
|
||||
nick_name: string;
|
||||
biz_group_id: string;
|
||||
permission: number;
|
||||
}
|
||||
|
||||
type AuthState = {
|
||||
accessToken: string | null;
|
||||
tokenPayload: JwtPayload | null;
|
||||
setAccessToken: (token: string) => void;
|
||||
clearAuth: () => void;
|
||||
};
|
||||
|
||||
export const useAuthStore = create<AuthState>((set: any) => ({
|
||||
accessToken: null,
|
||||
tokenPayload: null,
|
||||
|
||||
setAccessToken: (token: string) => {
|
||||
set({ accessToken: token, tokenPayload: jwtDecode<JwtPayload>(token) });
|
||||
},
|
||||
clearAuth: () => set({ accessToken: null, tokenPayload: null }),
|
||||
}));
|
||||
|
||||
type UiLoadingState = {
|
||||
pendingCount: number;
|
||||
treePendingCount: number;
|
||||
tablePendingCount: number;
|
||||
isLoading: boolean;
|
||||
isTreeLoading: boolean;
|
||||
isTableLoading: boolean;
|
||||
startLoading: (scope?: "tree" | "table" | "global") => void;
|
||||
endLoading: (scope?: "tree" | "table" | "global") => void;
|
||||
};
|
||||
|
||||
export const useUiLoadingStore = create<UiLoadingState>((set, get) => ({
|
||||
pendingCount: 0,
|
||||
treePendingCount: 0,
|
||||
tablePendingCount: 0,
|
||||
isLoading: false,
|
||||
isTreeLoading: false,
|
||||
isTableLoading: false,
|
||||
startLoading: (scope = "global") => {
|
||||
const pendingNext = get().pendingCount + 1;
|
||||
const treeNext = scope === "tree" ? get().treePendingCount + 1 : get().treePendingCount;
|
||||
const tableNext = scope === "table" ? get().tablePendingCount + 1 : get().tablePendingCount;
|
||||
set({
|
||||
pendingCount: pendingNext,
|
||||
treePendingCount: treeNext,
|
||||
tablePendingCount: tableNext,
|
||||
isLoading: pendingNext > 0,
|
||||
isTreeLoading: treeNext > 0,
|
||||
isTableLoading: tableNext > 0,
|
||||
});
|
||||
},
|
||||
endLoading: (scope = "global") => {
|
||||
const pendingNext = Math.max(0, get().pendingCount - 1);
|
||||
const treeNext = scope === "tree" ? Math.max(0, get().treePendingCount - 1) : get().treePendingCount;
|
||||
const tableNext = scope === "table" ? Math.max(0, get().tablePendingCount - 1) : get().tablePendingCount;
|
||||
set({
|
||||
pendingCount: pendingNext,
|
||||
treePendingCount: treeNext,
|
||||
tablePendingCount: tableNext,
|
||||
isLoading: pendingNext > 0,
|
||||
isTreeLoading: treeNext > 0,
|
||||
isTableLoading: tableNext > 0,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
const TREE_LOADING_ENDPOINTS = [
|
||||
"/api/get-biz-group2.do",
|
||||
"/api/get-biz-group3.do",
|
||||
];
|
||||
|
||||
const TABLE_LOADING_ENDPOINTS = [
|
||||
"/api/get-account.do",
|
||||
"/api/get-device.do",
|
||||
"/api/get-goods.do",
|
||||
"/api/get-goods-templates.do",
|
||||
"/api/get-terminal.do",
|
||||
"/api/get-transaction.do",
|
||||
"/api/get-u-transaction.do",
|
||||
"/api/get-sales.do",
|
||||
"/api/get-notice.do",
|
||||
"/api/get-voc.do",
|
||||
"/api/get-resource-board.do",
|
||||
];
|
||||
|
||||
const resolveLoadingScope = (url?: string): "tree" | "table" | "global" => {
|
||||
if (!url) return "global";
|
||||
if (TREE_LOADING_ENDPOINTS.some((x) => url.includes(x))) return "tree";
|
||||
if (TABLE_LOADING_ENDPOINTS.some((x) => url.includes(x))) return "table";
|
||||
return "global";
|
||||
};
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = useAuthStore.getState().accessToken;
|
||||
if (token) {
|
||||
config.headers = config.headers ?? {};
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
const loadingScope = resolveLoadingScope(config.url);
|
||||
(config as AxiosRequestConfig & { __loadingScope?: "tree" | "table" | "global" }).__loadingScope = loadingScope;
|
||||
useUiLoadingStore.getState().startLoading(loadingScope);
|
||||
return config;
|
||||
});
|
||||
|
||||
// refresh token part
|
||||
let g_isRefreshing = false;
|
||||
let g_queue_refresh_tok_value: {
|
||||
resolve: (token: string) => void;
|
||||
reject: (err: any) => void;
|
||||
}[] = [];
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
const loadingScope = (response.config as AxiosRequestConfig & { __loadingScope?: "tree" | "table" | "global" }).__loadingScope ?? resolveLoadingScope(response.config?.url);
|
||||
useUiLoadingStore.getState().endLoading(loadingScope);
|
||||
return response;
|
||||
},
|
||||
|
||||
async (error: AxiosError) => {
|
||||
const loadingScope = (error.config as AxiosRequestConfig & { __loadingScope?: "tree" | "table" | "global" } | undefined)?.__loadingScope ?? resolveLoadingScope(error.config?.url);
|
||||
useUiLoadingStore.getState().endLoading(loadingScope);
|
||||
const original = error.config as AxiosRequestConfig & { _retry?: boolean };
|
||||
|
||||
if (!original) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (original._retry) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const status = error.response?.status;
|
||||
|
||||
if (status !== 401) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (original.url === TOK_REFRESH_URI) {
|
||||
useAuthStore.getState().clearAuth();
|
||||
window.location.href = SIGN_IN_PAGE_URI;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (g_isRefreshing) {
|
||||
return new Promise((resolve, reject) => {
|
||||
g_queue_refresh_tok_value.push({ resolve, reject });
|
||||
}).then((newToken) => {
|
||||
original.headers = original.headers ?? {};
|
||||
original.headers.Authorization = `Bearer ${newToken}`;
|
||||
return api(original);
|
||||
});
|
||||
}
|
||||
|
||||
original._retry = true;
|
||||
g_isRefreshing = true;
|
||||
|
||||
try {
|
||||
const resp = await api.post(TOK_REFRESH_URI);
|
||||
const newToken = resp.data.result.sval1;
|
||||
|
||||
useAuthStore.getState().setAccessToken(newToken);
|
||||
|
||||
g_queue_refresh_tok_value.forEach(({ resolve }) => resolve(newToken));
|
||||
g_queue_refresh_tok_value = [];
|
||||
|
||||
original.headers = original.headers ?? {};
|
||||
original.headers.Authorization = `Bearer ${newToken}`;
|
||||
return api(original);
|
||||
|
||||
} catch (refreshError) {
|
||||
g_queue_refresh_tok_value.forEach(({ reject }) => reject(refreshError));
|
||||
g_queue_refresh_tok_value = [];
|
||||
|
||||
useAuthStore.getState().clearAuth();
|
||||
window.location.href = SIGN_IN_PAGE_URI;
|
||||
return Promise.reject(refreshError);
|
||||
|
||||
} finally {
|
||||
g_isRefreshing = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const clearAuthStore = async () => {
|
||||
useAuthStore.getState().clearAuth();
|
||||
};
|
||||
|
||||
export const refreshAuthStore = async () => {
|
||||
if (useAuthStore.getState().tokenPayload != null)
|
||||
return;
|
||||
|
||||
try {
|
||||
const resp = await api.post(TOK_REFRESH_URI);
|
||||
const newToken = resp.data.result.sval1;
|
||||
|
||||
useAuthStore.getState().setAccessToken(newToken);
|
||||
}
|
||||
catch (e) {}
|
||||
};
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
export const postFileDownload = (url: string, params: any) => {
|
||||
api.post(url, params, {responseType: 'blob'}).then((resp) => {
|
||||
const disposition = resp.headers['content-disposition'];
|
||||
let filename : string = "";
|
||||
if (disposition && disposition.indexOf('attachment') !== -1) {
|
||||
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
|
||||
const matches = filenameRegex.exec(disposition);
|
||||
if (matches != null && matches[1])
|
||||
filename = matches[1].replace(/['"]/g, '');
|
||||
}
|
||||
const blob = new Blob([resp.data], {type: resp.headers['content-type']});
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
//link.download = filename;
|
||||
link.download = decodeURIComponent(filename);
|
||||
//link.download = unespace(filename);
|
||||
link.click();
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
alert(err.message);
|
||||
});
|
||||
}
|
||||
|
||||
// DB TIMESTAMP DATETIME modify (yyyy-MM-dd HH:mm:ss.0 -> yyyy-MM-dd HH:mm)
|
||||
export const convertDateTime = (tsDateTime: string) => {
|
||||
if (tsDateTime == undefined)
|
||||
return "";
|
||||
|
||||
return tsDateTime.replace(".0", "");
|
||||
}
|
||||
|
||||
// DB TIMESTAMP DATETIME modify (UTC time -> yyyy-MM-dd HH:mm:ss)
|
||||
export const convertDateTime2 = (tsDateTime: string) => {
|
||||
if (tsDateTime == undefined)
|
||||
return "";
|
||||
|
||||
const localDate = new Date(tsDateTime);
|
||||
const formatted = localDate.toLocaleString("ko-KR", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false, // 0~23hour
|
||||
});
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// DB TIMESTAMP DATETIME modify (UTC time -> yyyy-MM-dd HH:mm)
|
||||
export const convertDateTime3 = (tsDateTime: string) => {
|
||||
if (tsDateTime == undefined || tsDateTime == "")
|
||||
return "";
|
||||
|
||||
const localDate = new Date(tsDateTime);
|
||||
const formatted = localDate.toLocaleString("ko-KR", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
//second: "2-digit",
|
||||
hour12: false, // 0~23hour
|
||||
});
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// yyyyMMddHHmmss => yyyy-MM-dd HH:mm:ss
|
||||
export const convertDateTime4 = (tsDateTime: string) => {
|
||||
if (tsDateTime == undefined || tsDateTime == "")
|
||||
return "";
|
||||
|
||||
const yyyy = tsDateTime.slice(0, 4);
|
||||
const mm = tsDateTime.slice(4, 6);
|
||||
const dd = tsDateTime.slice(6, 8);
|
||||
const HH = tsDateTime.slice(8, 10);
|
||||
const MM = tsDateTime.slice(10, 12);
|
||||
const SS = tsDateTime.slice(12, 14);
|
||||
|
||||
return `${yyyy}-${mm}-${dd} ${HH}:${MM}:${SS}`;
|
||||
}
|
||||
|
||||
// yyyyMMddHHmmss => yyyy-MM-dd HH:mm
|
||||
export const convertDateTime5 = (tsDateTime: string) => {
|
||||
if (tsDateTime == undefined || tsDateTime == "")
|
||||
return "";
|
||||
|
||||
const yyyy = tsDateTime.slice(0, 4);
|
||||
const mm = tsDateTime.slice(4, 6);
|
||||
const dd = tsDateTime.slice(6, 8);
|
||||
const HH = tsDateTime.slice(8, 10);
|
||||
const MM = tsDateTime.slice(10, 12);
|
||||
|
||||
return `${yyyy}-${mm}-${dd} ${HH}:${MM}`;
|
||||
}
|
||||
|
||||
// yyyyMMddHHmmss => yyyy-MM-dd HH:mm
|
||||
export const convertDateTime6 = (tsDateTime: string) => {
|
||||
if (tsDateTime == undefined || tsDateTime == "")
|
||||
return "";
|
||||
|
||||
let startPos = 0;
|
||||
let yy = tsDateTime.slice(startPos, startPos + 2);
|
||||
if (yy == "20") {
|
||||
startPos += 2;
|
||||
yy = tsDateTime.slice(startPos, startPos + 2);
|
||||
}
|
||||
|
||||
startPos += 2;
|
||||
const mm = tsDateTime.slice(startPos, startPos + 2);
|
||||
|
||||
startPos += 2;
|
||||
const dd = tsDateTime.slice(startPos, startPos + 2);
|
||||
|
||||
startPos += 2;
|
||||
const HH = tsDateTime.slice(startPos, startPos + 2);
|
||||
|
||||
startPos += 2;
|
||||
const MM = tsDateTime.slice(startPos, startPos + 2);
|
||||
|
||||
return `20${yy}-${mm}-${dd} ${HH}:${MM}`;
|
||||
}
|
||||
|
||||
export const getUrlParam = (key: string) => {
|
||||
if (window == undefined)
|
||||
return "";
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const value = params.get(key);
|
||||
if (value == null)
|
||||
return "";
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// 사업자등록번호 하이픈(-) 자동삽입
|
||||
//const formatted = formatBusinessNumber("1234567890"); // "123-45-67890"
|
||||
export function formatBusinessNumber(input: string): string {
|
||||
const allowed = input.replace(/[^0-9-]/g, "");
|
||||
const digits = allowed.replace(/-/g, "").substring(0, 10);
|
||||
|
||||
let formatted = digits;
|
||||
if (digits.length > 3 && digits.length <= 5) {
|
||||
formatted = digits.replace(/^(\d{3})(\d{1,2})$/, "$1-$2");
|
||||
} else if (digits.length > 5) {
|
||||
formatted = digits.replace(/^(\d{3})(\d{2})(\d{1,5})$/, "$1-$2-$3");
|
||||
}
|
||||
|
||||
if (allowed.endsWith("-") && !formatted.endsWith("-")) {
|
||||
formatted += "-";
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// 사업자등록번호 유효성 검사
|
||||
//console.log(isValidBusinessNumber("123-45-67890")); // false
|
||||
//console.log(isValidBusinessNumber("220-81-62517")); // true (실제 유효 번호 예시)
|
||||
export function isValidBusinessNumber(input: string): boolean {
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
const digits = input.replace(/\D/g, "");
|
||||
if (digits.length !== 10)
|
||||
return false;
|
||||
|
||||
const weights = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const num = parseInt(digits.charAt(i), 10);
|
||||
sum += num * weights[i];
|
||||
}
|
||||
|
||||
sum += Math.floor((parseInt(digits.charAt(8), 10) * 5) / 10);
|
||||
|
||||
const checkDigit = (10 - (sum % 10)) % 10;
|
||||
return checkDigit === parseInt(digits.charAt(9), 10);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user