연계 모듈 관리
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
import { LinkPage } from "@/pages/link";
|
||||
export default LinkPage;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ModifyLinkPage as VModifyLinkPage } from "@/pages/link";
|
||||
|
||||
export default async function ModifyLinkPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const { id } = await params;
|
||||
return <VModifyLinkPage linkSn={id} />;
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
import { RegistLinkPage } from "@/pages/link";
|
||||
export default RegistLinkPage;
|
||||
@@ -0,0 +1,2 @@
|
||||
import { LinkLogPage } from "@/pages/linkLog";
|
||||
export default LinkLogPage;
|
||||
@@ -0,0 +1,2 @@
|
||||
import { LinkParamsPage } from "@/pages/linkParams";
|
||||
export default LinkParamsPage;
|
||||
@@ -0,0 +1,2 @@
|
||||
import { LinkStatsPage } from "@/pages/linkStats";
|
||||
export default LinkStatsPage;
|
||||
@@ -0,0 +1,2 @@
|
||||
import { LinkTablePage } from "@/pages/linkTable";
|
||||
export default LinkTablePage;
|
||||
@@ -0,0 +1,191 @@
|
||||
import axios from "axios";
|
||||
import {
|
||||
type LinkService,
|
||||
type LinkSearchParam,
|
||||
type LinkList,
|
||||
type LinkFormData,
|
||||
type LinkInfo,
|
||||
type LinkLogList,
|
||||
type LinkStatsList,
|
||||
type LinkVrblList,
|
||||
type LinkVrblFormData,
|
||||
type LinkVrbl,
|
||||
type LinkTable,
|
||||
type LinkTableColumnsList,
|
||||
type LinkTableColumns,
|
||||
type LinkTableColData,
|
||||
type LinkTableFormData,
|
||||
} from "../model/link-interface";
|
||||
|
||||
export class LinkApiService implements LinkService {
|
||||
public async getLinkList(params: LinkSearchParam): Promise<LinkList> {
|
||||
const response = await axios.get<LinkList>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`,
|
||||
{ params }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getLinkInfo(linkSn: string): Promise<LinkInfo> {
|
||||
const response = await axios.get<LinkInfo>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo/${linkSn}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async linkQuickStart(linkSn: string): Promise<string> {
|
||||
const response = await axios.patch<string>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/linkQuickStart/${linkSn}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateLinkInfo(data: LinkFormData): Promise<void> {
|
||||
await axios.put<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkInfo`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
public async insertLinkInfo(data: LinkFormData): Promise<void> {
|
||||
await axios.post<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/insertLinkInfo`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
public async deleteLink(linkSn: string): Promise<void> {
|
||||
await axios.delete<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/deleteLink/${linkSn}`,
|
||||
);
|
||||
}
|
||||
|
||||
public async getLinkLogList(params: LinkSearchParam): Promise<LinkLogList> {
|
||||
const response = await axios.get<LinkLogList>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/selectLinkLoglist`,
|
||||
{ params }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getLinkStatsList(params: LinkSearchParam): Promise<LinkStatsList> {
|
||||
const response = await axios.get<LinkStatsList>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/selectLinkStats`,
|
||||
{ params }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getLinkVrblList(linkSn: string): Promise<LinkVrblList> {
|
||||
const response = await axios.get<LinkVrblList>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkVrblList/${linkSn}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async insertLinkVrbl(data: LinkVrblFormData): Promise<number> {
|
||||
const response = await axios.post<number>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/insertLinkVrbl`,
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async deleteLinkVrbl(data: string[]): Promise<void> {
|
||||
await axios.delete<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/deleteLinkVrbl`,
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async getLinkVrblInfo(linkVrblSn: string): Promise<LinkVrbl> {
|
||||
const response = await axios.get<LinkVrbl>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkVrblInfo/${linkVrblSn}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateLinkVrblInfo(data: LinkVrblFormData): Promise<void> {
|
||||
await axios.put<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkVrblInfo`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
public async getLinkTableInfo(linkSn: string): Promise<LinkTable> {
|
||||
const response = await axios.get<LinkTable>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo/${linkSn}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getLinkTableColList(linkSn: string): Promise<LinkTableColumnsList> {
|
||||
const response = await axios.get<LinkTableColumnsList>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList/${linkSn}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getLinkTableColInfo(params: { colSn: string; tblSn:string; }): Promise<LinkTableColumns> {
|
||||
const response = await axios.get<LinkTableColumns>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColInfo`,
|
||||
{ params }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async insertLinkTableCol(data: LinkTableColData): Promise<void> {
|
||||
await axios.post<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/insertLinkTableCol`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
public async deleteLinkTableCol(data: string[]): Promise<void> {
|
||||
await axios.delete<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/deleteLinkTableCol`,
|
||||
{
|
||||
data
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public async updateLinkTableCol(data: LinkTableColData): Promise<void> {
|
||||
axios.put<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkTableCol`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
public async insertLinkTableInfo(data: LinkTableFormData): Promise<void> {
|
||||
await axios.post<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/insertLinkTableInfo`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
public async updateLinkTableInfo(data: LinkTableFormData): Promise<void> {
|
||||
await axios.put<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkTableInfo`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
public async countLinkTblNm(tblNm: string): Promise<{count: number, tbl_sn: string}> {
|
||||
const response = await axios.get<{count: number, tbl_sn: string}>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/countLinkTblNm/${tblNm}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateLinkResultCode(linkSn: string): Promise<void> {
|
||||
await axios.patch<void>(
|
||||
`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkResultCode/${linkSn}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const LinkApi = new LinkApiService();
|
||||
@@ -0,0 +1,18 @@
|
||||
export { LinkApi } from "./api/link-service";
|
||||
export {
|
||||
type LinkSearchParam,
|
||||
type Link,
|
||||
type LinkFormData,
|
||||
type LinkLog,
|
||||
type LinkStats,
|
||||
type LinkVrbl,
|
||||
type LinkVrblFormData,
|
||||
type LinkTable,
|
||||
type LinkTableColumns,
|
||||
type LinkTableColData,
|
||||
type LinkTableFormData,
|
||||
linkFormSchema,
|
||||
linkVrblFormSchema,
|
||||
LinkTableColumnsSchema,
|
||||
LinkTableSchema,
|
||||
} from "./model/link-interface"
|
||||
@@ -0,0 +1,235 @@
|
||||
import { z } from "zod";
|
||||
import type { PageableParams, PageableList } from "@/shared/model";
|
||||
|
||||
export interface Link {
|
||||
link_sn: string;
|
||||
link_nm: string;
|
||||
link_clsf_cd: string;
|
||||
link_url: string;
|
||||
link_srvr_ip: string;
|
||||
link_db_cd: string;
|
||||
link_key: string;
|
||||
link_id: string;
|
||||
link_pswd: string;
|
||||
link_cycle_stng: string;
|
||||
srvc_usg: string;
|
||||
last_link_rslt_cd: string;
|
||||
last_link_excn_dt: string;
|
||||
next_link_excn_dt: string;
|
||||
last_link_err_cd: string;
|
||||
mdfcn_dt: string;
|
||||
mdfr: string;
|
||||
link_batch_time: string;
|
||||
}
|
||||
|
||||
export interface LinkLog {
|
||||
rownum: number;
|
||||
link_sn: string;
|
||||
link_nm: string;
|
||||
link_rslt_cd: string;
|
||||
err_cn: string;
|
||||
link_err_cd: string;
|
||||
log_crt_dt: string;
|
||||
}
|
||||
|
||||
export interface LinkInfo extends LinkFormData {
|
||||
link_sn: string;
|
||||
link_nm: string;
|
||||
link_clsf_cd: string;
|
||||
link_url: string;
|
||||
link_srvr_ip: string;
|
||||
link_db_cd: string;
|
||||
link_key: string;
|
||||
link_id: string;
|
||||
link_pswd: string;
|
||||
srvc_usg: string;
|
||||
last_link_rslt_cd: string;
|
||||
last_link_excn_dt: string;
|
||||
next_link_excn_dt: string;
|
||||
last_link_err_cd: string;
|
||||
mdfcn_dt: string;
|
||||
mdfr: string;
|
||||
link_batch_time: number;
|
||||
}
|
||||
|
||||
export interface LinkStats {
|
||||
link_sn: string;
|
||||
link_nm: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface LinkVrbl {
|
||||
link_vrbl_sn: string;
|
||||
link_vrbl_nm: string;
|
||||
link_vrbl_vl: string;
|
||||
link_vrbl_cd: string;
|
||||
link_vrbl_expln: string;
|
||||
mdfcn_dt: string;
|
||||
mdfr: string;
|
||||
}
|
||||
|
||||
export interface LinkTable {
|
||||
link_nm: string;
|
||||
link_sn: string;
|
||||
tbl_sn: string;
|
||||
sou_tbl_nm: string;
|
||||
sou_tbl_korn_nm: string;
|
||||
sou_tbl_expln: string;
|
||||
trsf_tbl_nm: string;
|
||||
trsf_tbl_korn_nm: string;
|
||||
trsf_tbl_expln: string;
|
||||
}
|
||||
|
||||
export interface LinkTableColumns {
|
||||
tbl_sn: string;
|
||||
col_sn: string;
|
||||
sou_col_nm: string;
|
||||
sou_col_korn_nm: string;
|
||||
sou_col_data_frm: string;
|
||||
sou_col_sz: number;
|
||||
trsf_col_nm: string;
|
||||
trsf_col_korn_nm: string;
|
||||
trsf_col_data_frm: string;
|
||||
trsf_col_sz: number
|
||||
pk_yn: string;
|
||||
col_seq: number;
|
||||
mdfcn_dt: string;
|
||||
mdfr: string;
|
||||
}
|
||||
|
||||
export const linkFormSchema = z.object({
|
||||
link_clsf_cd: z.string().max(8).nullable().refine((val) => {
|
||||
// 빈 값이나 "none" 값이면 false 반환
|
||||
return val && val !== "none";
|
||||
}, {
|
||||
message: "연계 분류를 선택해주세요"
|
||||
}),
|
||||
link_sn: z.string().max(50).nullable().optional(),
|
||||
link_nm: z.string().max(40, { message: '연계명은 40자 이하로 입력해 주세요.' }).nonempty({ message: '연계명은 필수입니다.' }),
|
||||
link_url: z.string().max(1000, { message: '연계 URL은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '연계 URL은 필수입니다.' }),
|
||||
link_srvr_ip: z.string().max(20).nullable().optional(),
|
||||
link_db_cd: z.string().max(10).nullable().optional(),
|
||||
link_key: z.string().max(100, { message: '연계 KEY는 100자 이하로 입력해 주세요.' }).nonempty({ message: '연계 KEY는 필수입니다.' }),
|
||||
link_id: z.string().max(20).nullable().optional(),
|
||||
link_pswd: z.string().max(128).nullable().optional(),
|
||||
srvc_usg: z.string().max(40).nullable().optional(),
|
||||
last_link_rslt_cd: z.string().max(4).nullable().optional(),
|
||||
last_link_excn_dt: z.string().max(40).nullable().optional(),
|
||||
next_link_excn_dt: z.string().max(40).nullable().optional(),
|
||||
last_link_err_cd: z.string().max(10).nullable().optional(),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr: z.string().nullable().optional(),
|
||||
link_batch_time: z.coerce.number().nullable().optional().refine((val) => {
|
||||
if (val == null) return true;
|
||||
return val >= 5;
|
||||
}, {
|
||||
message: "연계 주기는 5분 이상이어야 합니다."
|
||||
}),
|
||||
});
|
||||
|
||||
export const linkVrblFormSchema = z.object({
|
||||
link_sn: z.string().max(50).nullable().optional(),
|
||||
link_vrbl_sn: z.string().max(50).nullable().optional(),
|
||||
link_vrbl_nm: z.string().max(40, { message: '변수명은 40자 이하로 입력해 주세요.' }).nonempty({ message: '변수명은 필수입니다.' }),
|
||||
link_vrbl_expln: z.string().max(4000, { message: '설명은 400자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
link_vrbl_cd: z.string().max(10, { message: '변수코드는 10자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
link_vrbl_vl: z.string().max(1000, { message: '변수값은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '변수값은 필수입니다.' }),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export const LinkTableColumnsSchema = z.object({
|
||||
link_sn: z.string().max(50).nullable().optional(),
|
||||
tbl_sn: z.string().max(50).nullable().optional(),
|
||||
col_sn: z.string().max(50).nullable().optional(),
|
||||
sou_col_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
sou_col_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
sou_col_data_frm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
sou_col_sz: z.coerce.number().nullable().optional().refine((val) => {
|
||||
if (val == null) return true;
|
||||
return val.toString().replace('.', '').length <= 14;
|
||||
}, {
|
||||
message: '최대 14자리까지 입력 가능합니다.',
|
||||
}),
|
||||
pk_yn: z.string().max(1).nonempty(),
|
||||
trsf_col_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼명은 필수입니다.' }),
|
||||
trsf_col_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼 한글명은 필수입니다.' }),
|
||||
trsf_col_data_frm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼 데이터 형식은 필수입니다.' }),
|
||||
trsf_col_sz: z.coerce.number().nullable().optional().refine((val) => {
|
||||
if (val == null) return true;
|
||||
return val.toString().replace('.', '').length <= 14;
|
||||
}, {
|
||||
message: '최대 14자리까지 입력 가능합니다.',
|
||||
}),
|
||||
col_seq: z.coerce.number().nullable().optional().refine((val) => {
|
||||
if (val == null) return true;
|
||||
return val > 0;
|
||||
}, {
|
||||
message: "컬럼 매칭 순서는 0 이상이어야 합니다."
|
||||
}),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export const LinkTableSchema = z.object({
|
||||
link_sn: z.string().max(50).nullable().optional(),
|
||||
tbl_sn: z.string().max(50).nullable().optional(),
|
||||
link_nm: z.string().max(50).nullable().optional(),
|
||||
sou_tbl_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
sou_tbl_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
sou_tbl_expln: z.string().max(2000, { message: '설명은 2000자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
trsf_tbl_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환테이블명은 필수입니다.' }),
|
||||
trsf_tbl_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환테이블 한글명은 필수입니다.' }),
|
||||
trsf_tbl_expln: z.string().max(2000, { message: '설명은 2000자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
interface LinkStatsListObject {
|
||||
linkSuccStats: LinkStats[] | null;
|
||||
linkFailStats: LinkStats[] | null;
|
||||
}
|
||||
|
||||
export interface LinkSearchParam extends PageableParams {
|
||||
linkSn?: string;
|
||||
linkNm?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
isSucc?: string;
|
||||
}
|
||||
|
||||
export type LinkFormData = z.infer<typeof linkFormSchema>;
|
||||
export type LinkVrblFormData = z.infer<typeof linkVrblFormSchema>;
|
||||
export type LinkTableColData = z.infer<typeof LinkTableColumnsSchema>;
|
||||
export type LinkTableFormData = z.infer<typeof LinkTableSchema>;
|
||||
export type LinkList = PageableList<Link>;
|
||||
export type LinkLogList = PageableList<LinkLog>;
|
||||
export type LinkStatsList = LinkStatsListObject;
|
||||
export type LinkVrblList = PageableList<LinkVrbl>;
|
||||
export type LinkTableColumnsList = PageableList<LinkTableColumns>;
|
||||
|
||||
export interface LinkService {
|
||||
getLinkList(params: LinkSearchParam): Promise<LinkList>;
|
||||
getLinkInfo(params: string): Promise<LinkInfo>;
|
||||
linkQuickStart(params: string): Promise<string>;
|
||||
updateLinkInfo(data: LinkFormData): Promise<void>;
|
||||
deleteLink(params: string): Promise<void>;
|
||||
getLinkLogList(params: LinkSearchParam): Promise<LinkLogList>;
|
||||
getLinkStatsList(params: LinkSearchParam): Promise<LinkStatsList>;
|
||||
getLinkVrblList(params: string): Promise<LinkVrblList>;
|
||||
insertLinkVrbl(data: LinkVrblFormData): Promise<number>;
|
||||
deleteLinkVrbl(data: string[]): Promise<void>;
|
||||
getLinkVrblInfo(params: string): Promise<LinkVrbl>;
|
||||
updateLinkVrblInfo(data: LinkVrblFormData): Promise<void>;
|
||||
getLinkTableInfo(params: string): Promise<LinkTable>;
|
||||
getLinkTableColList(params: string): Promise<LinkTableColumnsList>;
|
||||
getLinkTableColInfo(params: { colSn: string; tblSn: string; }): Promise<LinkTableColumns>;
|
||||
insertLinkTableCol(data: LinkTableColData): Promise<void>;
|
||||
deleteLinkTableCol(data: string[]): Promise<void>;
|
||||
updateLinkTableCol(data: LinkTableColData): Promise<void>;
|
||||
insertLinkTableInfo(data: LinkTableFormData): Promise<void>;
|
||||
updateLinkTableInfo(data: LinkTableFormData): Promise<void>;
|
||||
insertLinkInfo(data: LinkFormData): Promise<void>;
|
||||
countLinkTblNm(tblNm: string): Promise<{ count: number, tbl_sn: string }>;
|
||||
updateLinkResultCode(linkSn: string): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { ModifyLinkPage } from "./ui/modify-page";
|
||||
export { RegistLinkPage } from "./ui/regist-page";
|
||||
export { LinkPage } from "./ui/link-page";
|
||||
@@ -0,0 +1,114 @@
|
||||
import dayjs from "dayjs";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { type Link } from "@/entities/link";
|
||||
import { Button } from "@common/components";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { LinkApi } from "@/entities/link";
|
||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||
|
||||
const columnHelper = createColumnHelper<Link>();
|
||||
|
||||
const convertMinutesToPeriod = (minutes: number): string => {
|
||||
if (!minutes) return '';
|
||||
|
||||
const year = Math.floor(minutes / (525600)); // 365일 * 24시간 * 60분
|
||||
minutes = minutes % 525600;
|
||||
|
||||
const month = Math.floor(minutes / (43200)); // 30일 * 24시간 * 60분
|
||||
minutes = minutes % 43200;
|
||||
|
||||
const day = Math.floor(minutes / 1440); // 24시간 * 60분
|
||||
minutes = minutes % 1440;
|
||||
|
||||
const hour = Math.floor(minutes / 60);
|
||||
const minute = minutes % 60;
|
||||
|
||||
const parts = [];
|
||||
if (year > 0) parts.push(`${year}년`);
|
||||
if (month > 0) parts.push(`${month}개월`);
|
||||
if (day > 0) parts.push(`${day}일`);
|
||||
if (hour > 0) parts.push(`${hour}시간`);
|
||||
if (minute > 0) parts.push(`${minute}분`);
|
||||
|
||||
return parts.join(' ');
|
||||
};
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor((_, index) => index + 1, {
|
||||
id: 'no',
|
||||
header: 'NO',
|
||||
}),
|
||||
columnHelper.accessor('link_sn', {
|
||||
id: 'link_sn',
|
||||
header: '연계일련번호',
|
||||
}),
|
||||
columnHelper.accessor('link_nm', {
|
||||
id: 'link_nm',
|
||||
header: '연계 명',
|
||||
}),
|
||||
columnHelper.accessor('link_url', {
|
||||
id: 'link_url',
|
||||
header: '연계 URL',
|
||||
cell: (info) => (info.getValue() ? info.getValue() : '-'),
|
||||
}),
|
||||
columnHelper.accessor('link_srvr_ip', {
|
||||
id: 'link_srvr_ip',
|
||||
header: '연계서버 IP',
|
||||
cell: (info) => (info.getValue() ? info.getValue() : '-'),
|
||||
}),
|
||||
columnHelper.accessor('link_batch_time', {
|
||||
id: 'link_batch_time',
|
||||
header: '연계 주기',
|
||||
cell: (info) => convertMinutesToPeriod(Number(info.getValue())),
|
||||
}),
|
||||
columnHelper.accessor('srvc_usg', {
|
||||
id: 'srvc_usg',
|
||||
header: '서비스용도',
|
||||
cell: (info) => (info.getValue() ? info.getValue() : '-'),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'last_link_rslt_cd',
|
||||
header: '연계상태',
|
||||
cell: (info) => {
|
||||
const resultCd = info.row.original.last_link_rslt_cd;
|
||||
return <span className={`${(resultCd === 'SUCC') ? 'text-[rgb(53,162,235)]' : (resultCd === 'FAIL' ? 'text-[rgb(255,99,132)]' : 'text-[rgba(31,208,120,1)]')}`}>{resultCd === 'SUCC' ? '성공' : (resultCd === 'FAIL' ? '실패' : '대기')}</span>
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('last_link_excn_dt', {
|
||||
id: 'last_link_excn_dt',
|
||||
header: '최종연계일',
|
||||
cell: (info) => (info.getValue() ? dayjs(info.getValue()).format('YYYY-MM-DD HH:mm') : '-'),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'quick_start_btn',
|
||||
header: '즉시 실행',
|
||||
cell: (info) => {
|
||||
const row = info.row.original;
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const { mutateAsync: updateQuickStart } = useMutation({
|
||||
mutationFn: (linkSn: string) => LinkApi.linkQuickStart(linkSn),
|
||||
onSuccess: (resut) => {
|
||||
if (resut === 'succ') {
|
||||
alert('즉시 실행 요청이 완료되었습니다.');
|
||||
} else {
|
||||
alert('기존 요청이 실행 대기중입니다.');
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
alert('즉시 실행 요청을 실패했습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Button color="sky" onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateQuickStart(row.link_sn);
|
||||
}
|
||||
}>즉시 실행</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
export { columns };
|
||||
@@ -0,0 +1,586 @@
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import { type LinkFormData } from "@/entities/link";
|
||||
import dayjs from "dayjs";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useState } from "react";
|
||||
|
||||
|
||||
type LinkFormProps = {
|
||||
form: UseFormReturn<LinkFormData>;
|
||||
linkSn?: string;
|
||||
updateLinkInfo?: (data: LinkFormData) => void;
|
||||
handleInsertLink?: (data: LinkFormData) => void;
|
||||
deleteLink?: () => void;
|
||||
updateLinkResultCode?: () => void;
|
||||
}
|
||||
|
||||
const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, updateLinkResultCode }: LinkFormProps) => {
|
||||
const isModifyMode = !!linkSn;
|
||||
const router = useRouter();
|
||||
|
||||
const { confirm } = useConfirm();
|
||||
const [onConfirm, setOnConfirm] = useState(false);
|
||||
|
||||
const handleConfirm = async (callback: () => Promise<void>) => {
|
||||
setOnConfirm(true);
|
||||
await callback();
|
||||
setOnConfirm(false);
|
||||
};
|
||||
|
||||
const handleSubmit = async (data: LinkFormData) => {
|
||||
const confirmMsg = !!linkSn ? '수정 하시겠습니까?' : '저장 하시겠습니까?';
|
||||
|
||||
await handleConfirm(async () => {
|
||||
|
||||
if (await confirm(confirmMsg)) {
|
||||
!!linkSn ? updateLinkInfo?.(data) : handleInsertLink?.(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
await handleConfirm(async () => {
|
||||
if (await confirm('삭제하시겠습니까?')) {
|
||||
deleteLink?.();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const convertMinutesToPeriod = (minutes: number) => {
|
||||
if (!minutes) return {
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
hour: 0,
|
||||
minute: 0
|
||||
};
|
||||
|
||||
const year = Math.floor(minutes / (525600)); // 365일 * 24시간 * 60분
|
||||
minutes = minutes % 525600;
|
||||
|
||||
const month = Math.floor(minutes / (43200)); // 30일 * 24시간 * 60분
|
||||
minutes = minutes % 43200;
|
||||
|
||||
const day = Math.floor(minutes / 1440); // 24시간 * 60분
|
||||
minutes = minutes % 1440;
|
||||
|
||||
const hour = Math.floor(minutes / 60);
|
||||
const minute = minutes % 60;
|
||||
|
||||
const timeParts = {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
};
|
||||
|
||||
return timeParts;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
<div className="card">
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="20%" />
|
||||
<col width="80%" />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
{isModifyMode &&
|
||||
<TableRow>
|
||||
<TableHead>연계 일련번호</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_sn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="w-[12rem] !text-[1.3rem]"
|
||||
disabled={true}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
<TableRow>
|
||||
<TableHead>* 연계 명</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 연계 분류</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_clsf_cd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select value={field.value ?? "none"} onValueChange={(value) => field.onChange(value)}>
|
||||
<SelectTrigger id="link_clsf_cd" className="w-[20rem]">
|
||||
<SelectValue placeholder="전체"></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">선택</SelectItem>
|
||||
<SelectItem value="link0001">TIBERO 데이터베이스</SelectItem>
|
||||
<SelectItem value="link0002">ORACLE 데이터베이스</SelectItem>
|
||||
<SelectItem value="link0003">공공데이터 포털 API</SelectItem>
|
||||
<SelectItem value="link0004">한강홍수통제소 API</SelectItem>
|
||||
<SelectItem value="link0005">기상청 API</SelectItem>
|
||||
<SelectItem value="link0006">국가지하수정보센터 API</SelectItem>
|
||||
<SelectItem value="link0007">행정정보공동이용센터 연계</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_clsf_cd && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_clsf_cd.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 연계 URL</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_url && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_url.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>연계서버 IP</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_srvr_ip"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 연계 KEY</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_key"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_key && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_key.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>연계 ID</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>연계 비밀번호</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_pswd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>서비스 용도</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="srvc_usg"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>연계 주기</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_batch_time"
|
||||
render={({ field }) => {
|
||||
const timeParts = convertMinutesToPeriod(Number(field.value));
|
||||
return (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
value={timeParts.year}
|
||||
className="w-20 !text-[1.3rem]"
|
||||
type="number"
|
||||
min={0}
|
||||
onChange={(e) => {
|
||||
const total = (Number(e.target.value) * 525600) +
|
||||
(timeParts.month * 43200) +
|
||||
(timeParts.day * 1440) +
|
||||
(timeParts.hour * 60) +
|
||||
timeParts.minute;
|
||||
field.onChange(total);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">년</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
value={timeParts.month}
|
||||
className="w-20 !text-[1.3rem]"
|
||||
type="number"
|
||||
min={0}
|
||||
max={11}
|
||||
onChange={(e) => {
|
||||
const total = (timeParts.year * 525600) +
|
||||
(Number(e.target.value) * 43200) +
|
||||
(timeParts.day * 1440) +
|
||||
(timeParts.hour * 60) +
|
||||
timeParts.minute;
|
||||
field.onChange(total);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">월</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
value={timeParts.day}
|
||||
className="w-20 !text-[1.3rem]"
|
||||
type="number"
|
||||
min={0}
|
||||
max={30}
|
||||
onChange={(e) => {
|
||||
const total = (timeParts.year * 525600) +
|
||||
(timeParts.month * 43200) +
|
||||
(Number(e.target.value) * 1440) +
|
||||
(timeParts.hour * 60) +
|
||||
timeParts.minute;
|
||||
field.onChange(total);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">일</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
value={timeParts.hour}
|
||||
className="w-20 !text-[1.3rem]"
|
||||
type="number"
|
||||
min={0}
|
||||
max={23}
|
||||
onChange={(e) => {
|
||||
const total = (timeParts.year * 525600) +
|
||||
(timeParts.month * 43200) +
|
||||
(timeParts.day * 1440) +
|
||||
(Number(e.target.value) * 60) +
|
||||
timeParts.minute;
|
||||
field.onChange(total);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">시간</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
value={timeParts.minute}
|
||||
className="w-20 !text-[1.3rem]"
|
||||
type="number"
|
||||
min={0}
|
||||
max={59}
|
||||
onChange={(e) => {
|
||||
const total = (timeParts.year * 525600) +
|
||||
(timeParts.month * 43200) +
|
||||
(timeParts.day * 1440) +
|
||||
(timeParts.hour * 60) +
|
||||
Number(e.target.value);
|
||||
field.onChange(total);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-2">분</span>
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_batch_time && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_batch_time.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{isModifyMode &&
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead>연계 상태</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="last_link_rslt_cd"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="flex items-center gap-6">
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ? (field.value === 'SUCC' ? '성공' : (field.value === 'FAIL' ? '실패' : '대기')) : ''}
|
||||
className="!text-[1.3rem] w-[15rem]"
|
||||
disabled
|
||||
/>
|
||||
<Button color="green" type="button" onClick={() => updateLinkResultCode?.()}>대기로 전환</Button>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>최종 연계 실행일</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="last_link_excn_dt"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ? dayjs(field.value).format('YYYY-MM-DD HH:mm:ss') : ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>다음 연계 실행일</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="next_link_excn_dt"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ? dayjs(field.value).format('YYYY-MM-DD HH:mm:ss') : ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>수정자</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfr"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>수정일시</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfcn_dt"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ? dayjs(field.value).format('YYYY-MM-DD') : ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
||||
<Button
|
||||
size="lg"
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
목록
|
||||
</Button>
|
||||
<Button size="lg" type="submit">
|
||||
저장
|
||||
</Button>
|
||||
{isModifyMode && (
|
||||
<Button size="lg" color="red" type="button" onClick={handleDelete}>
|
||||
삭제
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<ConfirmDialog open={onConfirm} />
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default LinkForm;
|
||||
@@ -0,0 +1,134 @@
|
||||
"use client";
|
||||
|
||||
import "@common/assets/styles/grid.css";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button, DataTable, Input, Label, LoadingWithAnimation } from "@common/components";
|
||||
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { LinkApi, type Link, type LinkSearchParam } from "@/entities/link";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { columns } from "../model/table-columns";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
|
||||
function LinkPage() {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [linkSn, setLinkSn] = useState<string>('');
|
||||
const [linkNm, setLinkNm] = useState<string>('');
|
||||
|
||||
const [linkSearchParam, setLinkSearchParam] = useState<LinkSearchParam>({
|
||||
linkSn: '',
|
||||
linkNm: ''
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, linkSearchParam],
|
||||
queryFn: () => LinkApi.getLinkList(linkSearchParam),
|
||||
});
|
||||
|
||||
const handleResetSearchParam = () => {
|
||||
setLinkSn('');
|
||||
setLinkNm('');
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
linkSn: '',
|
||||
linkNm: '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
linkSn,
|
||||
linkNm
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (link: Link) => {
|
||||
queryClient.removeQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`],
|
||||
});
|
||||
queryClient.removeQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo`],
|
||||
});
|
||||
router.push(`link/register/${link.link_sn}`);
|
||||
};
|
||||
|
||||
const handleClickRegist = () => {
|
||||
router.push('link/register');
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
manualPagination: true,
|
||||
enableMultiRowSelection: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
onRowClick: handleRowClick,
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex items-center gap-2 p-2" onSubmit={handleSubmit}>
|
||||
<Label htmlFor="liSnSearchInput" className="mr-2 text-label">
|
||||
일련번호
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem] mr-4"
|
||||
id="liSnSearchInput"
|
||||
value={linkSn}
|
||||
onChange={(e) => setLinkSn(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Label htmlFor="liNmSearchInput" className="mr-2 text-label">
|
||||
연계 명
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem] mr-2"
|
||||
id="liNmSearchInput"
|
||||
value={linkNm}
|
||||
onChange={(e) => setLinkNm(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button color="green" className="h-[3.2rem]" type="button" onClick={handleResetSearchParam}>
|
||||
초기화
|
||||
</Button>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border flex flex-col h-[calc(100vh-28rem)]">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">연계 목록</h3>
|
||||
<div>
|
||||
<Button onClick={handleClickRegist}>등록</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] overflow-auto h-full !p-0">
|
||||
{!query.isLoading ?
|
||||
<div>{tableComponent}</div>
|
||||
:
|
||||
<div className="[data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center h-full">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { LinkPage };
|
||||
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { LinkApi, linkFormSchema, type LinkFormData } from "@/entities/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import LinkForm from "./link-form";
|
||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||
import { useEffect } from "react";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { getCookie } from "@common/components";
|
||||
|
||||
function ModifyLinkPage({ linkSn }: { linkSn: string }) {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userUniqId = getCookie(AUTH_USER_ID) ?? "";
|
||||
|
||||
// 컴포넌트 마운트 시 캐시 초기화
|
||||
useEffect(() => {
|
||||
queryClient.removeQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo`],
|
||||
});
|
||||
}, [queryClient]);
|
||||
|
||||
const { data: linkInfo, refetch } = useQuery({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo`],
|
||||
queryFn: () => LinkApi.getLinkInfo(linkSn),
|
||||
enabled: !!linkSn,
|
||||
});
|
||||
|
||||
const { mutateAsync: updateLinkInfo } = useMutation({
|
||||
mutationFn: (data: LinkFormData) => LinkApi.updateLinkInfo({
|
||||
...data,
|
||||
link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null,
|
||||
mdfr: userUniqId
|
||||
}),
|
||||
onSuccess: () => {
|
||||
alert('수정되었습니다.');
|
||||
router.push('/link');
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteLink } = useMutation({
|
||||
mutationFn: () => LinkApi.deleteLink(linkSn),
|
||||
onSuccess: () => {
|
||||
alert('삭제되었습니다.', router.back);
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: updateLinkResultCode } = useMutation({
|
||||
mutationFn: () => LinkApi.updateLinkResultCode(linkSn),
|
||||
onSuccess: () => {
|
||||
alert('대기로 전환되었습니다.');
|
||||
refetch();
|
||||
}
|
||||
});
|
||||
|
||||
const fetchUserInfo = async (id: string) => {
|
||||
const userData = await getUserInfo();
|
||||
return userData.userName;
|
||||
};
|
||||
|
||||
const form = useForm<LinkFormData>({
|
||||
resolver: zodResolver(linkFormSchema),
|
||||
defaultValues: {},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const applyFormData = async () => {
|
||||
if (linkInfo) {
|
||||
let newData = { ...linkInfo };
|
||||
|
||||
if (linkInfo.mdfr) {
|
||||
const userName = await fetchUserInfo(linkInfo.mdfr);
|
||||
newData = {
|
||||
...linkInfo,
|
||||
mdfr: userName,
|
||||
};
|
||||
}
|
||||
|
||||
form.reset(newData);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
applyFormData();
|
||||
}, [linkInfo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="card card-border !mb-0 !mt-6">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">연계정보 수정</h3>
|
||||
</div>
|
||||
<LinkForm
|
||||
form={form}
|
||||
linkSn={linkSn}
|
||||
updateLinkInfo={updateLinkInfo}
|
||||
deleteLink={deleteLink}
|
||||
updateLinkResultCode={updateLinkResultCode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { ModifyLinkPage };
|
||||
@@ -0,0 +1,64 @@
|
||||
"use client";
|
||||
|
||||
import { LinkApi, type LinkFormData, linkFormSchema } from "@/entities/link";
|
||||
import LinkForm from "./link-form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import dayjs from "dayjs";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { getCookie } from "@common/components";
|
||||
|
||||
function RegistLinkPage() {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const router = useRouter();
|
||||
const userUniqId = getCookie(AUTH_USER_ID) ?? "";
|
||||
|
||||
const form = useForm<LinkFormData>({
|
||||
resolver: zodResolver(linkFormSchema),
|
||||
defaultValues: {
|
||||
link_nm: '',
|
||||
link_url: '',
|
||||
link_srvr_ip: '',
|
||||
link_db_cd: '',
|
||||
link_key: '',
|
||||
link_id: '',
|
||||
link_pswd: '',
|
||||
srvc_usg: '',
|
||||
link_batch_time: 5,
|
||||
link_clsf_cd: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: insertLinkInfo } = useMutation({
|
||||
mutationFn: (data: LinkFormData) => LinkApi.insertLinkInfo(data),
|
||||
onSuccess: () => {
|
||||
alert('저장되었습니다.');
|
||||
router.push('/link');
|
||||
}
|
||||
});
|
||||
|
||||
const handleInsertLink = (data: LinkFormData) => {
|
||||
let insertData = {
|
||||
...data,
|
||||
link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null,
|
||||
next_link_excn_dt: dayjs().add(Number(data.link_batch_time), 'minute').format('YYYY-MM-DD HH:mm:ss'),
|
||||
mdfr: userUniqId,
|
||||
};
|
||||
|
||||
insertLinkInfo(insertData);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border !mb-0 !mt-6">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">연계 등록</h3>
|
||||
</div>
|
||||
<LinkForm form={form} handleInsertLink={handleInsertLink} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { RegistLinkPage };
|
||||
@@ -0,0 +1 @@
|
||||
export { LinkLogPage } from './ui/link-log-page';
|
||||
@@ -0,0 +1,55 @@
|
||||
import dayjs from "dayjs";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { type LinkLog } from "@/entities/link";
|
||||
|
||||
const columnHelper = createColumnHelper<LinkLog>();
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor('rownum', {
|
||||
id: 'rownum',
|
||||
header: 'NO',
|
||||
}),
|
||||
columnHelper.accessor('link_sn', {
|
||||
id: 'link_sn',
|
||||
header: '연계일련번호',
|
||||
}),
|
||||
columnHelper.accessor('link_nm', {
|
||||
id: 'link_nm',
|
||||
header: '연계 명',
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'link_rslt_cd',
|
||||
header: '연계실행결과',
|
||||
cell: (info) => {
|
||||
const isSucc = info.row.original.link_rslt_cd.trim() === 'SUCC';
|
||||
return <span className={`${isSucc ? 'text-[rgb(53,162,235)]' : 'text-[rgb(255,99,132)]'}`}>{isSucc ? '성공' : '실패'}</span>
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('link_err_cd', {
|
||||
id: 'link_err_cd',
|
||||
header: '연계오류코드',
|
||||
cell: (info) => (info.getValue() ? info.getValue() : '-'),
|
||||
}),
|
||||
columnHelper.accessor('err_cn', {
|
||||
id: 'err_cn',
|
||||
header: '연계오류메세지',
|
||||
cell: (info) => (
|
||||
<div
|
||||
className="max-w-[500px] truncate"
|
||||
title={info.getValue() ?? ''}
|
||||
>
|
||||
{info.getValue() || '-'}
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('log_crt_dt', {
|
||||
id: 'log_crt_dt',
|
||||
header: '연계실행일자',
|
||||
cell: (info) => {
|
||||
const date = dayjs(info.getValue());
|
||||
return date.format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
export { columns };
|
||||
@@ -0,0 +1,189 @@
|
||||
"use client";
|
||||
|
||||
import "@common/assets/styles/grid.css";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button, Input, Label, DatePicker, Select, SelectTrigger, SelectContent, SelectItem, SelectValue, LoadingWithAnimation } from "@common/components";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { getCoreRowModel, type PaginationState, type RowSelectionState, } from "@tanstack/react-table";
|
||||
import { LinkApi, type LinkSearchParam } from "@/entities/link";
|
||||
import { columns } from "../model/table-columns";
|
||||
import dayjs from "dayjs";
|
||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
|
||||
function LinkLogPage() {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const [linkSn, setLinkSn] = useState<string>('');
|
||||
const [linkNm, setLinkNm] = useState<string>('');
|
||||
const [isSucc, setIsSucc] = useState<string>('ALL');
|
||||
const [startDate, setStartDate] = useState<string>(dayjs().subtract(7, "day").format('YYYY-MM-DD'));
|
||||
const [endDate, setEndDate] = useState<string>(dayjs().format('YYYY-MM-DD'));
|
||||
|
||||
const [linkSearchParam, setLinkSearchParam] = useState<LinkSearchParam>({
|
||||
linkSn: '',
|
||||
linkNm: '',
|
||||
isSucc: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 100,
|
||||
});
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, linkSearchParam, pagination],
|
||||
queryFn: () =>
|
||||
LinkApi.getLinkLogList({
|
||||
...linkSearchParam,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
isSucc,
|
||||
startDate,
|
||||
endDate,
|
||||
}),
|
||||
});
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: {
|
||||
pagination,
|
||||
rowSelection,
|
||||
},
|
||||
manualPagination: true,
|
||||
enableMultiRowSelection: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
},
|
||||
dataTableOptions: {
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
const handleResetSearchParam = () => {
|
||||
setIsSucc('ALL');
|
||||
setLinkNm('');
|
||||
setLinkSn('');
|
||||
setStartDate(dayjs().subtract(7, "day").format('YYYY-MM-DD'));
|
||||
setEndDate(dayjs().format('YYYY-MM-DD'));
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
linkSn: '',
|
||||
linkNm: '',
|
||||
isSucc: 'ALL',
|
||||
startDate: dayjs().subtract(7, "day").format('YYYY-MM-DD'),
|
||||
endDate: dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e?: React.FormEvent<HTMLFormElement>) => {
|
||||
e?.preventDefault();
|
||||
|
||||
if (startDate && endDate) {
|
||||
|
||||
if (startDate > endDate) {
|
||||
alert('종료일이 시작일보다 이전일 수 없습니다.');
|
||||
return;
|
||||
} else {
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
linkSn,
|
||||
linkNm,
|
||||
isSucc,
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
alert('조회 날짜를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex items-center gap-2 p-2" onSubmit={handleSubmit}>
|
||||
<Label htmlFor="liSnSearchInput" className="mr-2 text-label">
|
||||
일련번호
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem] mr-4"
|
||||
id="liSnSearchInput"
|
||||
value={linkSn}
|
||||
onChange={(e) => setLinkSn(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Label htmlFor="liNmSearchInput" className="mr-2 text-label">
|
||||
연계 명
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem] mr-4"
|
||||
id="liNmSearchInput"
|
||||
value={linkNm}
|
||||
onChange={(e) => setLinkNm(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Label htmlFor="liResultCode" className="mr-2 text-label">
|
||||
실행결과
|
||||
</Label>
|
||||
<Select onValueChange={(e) => setIsSucc(e)} value={isSucc}>
|
||||
<SelectTrigger id="liResultCode" className="w-[10rem] mr-4">
|
||||
<SelectValue placeholder="전체"></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ALL">전체</SelectItem>
|
||||
<SelectItem value="SUCC">성공</SelectItem>
|
||||
<SelectItem value="FAIL">실패</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Label htmlFor="sDateSearchInput" className="mx-2 text-label">
|
||||
시작일
|
||||
</Label>
|
||||
<DatePicker id="sDateSearchInput" mode={"single"} selected={dayjs(startDate).toDate()} onSelect={(e) => setStartDate(dayjs(e).format('YYYY-MM-DD'))} />
|
||||
<Label htmlFor="eDateSearchInput" className="mx-2 text-label">
|
||||
종료일
|
||||
</Label>
|
||||
<DatePicker id="eDateSearchInput" mode={"single"} className="mr-2" selected={dayjs(endDate).toDate()} onSelect={(e) => setEndDate(dayjs(e).format('YYYY-MM-DD'))} />
|
||||
<Button color="green" className="h-[3.2rem]" type="button" onClick={handleResetSearchParam}>
|
||||
초기화
|
||||
</Button>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border flex flex-col h-[calc(100vh-28rem)]">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">로그 목록 (검색 조건에 따른 건수 : {query.data?.totalElements.toLocaleString('ko-KR') ?? 0} )</h3>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] overflow-auto h-full !p-0">
|
||||
{query.isLoading ?
|
||||
<div className="[data-theme='black']:bg-transparent rounded-lg p-10 h-full flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div>{tableComponent}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export { LinkLogPage };
|
||||
@@ -0,0 +1 @@
|
||||
export { LinkParamsPage } from './ui/link-params-page';
|
||||
@@ -0,0 +1,26 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { type Link } from "@/entities/link";
|
||||
|
||||
const columnHelper = createColumnHelper<Link>();
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor((_, index) => index + 1, {
|
||||
id: 'no',
|
||||
header: 'NO',
|
||||
}),
|
||||
columnHelper.accessor('link_sn', {
|
||||
id: 'link_sn',
|
||||
header: '연계 일련번호',
|
||||
}),
|
||||
columnHelper.accessor('link_nm', {
|
||||
id: 'link_nm',
|
||||
header: '연계 명',
|
||||
}),
|
||||
columnHelper.accessor('srvc_usg', {
|
||||
id: 'srvc_usg',
|
||||
header: '서비스용도',
|
||||
cell: (info) => (info.getValue() ? info.getValue() : '-'),
|
||||
}),
|
||||
];
|
||||
|
||||
export { columns };
|
||||
@@ -0,0 +1,98 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { type LinkVrbl } from "@/entities/link";
|
||||
import { Checkbox } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<LinkVrbl>();
|
||||
|
||||
function vrblColumns({
|
||||
selectedVrblIds,
|
||||
setSelectedVrblIds,
|
||||
}: {
|
||||
selectedVrblIds: string[];
|
||||
setSelectedVrblIds: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
}) {
|
||||
return [
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
const allRows = table.getRowModel().rows;
|
||||
const allSelected =
|
||||
allRows.length > 0 &&
|
||||
allRows.every((row) =>
|
||||
selectedVrblIds.includes(row.original.link_vrbl_sn)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center">
|
||||
<Checkbox
|
||||
className="bg-white"
|
||||
checked={
|
||||
allSelected
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setSelectedVrblIds(allRows.map((row) => row.original.link_vrbl_sn));
|
||||
} else {
|
||||
setSelectedVrblIds([]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const id = row.original.link_vrbl_sn;
|
||||
const checked = selectedVrblIds.includes(id);
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center">
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) => {
|
||||
setSelectedVrblIds((prev) =>
|
||||
checked ? [...prev, id] : prev.filter((x) => x !== id)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}),
|
||||
|
||||
columnHelper.accessor("link_vrbl_sn", {
|
||||
id: "link_vrbl_sn",
|
||||
header: "연계 변수 일련번호",
|
||||
}),
|
||||
columnHelper.accessor("link_vrbl_nm", {
|
||||
id: "link_vrbl_nm",
|
||||
header: "변수 명",
|
||||
}),
|
||||
columnHelper.accessor("link_vrbl_cd", {
|
||||
id: "link_vrbl_cd",
|
||||
header: "변수코드",
|
||||
cell: (info) => (info.getValue() ? info.getValue() : '-'),
|
||||
}),
|
||||
columnHelper.accessor("link_vrbl_vl", {
|
||||
id: "link_vrbl_vl",
|
||||
header: "변수 값",
|
||||
cell: (info) => (info.getValue() === 'now' ? '요청일자' : info.getValue() === 'nowMonth' ? '요청월' : info.getValue()),
|
||||
}),
|
||||
columnHelper.accessor("link_vrbl_expln", {
|
||||
id: "link_vrbl_expln",
|
||||
header: "설명",
|
||||
cell: (info) => (
|
||||
<div
|
||||
className="max-w-72 truncate"
|
||||
title={info.getValue() ?? ''}
|
||||
>
|
||||
{info.getValue() || '-'}
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
export { vrblColumns };
|
||||
@@ -0,0 +1,347 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
Button,
|
||||
Input,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Checkbox,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Label
|
||||
} from "@common/components";
|
||||
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
|
||||
import { type LinkVrblFormData } from "@/entities/link";
|
||||
import dayjs from "dayjs";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useState, useEffect } from "react";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
|
||||
type LinkParamsDialogProps = {
|
||||
selectedLink: {
|
||||
linkSn: string;
|
||||
linkNm: string;
|
||||
};
|
||||
form: UseFormReturn<LinkVrblFormData>;
|
||||
insertLinkVrbl: (data: LinkVrblFormData) => void;
|
||||
dialogOpen: boolean;
|
||||
onOpenChange: (val: boolean) => void;
|
||||
updateLinkVrbl: (data: LinkVrblFormData) => void;
|
||||
}
|
||||
|
||||
function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOpenChange, updateLinkVrbl }: LinkParamsDialogProps) {
|
||||
|
||||
const isModifyMode = !!form.getValues('link_vrbl_sn');
|
||||
|
||||
const { confirm } = useConfirm();
|
||||
const [onConfirm, setOnConfirm] = useState<boolean>(false);
|
||||
const [isToday, setIsToday] = useState<boolean>(false);
|
||||
const [checkNowMonth, setCheckNowMonth] = useState<string>('now');
|
||||
|
||||
const handleConfirm = async (callback: () => Promise<void>) => {
|
||||
setOnConfirm(true);
|
||||
await callback();
|
||||
setOnConfirm(false);
|
||||
};
|
||||
|
||||
function replaceEmptyWithNull<T extends Record<string, any>>(data: T): T {
|
||||
const result = {} as T;
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key as keyof T] = value === '' ? null : value;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const handleSubmit = async (data: LinkVrblFormData) => {
|
||||
const confirmMsg = isModifyMode ? '수정 하시겠습니까?' : '저장 하시겠습니까?';
|
||||
|
||||
await handleConfirm(async () => {
|
||||
const cleanData = replaceEmptyWithNull(data);
|
||||
|
||||
if (await confirm(confirmMsg)) {
|
||||
isModifyMode ? updateLinkVrbl(isToday ? { ...cleanData, link_vrbl_vl: checkNowMonth } : cleanData) : insertLinkVrbl(isToday ? { ...cleanData, link_vrbl_vl: checkNowMonth } : cleanData);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCheckTodayValue = (check: boolean | string) => {
|
||||
if (check) {
|
||||
setIsToday(true);
|
||||
form.setValue('link_vrbl_vl', (checkNowMonth === 'now' ? '요청일자' : '요청월'));
|
||||
} else {
|
||||
setIsToday(false);
|
||||
form.setValue('link_vrbl_vl', '');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialogOpen) {
|
||||
setIsToday(false);
|
||||
setCheckNowMonth('now');
|
||||
form.reset({
|
||||
link_vrbl_nm: '',
|
||||
link_vrbl_expln: '',
|
||||
link_vrbl_cd: '',
|
||||
link_vrbl_vl: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (isModifyMode && (form.getValues('link_vrbl_vl') === 'now' || form.getValues('link_vrbl_vl') === 'nowMonth')) {
|
||||
setIsToday(true);
|
||||
setCheckNowMonth(form.getValues('link_vrbl_vl'));
|
||||
}
|
||||
|
||||
}, [dialogOpen]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
onOpenChange(open);
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button color="green" className="h-[3.2rem]" onClick={() => onOpenChange(true)}>행 추가</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[55rem]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{selectedLink.linkNm} ({selectedLink.linkSn})
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
<div className="card">
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="30%" />
|
||||
<col width="70%" />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
{isModifyMode &&
|
||||
<TableRow>
|
||||
<TableHead>변수 일련번호</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_vrbl_sn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
<TableRow>
|
||||
<TableHead>* 변수 명</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_vrbl_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_vrbl_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_vrbl_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>변수 설명</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_vrbl_expln"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_vrbl_expln && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_vrbl_expln.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>변수 코드</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_vrbl_cd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_vrbl_cd && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_vrbl_cd.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 변수 값</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_vrbl_vl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-x-2 mb-4">
|
||||
<Checkbox id="isToday" className="!border-gray-600" checked={isToday} onCheckedChange={(check) => handleCheckTodayValue(check)} />
|
||||
<label htmlFor="isToday" className="text-gray-600">변수 값이 오늘 날짜 일시 체크해 주세요.</label>
|
||||
</div>
|
||||
{isToday &&
|
||||
<div className="flex items-center gap-x-2 mb-4">
|
||||
<RadioGroup
|
||||
name="checkNowMonth"
|
||||
onValueChange={(value) => {
|
||||
setCheckNowMonth(value);
|
||||
form.setValue('link_vrbl_vl', (value === 'now' ? '요청일자' : '요청월'));
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="now" id="now" checked={checkNowMonth === 'now'} />
|
||||
<Label htmlFor="now">요청일자(YYYY-MM-DD)</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="nowMonth" id="nowMonth" checked={checkNowMonth === 'nowMonth'} />
|
||||
<Label htmlFor="nowMonth">요청월(YYYY-MM)</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
}
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ? (field.value === 'now' ? '요청일자' : field.value === 'nowMonth' ? '요청월' : field.value) : ''}
|
||||
className={`!text-[1.3rem] ${isToday ? 'bg-gray-100' : ''}`}
|
||||
autoComplete="off"
|
||||
disabled={isToday}
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_vrbl_vl && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_vrbl_vl.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{isModifyMode &&
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead>수정자</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfr"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>수정일시</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfcn_dt"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ? dayjs(field.value).format('YYYY-MM-DD') : ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">저장</Button>
|
||||
<DialogClose asChild>
|
||||
<Button color="white">취소</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
<ConfirmDialog isOpen={onConfirm} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
};
|
||||
|
||||
export { LinkParamsDialog };
|
||||
@@ -0,0 +1,405 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import {
|
||||
Button,
|
||||
DataTable,
|
||||
Input,
|
||||
Label,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
import { getCoreRowModel, useReactTable, type PaginationState, type RowSelectionState, } from "@tanstack/react-table";
|
||||
import { LinkApi, type Link, type LinkSearchParam, type LinkVrblFormData, linkVrblFormSchema } from "@/entities/link";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { columns } from "../model/table-columns";
|
||||
import { vrblColumns } from "../model/vrbl-table-columns";
|
||||
import { type LinkVrbl } from "@/entities/link";
|
||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||
import { LinkParamsDialog } from "./link-params-dialog";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { getCookie } from "@common/components";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
|
||||
function LinkParamsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [linkSn, setLinkSn] = useState<string>('');
|
||||
const [linkNm, setLinkNm] = useState<string>('');
|
||||
|
||||
const [linkSearchParam, setLinkSearchParam] = useState<LinkSearchParam>({
|
||||
linkSn: '',
|
||||
linkNm: ''
|
||||
});
|
||||
|
||||
const [selectedLink, setSelectedLink] = useState<{
|
||||
linkSn: string;
|
||||
linkNm: string;
|
||||
}>({
|
||||
linkSn: '',
|
||||
linkNm: ''
|
||||
});
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 100,
|
||||
});
|
||||
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
const [selectedVrblIds, setSelectedVrblIds] = useState<string[]>([]);
|
||||
const [isDBdata, setIsDBData] = useState<boolean>(false);
|
||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
const [onConfirm, setOnConfirm] = useState<boolean>(false);
|
||||
|
||||
const userUniqId = getCookie(AUTH_USER_ID) ?? "";
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const dbIdList = ['link0001', 'link0002'];
|
||||
|
||||
// Link 목록 쿼리
|
||||
const query = useQuery({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, linkSearchParam],
|
||||
queryFn: () => LinkApi.getLinkList({
|
||||
...linkSearchParam,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
});
|
||||
|
||||
// LinkVrbl 목록 쿼리 - 별도 관리
|
||||
const linkVrblQuery = useQuery({
|
||||
queryKey: ['linkVrblList', selectedLink.linkSn],
|
||||
queryFn: () => LinkApi.getLinkVrblList(selectedLink.linkSn),
|
||||
enabled: !!selectedLink.linkSn && !dbIdList.includes(selectedLink.linkSn),
|
||||
staleTime: 0, // 항상 최신 데이터를 가져오도록 설정
|
||||
});
|
||||
|
||||
const handleResetSearchParam = () => {
|
||||
setSelectedLink({
|
||||
linkSn: '',
|
||||
linkNm: ''
|
||||
});
|
||||
|
||||
setLinkSn('');
|
||||
setLinkNm('');
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
linkSn: '',
|
||||
linkNm: ''
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirm = async (callback: () => Promise<void>) => {
|
||||
setOnConfirm(true);
|
||||
await callback();
|
||||
setOnConfirm(false);
|
||||
};
|
||||
|
||||
const { mutateAsync: insertLinkVrbl } = useMutation({
|
||||
mutationFn: async (data: LinkVrblFormData) =>
|
||||
await LinkApi.insertLinkVrbl({
|
||||
...data,
|
||||
link_sn: selectedLink.linkSn,
|
||||
mdfr: userUniqId,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
// 쿼리 무효화를 통한 최신 데이터 가져오기
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['linkVrblList', selectedLink.linkSn]
|
||||
});
|
||||
setDialogOpen(false);
|
||||
alert('저장되었습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: updateLinkVrbl } = useMutation({
|
||||
mutationFn: async (data: LinkVrblFormData) => await LinkApi.updateLinkVrblInfo({
|
||||
...data,
|
||||
link_sn: selectedLink.linkSn,
|
||||
mdfr: userUniqId,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['linkVrblList', selectedLink.linkSn]
|
||||
});
|
||||
setDialogOpen(false);
|
||||
alert('저장되었습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteLinkVrbl } = useMutation({
|
||||
mutationFn: async (data: string[]) => await LinkApi.deleteLinkVrbl(data),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['linkVrblList', selectedLink.linkSn]
|
||||
});
|
||||
setSelectedVrblIds([]);
|
||||
alert('삭제되었습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
// linkVrblQuery의 데이터를 사용
|
||||
const tableData = useMemo(() => {
|
||||
return linkVrblQuery.data?.content ?? [];
|
||||
}, [linkVrblQuery.data]);
|
||||
|
||||
const vrblCols = useMemo(
|
||||
() =>
|
||||
vrblColumns({
|
||||
selectedVrblIds,
|
||||
setSelectedVrblIds,
|
||||
}),
|
||||
[selectedVrblIds]
|
||||
);
|
||||
|
||||
const vrblTable = useReactTable({
|
||||
data: tableData,
|
||||
columns: vrblCols,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
const form = useForm<LinkVrblFormData>({
|
||||
resolver: zodResolver(linkVrblFormSchema),
|
||||
defaultValues: {},
|
||||
});
|
||||
|
||||
const handleRowClick = async (link: Link) => {
|
||||
setSelectedLink({
|
||||
linkSn: link.link_sn,
|
||||
linkNm: link.link_nm,
|
||||
});
|
||||
|
||||
setSelectedVrblIds([]);
|
||||
|
||||
if (dbIdList.includes(link.link_clsf_cd)) {
|
||||
setIsDBData(true);
|
||||
} else {
|
||||
setIsDBData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
setSelectedLink({
|
||||
linkSn: '',
|
||||
linkNm: ''
|
||||
});
|
||||
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
linkSn,
|
||||
linkNm
|
||||
});
|
||||
};
|
||||
|
||||
const fetchUserInfo = async (id: string) => {
|
||||
const userData = await getUserInfo();
|
||||
return userData.userName;
|
||||
};
|
||||
|
||||
const handleDeleteParam = async () => {
|
||||
if (selectedVrblIds.length === 0) {
|
||||
alert('삭제할 파라미터를 선택해 주세요.');
|
||||
} else {
|
||||
await handleConfirm(async () => {
|
||||
if (await confirm('선택한 파라미터 정보를 삭제하시겠습니까?')) {
|
||||
deleteLinkVrbl(selectedVrblIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleVrblRowClick = async (data: LinkVrbl) => {
|
||||
const linkVrblInfo = await LinkApi.getLinkVrblInfo(data.link_vrbl_sn);
|
||||
|
||||
if (linkVrblInfo) {
|
||||
let newData = { ...linkVrblInfo };
|
||||
|
||||
if (linkVrblInfo.mdfr) {
|
||||
const userName = await fetchUserInfo(linkVrblInfo.mdfr);
|
||||
newData = {
|
||||
...linkVrblInfo,
|
||||
mdfr: userName,
|
||||
};
|
||||
}
|
||||
|
||||
form.reset(newData);
|
||||
}
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: {
|
||||
pagination,
|
||||
rowSelection,
|
||||
},
|
||||
manualPagination: true,
|
||||
enableMultiRowSelection: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
},
|
||||
dataTableOptions: {
|
||||
onRowClick: handleRowClick,
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
// LinkVrbl 데이터 표시 로직
|
||||
const renderLinkVrblContent = () => {
|
||||
if (isDBdata) {
|
||||
return (
|
||||
<div className="flex items-center justify-center mt-14 text-gray-500 text-3xl flex-1">
|
||||
변수를 지원하지 않는 연계 데이터입니다.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!selectedLink.linkSn) {
|
||||
return (
|
||||
<div className="flex items-center justify-center mt-14 text-gray-500 text-3xl flex-1">
|
||||
연계 데이터를 선택해주세요.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (linkVrblQuery.isLoading) {
|
||||
return (
|
||||
<div className="[data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center h-full">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 mt-14 flex justify-end items-center flex-none">
|
||||
{tableData.length > 0 && (
|
||||
<Button color="red" className="h-[3.2rem] mr-2" type="button" onClick={handleDeleteParam}>
|
||||
행 삭제
|
||||
</Button>
|
||||
)}
|
||||
<LinkParamsDialog
|
||||
selectedLink={selectedLink}
|
||||
form={form}
|
||||
insertLinkVrbl={insertLinkVrbl}
|
||||
dialogOpen={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
updateLinkVrbl={updateLinkVrbl}
|
||||
/>
|
||||
</div>
|
||||
{tableData.length > 0 ? (
|
||||
<div className="overflow-auto flex-1 min-h-[300px]">
|
||||
<DataTable table={vrblTable} onRowClick={handleVrblRowClick} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-20 text-gray-500 text-3xl">
|
||||
등록된 연계 변수가 없습니다.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex items-center gap-2 p-2" onSubmit={handleSubmit}>
|
||||
<Label htmlFor="liSnSearchInput" className="mr-2 text-label">
|
||||
일련번호
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem] mr-4"
|
||||
id="liSnSearchInput"
|
||||
value={linkSn}
|
||||
onChange={(e) => setLinkSn(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Label htmlFor="liNmSearchInput" className="mr-2 text-label">
|
||||
연계 명
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem] mr-2"
|
||||
id="liNmSearchInput"
|
||||
value={linkNm}
|
||||
onChange={(e) => setLinkNm(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button color="green" className="h-[3.2rem]" type="button" onClick={handleResetSearchParam}>
|
||||
초기화
|
||||
</Button>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card !mb-0 flex h-[calc(100vh-28rem)] gap-5 !p-0">
|
||||
<div className="card card-border w-1/2 !mx-0 flex flex-col">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">연계 목록</h3>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] overflow-auto !p-0 h-full">
|
||||
{!query.isLoading ? (
|
||||
<div>{tableComponent}</div>
|
||||
) : (
|
||||
<div className="[data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center h-full">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="card card-border w-1/2 !mx-0 flex flex-col">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">연계 변수 정보</h3>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] !p-0 overflow-auto h-full flex flex-col">
|
||||
<Table variant="secondary" className="flex-none">
|
||||
<colgroup>
|
||||
<col width="20%" />
|
||||
<col width="80%" />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>연계 일련번호</TableHead>
|
||||
<TableCell>
|
||||
{selectedLink.linkSn ? selectedLink.linkSn : '-'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>연계 명</TableHead>
|
||||
<TableCell>
|
||||
{selectedLink.linkNm ? selectedLink.linkNm : '-'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
{renderLinkVrblContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmDialog isOpen={onConfirm} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { LinkParamsPage };
|
||||
@@ -0,0 +1 @@
|
||||
export { LinkStatsPage } from './ui/link-stats-page';
|
||||
@@ -0,0 +1,378 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import { LinkApi, type LinkStats, type LinkSearchParam } from "@/entities/link";
|
||||
import { Button, Label, DatePicker, LoadingWithAnimation } from "@common/components";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Tooltip,
|
||||
Legend,
|
||||
type ChartOptions,
|
||||
type Chart,
|
||||
} from "chart.js";
|
||||
import { Line } from "react-chartjs-2";
|
||||
import { getCoreRowModel, type PaginationState, type RowSelectionState, } from "@tanstack/react-table";
|
||||
import { columns } from "@/pages/linkLog/model/table-columns";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
interface LinkStatsListObject {
|
||||
linkSuccStats: LinkStats[] | null;
|
||||
linkFailStats: LinkStats[] | null;
|
||||
}
|
||||
|
||||
function LinkStatsPage() {
|
||||
|
||||
const succChartRef = useRef<Chart<"line"> | null>(null);
|
||||
const failChartRef = useRef<Chart<"line"> | null>(null);
|
||||
|
||||
const [startDate, setStartDate] = useState<string>(dayjs().subtract(7, "day").format('YYYY-MM-DD'));
|
||||
const [endDate, setEndDate] = useState<string>(dayjs().format('YYYY-MM-DD'));
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 100,
|
||||
});
|
||||
|
||||
const [linkStatsList, setLinkStatsList] = useState<LinkStatsListObject | null>(null);
|
||||
const [logQueryParam, setLogQueryParam] = useState<{
|
||||
isSucc: 'SUCC' | 'FAIL';
|
||||
linkSn?: string;
|
||||
linkNm?: string;
|
||||
} | null>(null);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, logQueryParam, pagination],
|
||||
queryFn: () =>
|
||||
LinkApi.getLinkLogList({
|
||||
...logQueryParam,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
startDate,
|
||||
endDate,
|
||||
}),
|
||||
enabled: !!logQueryParam,
|
||||
});
|
||||
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: {
|
||||
pagination,
|
||||
rowSelection,
|
||||
},
|
||||
manualPagination: true,
|
||||
enableMultiRowSelection: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
},
|
||||
dataTableOptions: {
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
const [linkSearchParam, setLinkSearchParam] = useState<LinkSearchParam>({
|
||||
linkSn: '',
|
||||
linkNm: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
ChartJS.register(
|
||||
LineElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
|
||||
const succData = {
|
||||
labels: linkStatsList?.linkSuccStats?.map((e) => e.link_nm),
|
||||
datasets: [
|
||||
{
|
||||
label: '연계 성공',
|
||||
data: linkStatsList?.linkSuccStats?.map((e) => e.count),
|
||||
fill: false,
|
||||
borderColor: 'rgb(53, 162, 235)',
|
||||
backgroundColor: 'rgb(53, 162, 235)',
|
||||
tension: 0.1,
|
||||
pointRadius: 6,
|
||||
pointHoverRadius: 8,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const succOptions: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const failData = {
|
||||
labels: linkStatsList?.linkFailStats?.map((e) => e.link_nm),
|
||||
datasets: [
|
||||
{
|
||||
label: '연계 실패',
|
||||
data: linkStatsList?.linkFailStats?.map((e) => e.count),
|
||||
fill: false,
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
backgroundColor: 'rgb(255, 99, 132)',
|
||||
tension: 0.1,
|
||||
pointRadius: 6,
|
||||
pointHoverRadius: 8,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const failOptions: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const handleResetSearchParam = async () => {
|
||||
setLinkSearchParam({
|
||||
linkSn: '',
|
||||
linkNm: '',
|
||||
startDate: dayjs().subtract(7, "day").format('YYYY-MM-DD'),
|
||||
endDate: dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
|
||||
setLogQueryParam({
|
||||
isSucc: 'SUCC',
|
||||
linkNm: '',
|
||||
linkSn: '',
|
||||
});
|
||||
|
||||
setStartDate(dayjs().subtract(7, "day").format('YYYY-MM-DD'));
|
||||
setEndDate(dayjs().format('YYYY-MM-DD'));
|
||||
|
||||
const data = await LinkApi.getLinkStatsList({
|
||||
...linkSearchParam,
|
||||
linkSn: '',
|
||||
linkNm: '',
|
||||
startDate: dayjs().subtract(7, "day").format('YYYY-MM-DD'),
|
||||
endDate: dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
setLinkStatsList(data);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e?: React.FormEvent<HTMLFormElement>) => {
|
||||
e?.preventDefault();
|
||||
|
||||
if (startDate && endDate) {
|
||||
|
||||
if (startDate > endDate) {
|
||||
alert('종료일이 시작일보다 이전일 수 없습니다.');
|
||||
return;
|
||||
} else {
|
||||
const data = await LinkApi.getLinkStatsList({
|
||||
...linkSearchParam,
|
||||
startDate,
|
||||
endDate
|
||||
});
|
||||
setLinkStatsList(data);
|
||||
setLogQueryParam({
|
||||
isSucc: 'SUCC',
|
||||
linkNm: '',
|
||||
linkSn: '',
|
||||
});
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
startDate,
|
||||
endDate
|
||||
});
|
||||
}
|
||||
} else {
|
||||
alert('조회 날짜를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSuccChartClick = async (event: React.MouseEvent<HTMLCanvasElement>) => {
|
||||
if (!succChartRef.current) return;
|
||||
|
||||
const chart = succChartRef.current;
|
||||
|
||||
const points = chart.getElementsAtEventForMode(
|
||||
event.nativeEvent,
|
||||
"nearest",
|
||||
{ intersect: true },
|
||||
false
|
||||
);
|
||||
|
||||
if (points[0] && linkStatsList?.linkSuccStats) {
|
||||
const index = points[0].index;
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
pageSize: 100,
|
||||
});
|
||||
setLogQueryParam({
|
||||
isSucc: 'SUCC',
|
||||
linkNm: linkStatsList.linkSuccStats[index]?.link_nm,
|
||||
linkSn: linkStatsList.linkSuccStats[index]?.link_sn,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleFailChartClick = async (event: React.MouseEvent<HTMLCanvasElement>) => {
|
||||
if (!failChartRef.current) return;
|
||||
|
||||
const chart = failChartRef.current;
|
||||
|
||||
const points = chart.getElementsAtEventForMode(
|
||||
event.nativeEvent,
|
||||
"nearest",
|
||||
{ intersect: true },
|
||||
false
|
||||
);
|
||||
|
||||
if (points[0] && linkStatsList?.linkFailStats) {
|
||||
const index = points[0].index;
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
pageSize: 100,
|
||||
});
|
||||
setLogQueryParam({
|
||||
isSucc: 'FAIL',
|
||||
linkNm: linkStatsList.linkFailStats[index]?.link_nm,
|
||||
linkSn: linkStatsList.linkFailStats[index]?.link_sn,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitToday = async () => {
|
||||
const data = await LinkApi.getLinkStatsList({
|
||||
...linkSearchParam,
|
||||
startDate: dayjs().format('YYYY-MM-DD'),
|
||||
endDate: dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
setEndDate(dayjs().format('YYYY-MM-DD'));
|
||||
setStartDate(dayjs().format('YYYY-MM-DD')),
|
||||
setLinkStatsList(data);
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
startDate: dayjs().format('YYYY-MM-DD'),
|
||||
endDate: dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
setLogQueryParam({
|
||||
isSucc: 'SUCC',
|
||||
linkNm: '',
|
||||
linkSn: '',
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const data = await LinkApi.getLinkStatsList({
|
||||
...linkSearchParam,
|
||||
startDate,
|
||||
endDate
|
||||
});
|
||||
setLinkStatsList(data);
|
||||
};
|
||||
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
startDate,
|
||||
endDate
|
||||
});
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="card !mb-0 !min-h-[calc(100vh-16.8rem)]">
|
||||
<div className="card card-border">
|
||||
<form className="flex items-center gap-2 p-2" onSubmit={handleSubmit}>
|
||||
<Label htmlFor="sDateSearchInput" className="mx-2 text-label">
|
||||
시작일
|
||||
</Label>
|
||||
<DatePicker id="sDateSearchInput" mode={"single"} selected={dayjs(startDate).toDate()} onSelect={(e) => setStartDate(dayjs(e).format('YYYY-MM-DD'))} />
|
||||
<Label htmlFor="eDateSearchInput" className="mx-2 text-label">
|
||||
종료일
|
||||
</Label>
|
||||
<DatePicker id="eDateSearchInput" mode={"single"} className="mr-2" selected={dayjs(endDate).toDate()} onSelect={(e) => setEndDate(dayjs(e).format('YYYY-MM-DD'))} />
|
||||
<Button color="green" className="h-[3.2rem]" type="button" onClick={handleResetSearchParam}>
|
||||
초기화
|
||||
</Button>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button className="h-[3.2rem]" type="button" onClick={handleSubmitToday}>
|
||||
오늘 날짜로 조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card !m-[1.2rem] !p-0 flex flex-col gap-y-8">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">조회 일자 : {`${linkSearchParam.startDate} ~ ${linkSearchParam.endDate}`}</h3>
|
||||
</div>
|
||||
<div className="h-[32rem] w-full">
|
||||
<Line ref={succChartRef} data={succData} options={succOptions} onClick={handleSuccChartClick} />
|
||||
</div>
|
||||
<div className="h-[32rem]">
|
||||
<Line ref={failChartRef} data={failData} options={failOptions} onClick={handleFailChartClick} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] !p-0">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">
|
||||
{logQueryParam?.linkNm ? `${logQueryParam.linkNm} 연계 ${logQueryParam.isSucc === 'FAIL' ? '실패' : '성공'} 로그 목록 (${linkSearchParam.startDate} ~ ${linkSearchParam.endDate}) - ${query.data?.totalElements.toLocaleString('ko-KR') ?? 0}건` : '로그 목록'}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] h-[40rem] overflow-auto !p-0">
|
||||
{logQueryParam?.linkNm ?
|
||||
query.isLoading ?
|
||||
<div className="[data-theme='black']:bg-transparent rounded-lg p-10 h-full flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div>{tableComponent}</div>
|
||||
:
|
||||
<div className="flex items-center justify-center h-full text-gray-500 text-3xl">
|
||||
차트 데이터를 선택해주세요.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export { LinkStatsPage };
|
||||
@@ -0,0 +1 @@
|
||||
export { LinkTablePage} from "./ui/link-table-page";
|
||||
@@ -0,0 +1,103 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { type LinkTableColumns } from "@/entities/link";
|
||||
import { Checkbox } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<LinkTableColumns>();
|
||||
|
||||
function tableColumns({ selectedColIds, setSelectedColIds }: {
|
||||
selectedColIds: string[];
|
||||
setSelectedColIds: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
}) {
|
||||
return [
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
const allRows = table.getRowModel().rows;
|
||||
const allSelected =
|
||||
allRows.length > 0 &&
|
||||
allRows.every((row) =>
|
||||
selectedColIds.includes(row.original.col_sn)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center">
|
||||
<Checkbox
|
||||
className="bg-white"
|
||||
checked={
|
||||
allSelected
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setSelectedColIds(allRows.map((row) => row.original.col_sn));
|
||||
} else {
|
||||
setSelectedColIds([]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const id = row.original.col_sn;
|
||||
const checked = selectedColIds.includes(id);
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center">
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) => {
|
||||
setSelectedColIds((prev) =>
|
||||
checked ? [...prev, id] : prev.filter((x) => x !== id)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('col_sn', {
|
||||
id: 'col_sn',
|
||||
header: '컬럼 일련번호'
|
||||
}),
|
||||
columnHelper.accessor('sou_col_nm', {
|
||||
id: 'sou_col_nm',
|
||||
header: '원천컬럼명',
|
||||
cell: (info) => info.getValue() ? info.getValue() : '-',
|
||||
}),
|
||||
columnHelper.accessor('sou_col_korn_nm', {
|
||||
id: 'sou_col_korn_nm',
|
||||
header: '원천컬럼 한글명',
|
||||
cell: (info) => info.getValue() ? info.getValue() : '-',
|
||||
}),
|
||||
columnHelper.accessor('sou_col_sz', {
|
||||
id: 'sou_col_sz',
|
||||
header: '원천컬럼 크기',
|
||||
cell: (info) => info.getValue() ? info.getValue() : '-',
|
||||
}),
|
||||
columnHelper.accessor('trsf_col_nm', {
|
||||
id: 'trsf_col_nm',
|
||||
header: '변환컬럼명',
|
||||
cell: (info) => info.getValue() ? info.getValue() : '-',
|
||||
}),
|
||||
columnHelper.accessor('trsf_col_korn_nm', {
|
||||
id: 'trsf_col_korn_nm',
|
||||
header: '변환컬럼 한글명',
|
||||
cell: (info) => info.getValue() ? info.getValue() : '-',
|
||||
}),
|
||||
columnHelper.accessor('trsf_col_sz', {
|
||||
id: 'trsf_col_sz',
|
||||
header: '변환컬럼 크기',
|
||||
cell: (info) => info.getValue() ? info.getValue() : '-',
|
||||
}),
|
||||
columnHelper.accessor('pk_yn', {
|
||||
id: 'pk_yn',
|
||||
header: 'PK 여부',
|
||||
cell: (info) => info.getValue() === 'Y' ? '예' : '아니오',
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
export { tableColumns };
|
||||
@@ -0,0 +1,501 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
Button,
|
||||
Input,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectValue,
|
||||
Checkbox
|
||||
} from "@common/components";
|
||||
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
|
||||
import { type LinkTableColData, type LinkTable } from "@/entities/link";
|
||||
import dayjs from "dayjs";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useState, useEffect } from "react";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
|
||||
interface LinkTableDialogProps {
|
||||
dialogOpen: boolean;
|
||||
onOpenChange: (val: boolean) => void;
|
||||
form: UseFormReturn<LinkTableColData>;
|
||||
tableInfo: LinkTable;
|
||||
insertLinkTableCol: (data: LinkTableColData) => void;
|
||||
updateLinkTableCol: (data: LinkTableColData) => void;
|
||||
}
|
||||
|
||||
function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLinkTableCol, updateLinkTableCol }: LinkTableDialogProps) {
|
||||
|
||||
const isModifyMode = !!form.getValues('col_sn');
|
||||
|
||||
const { confirm } = useConfirm();
|
||||
const [onConfirm, setOnConfirm] = useState<boolean>(false);
|
||||
const [useColSeq, setUseColSeq] = useState<boolean>(false);
|
||||
|
||||
const handleConfirm = async (callback: () => Promise<void>) => {
|
||||
setOnConfirm(true);
|
||||
await callback();
|
||||
setOnConfirm(false);
|
||||
};
|
||||
|
||||
const handleSubmit = async (data: LinkTableColData) => {
|
||||
const confirmMsg = isModifyMode ? '수정 하시겠습니까?' : '저장 하시겠습니까?';
|
||||
|
||||
await handleConfirm(async () => {
|
||||
if (await confirm(confirmMsg)) {
|
||||
!isModifyMode
|
||||
? insertLinkTableCol({
|
||||
...data,
|
||||
link_sn: tableInfo.link_sn,
|
||||
tbl_sn: tableInfo.tbl_sn,
|
||||
})
|
||||
: updateLinkTableCol({
|
||||
...data,
|
||||
link_sn: tableInfo.link_sn,
|
||||
tbl_sn: tableInfo.tbl_sn,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCheckColSeqValue = (check: boolean | string) => {
|
||||
if (check) {
|
||||
setUseColSeq(false);
|
||||
form.setValue('col_seq', null);
|
||||
} else {
|
||||
setUseColSeq(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialogOpen) {
|
||||
setUseColSeq(false);
|
||||
form.reset({
|
||||
tbl_sn: '',
|
||||
col_sn: '',
|
||||
sou_col_nm: '',
|
||||
sou_col_korn_nm: '',
|
||||
sou_col_data_frm: '',
|
||||
sou_col_sz: null,
|
||||
trsf_col_nm: '',
|
||||
trsf_col_korn_nm: '',
|
||||
trsf_col_data_frm: '',
|
||||
trsf_col_sz: null,
|
||||
col_seq: null,
|
||||
pk_yn: 'N',
|
||||
mdfcn_dt: '',
|
||||
mdfr: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (isModifyMode && form.getValues('col_seq')) {
|
||||
setUseColSeq(true);
|
||||
}
|
||||
|
||||
}, [dialogOpen]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
onOpenChange(open);
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button color="green" className="h-[3.2rem]" onClick={() => onOpenChange(true)}>행 추가</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[55rem]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{tableInfo.trsf_tbl_korn_nm} ({tableInfo.tbl_sn})
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
<div className="card">
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="30%" />
|
||||
<col width="70%" />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
{isModifyMode &&
|
||||
<TableRow>
|
||||
<TableHead>컬럼 일련번호</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="col_sn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
<TableRow>
|
||||
<TableHead>* 원천컬럼명</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sou_col_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.sou_col_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.sou_col_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 원천컬럼 한글명</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sou_col_korn_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.sou_col_korn_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.sou_col_korn_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 원천컬럼 데이터 형식</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sou_col_data_frm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.sou_col_data_frm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.sou_col_data_frm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>원천컬럼 크기</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sou_col_sz"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
type="number"
|
||||
min={0}
|
||||
max={99999999999999}
|
||||
onChange={(e) => {
|
||||
const onlyDigits = e.target.value.replace(/\D/g, "").slice(0, 14);
|
||||
field.onChange(onlyDigits); // react-hook-form에 반영
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<p className="text-gray-500 text-base pt-2">
|
||||
※ 14자리까지 입력 가능합니다.
|
||||
</p>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>PK 여부</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="pk_yn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select value={field.value ?? "N"} onValueChange={field.onChange}>
|
||||
<SelectTrigger id="liResultCode" className="w-[10rem] mr-4">
|
||||
<SelectValue placeholder="아니오"></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Y">예</SelectItem>
|
||||
<SelectItem value="N">아니오</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 변환컬럼명</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trsf_col_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.trsf_col_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.trsf_col_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 변환컬럼 한글명</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trsf_col_korn_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.trsf_col_korn_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.trsf_col_korn_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 변환컬럼 데이터 형식</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trsf_col_data_frm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{form.formState.errors.trsf_col_data_frm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.trsf_col_data_frm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>변환컬럼 크기</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trsf_col_sz"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
type="number"
|
||||
min={0}
|
||||
max={99999999999999}
|
||||
onChange={(e) => {
|
||||
const onlyDigits = e.target.value.replace(/\D/g, "").slice(0, 14);
|
||||
field.onChange(onlyDigits); // react-hook-form에 반영
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<p className="text-gray-500 text-base pt-2">
|
||||
※ 14자리까지 입력 가능합니다.
|
||||
</p>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>컬럼 매칭 순서</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="col_seq"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-x-2 mb-4">
|
||||
<Checkbox id="useColSeq" className="!border-gray-600" checked={!useColSeq} onCheckedChange={(check) => handleCheckColSeqValue(check)} />
|
||||
<label htmlFor="useColSeq" className="text-gray-600">사용안함</label>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
autoComplete="off"
|
||||
type="number"
|
||||
min={0}
|
||||
disabled={!useColSeq}
|
||||
max={999}
|
||||
onChange={(e) => {
|
||||
const onlyDigits = e.target.value.replace(/\D/g, "").slice(0, 3);
|
||||
field.onChange(onlyDigits); // react-hook-form에 반영
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<p className="text-gray-500 text-base pt-2">
|
||||
※ 3자리까지 입력 가능합니다.
|
||||
</p>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{isModifyMode &&
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead>수정자</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfr"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>수정일시</TableHead>
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfcn_dt"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ? dayjs(field.value).format('YYYY-MM-DD') : ""}
|
||||
className="!text-[1.3rem]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">저장</Button>
|
||||
<DialogClose asChild>
|
||||
<Button color="white">취소</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
<ConfirmDialog open={onConfirm} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export { LinkTableDialog };
|
||||
@@ -0,0 +1,710 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import {
|
||||
Button,
|
||||
DataTable,
|
||||
Input,
|
||||
Label,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
import { getCoreRowModel, type PaginationState, type RowSelectionState, useReactTable } from "@tanstack/react-table";
|
||||
import { LinkApi, type Link, type LinkSearchParam, type LinkTable, type LinkTableColumns, type LinkTableColData, type LinkTableFormData, LinkTableColumnsSchema, LinkTableSchema } from "@/entities/link";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { columns } from "../../linkParams/model/table-columns";
|
||||
import { tableColumns } from "../model/table-columns";
|
||||
import { LinkTableDialog } from "./link-table-dialog";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { getCookie } from "@common/components";
|
||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
|
||||
function LinkTablePage() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const userUniqId = getCookie(AUTH_USER_ID) ?? "";
|
||||
|
||||
const [linkSn, setLinkSn] = useState<string>('');
|
||||
const [linkNm, setLinkNm] = useState<string>('');
|
||||
|
||||
const [linkSearchParam, setLinkSearchParam] = useState<LinkSearchParam>({
|
||||
linkSn: '',
|
||||
linkNm: ''
|
||||
});
|
||||
|
||||
const [selectedTableInfo, setSelectedTableInfo] = useState<LinkTable | null>(null);
|
||||
const [tableColList, setTableColList] = useState<LinkTableColumns[]>([]);
|
||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
const [selectedColIds, setSelectedColIds] = useState<string[]>([]);
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 100,
|
||||
});
|
||||
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const { confirm } = useConfirm();
|
||||
const [onConfirm, setOnConfirm] = useState<boolean>(false);
|
||||
|
||||
const handleConfirm = async (callback: () => Promise<void>) => {
|
||||
setOnConfirm(true);
|
||||
await callback();
|
||||
setOnConfirm(false);
|
||||
};
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, linkSearchParam],
|
||||
queryFn: () => LinkApi.getLinkList({
|
||||
...linkSearchParam,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: insertLinkTableCol } = useMutation({
|
||||
mutationFn: async (data: LinkTableColData) => await LinkApi.insertLinkTableCol({
|
||||
...data,
|
||||
mdfr: userUniqId,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
if (selectedTableInfo) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`]
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`]
|
||||
});
|
||||
|
||||
const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn);
|
||||
const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn);
|
||||
|
||||
setSelectedTableInfo(tableData);
|
||||
setTableColList(tableColList.content);
|
||||
setSelectedColIds([]);
|
||||
setDialogOpen(false);
|
||||
alert('저장되었습니다.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: updateLinkTableCol } = useMutation({
|
||||
mutationFn: async (data: LinkTableColData) => await LinkApi.updateLinkTableCol({
|
||||
...data,
|
||||
mdfr: userUniqId,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
if (selectedTableInfo) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`]
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`]
|
||||
});
|
||||
|
||||
const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn);
|
||||
const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn);
|
||||
|
||||
setSelectedTableInfo(tableData);
|
||||
setTableColList(tableColList.content);
|
||||
setSelectedColIds([]);
|
||||
setDialogOpen(false);
|
||||
alert('수정되었습니다.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteLinkTableCol } = useMutation({
|
||||
mutationFn: async (data: string[]) => await LinkApi.deleteLinkTableCol(data),
|
||||
onSuccess: async () => {
|
||||
if (selectedTableInfo) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`]
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`]
|
||||
});
|
||||
|
||||
const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn);
|
||||
const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn);
|
||||
|
||||
setSelectedTableInfo(tableData);
|
||||
setTableColList(tableColList.content);
|
||||
setSelectedColIds([]);
|
||||
setDialogOpen(false);
|
||||
alert('삭제되었습니다.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: insertLinkTableInfo } = useMutation({
|
||||
mutationFn: async (data: LinkTableFormData) => await LinkApi.insertLinkTableInfo(data),
|
||||
onSuccess: async () => {
|
||||
if (selectedTableInfo) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`]
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`]
|
||||
});
|
||||
|
||||
const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn);
|
||||
const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn);
|
||||
|
||||
tableForm.reset({
|
||||
link_sn: tableData.link_sn,
|
||||
link_nm: tableData.link_nm,
|
||||
tbl_sn: tableData.tbl_sn ?? "",
|
||||
sou_tbl_nm: tableData.sou_tbl_nm ?? "",
|
||||
sou_tbl_korn_nm: tableData.sou_tbl_korn_nm ?? "",
|
||||
sou_tbl_expln: tableData.sou_tbl_expln ?? "",
|
||||
trsf_tbl_expln: tableData.trsf_tbl_expln ?? "",
|
||||
trsf_tbl_nm: tableData.trsf_tbl_nm ?? "",
|
||||
trsf_tbl_korn_nm: tableData.trsf_tbl_korn_nm ?? "",
|
||||
});
|
||||
setSelectedTableInfo(tableData);
|
||||
setTableColList(tableColList.content);
|
||||
setSelectedColIds([]);
|
||||
alert('저장되었습니다.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: updateLinkTableInfo } = useMutation({
|
||||
mutationFn: async (data: LinkTableFormData) => await LinkApi.updateLinkTableInfo(data),
|
||||
onSuccess: async () => {
|
||||
if (selectedTableInfo) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`]
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`]
|
||||
});
|
||||
|
||||
const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn);
|
||||
const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn);
|
||||
|
||||
setSelectedTableInfo(tableData);
|
||||
setTableColList(tableColList.content);
|
||||
setSelectedColIds([]);
|
||||
alert('수정되었습니다.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const tableCol = useMemo(
|
||||
() =>
|
||||
tableColumns({ selectedColIds, setSelectedColIds }),
|
||||
[selectedColIds]
|
||||
);
|
||||
|
||||
const colTable = useReactTable({
|
||||
data: tableColList,
|
||||
columns: tableCol,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
const form = useForm<LinkTableColData>({
|
||||
resolver: zodResolver(LinkTableColumnsSchema),
|
||||
defaultValues: {
|
||||
pk_yn: "N"
|
||||
}
|
||||
});
|
||||
|
||||
const tableForm = useForm<LinkTableFormData>({
|
||||
resolver: zodResolver(LinkTableSchema),
|
||||
defaultValues: {},
|
||||
});
|
||||
|
||||
const handleResetSearchParam = async () => {
|
||||
setSelectedTableInfo(null);
|
||||
setTableColList([]);
|
||||
setLinkSn('');
|
||||
setLinkNm('');
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
linkSn: '',
|
||||
linkNm: '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setSelectedTableInfo(null);
|
||||
setTableColList([]);
|
||||
|
||||
setLinkSearchParam({
|
||||
...linkSearchParam,
|
||||
linkSn,
|
||||
linkNm
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = async (data: Link) => {
|
||||
const tableData = await LinkApi.getLinkTableInfo(data.link_sn);
|
||||
const tableColList = await LinkApi.getLinkTableColList(data.link_sn);
|
||||
|
||||
setSelectedTableInfo(tableData);
|
||||
setTableColList(tableColList.content);
|
||||
setSelectedColIds([]);
|
||||
|
||||
tableForm.reset({
|
||||
link_sn: tableData.link_sn,
|
||||
link_nm: tableData.link_nm,
|
||||
tbl_sn: tableData.tbl_sn ?? "",
|
||||
sou_tbl_nm: tableData.sou_tbl_nm ?? "",
|
||||
sou_tbl_korn_nm: tableData.sou_tbl_korn_nm ?? "",
|
||||
sou_tbl_expln: tableData.sou_tbl_expln ?? "",
|
||||
trsf_tbl_expln: tableData.trsf_tbl_expln ?? "",
|
||||
trsf_tbl_nm: tableData.trsf_tbl_nm ?? "",
|
||||
trsf_tbl_korn_nm: tableData.trsf_tbl_korn_nm ?? "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleColumnRowClick = async (data: LinkTableColumns) => {
|
||||
const colInfo = await LinkApi.getLinkTableColInfo({ colSn: data.col_sn, tblSn: data.tbl_sn });
|
||||
|
||||
if (colInfo) {
|
||||
let newData = { ...colInfo };
|
||||
|
||||
if (colInfo.mdfr) {
|
||||
const userName = await fetchUserInfo(colInfo.mdfr);
|
||||
newData = {
|
||||
...colInfo,
|
||||
mdfr: userName,
|
||||
};
|
||||
}
|
||||
|
||||
form.reset(newData);
|
||||
|
||||
}
|
||||
setDialogOpen(true)
|
||||
};
|
||||
|
||||
const fetchUserInfo = async (id: string) => {
|
||||
const userData = await getUserInfo();
|
||||
return userData.userName;
|
||||
};
|
||||
|
||||
const handleDeleteParam = async () => {
|
||||
if (selectedColIds.length === 0) {
|
||||
alert('삭제할 컬럼을 선택해 주세요.');
|
||||
} else {
|
||||
await handleConfirm(async () => {
|
||||
if (await confirm('선택한 컬럼 정보를 삭제하시겠습니까?')) {
|
||||
deleteLinkTableCol(selectedColIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleTableSubmit = async (data: LinkTableFormData) => {
|
||||
const regTblInfo = await LinkApi.countLinkTblNm(data.trsf_tbl_nm);
|
||||
const confirmMsg = (regTblInfo.count > 0 && data.trsf_tbl_nm !== selectedTableInfo?.trsf_tbl_nm) ? `이미 사용중인 테이블 명입니다. 해당 테이블 명으로 ${!data.tbl_sn ? '저장' : '수정'}하시겠습니까?` : `${!data.tbl_sn ? '저장' : '수정'}하시겠습니까?`;
|
||||
if (!data.tbl_sn) {
|
||||
await handleConfirm(async () => {
|
||||
if (await confirm(confirmMsg)) {
|
||||
insertLinkTableInfo({
|
||||
...data,
|
||||
mdfr: userUniqId,
|
||||
tbl_sn: (regTblInfo.count > 0) ? regTblInfo.tbl_sn : "",
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await handleConfirm(async () => {
|
||||
if (await confirm(confirmMsg)) {
|
||||
updateLinkTableInfo({
|
||||
...data,
|
||||
tbl_sn: (regTblInfo.count > 0) ? regTblInfo.tbl_sn : "",
|
||||
mdfr: userUniqId,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: {
|
||||
pagination,
|
||||
rowSelection,
|
||||
},
|
||||
manualPagination: true,
|
||||
enableMultiRowSelection: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
},
|
||||
dataTableOptions: {
|
||||
onRowClick: handleRowClick,
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form className="flex items-center gap-2 p-2" onSubmit={handleSubmit}>
|
||||
<Label htmlFor="liSnSearchInput" className="mr-2 text-label">
|
||||
일련번호
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem] mr-4"
|
||||
id="liSnSearchInput"
|
||||
value={linkSn}
|
||||
onChange={(e) => setLinkSn(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Label htmlFor="liNmSearchInput" className="mr-2 text-label">
|
||||
연계 명
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem] mr-2"
|
||||
id="liNmSearchInput"
|
||||
value={linkNm}
|
||||
onChange={(e) => setLinkNm(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button color="green" className="h-[3.2rem]" type="button" onClick={handleResetSearchParam}>
|
||||
초기화
|
||||
</Button>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card !mb-0 flex h-[calc(100vh-28rem)] gap-5 !p-0">
|
||||
<div className="card card-border w-1/2 !mx-0 flex flex-col">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">연계 목록</h3>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] overflow-auto !p-0 h-full">
|
||||
{!query.isLoading ?
|
||||
<div>{tableComponent}</div>
|
||||
:
|
||||
<div className="[data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center h-full">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="card card-border w-1/2 !mx-0 flex flex-col">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">연계 테이블 정보</h3>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] !p-0 h-full overflow-auto flex flex-col">
|
||||
<Form {...tableForm}>
|
||||
<form onSubmit={tableForm.handleSubmit(handleTableSubmit)}>
|
||||
{selectedTableInfo &&
|
||||
<div className="flex justify-end mb-2">
|
||||
<Button className="h-[3.2rem]" type="submit" color={selectedTableInfo.tbl_sn ? "green" : "primary"}>
|
||||
{selectedTableInfo.tbl_sn ? "수정" : "저장"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
<Table variant="secondary" className="flex-none">
|
||||
<colgroup>
|
||||
<col width="20%" />
|
||||
<col width="80%" />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>연계 일련번호</TableHead>
|
||||
<TableCell className="!p-2">
|
||||
{selectedTableInfo ?
|
||||
<FormField
|
||||
control={tableForm.control}
|
||||
name="link_sn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem] w-[400px]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
:
|
||||
<span className="px-2 py-[1.2rem]">-</span>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>연계 명</TableHead>
|
||||
<TableCell className="!p-2">
|
||||
{selectedTableInfo ?
|
||||
<FormField
|
||||
control={tableForm.control}
|
||||
name="link_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem] w-[400px]"
|
||||
disabled
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
:
|
||||
<span className="px-2 py-[1.2rem]">-</span>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>원천 테이블 명</TableHead>
|
||||
<TableCell className="!p-2">
|
||||
{selectedTableInfo ?
|
||||
<FormField
|
||||
control={tableForm.control}
|
||||
name="sou_tbl_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem] w-[400px]"
|
||||
placeholder={!field.value ? "-" : ""}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{tableForm.formState.errors.sou_tbl_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{tableForm.formState.errors.sou_tbl_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
:
|
||||
<span className="px-2 py-[1.2rem]">-</span>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>원천 테이블 한글명</TableHead>
|
||||
<TableCell className="!p-2">
|
||||
{selectedTableInfo ?
|
||||
<FormField
|
||||
control={tableForm.control}
|
||||
name="sou_tbl_korn_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem] w-[400px]"
|
||||
placeholder={!field.value ? "-" : ""}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{tableForm.formState.errors.sou_tbl_korn_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{tableForm.formState.errors.sou_tbl_korn_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
:
|
||||
<span className="px-2 py-[1.2rem]">-</span>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>원천 테이블 설명</TableHead>
|
||||
<TableCell className="!p-2">
|
||||
{selectedTableInfo ?
|
||||
<FormField
|
||||
control={tableForm.control}
|
||||
name="sou_tbl_expln"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem] w-[400px]"
|
||||
placeholder={!field.value ? "-" : ""}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{tableForm.formState.errors.sou_tbl_expln && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{tableForm.formState.errors.sou_tbl_expln.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
:
|
||||
<span className="px-2 py-[1.2rem]">-</span>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 변환 테이블 명</TableHead>
|
||||
<TableCell className="!p-2">
|
||||
{selectedTableInfo ?
|
||||
<FormField
|
||||
control={tableForm.control}
|
||||
name="trsf_tbl_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem] w-[400px]"
|
||||
placeholder={!field.value ? "-" : ""}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{tableForm.formState.errors.trsf_tbl_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{tableForm.formState.errors.trsf_tbl_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
:
|
||||
<span className="px-2 py-[1.2rem]">-</span>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>* 변환 테이블 한글명</TableHead>
|
||||
<TableCell className="!p-2">
|
||||
{selectedTableInfo ?
|
||||
<FormField
|
||||
control={tableForm.control}
|
||||
name="trsf_tbl_korn_nm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem] w-[400px]"
|
||||
placeholder={!field.value ? "-" : ""}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{tableForm.formState.errors.trsf_tbl_korn_nm && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{tableForm.formState.errors.trsf_tbl_korn_nm.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
:
|
||||
<span className="px-2 py-[1.2rem]">-</span>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>변환 테이블 설명</TableHead>
|
||||
<TableCell className="!p-2">
|
||||
{selectedTableInfo ?
|
||||
<FormField
|
||||
control={tableForm.control}
|
||||
name="trsf_tbl_expln"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem] w-[400px]"
|
||||
placeholder={!field.value ? "-" : ""}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
{tableForm.formState.errors.trsf_tbl_expln && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{tableForm.formState.errors.trsf_tbl_expln.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
:
|
||||
<span className="px-2 py-[1.2rem]">-</span>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</form>
|
||||
</Form>
|
||||
{selectedTableInfo ?
|
||||
<>
|
||||
<div className="mb-2 mt-14 flex justify-end items-center flex-none">
|
||||
{tableColList.length > 0 &&
|
||||
<Button color="red" className="h-[3.2rem] mr-2" type="button" onClick={handleDeleteParam}>
|
||||
행 삭제
|
||||
</Button>
|
||||
}
|
||||
{selectedTableInfo.tbl_sn &&
|
||||
<LinkTableDialog dialogOpen={dialogOpen} onOpenChange={setDialogOpen} form={form} tableInfo={selectedTableInfo} insertLinkTableCol={insertLinkTableCol} updateLinkTableCol={updateLinkTableCol} />
|
||||
}
|
||||
</div>
|
||||
{selectedTableInfo.tbl_sn ?
|
||||
(tableColList.length > 0 ?
|
||||
<div className="flex-1 overflow-auto min-h-[300px]">
|
||||
<DataTable table={colTable} onRowClick={handleColumnRowClick} />
|
||||
</div>
|
||||
:
|
||||
<div className="flex items-center justify-center h-20 text-gray-500 text-3xl">
|
||||
등록된 테이블 컬럼이 없습니다.
|
||||
</div>)
|
||||
:
|
||||
<div className="flex items-center justify-center h-20 text-gray-500 text-3xl">
|
||||
테이블 정보를 등록해주세요.
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
:
|
||||
<div className="flex items-center justify-center text-gray-500 text-3xl mt-14 flex-1">
|
||||
연계 데이터를 선택해주세요.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmDialog isOpen={onConfirm} />
|
||||
</div >
|
||||
)
|
||||
};
|
||||
|
||||
export { LinkTablePage };
|
||||
Reference in New Issue
Block a user