diff --git a/apps/st/package.json b/apps/st/package.json
index 5cf4bf67d..ef32c14cd 100644
--- a/apps/st/package.json
+++ b/apps/st/package.json
@@ -14,7 +14,9 @@
"@common/components": "workspace:*",
"@common/config": "workspace:*",
"@common/network": "workspace:*",
- "@rsone/components": "workspace:*",
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^8.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@faker-js/faker": "^9.9.0",
"@fontsource-variable/noto-sans-kr": "^5.2.5",
"@frontend-opensource/use-react-hooks": "^1.1.4",
@@ -29,6 +31,7 @@
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
+ "@rsone/components": "workspace:*",
"@tanstack/react-query": "^5.67.1",
"@tanstack/react-query-devtools": "^5.75.5",
"@tanstack/react-table": "^8.21.2",
diff --git a/apps/st/src/components/statistics/Dashboard.tsx b/apps/st/src/components/statistics/Dashboard.tsx
index f3682d1e0..b3734e386 100644
--- a/apps/st/src/components/statistics/Dashboard.tsx
+++ b/apps/st/src/components/statistics/Dashboard.tsx
@@ -1,9 +1,96 @@
-const Dashboard = () => {
- return (
-
- Dashboard
-
- );
-};
+"use client";
-export default Dashboard;
\ No newline at end of file
+import { useEffect, useState } from "react";
+
+import { closestCenter, DndContext } from "@dnd-kit/core";
+import { rectSortingStrategy, SortableContext } from "@dnd-kit/sortable";
+import dayjs from "dayjs";
+
+import { useUserStore } from "@/shared/lib";
+import { Skeleton } from "@common/components";
+
+import { useDndSensors } from "@/pages/statistics/lib/hooks/use-dnd-sensors";
+import { usePanelOrder } from "@/pages/statistics/lib/hooks/use-panel-order";
+import { getInitialPanels } from "@/pages/statistics/model/panel-config";
+import { SortablePanel } from "@/pages/statistics/ui/sortable-panel";
+
+interface PanelItem {
+ id: string;
+ component: React.ReactNode;
+ className?: string;
+}
+
+function Dashboard() {
+ const userInfo = useUserStore((state) => state.userInfo);
+ const [initialPanels, setInitialPanels] = useState([]);
+ const [isMounted, setIsMounted] = useState(false);
+ const sensors = useDndSensors();
+
+ const { panels, handleDragEnd } = usePanelOrder({
+ initialPanels,
+ storageKey: "statistics-dashboard-panels-order",
+ });
+
+ useEffect(() => {
+ setIsMounted(true);
+ }, []);
+
+ useEffect(() => {
+ if (!userInfo) return;
+ const initialPanels = getInitialPanels({
+ dashboardParams: {
+ statsYr: String(dayjs().year() - 1),
+ dtlInstCd: userInfo.dtlInstCd,
+ enoCd: userInfo.enoCd,
+ laeSeCd: userInfo.laeSeCd,
+ },
+ scheduleInfoParams: {
+ statsYr: String(dayjs().year() - 1),
+ },
+ });
+ setInitialPanels(initialPanels);
+ }, [userInfo]);
+
+ return (
+ <>
+ {isMounted && initialPanels.length > 0 ? (
+
+ p.id)}
+ strategy={rectSortingStrategy}
+ >
+ {/* TBD: 그리드 레이아웃 비율 정의 필요
+ 현재: 3열 그리드, 반응형 브레이크포인트 적용
+ SPEC 레이아웃에 따라 조정 필요 */}
+
+ {panels.map((panel) => (
+
+ {panel.component}
+
+ ))}
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+}
+
+export default Dashboard;
diff --git a/apps/st/src/components/statistics/verification/StatisticsVerificationReport.tsx b/apps/st/src/components/statistics/verification/StatisticsVerificationReport.tsx
index 896ee061c..6cd57b4f1 100644
--- a/apps/st/src/components/statistics/verification/StatisticsVerificationReport.tsx
+++ b/apps/st/src/components/statistics/verification/StatisticsVerificationReport.tsx
@@ -129,6 +129,7 @@ const DEFAULT_FILTERS: StatisticsVerificationFilterState = {
statsYr: String(dayjs().year() - 1),
sewerBzmnCd: null,
statsCd: null,
+ laeSbmsnSttsCd: "2",
};
const DEFAULT_SUB_FILTERS: StatisticsVerificationSubFilterState = {
@@ -156,15 +157,6 @@ function StatisticsVerificationReport() {
useState(DEFAULT_FILTERS);
const [subFilters, setSubFilters] =
useState(DEFAULT_SUB_FILTERS);
- const [submittedFilters, setSubmittedFilters] =
- useState(DEFAULT_FILTERS);
- const [submittedParams, setSubmittedParams] = useState<{
- main: StatisticsVerificationFilterState;
- sub: StatisticsVerificationSubFilterState;
- }>({
- main: DEFAULT_FILTERS,
- sub: DEFAULT_SUB_FILTERS,
- });
const [selectedReportRows, setSelectedReportRows] = useState<
StatsInputStatusList[]
>([]);
@@ -182,11 +174,30 @@ function StatisticsVerificationReport() {
const { data: sigunguData } = useStAnlsSigunguCombo(
filters.sewerBzmnCd ?? "",
);
+ const detailParams = useMemo(
+ () => ({
+ statsYr: filters.statsYr,
+ ctpvNm:
+ subFilters.ctpvNm !== "" ? subFilters.ctpvNm : filters.ctpvNm,
+ sggNm: subFilters.sggNm !== "" ? subFilters.sggNm : filters.sggNm,
+ sewerBzmnCd:
+ subFilters.sewerBzmnCd !== ""
+ ? subFilters.sewerBzmnCd
+ : filters.sewerBzmnCd,
+ statsCd:
+ subFilters.statsCd !== ""
+ ? subFilters.statsCd
+ : filters.statsCd,
+ prcsYnList: subFilters.prcsYnList,
+ pbcpAprvYn: subFilters.pbcpAprvYn,
+ laeSbmsnSttsCd: subFilters.laeSbmsnSttsCd,
+ }),
+ [filters, subFilters],
+ );
- const statisticsVerificationReportQuery =
- useStatsVrfcRptData(submittedFilters);
+ const statisticsVerificationReportQuery = useStatsVrfcRptData(filters);
const statisticsVerificationResultQuery =
- useStatsVrfcRsltData(submittedParams);
+ useStatsVrfcRsltData(detailParams);
const statisticsVerificationReportData =
statisticsVerificationReportQuery.data ?? [];
@@ -253,11 +264,17 @@ function StatisticsVerificationReport() {
const nextRows = statisticsVerificationResultGridRows;
const prevLength = prevLengthRef.current;
- if (!dataProvider || nextRows.length === 0) {
+ if (!dataProvider) {
prevLengthRef.current = nextRows.length;
return;
}
+ if (nextRows.length === 0) {
+ dataProvider.setRows?.([]);
+ prevLengthRef.current = 0;
+ return;
+ }
+
if (nextRows.length > prevLength && prevLength > 0) {
// 무한스크롤 시 데이터 처리
const newRows = nextRows.slice(prevLength);
@@ -327,17 +344,16 @@ function StatisticsVerificationReport() {
statisticsVerificationReportQuery.isError,
]);
- function handleSearch(
- nextFilters: StatisticsVerificationFilterState,
- nextSubFilters: StatisticsVerificationSubFilterState,
- ) {
- setSelectedReportRows([]);
+ function resetResultState() {
setSelectedResultRows([]);
- setSubmittedFilters({ ...nextFilters });
- setSubmittedParams({
- main: { ...nextFilters },
- sub: { ...nextSubFilters },
- });
+ editedResultRowMapRef.current = new Map();
+ prevLengthRef.current = 0;
+ resultGridRef.current?.dataProvider?.setRows?.([]);
+ }
+
+ function resetReportState() {
+ setSelectedReportRows([]);
+ resetResultState();
}
async function handleLoadMoreDetail() {
@@ -350,14 +366,6 @@ function StatisticsVerificationReport() {
}
}
- function handleResultSearch() {
- setSelectedResultRows([]);
- setSubmittedParams({
- main: { ...filters },
- sub: { ...subFilters },
- });
- }
-
function handleFilterChange<
K extends keyof StatisticsVerificationFilterState,
>(key: K) {
@@ -365,6 +373,7 @@ function StatisticsVerificationReport() {
value?: StatisticsVerificationFilterState[K],
optionData?: { label: string; value: string },
) => {
+ resetReportState();
if (key === "sewerBzmnCd") {
setFilters((prev) => ({
...prev,
@@ -372,8 +381,17 @@ function StatisticsVerificationReport() {
sewerBzmnCd: optionData?.value,
sggNm: "",
}));
+ setSubFilters((prev) => ({
+ ...prev,
+ ctpvNm: "",
+ sggNm: "",
+ sewerBzmnCd: "",
+ }));
return;
}
+ if (key === "statsCd") {
+ setSubFilters((prev) => ({ ...prev, statsCd: "" }));
+ }
setFilters((prev) => ({ ...prev, [key]: value }));
};
}
@@ -382,10 +400,16 @@ function StatisticsVerificationReport() {
K extends keyof StatisticsVerificationSubFilterState,
>(key: K) {
return (value?: StatisticsVerificationSubFilterState[K]) => {
+ resetResultState();
setSubFilters((prev) => ({ ...prev, [key]: value }));
};
}
+ function handleSubFilterReset() {
+ setSubFilters(DEFAULT_SUB_FILTERS);
+ resetResultState();
+ }
+
function handleCellEdited(params: CellEditedCallbackParams) {
const { itemIndex, fieldName, newValue } = params;
const dataProvider = resultGridRef.current?.dataProvider as
@@ -403,11 +427,7 @@ function StatisticsVerificationReport() {
function handleReset() {
setFilters(DEFAULT_FILTERS);
setSubFilters(DEFAULT_SUB_FILTERS);
- setSelectedReportRows([]);
- setSelectedResultRows([]);
- editedResultRowMapRef.current = new Map();
- prevLengthRef.current = 0;
- handleSearch(DEFAULT_FILTERS, DEFAULT_SUB_FILTERS);
+ resetReportState();
}
function handleApprove() {
@@ -532,9 +552,6 @@ function StatisticsVerificationReport() {
-