This commit is contained in:
2025-10-17 09:43:22 +09:00
19 changed files with 187 additions and 18 deletions
@@ -20,6 +20,7 @@ export interface Link {
mdfcn_dt: string;
mdfr_id_id: string;
link_cycle_mi: string;
link_key_expry_ymd: string;
}
export interface LinkLog {
@@ -50,6 +51,7 @@ export interface LinkInfo extends LinkFormData {
mdfcn_dt: string;
mdfr_id: string;
link_cycle_mi: number;
link_key_expry_ymd: string;
}
export interface LinkStats {
@@ -125,6 +127,7 @@ export const linkFormSchema = z.object({
}, {
message: "연계 주기는 5분 이상이어야 합니다."
}),
link_key_expry_ymd: z.string().nullable().optional(),
});
export const linkVrblFormSchema = z.object({
@@ -68,7 +68,7 @@ const columns = [
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>
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' ? '실패' : (resultCd === 'RUNN' ? '진행중' : '대기'))}</span>
},
}),
columnHelper.accessor('last_link_excn_dt', {
@@ -0,0 +1,90 @@
import type { Link, LinkList } from "@/entities/link/model/link-interface";
import {
Button,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem
} from "@common/components";
import type { UseQueryResult } from "@tanstack/react-query";
import { useState, useEffect } from "react";
import dayjs from "dayjs";
import { useRouter } from "next/navigation";
const LinkExpiredAlert = ({ linkList }: { linkList: UseQueryResult<LinkList> }) => {
const [expriedLinkList, setExpriedLinkLikst] = useState<Link[]>([]);
const router = useRouter();
useEffect(() => {
if (linkList?.data?.content) {
const expriedLinks = linkList.data.content.filter((l) => {
if (l.link_key_expry_ymd === 'none' || !l.link_key_expry_ymd) {
return false;
}
const expryDate = dayjs(l.link_key_expry_ymd, 'YYMMDD');
const today = dayjs();
const daysDiff = expryDate.diff(today, 'day');
return daysDiff <= 7
});
setExpriedLinkLikst(expriedLinks);
}
}, [linkList]);
return (
<div className="relative">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div>
<Button
type="button"
size="icon"
className="size-[2.8rem] rounded-full p-2 mr-4"
>
<i className="diveicon di-bell-notification-fill" />
</Button>
{expriedLinkList.length > 0 &&
<span className="absolute -top-3 -right-0 bg-red-500 text-white text-xs font-bold px-1.5 py-0.5 rounded-full min-w-[20px] text-center">
NEW
</span>
}
</div>
</DropdownMenuTrigger>
{expriedLinkList.length > 0 ?
<DropdownMenuContent className="max-h-[400px] overflow-y-auto">
{expriedLinkList.map((l) => {
const expryDate = dayjs(l.link_key_expry_ymd, 'YYMMDD');
const today = dayjs();
const daysDiff = expryDate.diff(today, 'day');
return <DropdownMenuItem key={l.link_sn} className="p-1 cursor-pointer" onClick={() => router.push(`link/register/${l.link_sn}`)}>
<div className="card-border p-4 w-full grid grid-cols-1 gap-y-2 rounded-sm hover:bg-gray-100">
{daysDiff > 0 ?
<p className="text-xl">{l.link_nm} API KEY 만료 <span className="text-red-600">{daysDiff}일전</span></p>
:
<p className="text-xl">{l.link_nm} API KEY <span className="text-red-600">만료됨</span></p>
}
<p className="border-2 border-red-600 w-fit py-1 px-2 text-red-500 rounded-full font-bold">만료일자: {expryDate.format('YYYY-MM-DD')}</p>
</div>
</DropdownMenuItem>
})}
</DropdownMenuContent>
:
<DropdownMenuContent className="max-h-[400px] overflow-y-auto">
<DropdownMenuItem className="p-1">
<div className="card-border p-6 text-2xl w-full flex justify-center items-center gap-x-2 rounded-sm hover:bg-gray-100">
<i className="diveicon di-announcement text-[#2094fa]" />
새로운 알림이 없습니다.
</div>
</DropdownMenuItem>
</DropdownMenuContent>
}
</DropdownMenu>
</div>
);
};
export default LinkExpiredAlert;
@@ -22,7 +22,7 @@ const QuickStartButton: React.FC<{ linkSn: string }> = ({ linkSn }) => {
return (
<Button
color="sky"
type="button"
onClick={(e) => {
e.stopPropagation();
updateQuickStart(linkSn);
+67 -5
View File
@@ -11,6 +11,9 @@ import {
SelectContent,
SelectItem,
SelectValue,
DatePicker,
Checkbox,
Label
} from "@common/components";
import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
@@ -19,7 +22,7 @@ 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";
import { useState, useEffect } from "react";
type LinkFormProps = {
@@ -29,14 +32,17 @@ type LinkFormProps = {
handleInsertLink?: (data: LinkFormData) => void;
deleteLink?: () => void;
updateLinkResultCode?: () => void;
initialIsNoExprie?: boolean;
}
const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, updateLinkResultCode }: LinkFormProps) => {
const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, updateLinkResultCode, initialIsNoExprie }: LinkFormProps) => {
const isModifyMode = !!linkSn;
const router = useRouter();
const { confirm } = useConfirm();
const [onConfirm, setOnConfirm] = useState(false);
const [onConfirm, setOnConfirm] = useState<boolean>(false);
const [expryDate, setExpryDate] = useState<string>(dayjs().toDate().toISOString());
const [isNoExprie, setIsNoExprie] = useState<boolean>(true);
const handleConfirm = async (callback: () => Promise<void>) => {
setOnConfirm(true);
@@ -47,13 +53,18 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
const handleSubmit = async (data: LinkFormData) => {
const confirmMsg = !!linkSn ? '수정 하시겠습니까?' : '저장 하시겠습니까?';
const newData = {
...data,
link_key_expry_ymd: isNoExprie ? 'none' :dayjs(expryDate).format('YYYYMMDD'),
};
await handleConfirm(async () => {
if (await confirm(confirmMsg)) {
if (linkSn) {
updateLinkInfo?.(data);
updateLinkInfo?.(newData);
} else {
handleInsertLink?.(data);
handleInsertLink?.(newData);
}
}
});
@@ -99,6 +110,12 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
return timeParts;
};
useEffect(() => {
if (initialIsNoExprie !== undefined) {
setIsNoExprie(initialIsNoExprie);
}
}, [initialIsNoExprie]);
return (
<>
<Form {...form}>
@@ -279,6 +296,51 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>* 연계 KEY 만료일자</TableHead>
<TableCell>
<FormField
control={form.control}
name="link_key_expry_ymd"
render={({ field }) => (
<FormItem>
<FormControl>
<div className="flex items-center gap-x-2">
<DatePicker
{...field}
id="linkKeyExpryYmd"
mode={"single"}
selected={field.value !== 'none' ? dayjs(field.value).toDate() : dayjs().toDate()}
onSelect={(e) => {
setExpryDate(dayjs(e).toDate().toISOString());
const newDate = dayjs(e).format('YYYYMMDD');
field.onChange(newDate);
}}
disabled={isNoExprie}
toYear={2050}
/>
<div className="flex items-center gap-x-1">
<Checkbox
value='none'
id="noExpryKey"
className="!border-[#ccc]"
checked={isNoExprie}
onCheckedChange={(e) => setIsNoExprie(!!e)}
/>
<Label htmlFor="noExpryKey" className="text-gray-500">별도의 만기일 없음</Label>
</div>
</div>
</FormControl>
{form.formState.errors.link_key_expry_ymd && (
<p className="text-red-500 text-base pt-2">
{form.formState.errors.link_key_expry_ymd.message}
</p>
)}
</FormItem>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead>연계 ID</TableHead>
<TableCell>
+4 -1
View File
@@ -11,6 +11,7 @@ import { useQuery } from "@tanstack/react-query";
import { columns } from "../model/table-columns";
import { useQueryClient } from "@tanstack/react-query";
import { useDataTable } from "@/shared/lib";
import LinkExpiredAlert from "./LinkExpiredAlert";
function LinkPage() {
const router = useRouter();
@@ -134,7 +135,9 @@ function LinkPage() {
<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>
<div className="flex items-center">
<LinkExpiredAlert linkList={query} />
<Button onClick={handleClickRegist}>등록</Button>
</div>
</div>
+6 -2
View File
@@ -7,7 +7,7 @@ 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 { useEffect, useState } from "react";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { AUTH_USER_ID } from "@/shared/config";
import { getCookie } from "@common/components";
@@ -18,6 +18,8 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) {
const { setMessage: alert } = useAlertStore((state) => state);
const userUniqId = getCookie(AUTH_USER_ID) ?? "";
const [initialIsNoExprie, setInitialIsNoExprie] = useState<boolean>(true);
// 컴포넌트 마운트 시 캐시 초기화
useEffect(() => {
queryClient.removeQueries({
@@ -82,7 +84,8 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) {
}
form.reset(newData);
setInitialIsNoExprie(newData.link_key_expry_ymd === 'none');
}
};
@@ -101,6 +104,7 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) {
updateLinkInfo={updateLinkInfo}
deleteLink={deleteLink}
updateLinkResultCode={updateLinkResultCode}
initialIsNoExprie={initialIsNoExprie}
/>
</div>
+1 -1
View File
@@ -15,7 +15,7 @@ NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
NEXT_PUBLIC_REUSE_API_URL= http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
+1 -1
View File
@@ -18,7 +18,7 @@ NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
+1 -1
View File
@@ -15,7 +15,7 @@ NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
# NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
+1 -1
View File
@@ -15,7 +15,7 @@ NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
NEXT_PUBLIC_REUSE_API_URL= http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://queen.hasuon.or.kr/auth/login
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
+2 -1
View File
@@ -14,4 +14,5 @@ NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login
+1
View File
@@ -16,6 +16,7 @@ NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
NEXT_PUBLIC_SEWERAGE_API_URL = http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com/dc-service
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
#NEXT_PUBLIC_SERVER_API_URL = http://localhost:8883/operation-service
+2 -1
View File
@@ -12,4 +12,5 @@ NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
+2 -1
View File
@@ -14,4 +14,5 @@ NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://queen.hasuon.or.kr/auth/login
+1
View File
@@ -12,3 +12,4 @@ NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000
NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login
+1 -1
View File
@@ -12,4 +12,4 @@ NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_NGII_URL = http://172.29.143.87:80/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000
NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://queen.hasuon.or.kr/auth/login
+1
View File
@@ -15,6 +15,7 @@ NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
NEXT_PRIVATE_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA=
+1
View File
@@ -15,6 +15,7 @@ NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://queen.hasuon.or.kr/auth/login
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
NEXT_PRIVATE_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA=