dashboard 초기 목록 로딩방식을 csr로 변경

This commit is contained in:
dohyeons 2025-11-20 16:37:52 +09:00
parent 818fd5ac0d
commit eb6fe57839
2 changed files with 18 additions and 84 deletions

View File

@ -18,32 +18,26 @@ import { Pagination, PaginationInfo } from "@/components/common/Pagination";
import { DeleteConfirmModal } from "@/components/common/DeleteConfirmModal"; import { DeleteConfirmModal } from "@/components/common/DeleteConfirmModal";
import { Plus, Search, Edit, Trash2, Copy, MoreVertical, AlertCircle, RefreshCw } from "lucide-react"; import { Plus, Search, Edit, Trash2, Copy, MoreVertical, AlertCircle, RefreshCw } from "lucide-react";
interface DashboardListClientProps {
initialDashboards: Dashboard[];
initialPagination: {
total: number;
page: number;
limit: number;
};
}
/** /**
* *
* - CSR
* - * -
* - /// * - ///
*/ */
export default function DashboardListClient({ initialDashboards, initialPagination }: DashboardListClientProps) { export default function DashboardListClient() {
const router = useRouter(); const router = useRouter();
const { toast } = useToast(); const { toast } = useToast();
const [dashboards, setDashboards] = useState<Dashboard[]>(initialDashboards);
const [loading, setLoading] = useState(false); // 초기 로딩은 서버에서 완료 // 상태 관리
const [dashboards, setDashboards] = useState<Dashboard[]>([]);
const [loading, setLoading] = useState(true); // CSR이므로 초기 로딩 true
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
// 페이지네이션 상태 // 페이지네이션 상태
const [currentPage, setCurrentPage] = useState(initialPagination.page); const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(initialPagination.limit); const [pageSize, setPageSize] = useState(10);
const [totalCount, setTotalCount] = useState(initialPagination.total); const [totalCount, setTotalCount] = useState(0);
// 모달 상태 // 모달 상태
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@ -73,17 +67,8 @@ export default function DashboardListClient({ initialDashboards, initialPaginati
} }
}; };
// 초기 로드 여부 추적 // 검색어/페이지 변경 시 fetch (초기 로딩 포함)
const [isInitialLoad, setIsInitialLoad] = useState(true);
useEffect(() => { useEffect(() => {
// 초기 로드는 건너뛰기 (서버에서 이미 데이터를 가져왔음)
if (isInitialLoad) {
setIsInitialLoad(false);
return;
}
// 이후 검색어/페이지 변경 시에만 fetch
loadDashboards(); loadDashboards();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchTerm, currentPage, pageSize]); }, [searchTerm, currentPage, pageSize]);
@ -91,7 +76,7 @@ export default function DashboardListClient({ initialDashboards, initialPaginati
// 페이지네이션 정보 계산 // 페이지네이션 정보 계산
const paginationInfo: PaginationInfo = { const paginationInfo: PaginationInfo = {
currentPage, currentPage,
totalPages: Math.ceil(totalCount / pageSize), totalPages: Math.ceil(totalCount / pageSize) || 1,
totalItems: totalCount, totalItems: totalCount,
itemsPerPage: pageSize, itemsPerPage: pageSize,
startItem: totalCount === 0 ? 0 : (currentPage - 1) * pageSize + 1, startItem: totalCount === 0 ? 0 : (currentPage - 1) * pageSize + 1,

View File

@ -1,73 +1,22 @@
import DashboardListClient from "@/app/(main)/admin/dashboard/DashboardListClient"; import DashboardListClient from "@/app/(main)/admin/dashboard/DashboardListClient";
import { cookies } from "next/headers";
/** /**
* fetch *
* -
* - CSR로
*/ */
async function getInitialDashboards() { export default function DashboardListPage() {
try {
// 서버 사이드 전용: 백엔드 API 직접 호출
// 도커 네트워크 내부에서는 서비스 이름 사용, 로컬에서는 127.0.0.1
const backendUrl = process.env.SERVER_API_URL || "http://backend:8080";
// 쿠키에서 authToken 추출
const cookieStore = await cookies();
const authToken = cookieStore.get("authToken")?.value;
if (!authToken) {
// 토큰이 없으면 빈 데이터 반환 (클라이언트에서 로드)
return {
dashboards: [],
pagination: { total: 0, page: 1, limit: 10 },
};
}
const response = await fetch(`${backendUrl}/api/dashboards/my?page=1&limit=10`, {
cache: "no-store", // 항상 최신 데이터
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`, // Authorization 헤더로 전달
},
});
if (!response.ok) {
throw new Error(`Failed to fetch dashboards: ${response.status}`);
}
const data = await response.json();
return {
dashboards: data.data || [],
pagination: data.pagination || { total: 0, page: 1, limit: 10 },
};
} catch (error) {
console.error("Server-side fetch error:", error);
// 에러 발생 시 빈 데이터 반환 (클라이언트에서 재시도 가능)
return {
dashboards: [],
pagination: { total: 0, page: 1, limit: 10 },
};
}
}
/**
* ( )
* - +
* -
*/
export default async function DashboardListPage() {
const initialData = await getInitialDashboards();
return ( return (
<div className="bg-background flex min-h-screen flex-col"> <div className="bg-background flex min-h-screen flex-col">
<div className="space-y-6 p-6"> <div className="space-y-6 p-6">
{/* 페이지 헤더 (서버에서 렌더링) */} {/* 페이지 헤더 */}
<div className="space-y-2 border-b pb-4"> <div className="space-y-2 border-b pb-4">
<h1 className="text-3xl font-bold tracking-tight"> </h1> <h1 className="text-3xl font-bold tracking-tight"> </h1>
<p className="text-muted-foreground text-sm"> </p> <p className="text-muted-foreground text-sm"> </p>
</div> </div>
{/* 나머지 컨텐츠 (클라이언트 컴포넌트 + 서버 데이터) */} {/* 클라이언트 컴포넌트 */}
<DashboardListClient initialDashboards={initialData.dashboards} initialPagination={initialData.pagination} /> <DashboardListClient />
</div> </div>
</div> </div>
); );