관로 분석 재질별, 경과년도&재질별 조회 오류 해결
This commit is contained in:
+5
-2
@@ -42,6 +42,9 @@ const PIPE_ANALYSIS_DETAIL_EXPORT_KEYS = [
|
||||
"materialName",
|
||||
] as const;
|
||||
|
||||
const resolveErrorMessage = (error: unknown, fallbackMessage: string) =>
|
||||
error instanceof Error && error.message.trim() ? error.message : fallbackMessage;
|
||||
|
||||
export default function PipeAnalysisDetailPopupPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const { ctpv, sgg, fetchCtpv, fetchSgg } = useStore();
|
||||
@@ -118,7 +121,7 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
setCurrentPage(page);
|
||||
} catch (error) {
|
||||
console.error("관로현황 조회 중 오류:", error);
|
||||
alert("관로현황 조회 중 오류가 발생했습니다.");
|
||||
alert(resolveErrorMessage(error, "관로현황 조회 중 오류가 발생했습니다."));
|
||||
setRows([]);
|
||||
setTotal(0);
|
||||
} finally {
|
||||
@@ -188,7 +191,7 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("관로현황 다운로드 중 오류:", error);
|
||||
alert("관로현황 다운로드 중 오류가 발생했습니다.");
|
||||
alert(resolveErrorMessage(error, "관로현황 다운로드 중 오류가 발생했습니다."));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button, Table, TableBody, TableCell, TableHeader, TableHead, TableRow } from "@common/components";
|
||||
import { UiTable, downloadExcelWithKoreanHeaders } from "@common/business_components/ui";
|
||||
import UiPageNations from "@common/business_components/ui/table/uiPageNations";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import useStore from "@/store/useStore";
|
||||
@@ -32,6 +33,7 @@ const SUMMARY_FILE_PREFIX: Record<string, string> = {
|
||||
MATERIAL: "관로분석_관재질별",
|
||||
YEAR_MATERIAL: "관로분석_경과년도별_관재질별",
|
||||
};
|
||||
const PIPE_ANALYSIS_SUMMARY_PAGE_SIZE = 10;
|
||||
|
||||
const toExcelColumnName = (index: number) => {
|
||||
let current = index;
|
||||
@@ -57,6 +59,9 @@ const formatGridNumber = (value: unknown) => {
|
||||
});
|
||||
};
|
||||
|
||||
const resolveErrorMessage = (error: unknown, fallbackMessage: string) =>
|
||||
error instanceof Error && error.message.trim() ? error.message : fallbackMessage;
|
||||
|
||||
export default function PipeAnalysisPopupPage() {
|
||||
const { ctpv, sgg, fetchCtpv, fetchSgg } = useStore();
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
@@ -70,6 +75,7 @@ export default function PipeAnalysisPopupPage() {
|
||||
const [materialColumns, setMaterialColumns] = useState<PipeAnalysisMaterialColumn[]>([]);
|
||||
const [rows, setRows] = useState<PipeAnalysisSummaryViewRow[]>([]);
|
||||
const [zoneOptions, setZoneOptions] = useState<Array<{ value: string; label: string }>>([]);
|
||||
const [summaryCurrentPage, setSummaryCurrentPage] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const initializedRef = useRef(false);
|
||||
|
||||
@@ -138,10 +144,11 @@ export default function PipeAnalysisPopupPage() {
|
||||
const scopedFilters = applyScopeToSummaryFilters(nextFilters, scope);
|
||||
setDraftFilters(scopedFilters);
|
||||
setAppliedFilters(scopedFilters);
|
||||
setSummaryCurrentPage(1);
|
||||
await fetchSummary(scopedFilters);
|
||||
} catch (error) {
|
||||
console.error("관로분석 집계 조회 중 오류:", error);
|
||||
alert("관로분석 집계 조회 중 오류가 발생했습니다.");
|
||||
alert(resolveErrorMessage(error, "관로분석 집계 조회 중 오류가 발생했습니다."));
|
||||
setRows([]);
|
||||
setMaterialColumns([]);
|
||||
} finally {
|
||||
@@ -216,7 +223,7 @@ export default function PipeAnalysisPopupPage() {
|
||||
return exportRow;
|
||||
});
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: exportRows as JSON[],
|
||||
data: exportRows as unknown as JSON[],
|
||||
fileName: `${SUMMARY_FILE_PREFIX[appliedFilters.analysisType]}_${buildPipeAnalysisFileDate()}`,
|
||||
sheetNameArray: ["관로분석 현황"],
|
||||
headerRange,
|
||||
@@ -225,7 +232,7 @@ export default function PipeAnalysisPopupPage() {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("관로분석 집계 다운로드 중 오류:", error);
|
||||
alert("관로분석 집계 다운로드 중 오류가 발생했습니다.");
|
||||
alert(resolveErrorMessage(error, "관로분석 집계 다운로드 중 오류가 발생했습니다."));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -280,6 +287,25 @@ export default function PipeAnalysisPopupPage() {
|
||||
);
|
||||
|
||||
const useHorizontalScrollTable = appliedFilters.analysisType !== "YEAR";
|
||||
const summaryPageOffset = (summaryCurrentPage - 1) * PIPE_ANALYSIS_SUMMARY_PAGE_SIZE;
|
||||
const pagedRows = useMemo(
|
||||
() =>
|
||||
appliedFilters.analysisType === "YEAR_MATERIAL"
|
||||
? rows
|
||||
: rows.slice(summaryPageOffset, summaryPageOffset + PIPE_ANALYSIS_SUMMARY_PAGE_SIZE),
|
||||
[appliedFilters.analysisType, rows, summaryPageOffset],
|
||||
);
|
||||
const visibleYearMaterialGroups = useMemo(
|
||||
() =>
|
||||
yearMaterialGroups.slice(
|
||||
summaryPageOffset,
|
||||
summaryPageOffset + PIPE_ANALYSIS_SUMMARY_PAGE_SIZE,
|
||||
),
|
||||
[summaryPageOffset, yearMaterialGroups],
|
||||
);
|
||||
const summaryTotalCount = appliedFilters.analysisType === "YEAR_MATERIAL"
|
||||
? yearMaterialGroups.length
|
||||
: rows.length;
|
||||
|
||||
const sggOptions = useMemo(
|
||||
() =>
|
||||
@@ -410,11 +436,8 @@ export default function PipeAnalysisPopupPage() {
|
||||
{appliedFilters.analysisType === "YEAR_MATERIAL" ? (
|
||||
<>
|
||||
<div className="card-header">
|
||||
<h3 className="hidden">
|
||||
경과년도&재질 관로현황 총 {yearMaterialGroups.length.toLocaleString("ko-KR")}건
|
||||
</h3>
|
||||
<h3 className="text-2xl font-medium text-label">
|
||||
{resolvePipeAnalysisTypeLabel(appliedFilters.analysisType)} 관로현황 총 {yearMaterialGroups.length.toLocaleString("ko-KR")}건
|
||||
{resolvePipeAnalysisTypeLabel(appliedFilters.analysisType)} 관로현황 {summaryTotalCount.toLocaleString("ko-KR")}건
|
||||
</h3>
|
||||
</div>
|
||||
<div className="card !p-0r !m-[1.2rem] overflow-x-auto">
|
||||
@@ -434,8 +457,8 @@ export default function PipeAnalysisPopupPage() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="text-center">
|
||||
{yearMaterialGroups.length > 0 ? (
|
||||
yearMaterialGroups.flatMap((group) =>
|
||||
{visibleYearMaterialGroups.length > 0 ? (
|
||||
visibleYearMaterialGroups.flatMap((group) =>
|
||||
group.rows.map((yearRow, rowIndex) => (
|
||||
<TableRow key={`${group.base.ctpvCd}-${group.base.sggCd}-${group.base.swtrmtZoneNm}-${yearRow.elapsedYearBucket}`}>
|
||||
{rowIndex === 0 ? (
|
||||
@@ -492,6 +515,16 @@ export default function PipeAnalysisPopupPage() {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{yearMaterialGroups.length > PIPE_ANALYSIS_SUMMARY_PAGE_SIZE ? (
|
||||
<div className="flex justify-center">
|
||||
<UiPageNations
|
||||
total={yearMaterialGroups.length}
|
||||
cur={summaryCurrentPage}
|
||||
pageSize={PIPE_ANALYSIS_SUMMARY_PAGE_SIZE}
|
||||
onPageChange={setSummaryCurrentPage}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -503,17 +536,22 @@ export default function PipeAnalysisPopupPage() {
|
||||
: ""}
|
||||
headName={
|
||||
appliedFilters.analysisType === "YEAR_MATERIAL"
|
||||
? "경과년도&재질 관로현황"
|
||||
: "관로분석현황"
|
||||
? `경과년도&재질 관로현황 ${summaryTotalCount.toLocaleString("ko-KR")}건`
|
||||
: `관로분석현황 ${summaryTotalCount.toLocaleString("ko-KR")}건`
|
||||
}
|
||||
tableData={rows}
|
||||
tableData={pagedRows}
|
||||
headlist={headList}
|
||||
pageSize={Math.max(rows.length, 1)}
|
||||
pageSize={PIPE_ANALYSIS_SUMMARY_PAGE_SIZE}
|
||||
total={rows.length}
|
||||
cellClick={(index, columnId) => handleCellClick(index, columnId)}
|
||||
onPageChange={() => undefined}
|
||||
curPage={1}
|
||||
cellClick={(index, columnId) =>
|
||||
handleCellClick(
|
||||
typeof index === "number" ? index + summaryPageOffset : index,
|
||||
columnId,
|
||||
)}
|
||||
onPageChange={setSummaryCurrentPage}
|
||||
curPage={summaryCurrentPage}
|
||||
widthFixed={!useHorizontalScrollTable}
|
||||
isHideCount={true}
|
||||
cellClassName={(row, columnId) =>
|
||||
!row.__isNoZone && row.__cellFilters[columnId]
|
||||
? "cursor-pointer font-semibold text-blue-700 underline underline-offset-2"
|
||||
|
||||
@@ -29,6 +29,28 @@ const withSiteIdHeaders = (headers: HeadersInit = {}) => ({
|
||||
const resolveApiUrl = (url: string) =>
|
||||
url.startsWith('/') ? `${url}` : `${BASE_URL}/${url}`;
|
||||
|
||||
const buildRequestError = (
|
||||
fallbackMessage: string,
|
||||
status?: number,
|
||||
body?: unknown,
|
||||
) => {
|
||||
const bodyMessage =
|
||||
typeof body === "object" && body !== null
|
||||
? (
|
||||
(body as { message?: unknown }).message
|
||||
?? (body as { error?: { message?: unknown } }).error?.message
|
||||
)
|
||||
: undefined;
|
||||
const message =
|
||||
typeof bodyMessage === "string" && bodyMessage.trim()
|
||||
? bodyMessage
|
||||
: fallbackMessage;
|
||||
const error = new Error(message) as Error & { status?: number; body?: unknown };
|
||||
error.status = status;
|
||||
error.body = body;
|
||||
return error;
|
||||
};
|
||||
|
||||
const handleUnauthorizedResponse = () => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
@@ -79,7 +101,22 @@ export const getData = async (url: string): Promise<any> => {
|
||||
throw new Error('Invalid JSON response from server');
|
||||
}
|
||||
}
|
||||
throw new Error('Failed to fetch data');
|
||||
|
||||
const text = await res.text().catch(() => "");
|
||||
let body: unknown = null;
|
||||
if (text.trim()) {
|
||||
try {
|
||||
body = JSON.parse(text);
|
||||
} catch {
|
||||
body = text;
|
||||
}
|
||||
}
|
||||
|
||||
throw buildRequestError(
|
||||
`Failed to fetch data: ${res.status} ${res.statusText}`,
|
||||
res.status,
|
||||
body,
|
||||
);
|
||||
};
|
||||
|
||||
export const postData = async (url: string, data: object): Promise<any> => {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# SFR-011-002 관로분석 FE 통합검증결과
|
||||
|
||||
## 1. 검증 목적
|
||||
- 관로분석 프론트 변경이 오류 전달 구조를 깨지 않고 유지되는지 확인한다.
|
||||
|
||||
## 2. 수행한 검증
|
||||
- 코드 리뷰로 `PipeAnalysisPopupPage`, `PipeAnalysisDetailPopupPage`, `getData`의 예외 흐름을 확인했다.
|
||||
- `pnpm --filter sn exec tsc --noEmit`를 실행했다.
|
||||
|
||||
## 3. 검증 결과
|
||||
- `PipeAnalysisPopupPage.tsx`의 엑셀 다운로드 캐스팅 경고는 수정 후 해소했다.
|
||||
- 집계/상세 팝업은 이제 `Error.message`가 존재하면 공통 경고문 대신 해당 메시지를 노출한다.
|
||||
- 관로분석 집계 헤더의 `총 N건` 문구가 제거됐다.
|
||||
- 집계 팝업은 10개 단위 페이지 렌더링으로 바뀌어 한 화면에 그리는 행 수를 제한한다.
|
||||
- 전체 TypeScript 검증은 저장소 기존 오류로 실패했다.
|
||||
|
||||
## 4. 기존 오류 메모
|
||||
- `app/(map-layout)/map/components/common/index.ts` 타입 불일치
|
||||
- `app/html/(with-base-layout)/layout.tsx` import 누락
|
||||
- `packages/common/business_components/src/ui/chart/*` implicit any
|
||||
|
||||
## 5. 미수행 검증
|
||||
- 로컬 브라우저에서 실제 관로분석 팝업을 열어 세 분석구분을 재조회하는 수동 검증은 이번 턴에서 수행하지 못했다.
|
||||
|
||||
## 6. 판단
|
||||
- 프론트 수정 범위 내에서 신규 타입 오류를 만들지는 않았다.
|
||||
- 실제 장애 해소 여부의 최종 판정은 백엔드 반영본과 함께 브라우저 재조회 확인이 필요하다.
|
||||
@@ -0,0 +1,37 @@
|
||||
# SFR-011-002 관로분석 FE 통합구현기록
|
||||
|
||||
## 1. 문서 목적
|
||||
- 관로분석 팝업 유지보수 과정에서 프론트에 반영한 오류 처리 변경을 기록한다.
|
||||
|
||||
## 2. 유지보수 배경
|
||||
- `경과년도별 조회`는 정상 동작하지만 `관재질별 조회`, `경과년도 & 관재질별 조회`는 집계 조회 실패 시점에 프론트가 `Failed to fetch data`만 표시해 실제 원인 파악이 어려웠다.
|
||||
|
||||
## 3. 수정 파일
|
||||
- `FE_APP_ROOT/app/components/lib/api/index.ts`
|
||||
- `FE_APP_ROOT/app/(map-layout)/map/components/pipeAnalysis/PipeAnalysisPopupPage.tsx`
|
||||
- `FE_APP_ROOT/app/(map-layout)/map/components/pipeAnalysis/PipeAnalysisDetailPopupPage.tsx`
|
||||
|
||||
## 4. 구현 내용
|
||||
- `getData`
|
||||
- 비정상 응답 본문을 읽어 JSON 또는 text로 보존한다.
|
||||
- 응답 body의 `message` 또는 중첩 error message가 있으면 그 값을 `Error.message`로 사용한다.
|
||||
- fallback에는 HTTP status와 statusText를 포함한다.
|
||||
- `PipeAnalysisPopupPage`
|
||||
- 집계 조회/다운로드 실패 시 실제 `Error.message`를 우선 alert에 노출한다.
|
||||
- 엑셀 다운로드 data 캐스팅을 `unknown as JSON[]`로 조정했다.
|
||||
- 집계 헤더의 `총 N건` 문구를 제거했다.
|
||||
- 집계 결과는 10개 단위 클라이언트 페이징으로 잘라 렌더링한다.
|
||||
- `경과년도&재질`은 rowSpan 구조를 유지하기 위해 처리구역 10묶음 기준으로 페이지를 나눈다.
|
||||
- 모든 분석구분 헤더에 현재 집계 건수를 `N건` 형식으로 노출한다.
|
||||
- `PipeAnalysisDetailPopupPage`
|
||||
- 상세 조회/다운로드 실패 시 실제 `Error.message`를 우선 alert에 노출한다.
|
||||
|
||||
## 5. 영향 범위
|
||||
- 관로분석 팝업 집계 조회
|
||||
- 관로분석 상세 팝업 조회
|
||||
- 동일 `getData` 유틸을 사용하는 다른 화면의 오류 메시지 표현
|
||||
- 재질별/경과년도&재질별 요약 렌더링 성능
|
||||
|
||||
## 6. 리스크 및 메모
|
||||
- `getData` 공통 유틸 수정이라 다른 화면에서도 이제 서버 메시지가 더 직접적으로 노출된다.
|
||||
- 서버 메시지 문구가 사용자 친화적이지 않은 API는 추후 별도 화면 단에서 다듬을 수 있다.
|
||||
Reference in New Issue
Block a user