fix: 외부 API 연계 데이터 연계 key 만료 체크 기능 추가
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user