포탈 데시보드 기능
This commit is contained in:
@@ -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;
|
||||
@@ -10,7 +10,6 @@ function PageLayout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<div className="h-auto min-h-full bg-container pb-2">
|
||||
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user