포탈 데시보드 기능

This commit is contained in:
2025-12-12 10:59:59 +09:00
parent b25e89099d
commit 56d8f7445e
7 changed files with 173 additions and 34 deletions
+34
View File
@@ -0,0 +1,34 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
export interface Banner {
bnrSn: number;
bnrTypeCd: string;
bnrTypeNm: string;
bnrTtl: string;
lnkgUrlAddr: string;
useYn: string;
atflCd: string;
mdfcnDt: string;
mdfr: string;
mdfrNm: string;
}
export const getBannerList = async (siteId: string) => {
const response = await client.get<Banner[]>(
`${ENDPOINT.CMS_SERVICE.BANNERS}/${siteId}`,
);
return response.data;
};
export const getImageUrlByAtflCd = async (atflCd: string): Promise<string> => {
if (!atflCd) return "";
const response = await client.get<Blob>(
`${ENDPOINT.ATTACH_SERVICE.IMAGES}/atflCd/${atflCd}`,
{
responseType: "blob",
},
);
const blob = response.data;
return URL.createObjectURL(blob);
};
@@ -0,0 +1,106 @@
"use client";
import { useRef } from "react";
import { useQuery } from "@tanstack/react-query";
import Autoplay from "embla-carousel-autoplay";
import { ENDPOINT } from "@/shared/config";
import { getImageUrlByAtflCd, getBannerList } from "../api/banner";
import { getCookie } from "@/shared/lib";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@common/components";
type BannerImageProps = {
atflCd: string;
bnrSn: number;
lnkgUrlAddr: string;
};
const BannerList: React.FC = () => {
const siteId = getCookie("site-id-key") ?? "";
const BannerImage = ({ atflCd, bnrSn, lnkgUrlAddr }: BannerImageProps) => {
const { data: imageUrl, isLoading } = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, atflCd],
queryFn: () => getImageUrlByAtflCd(atflCd),
enabled: !!atflCd,
});
const handleClick = () => {
if (lnkgUrlAddr) {
window.open(lnkgUrlAddr, '_blank');
}
};
if (isLoading) {
return (
<div className="relative flex h-24 items-center justify-center bg-accent mx-auto">
<div className="text-sm text-muted-foreground">로딩 중...</div>
</div>
);
}
if (!imageUrl) {
return (
<div className="relative flex h-24 items-center justify-center bg-accent mx-auto">
<div className="text-sm text-muted-foreground">이미지 없음</div>
</div>
);
}
return (
<div
className="relative flex h-24 items-center justify-center cursor-pointer hover:opacity-80 transition-opacity mx-auto"
onClick={handleClick}
>
<img src={imageUrl} className="object-contain w-full h-full" alt="배너 이미지" />
</div>
);
};
const { data: bannerList } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.BANNERS, siteId],
queryFn: () => getBannerList(siteId),
});
const plugin = useRef(Autoplay({ delay: 2000, stopOnInteraction: true }));
return (
<div className="card card-border">
<Carousel
plugins={[plugin.current]}
opts={{ loop: true }}
className="relative my-8 h-24"
>
<CarouselContent>
{bannerList && bannerList.length > 0 ? (
bannerList.map((banner) => (
<CarouselItem key={`banner-${banner.bnrSn}`} className="basis-1/3">
<BannerImage
atflCd={banner.atflCd}
bnrSn={banner.bnrSn}
lnkgUrlAddr={banner.lnkgUrlAddr}
/>
</CarouselItem>
))
) : (
<CarouselItem className="basis-1/3">
<div className="relative flex h-24 w-full items-center justify-center bg-accent">
<div className="text-sm text-muted-foreground">배너가 없습니다</div>
</div>
</CarouselItem>
)}
</CarouselContent>
<CarouselPrevious className="absolute left-2 top-1/2 !bg-[rgba(255,255,255,0.8)] text-accent-foreground z-10" />
<CarouselNext className="absolute right-2 top-1/2 !bg-[rgba(255,255,255,0.8)] text-accent-foreground z-10" />
</Carousel>
</div>
);
};
export default BannerList;
@@ -24,6 +24,8 @@ import FoFcltCntRt from "./fo-fclt-cnt-rt";
import FoWqltInflEffl from "./fo-wqlt-Infl-effl";
import ReuseChart from "./reuse-chart";
import ReuseStatus from "./reuse-status";
import NoticeList from "./notice-list";
import BannerList from "./banner-list";
// Chart.js 등록
ChartJS.register(
@@ -198,16 +200,12 @@ function DashboardPage() {
</div>
</div>
<div className="card card-border">
<div className="card-header text-foreground">
<h3 className="text-2xl font-medium">공지사항</h3>
</div>
<div className="h-70 p-4">
</div>
<NoticeList />
</div>
{/* 배너 영역 */}
<div className="card card-border">
<BannerList />
</div>
</div>
</div>
@@ -11,7 +11,7 @@ import {
} from "../model/table-columns";
function NoticeList() {
const NoticeList: React.FC = () => {
const router = useRouter();
const noticePostQuery = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS],
@@ -48,4 +48,6 @@ function NoticeList() {
{noticePostTableComponent}
</div>
);
}
};
export default NoticeList;
-1
View File
@@ -10,7 +10,6 @@ function PageLayout({ children }: { children: React.ReactNode }) {
return (
<div className="h-auto min-h-full bg-container pb-2">
{children}
</div>
);
+2 -2
View File
@@ -49,5 +49,5 @@
}
},
"license": "ISC",
"packageManager": "pnpm@9.12.3"
}
"packageManager": "pnpm@10.25.0+sha512.5e82639027af37cf832061bcc6d639c219634488e0f2baebe785028a793de7b525ffcd3f7ff574f5e9860654e098fe852ba8ac5dd5cefe1767d23a020a92f501"
}
+23 -23
View File
@@ -121,8 +121,8 @@ importers:
specifier: ^0.474.0
version: 0.474.0(react@19.2.1)
next:
specifier: 15.3.6
version: 15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
specifier: 15.3.8
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
next-qrcode:
specifier: ^2.5.1
version: 2.5.1(react@19.2.1)
@@ -251,8 +251,8 @@ importers:
specifier: ^3.0.5
version: 3.0.5
next:
specifier: 15.3.6
version: 15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
specifier: 15.3.8
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
@@ -399,8 +399,8 @@ importers:
specifier: ^0.474.0
version: 0.474.0(react@19.2.1)
next:
specifier: 15.3.6
version: 15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
specifier: 15.3.8
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
next-qrcode:
specifier: ^2.5.1
version: 2.5.1(react@19.2.1)
@@ -514,8 +514,8 @@ importers:
specifier: ^0.474.0
version: 0.474.0(react@19.2.1)
next:
specifier: 15.3.6
version: 15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
specifier: 15.3.8
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
react-chartjs-2:
specifier: ^5.3.0
version: 5.3.1(chart.js@4.5.1)(react@19.2.1)
@@ -611,8 +611,8 @@ importers:
specifier: ^0.474.0
version: 0.474.0(react@19.2.1)
next:
specifier: 15.3.6
version: 15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
specifier: 15.3.8
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
react-hook-form:
specifier: ^7.54.2
version: 7.68.0(react@19.2.1)
@@ -696,8 +696,8 @@ importers:
specifier: ^3.0.5
version: 3.0.5
next:
specifier: 15.3.6
version: 15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
specifier: 15.3.8
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
ol:
specifier: ^7.3.0
version: 7.5.2
@@ -799,8 +799,8 @@ importers:
specifier: ^2.30.1
version: 2.30.1
next:
specifier: 15.3.6
version: 15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
specifier: 15.3.8
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
ol:
specifier: ^7.3.0
version: 7.5.2
@@ -941,8 +941,8 @@ importers:
specifier: ^2.30.1
version: 2.30.1
next:
specifier: 15.3.6
version: 15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
specifier: 15.3.8
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
ol:
specifier: ^7.3.0
version: 7.5.2
@@ -2341,8 +2341,8 @@ packages:
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
'@next/env@15.3.6':
resolution: {integrity: sha512-/cK+QPcfRbDZxmI/uckT4lu9pHCfRIPBLqy88MhE+7Vg5hKrEYc333Ae76dn/cw2FBP2bR/GoK/4DU+U7by/Nw==}
'@next/env@15.3.8':
resolution: {integrity: sha512-SAfHg0g91MQVMPioeFeDjE+8UPF3j3BvHjs8ZKJAUz1BG7eMPvfCKOAgNWJ6s1MLNeP6O2InKQRTNblxPWuq+Q==}
'@next/eslint-plugin-next@15.1.7':
resolution: {integrity: sha512-kRP7RjSxfTO13NE317ek3mSGzoZlI33nc/i5hs1KaWpK+egs85xg0DJ4p32QEiHnR0mVjuUfhRIun7awqfL7pQ==}
@@ -5478,8 +5478,8 @@ packages:
react: ^19.2.1
react-dom: ^19.2.1
next@15.3.6:
resolution: {integrity: sha512-oI6D1zbbsh6JzzZFDCSHnnx6Qpvd1fSkVJu/5d8uluqnxzuoqtodVZjYvNovooznUq8udSAiKp7MbwlfZ8Gm6w==}
next@15.3.8:
resolution: {integrity: sha512-L+4c5Hlr84fuaNADZbB9+ceRX9/CzwxJ+obXIGHupboB/Q1OLbSUapFs4bO8hnS/E6zV/JDX7sG1QpKVR2bguA==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
@@ -7660,7 +7660,7 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
'@next/env@15.3.6': {}
'@next/env@15.3.8': {}
'@next/eslint-plugin-next@15.1.7':
dependencies:
@@ -11250,9 +11250,9 @@ snapshots:
react: 19.2.1
react-dom: 19.2.1(react@19.2.1)
next@15.3.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
next@15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
dependencies:
'@next/env': 15.3.6
'@next/env': 15.3.8
'@swc/counter': 0.1.3
'@swc/helpers': 0.5.15
busboy: 1.6.0