diff --git a/apps/st/src/entities/statistics/api/dashboard/dashboard-queries.ts b/apps/st/src/entities/statistics/api/dashboard/dashboard-queries.ts index 5b4e124f8..dd775f21d 100644 --- a/apps/st/src/entities/statistics/api/dashboard/dashboard-queries.ts +++ b/apps/st/src/entities/statistics/api/dashboard/dashboard-queries.ts @@ -88,6 +88,23 @@ export function useDashboardEcptReqCount(params: DashboardSearchParams | null) { }); } +// 지자체/광역시도/공단 3단계 처리상태 조회 +export function useDashboardThreeStageStatus( + params: DashboardSearchParams | null, +) { + return useQuery({ + queryKey: createDashboardQueryKey("three-stage-status", params), + queryFn: async () => { + if (!params) + throw new Error("Dashboard search params are required."); + const response = await dashboardApi.getThreeStageStatus(params); + return response.data; + }, + enabled: hasRequiredDashboardParams(params), + refetchOnWindowFocus: false, + }); +} + function createDashboardQueryKey( name: string, params: DashboardSearchParams | null, diff --git a/apps/st/src/entities/statistics/api/dashboard/dashboard-service.ts b/apps/st/src/entities/statistics/api/dashboard/dashboard-service.ts index d23946f20..468d44254 100644 --- a/apps/st/src/entities/statistics/api/dashboard/dashboard-service.ts +++ b/apps/st/src/entities/statistics/api/dashboard/dashboard-service.ts @@ -6,6 +6,7 @@ import type { DashboardSearchParams, DashboardService, EcptReqCountResponse, + ThreeStageStatusResponse, StatsSubmissionResponse, UnSubmitCountResponse, VrfcErrCountResponse, @@ -57,10 +58,22 @@ async function getEcptReqCount( return response.data; } +async function getThreeStageStatus( + params: DashboardSearchParams, +): Promise> { + const response = await stClient.get>( + `${ENDPOINT.ST_SERVICE.DASHBOARD}/three-stage-status`, + { params }, + ); + return response.data; +} + + export const dashboardApi: DashboardService = { getVrfcReport, getVrfcErrCount, getUnSubmitCount, getStatsSubmissionRate, getEcptReqCount, + getThreeStageStatus, }; diff --git a/apps/st/src/entities/statistics/model/dashboard/dashboard-interface.ts b/apps/st/src/entities/statistics/model/dashboard/dashboard-interface.ts index b0d9093a0..cb3d69db7 100644 --- a/apps/st/src/entities/statistics/model/dashboard/dashboard-interface.ts +++ b/apps/st/src/entities/statistics/model/dashboard/dashboard-interface.ts @@ -65,6 +65,16 @@ export interface EcptReqCountResponse { ecptReqCount: number; } +export interface ThreeStageStatusResponse { + statsYr: string; + laeStatusCd: string; + laeStatusNm: string; + ctpvStatusCd: string; + ctpvStatusNm: string; + pbcpStatusCd: string; + pbcpStatusNm: string; +} + export interface DashboardService { getVrfcReport( params: DashboardSearchParams, @@ -81,4 +91,7 @@ export interface DashboardService { getEcptReqCount( params: DashboardSearchParams, ): Promise>; + getThreeStageStatus( + params: DashboardSearchParams, + ): Promise>; } diff --git a/apps/st/src/pages/statistics/model/panel-config.tsx b/apps/st/src/pages/statistics/model/panel-config.tsx index 98d4ea0f2..ed81ac31a 100644 --- a/apps/st/src/pages/statistics/model/panel-config.tsx +++ b/apps/st/src/pages/statistics/model/panel-config.tsx @@ -2,9 +2,10 @@ import type { DashboardSearchParams } from "@/entities/statistics/model/dashboar import type { ScheduleInfoSearchParams } from "@/entities/statistics/model/dashboard/schedule-info-interface"; import { NoticePanel } from "../ui/panels/notice-panel"; -import { ScheduleCalendarPanel } from "../ui/panels/schedule-calendar-panel"; +// import { ScheduleCalendarPanel } from "../ui/panels/schedule-calendar-panel"; import { ScheduleInfoPanel } from "../ui/panels/schedule-info-panel"; import { StatsSubmissionPanel } from "../ui/panels/stats-submission-panel"; +import { ThreeStageStatusPanel } from "../ui/panels/three-stage-status-panel"; import { UnSubmitPanel } from "../ui/panels/unsubmit-panel"; // import { VrfcErrPanel } from "../ui/panels/vrfc-err-panel"; import { VrfcReportPanel } from "../ui/panels/vrfc-report-panel"; @@ -33,7 +34,7 @@ export function getInitialPanels({ }, { id: "panel-c", - component: , + component: , className: "", }, // { @@ -51,10 +52,15 @@ export function getInitialPanels({ component: , className: "", }, + // { + // id: "panel-f", + // component: , + // className: "", + // }, { id: "panel-f", - component: , + component: , className: "", }, ]; -} +} \ No newline at end of file diff --git a/apps/st/src/pages/statistics/ui/panels/three-stage-status-panel.tsx b/apps/st/src/pages/statistics/ui/panels/three-stage-status-panel.tsx new file mode 100644 index 000000000..de90fca3a --- /dev/null +++ b/apps/st/src/pages/statistics/ui/panels/three-stage-status-panel.tsx @@ -0,0 +1,187 @@ +"use client"; + +import type { DashboardSearchParams } from "@/entities/statistics/model/dashboard/dashboard-interface"; +import { useDashboardThreeStageStatus } from "@/entities/statistics/api/dashboard/dashboard-queries"; +import { LoadingWithAnimation } from "@common/components"; + +import { useDragHandle } from "../sortable-panel"; + +type StepKey = "input" | "submit" | "approve" | "review" | "verify"; + +interface StepDef { + key: StepKey; + label: string; + subLabel: string; + description: string; +} + +const STEPS: StepDef[] = [ + { key: "input", label: "입력", subLabel: "작성자", description: "담당자가 통계 자료를 입력합니다." }, + { key: "submit", label: "제출", subLabel: "작성자", description: "입력을 마친 자료를 제출합니다." }, + { key: "approve", label: "승인", subLabel: "승인자", description: "지자체 승인자가 제출 내용을 승인합니다." }, + { key: "review", label: "검토", subLabel: "시도", description: "광역시도 담당자가 자료를 검토합니다." }, + { key: "verify", label: "검증", subLabel: "공단", description: "환경공단이 최종 검증을 진행합니다." }, +]; + +/** + * lae_sbmsn_stts_cd(지자체) : 0=미작성 1=작성중 2=제출완료 3=승인 + * stat_pic_sbmsn_stts_cd(광역시도), pbcp_sbmsn_stts_cd(공단) 도 동일 코드 도메인 가정 + * - 실제 코드값 의미는 STINPUTSTP 코드 그룹 기준 재확인 필요 + */ +function resolveCheckedSteps(data?: { + laeStatusCd: string; + ctpvStatusCd: string; + pbcpStatusCd: string; +}): boolean[] { + if (!data) return STEPS.map(() => false); + + const lae = Number(data.laeStatusCd); + const ctpv = Number(data.ctpvStatusCd); + const pbcp = Number(data.pbcpStatusCd); + + return [ + lae >= 1, + lae >= 2, + lae >= 3, + lae >= 3 && ctpv >= 3, + lae >= 3 && ctpv >= 3 && pbcp >= 3, + ]; +} + +interface ThreeStageStatusPanelProps { + params: DashboardSearchParams | null; +} + +export function ThreeStageStatusPanel({ params }: ThreeStageStatusPanelProps) { + const { dragHandleProps, isDragging } = useDragHandle(); + const { data, isLoading, isFetching } = useDashboardThreeStageStatus(params); + + const checkedList = resolveCheckedSteps(data); + const currentIndex = checkedList.findIndex((v) => !v); + const isAllDone = currentIndex === -1; + const currentStep = STEPS[isAllDone ? STEPS.length - 1 : currentIndex]!; + const currentLabel = isAllDone ? "전 단계 완료" : `${currentStep.label} 대기중`; + + return ( +
+
+

통계 처리 현황

+ + {currentLabel} + +
+ +
+
+
+ {STEPS.map((step, index) => { + const isChecked = checkedList[index]; + const isCurrent = index === currentIndex; + const isLast = index === STEPS.length - 1; + const lineFilled = checkedList[index]; + + return ( +
+
+
+ {isCurrent && ( + + )} +
+ {isChecked ? ( + + + + ) : ( + + )} +
+
+ + {!isLast && ( +
+
+
+ )} +
+ +
+

+ {step.label} +

+

+ ({step.subLabel}) +

+
+
+ ); + })} +
+ +
+

+ {isAllDone ? "현재 단계" : "다음 진행 단계"} · {currentStep.label} +

+

+ {isAllDone + ? "모든 절차가 완료되었습니다." + : currentStep.description} +

+
+
+ + {(isLoading || isFetching) && ( +
+
+ +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b95e9068..4cce9a1a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1217,6 +1217,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.20 + decimal.js: + specifier: ^10.6.0 + version: 10.6.0 js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -4886,6 +4889,9 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -10599,6 +10605,8 @@ snapshots: decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} + deep-eql@5.0.2: {} deep-is@0.1.4: {}