대시보드 수정정
This commit is contained in:
@@ -73,12 +73,32 @@
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.card_paddingnone {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.card_nomargin {
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.card_title {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card_title .leftArea {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card_title h4 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.topline {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
@@ -127,3 +147,300 @@
|
||||
border-bottom-color: #3f4042;
|
||||
}
|
||||
}
|
||||
|
||||
/* 자료현황 스타일 */
|
||||
.mid_info_area {
|
||||
padding: 10px 5px;
|
||||
height: 220px;
|
||||
position: relative;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mi_list_swiper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mi_list {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ml_box {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 90%;
|
||||
height: 150px;
|
||||
padding: 12px;
|
||||
margin: 0 8px;
|
||||
display: flex;
|
||||
border-radius: 12px;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.ml_box:hover {
|
||||
background: #e3f2fd;
|
||||
border-color: #2196f3;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mb_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tit i {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.tt {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.info_row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info_item {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info_label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.info_value {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #2196f3;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Swiper 네비게이션 스타일 */
|
||||
.mi_list_swiper .swiper-button-next,
|
||||
.mi_list_swiper .swiper-button-prev {
|
||||
color: #2196f3;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.mi_list_swiper .swiper-button-next:after,
|
||||
.mi_list_swiper .swiper-button-prev:after {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mi_list_swiper .swiper-pagination {
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
.mi_list_swiper .swiper-pagination-bullet {
|
||||
background: #2196f3;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.mi_list_swiper .swiper-pagination-bullet-active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 특별시 스타일 */
|
||||
.ml_box.special-city {
|
||||
background: linear-gradient(135deg, #ff6b6b, #ff8e8e);
|
||||
border-color: #ff4757;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ml_box.special-city .tt {
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ml_box.special-city .info_label {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.ml_box.special-city .info_value {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ml_box.special-city:hover {
|
||||
background: linear-gradient(135deg, #ff5252, #ff7979);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 12px rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
/* 광역시 스타일 */
|
||||
.ml_box.metropolitan-city {
|
||||
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
||||
border-color: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ml_box.metropolitan-city .tt {
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ml_box.metropolitan-city .info_label {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.ml_box.metropolitan-city .info_value {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ml_box.metropolitan-city:hover {
|
||||
background: linear-gradient(135deg, #2196f3, #21cbf3);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 12px rgba(33, 150, 243, 0.3);
|
||||
}
|
||||
|
||||
/* 광역도 스타일 */
|
||||
.ml_box.province {
|
||||
background: linear-gradient(135deg, #a8edea, #fed6e3);
|
||||
border-color: #00bcd4;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.ml_box.province .tt {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ml_box.province .info_label {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.ml_box.province .info_value {
|
||||
color: #00bcd4;
|
||||
}
|
||||
|
||||
.ml_box.province:hover {
|
||||
background: linear-gradient(135deg, #00bcd4, #26c6da);
|
||||
color: white;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 12px rgba(0, 188, 212, 0.3);
|
||||
}
|
||||
|
||||
.ml_box.province:hover .tt,
|
||||
.ml_box.province:hover .info_label,
|
||||
.ml_box.province:hover .info_value {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 네이버 스타일 로그인 박스 */
|
||||
.login_info_box {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border: 1px solid #e1e5e9;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login_header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login_avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 12px;
|
||||
border: 2px solid #e1e5e9;
|
||||
}
|
||||
|
||||
.login_avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.login_user_info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user_name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.user_status {
|
||||
font-size: 12px;
|
||||
color: #03c75a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.login_actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.login_btn {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
background: #03c75a;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.login_btn:hover {
|
||||
background: #02b351;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.signup_btn {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
background: #fff;
|
||||
color: #03c75a;
|
||||
border: 1px solid #03c75a;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.signup_btn:hover {
|
||||
background: #03c75a;
|
||||
color: #fff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageableList } from "@/shared/model";
|
||||
|
||||
export interface DataStatus {
|
||||
ctpvgnsvycnt: number;
|
||||
filecnt: number;
|
||||
ctpvnm: string;
|
||||
}
|
||||
|
||||
export const getDataStatusList = async (): Promise<PageableList<DataStatus>> => {
|
||||
const response = await client.get<DataStatus[]>(
|
||||
`${ENDPOINT.DC_SERVICE.DATA_STATUS}`,
|
||||
);
|
||||
const data = response.data;
|
||||
|
||||
console.log("getDataStatusList data:", data);
|
||||
|
||||
return {
|
||||
content: data as DataStatus[],
|
||||
pageable: {
|
||||
sort: {
|
||||
sorted: false,
|
||||
unsorted: true,
|
||||
empty: true,
|
||||
},
|
||||
offset: 0,
|
||||
pageNumber: 0,
|
||||
pageSize: data.length as number,
|
||||
paged: false,
|
||||
unpaged: true,
|
||||
},
|
||||
sort: {
|
||||
sorted: false,
|
||||
unsorted: true,
|
||||
empty: true,
|
||||
},
|
||||
totalPages: 1,
|
||||
totalElements: data.length,
|
||||
number: 0,
|
||||
numberOfElements: data.length,
|
||||
size: data.length,
|
||||
first: true,
|
||||
last: true,
|
||||
empty: data.length === 0,
|
||||
};
|
||||
};
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ export const getSupportList = async (): Promise<PageableList<Support>> => {
|
||||
console.log("getSupportList data:", data);
|
||||
|
||||
return {
|
||||
content: data,
|
||||
content: data as Support[],
|
||||
pageable: {
|
||||
sort: {
|
||||
sorted: false,
|
||||
@@ -31,7 +31,7 @@ export const getSupportList = async (): Promise<PageableList<Support>> => {
|
||||
},
|
||||
offset: 0,
|
||||
pageNumber: 0,
|
||||
pageSize: data.length,
|
||||
pageSize: data.length as number,
|
||||
paged: false,
|
||||
unpaged: true,
|
||||
},
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import type { Support } from "../api/support";
|
||||
import type { Support } from "../api/support-service";
|
||||
|
||||
const typeLabelMap: Record<string, string> = {
|
||||
waterQuality: "수질",
|
||||
|
||||
@@ -4,25 +4,6 @@ import { Badge } from "@common/components";
|
||||
import type { MyPost, NoticePost } from "@/entities/post";
|
||||
|
||||
const noticePostTableColumns: ColumnDef<NoticePost>[] = [
|
||||
{
|
||||
header: "번호",
|
||||
cell: ({ row, table }) => {
|
||||
if (row.original.upndFixYn === "Y") {
|
||||
return (
|
||||
<Badge
|
||||
color="orange"
|
||||
className="flex size-8 items-center justify-center"
|
||||
>
|
||||
<i className="diveicon di-volume-up-fill" />
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
const totalRows = table.getRowCount();
|
||||
const currentPage = table.getState().pagination.pageIndex;
|
||||
const pageSize = table.getState().pagination.pageSize;
|
||||
return totalRows - (currentPage * pageSize + row.index);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "pstTtl",
|
||||
header: "제목",
|
||||
@@ -43,15 +24,6 @@ const noticePostTableColumns: ColumnDef<NoticePost>[] = [
|
||||
];
|
||||
|
||||
const myPostTableColumns: ColumnDef<MyPost>[] = [
|
||||
{
|
||||
header: "번호",
|
||||
cell: ({ row, table }) => {
|
||||
const totalRows = table.getRowCount();
|
||||
const currentPage = table.getState().pagination.pageIndex;
|
||||
const pageSize = table.getState().pagination.pageSize;
|
||||
return totalRows - (currentPage * pageSize + row.index);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "pstTtl",
|
||||
header: "제목",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useRef } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { getImageUrlByAtflCd, getBannerList, type Banner } from "../api/banner";
|
||||
import { getImageUrlByAtflCd, getBannerList, type Banner } from "../api/banner-service";
|
||||
import { getCookie } from "@/shared/lib";
|
||||
|
||||
import {
|
||||
@@ -54,7 +54,7 @@ const BannerList: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative h-[280px] w-full cursor-pointer overflow-hidden transition-opacity hover:opacity-80"
|
||||
className="relative h-[210px] w-full cursor-pointer overflow-hidden transition-opacity hover:opacity-80"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div
|
||||
@@ -147,7 +147,7 @@ const BannerList: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card card-border">
|
||||
<div className="card card-border" style={{ height: "230px" }}>
|
||||
<Carousel
|
||||
plugins={[plugin.current]}
|
||||
opts={{ loop: true }}
|
||||
@@ -168,7 +168,7 @@ const BannerList: React.FC = () => {
|
||||
))
|
||||
) : (
|
||||
<CarouselItem className="basis-full pl-0">
|
||||
<div className="relative flex h-[280px] w-full items-center justify-center bg-accent">
|
||||
<div className="relative flex h-[210px] w-full items-center justify-center bg-accent">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
배너가 없습니다
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ import ReuseStatus from "./reuse-status";
|
||||
import NoticeList from "./notice-list";
|
||||
import BannerList from "./banner-list";
|
||||
import SupportList from "./support-list";
|
||||
import DataStatusList from "./datastatus-list";
|
||||
|
||||
// Chart.js 등록
|
||||
ChartJS.register(
|
||||
@@ -141,55 +142,67 @@ function DashboardPage() {
|
||||
<KoreaBasinMap />
|
||||
|
||||
{/* 사용자 정보 카드 */}
|
||||
<div className="card card-border col-span-1 h-80 p-10">
|
||||
<h2 className="mb-6 text-2xl">사용자 정보</h2>
|
||||
<div className="col-span-1 !p-0">
|
||||
{error && <div className="mb-4 text-red-500">{error}</div>}
|
||||
{userInfo ? (
|
||||
<div className="mb-4">
|
||||
<div>
|
||||
<b>아이디:</b> {userInfo.userId}
|
||||
<div className="card card_border login_info_box" style={{ height: "150px" }}>
|
||||
<div className="login_header">
|
||||
<div className="login_avatar">
|
||||
<img src="/images/icon_loginID.png" alt="프로필" onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Ccircle cx='20' cy='20' r='18' fill='%23e1e5e9'/%3E%3Ctext x='20' y='26' font-size='20' text-anchor='middle' fill='%23999'%3E%3F%3C/text%3E%3C/svg%3E";
|
||||
}} />
|
||||
</div>
|
||||
<div>
|
||||
<b>이메일:</b> {userInfo.email || userInfo.userId}
|
||||
</div>
|
||||
<div>
|
||||
<b>이름:</b> {userInfo.userName || "-"}
|
||||
</div>
|
||||
<div>
|
||||
<b>2FA 활성화:</b>{" "}
|
||||
{userInfo.twoFactorEnabled ? "예" : "아니오"}
|
||||
<div className="login_user_info">
|
||||
<div className="user_name">{userInfo.userName || "-"}</div>
|
||||
<div className="user_status">온라인</div>
|
||||
<div className="user_status">
|
||||
<b>2FA 활성화:</b>{" "}
|
||||
{userInfo.twoFactorEnabled ? "예" : "아니오"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="login_actions">
|
||||
<button className="login_btn" onClick={handleLogout}>로그아웃</button>
|
||||
<button className="signup_btn" onClick={handleChangePassword}>마이페이지</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
!error && <div>사용자 정보를 불러오는 중...</div>
|
||||
)}
|
||||
<button className="btn mb-2 w-full" onClick={handleLogout}>
|
||||
로그아웃
|
||||
</button>
|
||||
<button className="btn w-full" onClick={handleChangePassword}>
|
||||
패스워드 변경
|
||||
</button>
|
||||
</div>
|
||||
<div className="card card-border" style={{ height: "300px" }}>
|
||||
<div className="card-header text-foreground">
|
||||
<h3 className="text-2xl font-medium">일정 알림</h3>
|
||||
</div>
|
||||
<div className="card paddingnone">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="card card-border" style={{ height: "300px" }}>
|
||||
<div className="card-header text-foreground">
|
||||
<h3 className="text-2xl font-medium">내 작성글</h3>
|
||||
</div>
|
||||
<div className="card paddingnone">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 두번째 라인 */}
|
||||
<div className="grid grid-cols-4 gap-1">
|
||||
<div className="card card-border">
|
||||
<div className="card card-border" style={{ height: "250px" }}>
|
||||
<SupportList />
|
||||
</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>
|
||||
<div className="card card-border" style={{ height: "250px" }}>
|
||||
<DataStatusList />
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card card-border" style={{ height: "250px" }}>
|
||||
<NoticeList />
|
||||
</div>
|
||||
|
||||
{/* 배너 영역 */}
|
||||
<div className="card card-border" style={{ height: "320px" }}>
|
||||
<div className="card card-border" style={{ height: "250px" }}>
|
||||
<BannerList />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useMemo } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { getDataStatusList, type DataStatus } from "../api/datastatus-service";
|
||||
import type { PageableList } from "@/shared/model";
|
||||
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
Autoplay,
|
||||
} from "@common/components";
|
||||
|
||||
// 지역 타입에 따른 아이콘과 클래스 결정
|
||||
const getRegionInfo = (regionName: string) => {
|
||||
if (regionName.includes("서울특별시")) {
|
||||
return { icon: "🏛️", className: "special-city" };
|
||||
}
|
||||
if (
|
||||
regionName.includes("광역시") ||
|
||||
regionName.includes("부산") ||
|
||||
regionName.includes("대구") ||
|
||||
regionName.includes("인천") ||
|
||||
regionName.includes("광주") ||
|
||||
regionName.includes("대전") ||
|
||||
regionName.includes("울산")
|
||||
) {
|
||||
return { icon: "🏭", className: "metropolitan-city" };
|
||||
}
|
||||
return { icon: "🌊", className: "province" };
|
||||
};
|
||||
|
||||
const DataStatusList: React.FC = () => {
|
||||
const plugin = useRef(
|
||||
Autoplay({ delay: 3000, stopOnInteraction: true })
|
||||
);
|
||||
|
||||
const {
|
||||
data: dataStatusResponse,
|
||||
error: dataStatusError,
|
||||
isLoading: isLoadingDataStatus,
|
||||
} = useQuery<PageableList<DataStatus>>({
|
||||
queryKey: [ENDPOINT.DC_SERVICE.DATA_STATUS],
|
||||
queryFn: () => getDataStatusList(),
|
||||
enabled: true,
|
||||
retry: false,
|
||||
staleTime: 5 * 60 * 1000, // 5분간 fresh 상태 유지
|
||||
refetchOnWindowFocus: false, // 윈도우 포커스 시 리페칭 방지
|
||||
refetchOnMount: false, // 마운트 시 리페칭 방지 (캐시된 데이터 사용)
|
||||
});
|
||||
|
||||
const dataStatusList = dataStatusResponse?.content || [];
|
||||
|
||||
// 에러 상태 판별 헬퍼 함수
|
||||
const getErrorStatus = (e: unknown): number | undefined => {
|
||||
if (!e || typeof e !== "object") return undefined;
|
||||
const maybe = e as { status?: number; response?: { status?: number } };
|
||||
return maybe.status ?? maybe.response?.status;
|
||||
};
|
||||
|
||||
// 401 에러 처리
|
||||
if (dataStatusError) {
|
||||
const is401Error =
|
||||
(dataStatusError instanceof Error && dataStatusError.message.includes("401")) ||
|
||||
getErrorStatus(dataStatusError) === 401;
|
||||
|
||||
if (is401Error) {
|
||||
console.error("[DataStatusList] 401 인증 오류:", dataStatusError);
|
||||
return (
|
||||
<div className="card card-border">
|
||||
<div className="relative flex h-[280px] w-full items-center justify-center bg-accent">
|
||||
<div className="text-sm text-red-500">
|
||||
인증이 필요합니다. 로그인해주세요.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
console.error("[DataStatusList] 데이터 조회 오류:", dataStatusError);
|
||||
return (
|
||||
<div className="card card-border">
|
||||
<div className="relative flex h-[240px] w-full items-center justify-center bg-accent">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
데이터를 불러올 수 없습니다
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 로딩 중
|
||||
if (isLoadingDataStatus) {
|
||||
return (
|
||||
<div className="card card-border">
|
||||
<div className="relative flex h-[200px] w-full items-center justify-center bg-accent" >
|
||||
<div className="text-sm text-muted-foreground">데이터 로딩 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="card_title bottomline">
|
||||
<div className="leftArea">
|
||||
<h4>자료현황</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card card_paddingnone">
|
||||
<div className="mid_info_area" style={{ width: "100%", padding: "5px" }}>
|
||||
<Carousel
|
||||
plugins={[plugin.current]}
|
||||
opts={{ loop: true }}
|
||||
className="relative mi_list_swiper"
|
||||
>
|
||||
<CarouselContent className="-ml-4 mi_list">
|
||||
{dataStatusList.length > 0 ? (
|
||||
dataStatusList.map((dataStatus) => {
|
||||
const regionInfo = getRegionInfo(dataStatus.ctpvnm);
|
||||
return (
|
||||
<CarouselItem
|
||||
key={`dataStatus-${dataStatus.ctpvnm}`}
|
||||
className={`basis-1/2 pl-4 ml_box ${regionInfo.className}`}
|
||||
style={{ width: "200px" }}
|
||||
>
|
||||
<div className="mb_section">
|
||||
<div className="tit">
|
||||
<i className="cloud-arrow-down-fill color_3">
|
||||
{regionInfo.icon}
|
||||
</i>
|
||||
<span className="tt">{dataStatus.ctpvnm}</span>
|
||||
</div>
|
||||
<div className="info_row">
|
||||
<div className="info_item">
|
||||
<div className="info_label">기본계획</div>
|
||||
<div className="info_value">
|
||||
{dataStatus.ctpvgnsvycnt}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info_item">
|
||||
<div className="info_label">파일수</div>
|
||||
<div className="info_value">{dataStatus.filecnt}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<CarouselItem className="basis-full pl-0" style={{ height: "210px" }}>
|
||||
<div className="relative flex h-[280px] 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 z-10 !bg-[rgba(255,255,255,0.8)] text-accent-foreground" />
|
||||
<CarouselNext className="absolute right-2 top-1/2 z-10 !bg-[rgba(255,255,255,0.8)] text-accent-foreground" />
|
||||
|
||||
</Carousel>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataStatusList;
|
||||
@@ -19,7 +19,7 @@ const NoticeList: React.FC = () => {
|
||||
queryFn: () =>
|
||||
PostApi.getNoticePostList({
|
||||
page: 0,
|
||||
size: 5,
|
||||
size: 3,
|
||||
keywordType: "bbsTypeCd",
|
||||
keyword: "BBSNOTICE",
|
||||
}),
|
||||
@@ -50,7 +50,7 @@ const NoticeList: React.FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="card card-border !p-0">
|
||||
<div className="card card-border !p-0" style={{ height: "220px" }}>
|
||||
<div className="card-header !m-0 !border-none text-foreground">
|
||||
<h3 className="text-2xl font-medium">공지사항</h3>
|
||||
<Button className="h-[3.2rem]" onClick={handleMoreClick}>더보기</Button>
|
||||
|
||||
@@ -6,33 +6,13 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import type { PageableList } from "@/shared/model";
|
||||
import { supportTableColumns } from "../model/support-columns";
|
||||
import { getSupportList, type Support } from "../api/support";
|
||||
import { getSupportList, type Support } from "../api/support-service";
|
||||
|
||||
const SupportList: React.FC = () => {
|
||||
const supportQuery = useQuery<PageableList<Support>>({
|
||||
queryKey: [ENDPOINT.IC_SERVICE.GROUPING_STATISTICS],
|
||||
queryFn: async () => {
|
||||
const data = await getSupportList();
|
||||
return {
|
||||
content: data,
|
||||
pageable: {
|
||||
sort: { empty: true, sorted: false, unsorted: true },
|
||||
offset: 0,
|
||||
pageNumber: 0,
|
||||
pageSize: data.length,
|
||||
paged: false,
|
||||
unpaged: true,
|
||||
},
|
||||
sort: { empty: true, sorted: false, unsorted: true },
|
||||
totalPages: 1,
|
||||
totalElements: data.length,
|
||||
number: 0,
|
||||
numberOfElements: data.length,
|
||||
size: data.length,
|
||||
first: true,
|
||||
last: true,
|
||||
empty: data.length === 0,
|
||||
};
|
||||
return await getSupportList();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,7 +28,7 @@ const SupportList: React.FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="card card-border !p-0">
|
||||
<div className="card card-border !p-0" style={{ height: "220px" }}>
|
||||
<div className="card-header !m-0 !border-none text-foreground">
|
||||
<h3 className="text-2xl font-medium">지원현황</h3>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user