[st] feat: 입력-지난검증결과 수정 및 컬럼 툴팁 정보 추가, 검토 - 2,6 메뉴 작업중, 관리 - 입력관리 작업중
This commit is contained in:
@@ -71,6 +71,38 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function realGridHeaderWithTooltip<T extends { text: string; tooltip?: string; showTooltip?: boolean; template?: string }>(
|
||||
header: T,
|
||||
): T {
|
||||
const tip = header.tooltip?.trim();
|
||||
if (!tip) return header;
|
||||
return {
|
||||
...header,
|
||||
tooltip: tip,
|
||||
showTooltip: header.showTooltip ?? true,
|
||||
template:
|
||||
header.template ??
|
||||
`<span style="display:inline-flex;align-items:center;justify-content:center;gap:4px;width:100%;"><span>${escapeHtml(header.text)}</span><i class="diveicon di-info-circle" aria-hidden="true"></i></span>`,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveColumnTooltip(column: {
|
||||
tooltip?: string;
|
||||
header: { text: string; tooltip?: string; showTooltip?: boolean; template?: string };
|
||||
}): string | undefined {
|
||||
const tip = column.tooltip?.trim() || column.header.tooltip?.trim();
|
||||
return tip && tip.length > 0 ? tip : undefined;
|
||||
}
|
||||
|
||||
const CompactDataGrid = ({
|
||||
columnInfo,
|
||||
fixedColumns,
|
||||
@@ -88,8 +120,10 @@ const CompactDataGrid = ({
|
||||
}: DataGridProps) => {
|
||||
const { columns, columnLayout } = columnInfo;
|
||||
const gridRef = useRef<RealGridReact>(null);
|
||||
const columnsRef = useRef(columns);
|
||||
const rowDataRef = useRef<Record<string, string | number>[]>(rowData);
|
||||
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(cellStyleCallback);
|
||||
columnsRef.current = columns;
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
|
||||
@@ -155,6 +189,66 @@ const CompactDataGrid = ({
|
||||
});
|
||||
|
||||
gridView.header.height = 60;
|
||||
if (columns.some((c) => Boolean(resolveColumnTooltip(c)))) {
|
||||
(gridView.header as { showTooltip?: boolean }).showTooltip = true;
|
||||
}
|
||||
columns.forEach((column) => {
|
||||
(
|
||||
gridView as {
|
||||
setColumnProperty?: (
|
||||
col: string,
|
||||
key: string,
|
||||
value: unknown,
|
||||
) => void;
|
||||
}
|
||||
).setColumnProperty?.(
|
||||
column.name,
|
||||
"header",
|
||||
realGridHeaderWithTooltip({
|
||||
...column.header,
|
||||
tooltip: resolveColumnTooltip(column),
|
||||
}),
|
||||
);
|
||||
});
|
||||
(
|
||||
gridView as {
|
||||
onShowHeaderTooltip?: (
|
||||
grid: unknown,
|
||||
column: {
|
||||
name?: string;
|
||||
fieldName?: string;
|
||||
header?: { tooltip?: string };
|
||||
},
|
||||
value: string,
|
||||
layout?: {
|
||||
name?: string;
|
||||
fieldName?: string;
|
||||
column?: string | { name?: string; fieldName?: string };
|
||||
},
|
||||
isEllipsis?: boolean,
|
||||
) => string | undefined;
|
||||
}
|
||||
).onShowHeaderTooltip = (_grid, headerColumn, _value, layout) => {
|
||||
const currentColumns = columnsRef.current;
|
||||
const directTip = headerColumn?.header?.tooltip?.trim();
|
||||
if (directTip) return directTip;
|
||||
|
||||
const layoutColumn =
|
||||
typeof layout?.column === "string"
|
||||
? layout.column
|
||||
: layout?.column?.name ?? layout?.column?.fieldName;
|
||||
|
||||
const col = currentColumns.find(
|
||||
(c) =>
|
||||
c.name === headerColumn?.name ||
|
||||
c.fieldName === headerColumn?.fieldName ||
|
||||
c.name === layout?.name ||
|
||||
c.fieldName === layout?.fieldName ||
|
||||
c.name === layoutColumn ||
|
||||
c.fieldName === layoutColumn,
|
||||
);
|
||||
return col ? resolveColumnTooltip(col) : undefined;
|
||||
};
|
||||
|
||||
// 헤더 체크박스 표시 여부 설정
|
||||
gridView.setOptions({
|
||||
@@ -235,7 +329,10 @@ const CompactDataGrid = ({
|
||||
const colInfo: RealGridColumnInfo = {
|
||||
name: col.name,
|
||||
fieldName: col.fieldName,
|
||||
header: col.header,
|
||||
header: realGridHeaderWithTooltip({
|
||||
...col.header,
|
||||
tooltip: resolveColumnTooltip(col),
|
||||
}),
|
||||
editable: col.editable,
|
||||
valueType: col.valueType,
|
||||
dataFormat: col.dataFormat,
|
||||
@@ -427,7 +524,10 @@ const CompactDataGrid = ({
|
||||
fieldName={column.fieldName}
|
||||
valueType={column.valueType ? column.valueType : ValueType.TEXT}
|
||||
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })}
|
||||
header={column.header}
|
||||
header={realGridHeaderWithTooltip({
|
||||
...column.header,
|
||||
tooltip: resolveColumnTooltip(column),
|
||||
})}
|
||||
editable={column.editable}
|
||||
{...(column.styleName != null
|
||||
? { styleName: column.styleName }
|
||||
|
||||
@@ -81,6 +81,42 @@ function createNumberComparer(
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* RealGrid 헤더 툴팁: `grid.header.showTooltip`이 비어 있으면 `column.header.showTooltip === true`인 컬럼만 표시됨.
|
||||
* `tooltip`이 있는 헤더는 info-circle 아이콘이 함께 보이도록 template도 자동 구성한다.
|
||||
*/
|
||||
function realGridHeaderWithTooltip<T extends { text: string; tooltip?: string; showTooltip?: boolean; template?: string }>(
|
||||
header: T,
|
||||
): T {
|
||||
const tip = header.tooltip?.trim();
|
||||
if (!tip) return header;
|
||||
return {
|
||||
...header,
|
||||
tooltip: tip,
|
||||
showTooltip: header.showTooltip ?? true,
|
||||
template:
|
||||
header.template ??
|
||||
`<span style="display:inline-flex;align-items:center;justify-content:center;gap:4px;width:100%;"><span>${escapeHtml(header.text)}</span><i class="diveicon di-info-circle" aria-hidden="true"></i></span>`,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveColumnTooltip(column: {
|
||||
tooltip?: string;
|
||||
header: { text: string; tooltip?: string; showTooltip?: boolean; template?: string };
|
||||
}): string | undefined {
|
||||
const tip = column.tooltip?.trim() || column.header.tooltip?.trim();
|
||||
return tip && tip.length > 0 ? tip : undefined;
|
||||
}
|
||||
|
||||
/** 정렬 필드 값을 숫자로 파싱 (쉼표 제거) */
|
||||
function toNum(v: unknown): number {
|
||||
if (v == null) return NaN;
|
||||
@@ -354,6 +390,7 @@ const DataGrid = ({
|
||||
const { columns, columnLayout } = columnInfo;
|
||||
const gridRef = useRef<RealGridReact>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const columnsRef = useRef(columns);
|
||||
const sortDirRef = useRef<Record<string, "asc" | "desc" | "none">>({});
|
||||
const originalRowsRef = useRef<
|
||||
Record<string, Record<string, string | number>[]>
|
||||
@@ -372,6 +409,7 @@ const DataGrid = ({
|
||||
const onItemCheckedRef = useRef<typeof onItemChecked>(onItemChecked);
|
||||
const onItemAllCheckedRef =
|
||||
useRef<typeof onItemAllChecked>(onItemAllChecked);
|
||||
columnsRef.current = columns;
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
cellTooltipCallbackRef.current = cellTooltipCallback;
|
||||
@@ -813,6 +851,66 @@ const DataGrid = ({
|
||||
GridFitStyle.EVEN;
|
||||
gridView.displayOptions.rowHeight = 30;
|
||||
gridView.header.height = 60;
|
||||
if (columns.some((c) => Boolean(resolveColumnTooltip(c)))) {
|
||||
(gridView.header as { showTooltip?: boolean }).showTooltip = true;
|
||||
}
|
||||
columns.forEach((column) => {
|
||||
(
|
||||
gridView as {
|
||||
setColumnProperty?: (
|
||||
col: string,
|
||||
key: string,
|
||||
value: unknown,
|
||||
) => void;
|
||||
}
|
||||
).setColumnProperty?.(
|
||||
column.name,
|
||||
"header",
|
||||
realGridHeaderWithTooltip({
|
||||
...column.header,
|
||||
tooltip: resolveColumnTooltip(column),
|
||||
}),
|
||||
);
|
||||
});
|
||||
(
|
||||
gridView as {
|
||||
onShowHeaderTooltip?: (
|
||||
grid: unknown,
|
||||
column: {
|
||||
name?: string;
|
||||
fieldName?: string;
|
||||
header?: { tooltip?: string };
|
||||
},
|
||||
value: string,
|
||||
layout?: {
|
||||
name?: string;
|
||||
fieldName?: string;
|
||||
column?: string | { name?: string; fieldName?: string };
|
||||
},
|
||||
isEllipsis?: boolean,
|
||||
) => string | undefined;
|
||||
}
|
||||
).onShowHeaderTooltip = (_grid, headerColumn, _value, layout) => {
|
||||
const currentColumns = columnsRef.current;
|
||||
const directTip = headerColumn?.header?.tooltip?.trim();
|
||||
if (directTip) return directTip;
|
||||
|
||||
const layoutColumn =
|
||||
typeof layout?.column === "string"
|
||||
? layout.column
|
||||
: layout?.column?.name ?? layout?.column?.fieldName;
|
||||
|
||||
const col = currentColumns.find(
|
||||
(c) =>
|
||||
c.name === headerColumn?.name ||
|
||||
c.fieldName === headerColumn?.fieldName ||
|
||||
c.name === layout?.name ||
|
||||
c.fieldName === layout?.fieldName ||
|
||||
c.name === layoutColumn ||
|
||||
c.fieldName === layoutColumn,
|
||||
);
|
||||
return col ? resolveColumnTooltip(col) : undefined;
|
||||
};
|
||||
|
||||
// 헤더 체크박스 표시 여부 설정
|
||||
gridView.setOptions({
|
||||
@@ -917,7 +1015,7 @@ const DataGrid = ({
|
||||
const colInfo: RealGridColumnInfo = {
|
||||
name: col.name,
|
||||
fieldName: col.fieldName,
|
||||
header: col.header,
|
||||
header: realGridHeaderWithTooltip(col.header),
|
||||
editable: col.editable,
|
||||
valueType: col.valueType,
|
||||
dataFormat: col.dataFormat,
|
||||
@@ -1334,6 +1432,48 @@ const DataGrid = ({
|
||||
setTimeout(() => {
|
||||
const gv = gridRef.current?.gridView;
|
||||
if (!gv?.columnByName) return;
|
||||
// 공식 문서: 헤더 툴팁 = header.showTooltip + onShowHeaderTooltip
|
||||
(
|
||||
gv as {
|
||||
onShowHeaderTooltip?: (
|
||||
grid: unknown,
|
||||
column: {
|
||||
name?: string;
|
||||
fieldName?: string;
|
||||
header?: {
|
||||
tooltip?: string;
|
||||
};
|
||||
},
|
||||
value: string,
|
||||
layout?: {
|
||||
name?: string;
|
||||
fieldName?: string;
|
||||
column?: string | { name?: string; fieldName?: string };
|
||||
},
|
||||
isEllipsis?: boolean,
|
||||
) => string | undefined;
|
||||
}
|
||||
).onShowHeaderTooltip = (_grid, headerColumn, _value, layout) => {
|
||||
const currentColumns = columnsRef.current;
|
||||
const directTip = headerColumn?.header?.tooltip?.trim();
|
||||
if (directTip) return directTip;
|
||||
|
||||
const layoutColumn =
|
||||
typeof layout?.column === "string"
|
||||
? layout.column
|
||||
: layout?.column?.name ?? layout?.column?.fieldName;
|
||||
|
||||
const col = currentColumns.find(
|
||||
(c) =>
|
||||
c.name === headerColumn?.name ||
|
||||
c.fieldName === headerColumn?.fieldName ||
|
||||
c.name === layout?.name ||
|
||||
c.fieldName === layout?.fieldName ||
|
||||
c.name === layoutColumn ||
|
||||
c.fieldName === layoutColumn,
|
||||
);
|
||||
return col ? resolveColumnTooltip(col) : undefined;
|
||||
};
|
||||
// 공식 문서: 데이터 영역 툴팁 = renderer.showTooltip + onShowTooltip (displayOptions는 Ellipsis용이라 사용 안 함)
|
||||
if (cellTooltipCallbackRef.current) {
|
||||
(
|
||||
@@ -1387,6 +1527,22 @@ const DataGrid = ({
|
||||
renderer?: { showTooltip?: boolean };
|
||||
} | null;
|
||||
if (gridCol) {
|
||||
(
|
||||
gv as {
|
||||
setColumnProperty?: (
|
||||
col: string,
|
||||
key: string,
|
||||
value: unknown,
|
||||
) => void;
|
||||
}
|
||||
).setColumnProperty?.(
|
||||
column.name,
|
||||
"header",
|
||||
realGridHeaderWithTooltip({
|
||||
...column.header,
|
||||
tooltip: resolveColumnTooltip(column),
|
||||
}),
|
||||
);
|
||||
if (cellTooltipCallbackRef.current) {
|
||||
(
|
||||
gv as {
|
||||
@@ -1456,7 +1612,10 @@ const DataGrid = ({
|
||||
? column.dataFormat
|
||||
: "#,##0.000",
|
||||
})}
|
||||
header={column.header}
|
||||
header={realGridHeaderWithTooltip({
|
||||
...column.header,
|
||||
tooltip: resolveColumnTooltip(column),
|
||||
})}
|
||||
editable={column.editable}
|
||||
{...(column.width != null && {
|
||||
width: column.width,
|
||||
|
||||
@@ -13,6 +13,10 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
LoadingWithAnimation,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@common/components";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
@@ -20,6 +24,13 @@ import { VerificationResultHistoryPagination } from "@/components/module/Verific
|
||||
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
|
||||
type VerificationResultGridRow = Record<string, string | number>;
|
||||
type VerificationHistoryTabKey = "NV" | "IMR" | "HR";
|
||||
|
||||
const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string }[] = [
|
||||
{ value: "NV", label: "일반검증 내역" },
|
||||
{ value: "IMR", label: "I-MR 검증내역" },
|
||||
{ value: "HR", label: "HR 검증내역" },
|
||||
];
|
||||
|
||||
function prcsYnCodeToNumber(raw: string | number | undefined): number | undefined {
|
||||
if (raw === undefined || raw === null) return undefined;
|
||||
@@ -76,30 +87,40 @@ export function VerificationResultHistoryDialog({
|
||||
title = "검증 결과 내역",
|
||||
emptyMessage = "표시할 검증 결과가 없습니다.",
|
||||
}: VerificationResultHistoryDialogProps) {
|
||||
const [activeTab, setActiveTab] = useState<VerificationHistoryTabKey>("NV");
|
||||
const [normalEntries, setNormalEntries] = useState<VerificationResultReportItem[]>([]);
|
||||
const [imrEntries, setImrEntries] = useState<VerificationResultReportItem[]>([]);
|
||||
const [hrEntries, setHrEntries] = useState<VerificationResultReportItem[]>([]);
|
||||
const [normalPageIndex, setNormalPageIndex] = useState(0);
|
||||
const [imrPageIndex, setImrPageIndex] = useState(0);
|
||||
const [hrPageIndex, setHrPageIndex] = useState(0);
|
||||
const [normalPageCount, setNormalPageCount] = useState(1);
|
||||
const [imrPageCount, setImrPageCount] = useState(1);
|
||||
const [hrPageCount, setHrPageCount] = useState(1);
|
||||
const [normalLoading, setNormalLoading] = useState(false);
|
||||
const [imrLoading, setImrLoading] = useState(false);
|
||||
const [hrLoading, setHrLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setActiveTab("NV");
|
||||
setNormalEntries([]);
|
||||
setImrEntries([]);
|
||||
setHrEntries([]);
|
||||
setNormalPageIndex(0);
|
||||
setImrPageIndex(0);
|
||||
setHrPageIndex(0);
|
||||
setNormalPageCount(1);
|
||||
setImrPageCount(1);
|
||||
setHrPageCount(1);
|
||||
setNormalLoading(false);
|
||||
setImrLoading(false);
|
||||
setHrLoading(false);
|
||||
setErrorMessage(null);
|
||||
}, []);
|
||||
|
||||
const fetchVerificationResultPage = useCallback(
|
||||
async (vrfcMthdCd: "NV" | "IMR", pageIndex: number) => {
|
||||
async (vrfcMthdCd: VerificationHistoryTabKey, pageIndex: number) => {
|
||||
if (!queryContext) return null;
|
||||
const param: VerificationResultListRequest = {
|
||||
sewerBzmnCd: queryContext.sewerBzmnCd,
|
||||
@@ -126,26 +147,34 @@ export function VerificationResultHistoryDialog({
|
||||
setErrorMessage(null);
|
||||
setNormalLoading(true);
|
||||
setImrLoading(true);
|
||||
setHrLoading(true);
|
||||
setNormalPageIndex(0);
|
||||
setImrPageIndex(0);
|
||||
setHrPageIndex(0);
|
||||
try {
|
||||
const [nvRes, imrRes] = await Promise.all([
|
||||
const [nvRes, imrRes, hrRes] = await Promise.all([
|
||||
fetchVerificationResultPage("NV", 0),
|
||||
fetchVerificationResultPage("IMR", 0),
|
||||
fetchVerificationResultPage("HR", 0),
|
||||
]);
|
||||
setNormalEntries(nvRes?.data?.verifyResultList ?? []);
|
||||
setImrEntries(imrRes?.data?.verifyResultList ?? []);
|
||||
setHrEntries(hrRes?.data?.verifyResultList ?? []);
|
||||
setNormalPageCount(nvRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageCount(imrRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
setHrPageCount(hrRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
} catch {
|
||||
setNormalEntries([]);
|
||||
setImrEntries([]);
|
||||
setHrEntries([]);
|
||||
setNormalPageCount(1);
|
||||
setImrPageCount(1);
|
||||
setHrPageCount(1);
|
||||
setErrorMessage("검증 결과 내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setNormalLoading(false);
|
||||
setImrLoading(false);
|
||||
setHrLoading(false);
|
||||
}
|
||||
}, [fetchVerificationResultPage, queryContext]);
|
||||
|
||||
@@ -187,6 +216,25 @@ export function VerificationResultHistoryDialog({
|
||||
[fetchVerificationResultPage, imrPageIndex, queryContext],
|
||||
);
|
||||
|
||||
const handleHrPageChange = useCallback(
|
||||
async (pageIndex: number) => {
|
||||
if (pageIndex === hrPageIndex || !queryContext) return;
|
||||
setErrorMessage(null);
|
||||
setHrLoading(true);
|
||||
try {
|
||||
const response = await fetchVerificationResultPage("HR", pageIndex);
|
||||
setHrEntries(response?.data?.verifyResultList ?? []);
|
||||
setHrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setHrPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("HR 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setHrLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchVerificationResultPage, hrPageIndex, queryContext],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
resetState();
|
||||
@@ -212,6 +260,7 @@ export function VerificationResultHistoryDialog({
|
||||
|
||||
const normalRows = useMemo(() => mapRows(normalEntries), [normalEntries]);
|
||||
const imrRows = useMemo(() => mapRows(imrEntries), [imrEntries]);
|
||||
const hrRows = useMemo(() => mapRows(hrEntries), [hrEntries]);
|
||||
|
||||
const noopSetRows = ((_: SetStateAction<Record<string, string | number>[]>) => {}) as Dispatch<
|
||||
SetStateAction<Record<string, string | number>[]>
|
||||
@@ -269,14 +318,24 @@ export function VerificationResultHistoryDialog({
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
<section className="rounded-md border border-border">
|
||||
<h3 className="border-b border-border px-4 py-2 text-[1.35rem] font-semibold">일반검증 내역</h3>
|
||||
{renderGrid(normalRows, normalPageIndex, normalPageCount, handleNormalPageChange, normalLoading)}
|
||||
</section>
|
||||
<section className="rounded-md border border-border">
|
||||
<h3 className="border-b border-border px-4 py-2 text-[1.35rem] font-semibold">I-MR 검증내역</h3>
|
||||
{renderGrid(imrRows, imrPageIndex, imrPageCount, handleImrPageChange, imrLoading)}
|
||||
</section>
|
||||
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as VerificationHistoryTabKey)} className="w-full" variant="default">
|
||||
<TabsList>
|
||||
{verificationHistoryTabs.map((tab) => (
|
||||
<TabsTrigger key={tab.value} value={tab.value}>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<TabsContent value="NV" className="rounded-md border border-border">
|
||||
{renderGrid(normalRows, normalPageIndex, normalPageCount, handleNormalPageChange, normalLoading)}
|
||||
</TabsContent>
|
||||
<TabsContent value="IMR" className="rounded-md border border-border">
|
||||
{renderGrid(imrRows, imrPageIndex, imrPageCount, handleImrPageChange, imrLoading)}
|
||||
</TabsContent>
|
||||
<TabsContent value="HR" className="rounded-md border border-border">
|
||||
{renderGrid(hrRows, hrPageIndex, hrPageCount, handleHrPageChange, hrLoading)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useConfirm } from '@frontend-opensource/use-react-hooks';
|
||||
|
||||
@@ -131,6 +131,10 @@ type MainFacilityMappingSaveDraft = {
|
||||
initialPrxyCtrtCn: string;
|
||||
};
|
||||
|
||||
const PUBLIC_SEWAGE_FACILITY_COLUMN_GUIDE_TAB_KEYS = Array.from(
|
||||
new Set(PublicSewageFactInpColumnList.map((tab) => tab.tabKey).filter(Boolean)),
|
||||
);
|
||||
|
||||
// 하수도 통계 관리 > 06. 공공하수처리시설
|
||||
const PublicSewageFacilityInp = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
@@ -149,6 +153,8 @@ const PublicSewageFacilityInp = () => {
|
||||
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatusInfoMap, setTabStatusInfoMap] = useState<Map<string, SewerageStatusInfo | null>>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
/** 탭키별 `engColNm -> columnGuide` 캐시 */
|
||||
const [columnGuidesByTab, setColumnGuidesByTab] = useState<Record<string, Record<string, string>>>({});
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<Map<string, TabToolbarSnapshot>>(() => new Map());
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
|
||||
@@ -489,6 +495,36 @@ const PublicSewageFacilityInp = () => {
|
||||
const firstTabKey = PublicSewageFactInpColumnList[0]?.tabKey ?? 'ST00060001';
|
||||
const searchParamRef = useRef<PublicSewageFacilityRequestParams | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
PUBLIC_SEWAGE_FACILITY_COLUMN_GUIDE_TAB_KEYS.map(async (tabKey) => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(tabKey);
|
||||
if (res.status !== 'success' || !Array.isArray(res.data)) {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [tabKey, next] as const;
|
||||
} catch {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
if (cancelled) return;
|
||||
setColumnGuidesByTab(Object.fromEntries(entries));
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const commitToolbarSnapshotForLoadedTab = (
|
||||
tabKey: string,
|
||||
statusInfo: SewerageStatusInfo | null,
|
||||
@@ -783,7 +819,7 @@ const PublicSewageFacilityInp = () => {
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: lastSearchedValues?.bsnmCd ?? '',
|
||||
ctpvNm: lastSearchedValues?.sido ?? '',
|
||||
sggNm: lastSearchedValues?.gugun ?? '',
|
||||
sggNm: lastSearchedValues?.gugun === 'ALL' ? '' : lastSearchedValues?.gugun ?? '',
|
||||
statsYr: lastSearchedValues?.year ?? '',
|
||||
mdfrId: userInfo.userUnqId,
|
||||
statsCd: statsCdForCancel,
|
||||
@@ -811,8 +847,15 @@ const PublicSewageFacilityInp = () => {
|
||||
|
||||
const getGridColumnInfo = (
|
||||
tab: ColumnInfoList,
|
||||
guideTabKey?: string,
|
||||
): ColumnInfo => ({
|
||||
columns: tab.columns ?? [],
|
||||
columns: (tab.columns ?? []).map((col) => {
|
||||
const guide = columnGuidesByTab[guideTabKey ?? tab.tabKey]?.[col.fieldName]?.trim();
|
||||
return {
|
||||
...col,
|
||||
tooltip: guide && guide.length > 0 ? guide : undefined,
|
||||
};
|
||||
}),
|
||||
columnLayout: tab.columnLayout ?? [],
|
||||
});
|
||||
|
||||
@@ -955,8 +998,8 @@ const PublicSewageFacilityInp = () => {
|
||||
return (
|
||||
<TabsContent key={tab.tabKey} value={tab.tabKey}>
|
||||
<SplitDataGrid
|
||||
leftColumnInfo={getGridColumnInfo(tab.left)}
|
||||
rightColumnInfo={getGridColumnInfo(tab.right)}
|
||||
leftColumnInfo={getGridColumnInfo(tab.left, tab.tabKey)}
|
||||
rightColumnInfo={getGridColumnInfo(tab.right, tab.tabKey)}
|
||||
leftFixedColumns={tab.left.fixedColumns}
|
||||
rightFixedColumns={tab.right.fixedColumns}
|
||||
showHeaderCheckbox={true}
|
||||
|
||||
@@ -192,6 +192,9 @@ type GridRowWithVerification = Record<string, string | number> & {
|
||||
};
|
||||
|
||||
const REGIONAL_FACT_TAB_KEY = "ST00020001";
|
||||
const SEWAGE_PIPE_COLUMN_GUIDE_TAB_KEYS = Array.from(
|
||||
new Set(SewagePipeInpColumnInfoList.map((tab) => tab.tabKey).filter(Boolean)),
|
||||
);
|
||||
|
||||
function hasRegionalFactVerificationErrors(verification?: SeweragePipeInputVerificationResult | null): boolean {
|
||||
if (!verification) return false;
|
||||
@@ -355,6 +358,8 @@ const SewagePipeInp = () => {
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
|
||||
/** 탭키별 `engColNm -> columnGuide` 캐시 */
|
||||
const [columnGuidesByTab, setColumnGuidesByTab] = useState<Record<string, Record<string, string>>>({});
|
||||
/** API 연결 전에도 검색 후 툴바 등 UI를 쓸 수 있게 함 */
|
||||
const [hasSearched, setHasSearched] = useState(false);
|
||||
/** checkInputPeriod — columnInfo useMemo가 입력기간 변경을 반영하도록 state로도 유지 */
|
||||
@@ -417,6 +422,37 @@ const SewagePipeInp = () => {
|
||||
}
|
||||
}, [activeTabId, inputFocusNonce]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE) return;
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const entries = await Promise.all(
|
||||
SEWAGE_PIPE_COLUMN_GUIDE_TAB_KEYS.map(async (tabKey) => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(tabKey);
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
return [tabKey, next] as const;
|
||||
} catch {
|
||||
return [tabKey, {}] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
if (cancelled) return;
|
||||
setColumnGuidesByTab(Object.fromEntries(entries));
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [activeTabId]);
|
||||
|
||||
const syncTopStatusIfStillOnTab = (loadedTabKey: string, nextStatsCd: string) => {
|
||||
if (loadedTabKey === activeTabRef.current) {
|
||||
setStatsCd(nextStatsCd);
|
||||
@@ -445,6 +481,14 @@ const SewagePipeInp = () => {
|
||||
};
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const applyColumnGuides = (cols: ColumnConfig[] | undefined, tabKey: string) =>
|
||||
(cols ?? []).map((col: ColumnConfig) => {
|
||||
const guide = columnGuidesByTab[tabKey]?.[col.fieldName]?.trim();
|
||||
return {
|
||||
...col,
|
||||
tooltip: guide && guide.length > 0 ? guide : undefined,
|
||||
};
|
||||
});
|
||||
return SewagePipeInpColumnInfoList.map((tab: ColumnInfoList) => {
|
||||
const canEdit =
|
||||
computeTabEnterable(tabStatusInfoMap.get(tab.tabKey) ?? null, inputPeriodEnterable) || isAdminInfo;
|
||||
@@ -458,16 +502,16 @@ const SewagePipeInp = () => {
|
||||
if (tab.left && tab.right) {
|
||||
return {
|
||||
...tab,
|
||||
left: { ...tab.left, columns: applyEditable(tab.left.columns) },
|
||||
right: { ...tab.right, columns: applyEditable(tab.right.columns) },
|
||||
left: { ...tab.left, columns: applyColumnGuides(applyEditable(tab.left.columns), tab.tabKey) },
|
||||
right: { ...tab.right, columns: applyColumnGuides(applyEditable(tab.right.columns), tab.tabKey) },
|
||||
};
|
||||
}
|
||||
return {
|
||||
...tab,
|
||||
columns: applyEditable(tab.columns),
|
||||
columns: applyColumnGuides(applyEditable(tab.columns), tab.tabKey),
|
||||
};
|
||||
});
|
||||
}, [tabStatusInfoMap, isAdminInfo, inputPeriodEnterable]);
|
||||
}, [tabStatusInfoMap, isAdminInfo, inputPeriodEnterable, columnGuidesByTab]);
|
||||
|
||||
const commitToolbarSnapshotForLoadedTab = (
|
||||
tabKey: string,
|
||||
@@ -1251,7 +1295,7 @@ const SewagePipeInp = () => {
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '',
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
sggNm: searchValues?.gugun ?? '',
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ""),
|
||||
statsYr: searchValues?.year ?? '',
|
||||
mdfrId: userInfo.userUnqId,
|
||||
statsCd: tabStatsCdMap.get(activeTab) ?? statsCd,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect,useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
@@ -57,6 +57,9 @@ type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: SewerageSupplyRateVerificationResult;
|
||||
};
|
||||
|
||||
/** 컬럼 가이드 API 조회용 통계코드 (하수도 보급률) */
|
||||
const SEWERAGE_PENETRATION_COLUMN_GUIDE_STATS_CD = "ST00010001";
|
||||
|
||||
const hasVerificationErrors = (verification?: SewerageSupplyRateVerificationResult | null): boolean => {
|
||||
if (!verification) return false;
|
||||
return Object.values(verification).some((errors) => Array.isArray(errors) && errors.length > 0);
|
||||
@@ -119,6 +122,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
/** 예외제출 버튼으로 연 다이얼로그인지 (확인 시 executeSubmit 대신 SubmitExceptionFunc 경로) */
|
||||
const [isExceptionSubmitFlow, setIsExceptionSubmitFlow] = useState(false);
|
||||
const [statsCd, setStatsCd] = useState<string>('');
|
||||
/** `engColNm` → `columnGuide` (헤더 hover) */
|
||||
const [columnGuidesByEngCol, setColumnGuidesByEngCol] = useState<Record<string, string>>({});
|
||||
const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null);
|
||||
const { confirm } = useConfirm();
|
||||
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
|
||||
@@ -149,6 +154,35 @@ const SeweragePenetrationRateInp = () => {
|
||||
setSearchFormRemountKey((k) => k + 1);
|
||||
}, [activeTabId, inputFocusNonce]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await statisticsApi.getColumnGuides(SEWERAGE_PENETRATION_COLUMN_GUIDE_STATS_CD);
|
||||
if (cancelled) return;
|
||||
if (res.status !== "success" || !Array.isArray(res.data)) {
|
||||
setColumnGuidesByEngCol({});
|
||||
return;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const item of res.data) {
|
||||
const key = item.engColNm?.trim();
|
||||
const guide = item.columnGuide?.trim();
|
||||
if (key && guide) next[key] = guide;
|
||||
}
|
||||
setColumnGuidesByEngCol(next);
|
||||
} catch {
|
||||
if (!cancelled) setColumnGuidesByEngCol({});
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [activeTabId]);
|
||||
|
||||
const { isAdminInfo } = useAdminStore();
|
||||
|
||||
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
||||
@@ -167,9 +201,17 @@ const SeweragePenetrationRateInp = () => {
|
||||
const { columns, columnLayout } = SeweragePenetrationRateInpColumn;
|
||||
const base = (isEnterable || isAdminInfo) ? columns : columns.map((col) => ({ ...col, editable: false as const }));
|
||||
|
||||
const withHeaderTooltips = base.map((col) => {
|
||||
const guide = columnGuidesByEngCol[col.fieldName]?.trim();
|
||||
return {
|
||||
...col,
|
||||
tooltip: guide && guide.length > 0 ? guide : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
columnLayout,
|
||||
columns: base.map((col) => {
|
||||
columns: withHeaderTooltips.map((col) => {
|
||||
if (col.fieldName === 'pbadmsWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnWholPpltnCnt', '#,##0') };
|
||||
if (col.fieldName === 'domplWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnDomplWholPpltnCnt', '#,##0') };
|
||||
if (col.fieldName === 'frgnrWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnFrgnrWholPpltnCnt', '#,##0') };
|
||||
@@ -178,7 +220,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
return col;
|
||||
}),
|
||||
};
|
||||
}, [isEnterable, isAdminInfo, footerFromApi]);
|
||||
}, [isEnterable, isAdminInfo, footerFromApi, columnGuidesByEngCol]);
|
||||
|
||||
const handleReset = () => {
|
||||
setRowData([]);
|
||||
@@ -276,7 +318,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
if (!values) return;
|
||||
postVerificationSearchRef.current = null;
|
||||
void handleSearch(values);
|
||||
|
||||
|
||||
}, [searchFormRemountKey]);
|
||||
|
||||
const SubmitExceptionFunc = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
|
||||
@@ -291,7 +333,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
...submitInfo,
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '',
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
sggNm: searchValues?.gugun ?? '',
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
|
||||
statsYr: searchValues?.year ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
statsCd: statsCd,
|
||||
@@ -428,7 +470,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
const requestData: SewerageSupplyRateSaveRequest = {
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '',
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
sggNm: searchValues?.gugun ?? '',
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
|
||||
statsYr: searchValues?.year ?? '',
|
||||
userId: userInfo.userUnqId,
|
||||
submitList: convertedData,
|
||||
@@ -579,7 +621,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
const requestData: SewerageSupplyRateSaveRequest = {
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '', // 하수도 사업자 코드
|
||||
ctpvNm: searchValues?.sido ?? '', // 시도
|
||||
sggNm: searchValues?.gugun ?? '',
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''),
|
||||
statsYr: searchValues?.year ?? '', // 통계 연도
|
||||
userId: userInfo.userUnqId, // 사용자
|
||||
saveList: convertedData, // 저장 데이터
|
||||
@@ -624,7 +666,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
const requestData: cancelSubmitRequest = {
|
||||
sewerBzmnCd: searchValues?.bsnmCd ?? '',
|
||||
ctpvNm: searchValues?.sido ?? '',
|
||||
sggNm: searchValues?.gugun ?? '',
|
||||
sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ""),
|
||||
statsYr: searchValues?.year ?? '',
|
||||
mdfrId: userInfo.userUnqId,
|
||||
statsCd: statsCd,
|
||||
|
||||
@@ -8,6 +8,7 @@ import useSWR from "swr";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { CodeSelectWithAll } from "@/entities/code";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { StatisticsLogResponse } from "@/entities/statistics/model/statistics-interface";
|
||||
import type {
|
||||
ApiResponse,
|
||||
SearchCondition,
|
||||
@@ -129,7 +130,7 @@ const LogHistory = () => {
|
||||
const selectedBsnm =
|
||||
value === "ALL"
|
||||
? (sidoList?.data.sidoList.find((v) => v.sido === sido)
|
||||
?.bsnmCd ?? "")
|
||||
?.bsnmCd ?? "")
|
||||
: (listForBsnm.find((v) => v.gugun === value)?.bsnmCd ?? "");
|
||||
setGugun(value);
|
||||
setBsnmCd(selectedBsnm);
|
||||
@@ -150,8 +151,8 @@ const LogHistory = () => {
|
||||
...(bsnmCd && { sewerBzmnCd: bsnmCd }),
|
||||
...(statsDataChgComCd &&
|
||||
statsDataChgComCd !== "ALL" && {
|
||||
statsDataChgComCd,
|
||||
}),
|
||||
statsDataChgComCd,
|
||||
}),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -160,15 +161,19 @@ const LogHistory = () => {
|
||||
const response = await statisticsApi.getLogList(param);
|
||||
|
||||
if (response.status === "success") {
|
||||
const lostList = response.data?.statsLogList ?? [];
|
||||
const logListRaw = response.data?.statsLogList ?? [];
|
||||
const logList = logListRaw.map((l) => ({
|
||||
...l,
|
||||
sggNm: String(l.sggNm ?? "").trim() || "-",
|
||||
}));
|
||||
|
||||
setRowData(toGridRows(lostList));
|
||||
setRowData(toGridRows(logList));
|
||||
setTotalPages(response.data?.pageInfo?.totalPages ?? 0);
|
||||
setPageIndex(Math.max(0, page1Based - 1));
|
||||
} else {
|
||||
alert(
|
||||
response.message ||
|
||||
"데이터 조회 중 오류가 발생했습니다.",
|
||||
"데이터 조회 중 오류가 발생했습니다.",
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
@@ -331,7 +336,7 @@ const LogHistory = () => {
|
||||
columnInfo={LogHistoryColumn}
|
||||
isExcelImport={false}
|
||||
rowData={rowData}
|
||||
setRowData={() => {}}
|
||||
setRowData={() => { }}
|
||||
onRowClick={handleRowClick}
|
||||
/>
|
||||
<LogHistoryDetailDialog
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { ColumnConfig } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
/** API `YYYYMMDD` 등 → Date */
|
||||
function seTypeToDisplayLabel(seType: StatsInputPeriodItem["seType"]): string {
|
||||
if (seType === "INPT") return "입력";
|
||||
if (seType === "VRFC") return "검증";
|
||||
@@ -32,7 +33,6 @@ function rowSeTypeToCode(row: Record<string, string | number>): "INPT" | "VRFC"
|
||||
return "INPT";
|
||||
}
|
||||
|
||||
/** API `YYYYMMDD` 등 → Date */
|
||||
function parseYmdToDate(ymd: unknown): Date | undefined {
|
||||
const s = String(ymd ?? "").replace(/\D/g, "");
|
||||
if (s.length !== 8) return undefined;
|
||||
@@ -71,10 +71,11 @@ const StatisticsInputManagement = () => {
|
||||
setIsLoading(true);
|
||||
const params: StatsInputPeriodListRequest = {
|
||||
statsYr: values.year,
|
||||
seType:
|
||||
values.clsfCd && values.clsfCd !== "ALL"
|
||||
? values.clsfCd
|
||||
: "",
|
||||
};
|
||||
if (values.clsfCd && values.clsfCd !== "ALL") {
|
||||
params.seType = values.clsfCd;
|
||||
}
|
||||
const res = await statisticsApi.getStatsInputPeriodList(params);
|
||||
if (res.status === "success" && Array.isArray(res.data)) {
|
||||
const rowsForGrid = res.data.map((row) => ({
|
||||
@@ -112,6 +113,11 @@ const StatisticsInputManagement = () => {
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Record<string, string | number>) => {
|
||||
const inptYmdSnRaw = row.inptYmdSn;
|
||||
const inptYmdSnNum =
|
||||
typeof inptYmdSnRaw === "number"
|
||||
? inptYmdSnRaw
|
||||
: parseInt(String(inptYmdSnRaw ?? ""), 10);
|
||||
const seCdRaw = row.seCd;
|
||||
const seCdNum =
|
||||
typeof seCdRaw === "number"
|
||||
@@ -121,6 +127,7 @@ const StatisticsInputManagement = () => {
|
||||
setPeriodDialogInitial({
|
||||
statsYr: String(row.statsYr ?? ""),
|
||||
seType: rowSeTypeToCode(row),
|
||||
inptYmdSn: Number.isFinite(inptYmdSnNum) ? inptYmdSnNum : 0,
|
||||
seCd: Number.isFinite(seCdNum) ? seCdNum : 0,
|
||||
inptBgngYmd: parseYmdToDate(row.inptBgngYmd),
|
||||
inptEndYmd: parseYmdToDate(row.inptEndYmd),
|
||||
@@ -159,6 +166,13 @@ const StatisticsInputManagement = () => {
|
||||
header: { text: "종료일" },
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: "inptYmdSn",
|
||||
fieldName: "inptYmdSn",
|
||||
header: { text: "inptYmdSn" },
|
||||
visible: false,
|
||||
editable: false,
|
||||
},
|
||||
];
|
||||
|
||||
const columnLayout = [
|
||||
|
||||
@@ -33,6 +33,7 @@ import { getUserInfo } from "../../../../../../packages/common/components/src/ap
|
||||
export type StatisticsInputPeriodDialogValues = {
|
||||
statsYr: string;
|
||||
seType: "INPT" | "VRFC";
|
||||
inptYmdSn: number;
|
||||
seCd: number;
|
||||
inptBgngYmd: Date | undefined;
|
||||
inptEndYmd: Date | undefined;
|
||||
@@ -137,15 +138,22 @@ export function StatisticsInputPeriodDialog({
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const inptYmdSn =
|
||||
mode === "create"
|
||||
? 0
|
||||
: (initial && Number.isFinite(initial.inptYmdSn) ? initial.inptYmdSn : 0);
|
||||
const param: StatsInputPeriodSaveRequest = {
|
||||
statsYr: yr,
|
||||
seType,
|
||||
inptYmdSn,
|
||||
seCd: seCdParsed,
|
||||
inptBgngYmd: bgng,
|
||||
inptEndYmd: end,
|
||||
rgtrId: userInfo.userUnqId,
|
||||
};
|
||||
const res = await statisticsApi.saveStatsInputPeriod(param);
|
||||
const res = isCreate
|
||||
? await statisticsApi.saveStatsInputPeriod(param)
|
||||
: await statisticsApi.updateStatsInputPeriod(param);
|
||||
if (res.status === "success") {
|
||||
alert(`${isCreate ? '등록 되었습니다.' : '수정 되었습니다.'}`);
|
||||
onOpenChange(false);
|
||||
@@ -184,6 +192,8 @@ export function StatisticsInputPeriodDialog({
|
||||
const param: StatsInputPeriodDeleteRequest = {
|
||||
statsYr: yr,
|
||||
seType: initial.seType,
|
||||
inptYmdSn:
|
||||
Number.isFinite(initial.inptYmdSn) ? initial.inptYmdSn : 0,
|
||||
seCd,
|
||||
inptBgngYmd: bgng,
|
||||
inptEndYmd: end,
|
||||
|
||||
@@ -1,47 +1,273 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { DataGrid } from "@rsone/components";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { PublicSewageFacilityReviewItem } from "@/entities/statistics/model/statistics-public-sewage-facility-review-interface";
|
||||
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
import { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn";
|
||||
|
||||
// 하수도 통계 관리 06.공공하수처리시설
|
||||
const PublicSewageFacilityRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [statsStatusInfo, setStatsStatusInfo] = useState<StatsStatusInfo>({});
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [reviewOpinionContext, setReviewOpinionContext] =
|
||||
useState<ReviewOpinionDialogContext | null>(null);
|
||||
const [latestReviewOpinion, setLatestReviewOpinion] = useState("");
|
||||
const [latestProcessHistory, setLatestProcessHistory] = useState("");
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
|
||||
const approveFunc = () => {
|
||||
console.log('approveFunc');
|
||||
const columnInfo = useMemo(() => {
|
||||
const { columns, columnLayout, fixedColumns, rowStartNum } = PublicSewageFacilityInfoList;
|
||||
return {
|
||||
columnLayout,
|
||||
fixedColumns,
|
||||
rowStartNum,
|
||||
columns: columns.map((col) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
})),
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const submit = statsStatusInfo.submitStatusCd != null ? String(statsStatusInfo.submitStatusCd) : "";
|
||||
const prvn = statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [statsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
|
||||
const handleReset = () => {
|
||||
setRowData([]);
|
||||
setStatsStatusInfo({});
|
||||
setReviewOpinionContext(null);
|
||||
setLatestReviewOpinion("");
|
||||
setLatestProcessHistory("");
|
||||
setLastSearchValues(null);
|
||||
};
|
||||
|
||||
const saveFunc = () => {
|
||||
console.log('submit');
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
setLastSearchValues(values);
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await statisticsManagementApi.getPublicSewageFacilityReviewList({
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
statsYr: values.year,
|
||||
fctName: values.fctName,
|
||||
});
|
||||
|
||||
if (response.status === "success" && response.data) {
|
||||
const rawList = response.data.publicSewageList;
|
||||
const convertedData: PublicSewageFacilityReviewItem[] = rawList.map((item) => ({
|
||||
...item,
|
||||
impdFcltPstnCn: item.fcltPstnCn ?? "",
|
||||
prxyBzentyNmCn: item.prxyBzentyNmCn ?? "",
|
||||
prxyCtrtCn: item.prxyCtrtCn ?? "",
|
||||
intgPrcsFcltCn: item.intgPrcsFcltCn ?? "",
|
||||
rmrk: item.rmrk ?? "",
|
||||
})) as PublicSewageFacilityReviewItem[];
|
||||
|
||||
setStatsStatusInfo(response.data.statsStatusInfo);
|
||||
setReviewOpinionContext({
|
||||
statsYr: values.year ?? "",
|
||||
sewerBzmnCd: values.bsnmCd ?? "",
|
||||
statsCd: response.data.statsStatusInfo.statsCd ?? "",
|
||||
});
|
||||
setLatestReviewOpinion(
|
||||
response.data.statsStatusInfo.splmntOpnnCn ?? "",
|
||||
);
|
||||
setLatestProcessHistory(
|
||||
response.data.statsStatusInfo.wrterOpnnCn ?? "",
|
||||
);
|
||||
setRowData(toGridRows(convertedData as unknown as object[]));
|
||||
} else {
|
||||
setRowData([]);
|
||||
setStatsStatusInfo({});
|
||||
setReviewOpinionContext(null);
|
||||
setLatestReviewOpinion("");
|
||||
setLatestProcessHistory("");
|
||||
alert(response.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("공공하수처리시설 조회 중 에러 발생:", error);
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = () => {
|
||||
console.log('reviewOpinionFunc');
|
||||
const submitFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await confirm("제출 하시겠습니까?"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const response = await statisticsApi.submitReview({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
});
|
||||
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "제출에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
alert(response.message || "제출되었습니다.");
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
} catch {
|
||||
alert("제출 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => {
|
||||
console.log('returnFunc');
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.wrterOpnnCn ?? "");
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setStatsStatusInfo((prev) => ({
|
||||
...prev,
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
}));
|
||||
setLatestReviewOpinion(param.splmntOpnnCn);
|
||||
setLatestProcessHistory(param.wrterOpnnCn);
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm fctName={true} />
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
<ApproveButton approveFunc={approveFunc} />
|
||||
<ReturnButton returnFunc={returnFunc} />
|
||||
<SaveButton saveFunc={saveFunc} />
|
||||
<div className="relative">
|
||||
<SearchForm fctName={true} onSubmit={handleSearch} onReset={handleReset} />
|
||||
{showActionBar && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && <SubmitButton submitFunc={submitFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataGrid columnInfo={PublicSewageFacilityInfoList} isExcelImport={false} rowStartNum={3} fixedColumns={3} rowData={rowData} setRowData={setRowData} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
/>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -14,25 +16,47 @@ import {
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import type { ReviewReturnSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
export type ReturnReasonDialogContext = Pick<
|
||||
ReviewReturnSaveRequest,
|
||||
"statsYr" | "sewerBzmnCd" | "statsCd"
|
||||
>;
|
||||
|
||||
export interface ReturnReasonDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSave?: (reason: string) => void | Promise<void>;
|
||||
context: ReturnReasonDialogContext | null;
|
||||
onSave?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
export function ReturnReasonDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
context,
|
||||
onSave,
|
||||
}: ReturnReasonDialogProps) {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const [reason, setReason] = useState("");
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [reasonError, setReasonError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const resetForm = () => {
|
||||
setReason("");
|
||||
setReasonError(false);
|
||||
};
|
||||
|
||||
const handleDialogOpenChange = (nextOpen: boolean) => {
|
||||
if (!nextOpen) resetForm();
|
||||
onOpenChange(nextOpen);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
resetForm();
|
||||
}, [open]);
|
||||
|
||||
const handleSave = async () => {
|
||||
@@ -41,24 +65,65 @@ export function ReturnReasonDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
context == null ||
|
||||
context.statsYr === "" ||
|
||||
context.sewerBzmnCd === "" ||
|
||||
context.statsCd === ""
|
||||
) {
|
||||
alert("시도·시군구·년도로 조회한 뒤 반려 처리할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await confirm("반려 처리 하시겠습니까?"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
await onSave?.(reason);
|
||||
let userInfo: Awaited<ReturnType<typeof getUserInfo>>;
|
||||
try {
|
||||
userInfo = await getUserInfo();
|
||||
} catch {
|
||||
alert("사용자 정보를 불러오지 못했습니다. 다시 시도해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const param: ReviewReturnSaveRequest = {
|
||||
type: "REVIEW",
|
||||
statsYr: context.statsYr,
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
rjctOpnnCn: reason,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
};
|
||||
|
||||
const response = await statisticsApi.saveReviewReturn(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "반려 처리 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
alert(response.message || "반려 처리 되었습니다.");
|
||||
await onSave?.();
|
||||
resetForm();
|
||||
onOpenChange(false);
|
||||
} catch {
|
||||
alert("반려 처리 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog open={open} onOpenChange={handleDialogOpenChange}>
|
||||
<DialogContent className="max-w-[56rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>반려 사유</DialogTitle>
|
||||
<DialogTitle>반려</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 py-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">반려 사유</Label>
|
||||
<Label className="text-label mb-2">반려 사유</Label>
|
||||
<Textarea
|
||||
value={reason}
|
||||
onChange={(e) => {
|
||||
@@ -86,11 +151,11 @@ export function ReturnReasonDialog({
|
||||
</DialogClose>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
color="red"
|
||||
disabled={isSaving}
|
||||
onClick={() => void handleSave()}
|
||||
>
|
||||
저장
|
||||
반려 제출
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -115,9 +115,9 @@ export function ReviewOpinionDialog({
|
||||
<DialogHeader>
|
||||
<DialogTitle>검토의견작성</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 py-2">
|
||||
<div className="flex flex-col gap-8 py-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">검토의견</Label>
|
||||
<Label className="text-label mb-2">검토의견</Label>
|
||||
<Textarea
|
||||
value={reviewOpinion}
|
||||
onChange={(e) => {
|
||||
@@ -137,7 +137,7 @@ export function ReviewOpinionDialog({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">처리내역</Label>
|
||||
<Label className="text-label mb-2">처리내역</Label>
|
||||
<Textarea
|
||||
value={processHistory}
|
||||
onChange={(e) => setProcessHistory(e.target.value)}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
SewagePipeReviewRequestParams,
|
||||
StatsStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
@@ -13,6 +17,11 @@ import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
|
||||
@@ -70,9 +79,13 @@ function extractStatsStatusInfo(data: unknown): StatsStatusInfo | null {
|
||||
// 하수도 통계 검토 02.하수관로
|
||||
const SewagePipeRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { confirm } = useConfirm();
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [activeTab, setActiveTab] = useState<string>(firstTabKey);
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
/** 탭별 조회 응답의 statsStatusInfo — 활성 탭에 맞춰 버튼 노출 (SewagePipeInp의 tabStatusInfoMap과 동일 패턴) */
|
||||
const [tabStatsStatusInfoMap, setTabStatsStatusInfoMap] = useState<
|
||||
Map<string, StatsStatusInfo>
|
||||
@@ -81,9 +94,23 @@ const SewagePipeRvw = () => {
|
||||
const searchParamRef = useRef<SewagePipeReviewRequestParams | null>(null);
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoggedRef = useRef<Set<string>>(new Set());
|
||||
const activeStatsStatusInfo = useMemo(
|
||||
() => tabStatsStatusInfoMap.get(activeTab) ?? {},
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
if (lastSearchValues == null) return null;
|
||||
return {
|
||||
statsYr: lastSearchValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchValues.bsnmCd ?? "",
|
||||
statsCd: activeStatsStatusInfo.statsCd ?? "",
|
||||
};
|
||||
}, [activeStatsStatusInfo.statsCd, lastSearchValues]);
|
||||
const latestReviewOpinion = activeStatsStatusInfo.splmntOpnnCn ?? "";
|
||||
const latestProcessHistory = activeStatsStatusInfo.wrterOpnnCn ?? "";
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const statsStatusInfo = tabStatsStatusInfoMap.get(activeTab) ?? {};
|
||||
const statsStatusInfo = activeStatsStatusInfo;
|
||||
const submit =
|
||||
statsStatusInfo.submitStatusCd != null
|
||||
? String(statsStatusInfo.submitStatusCd)
|
||||
@@ -97,7 +124,7 @@ const SewagePipeRvw = () => {
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [tabStatsStatusInfoMap, activeTab]);
|
||||
}, [activeStatsStatusInfo]);
|
||||
|
||||
const showActionBar =
|
||||
showReviewOpinionButton || showReturnButton || showSubmitButton;
|
||||
@@ -106,6 +133,9 @@ const SewagePipeRvw = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setTabStatsStatusInfoMap(new Map());
|
||||
setReviewOpinionOpen(false);
|
||||
setReturnDialogOpen(false);
|
||||
setLastSearchValues(null);
|
||||
searchParamRef.current = null;
|
||||
searchDoneRef.current = false;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
@@ -119,6 +149,7 @@ const SewagePipeRvw = () => {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setLastSearchValues(values);
|
||||
|
||||
const searchParam: SewagePipeReviewRequestParams = {
|
||||
statsYr: values.year,
|
||||
@@ -204,6 +235,103 @@ const SewagePipeRvw = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const submitFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await confirm("제출 하시겠습니까?"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const response = await statisticsApi.submitReview({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
});
|
||||
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "제출에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
alert(response.message || "제출되었습니다.");
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
} catch {
|
||||
alert("제출 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsApi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: row?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: row?.wrterOpnnCn ?? "",
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsApi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setTabStatsStatusInfoMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, {
|
||||
...(next.get(activeTab) ?? {}),
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
@@ -212,11 +340,11 @@ const SewagePipeRvw = () => {
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showReviewOpinionButton && (
|
||||
<ReviewOpinionButton reviewOpinionFunc={() => {}} />
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
)}
|
||||
{showReturnButton && <ReturnButton returnFunc={() => {}} />}
|
||||
{showReturnButton && <ReturnButton returnFunc={returnFunc} />}
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={() => {}} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -283,6 +411,20 @@ const SewagePipeRvw = () => {
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -211,36 +211,9 @@ const SeweragePenetrationRateRvw = () => {
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async (reason: string) => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 반려할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (await confirm('반려 처리 하시겠습니까?')) {
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const response = await statisticsApi.saveReviewReturn({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
rjctOpnnCn: reason,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
});
|
||||
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "반려 처리 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
alert(response.message || "반려 처리 되었습니다.");
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
|
||||
} catch {
|
||||
alert("반려 처리 중 오류가 발생했습니다.");
|
||||
}
|
||||
const handleSaveReturnReason = async () => {
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -288,6 +261,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
context={reviewOpinionContext}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export type DataGridStatusBannerInfo = {
|
||||
export type RealGridColumnInfo = {
|
||||
name: string;
|
||||
fieldName: string;
|
||||
header: { text: string };
|
||||
header: { text: string; tooltip?: string; showTooltip?: boolean; template?: string };
|
||||
editable?: boolean;
|
||||
valueType?: import("realgrid").ValueType;
|
||||
dataFormat?: string;
|
||||
@@ -154,7 +154,9 @@ export type ComputedFieldConfig =
|
||||
export type ColumnConfig = {
|
||||
name: string;
|
||||
fieldName: string;
|
||||
header: { text: string };
|
||||
header: { text: string; tooltip?: string; showTooltip?: boolean; template?: string };
|
||||
/** 헤더 hover 시 보여줄 컬럼 가이드 문구 */
|
||||
tooltip?: string;
|
||||
/** 계산 필드: fields 합계/평균 등. valueCallback으로 dataProvider.setFields 시 전달 */
|
||||
computed?: ComputedFieldConfig;
|
||||
editable?: boolean; // 셀 편집 가능 여부
|
||||
@@ -210,7 +212,7 @@ export type ColumnLayoutItem =
|
||||
| {
|
||||
name: string;
|
||||
hideChildHeaders?: boolean;
|
||||
header: { text: string };
|
||||
header: { text: string; tooltip?: string; showTooltip?: boolean; template?: string };
|
||||
items: (string | ColumnLayoutItem)[];
|
||||
footerUserSpans?: FooterUserSpan[]; // footer 병합 설정
|
||||
}
|
||||
|
||||
@@ -1,142 +1,190 @@
|
||||
import { ValueType } from "realgrid";
|
||||
import {
|
||||
footerAvgDecimalArrayByDivision,
|
||||
footerDecimal1ArrayByDivision,
|
||||
footerDecimal3ArrayByDivision,
|
||||
footerIntArrayByDivision,
|
||||
} from "@/components/util/grid/footerSummary";
|
||||
import type { GridView } from "@/components/type/DataGrid.type";
|
||||
|
||||
const getDivisionFooterLabel = (grid: GridView, divisionIndex: number, withSuffix: boolean) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
|
||||
for (let i = 0; i < dataSource.getRowCount(); i++) {
|
||||
const val = dataSource.getValue(i, "displayYear");
|
||||
if (val) uniqueValues.add(String(val));
|
||||
}
|
||||
|
||||
const divisionValues = Array.from(uniqueValues);
|
||||
if (divisionValues.length <= divisionIndex) {
|
||||
return withSuffix ? "합계" : "";
|
||||
}
|
||||
|
||||
const label = divisionValues[divisionIndex] ?? "";
|
||||
return withSuffix ? `${label}합계` : label;
|
||||
};
|
||||
|
||||
// 하수도 통계 관리 06.공공하수처리시설
|
||||
export const rawPublicSewageFacilityInfoList = {
|
||||
columns: [
|
||||
/* 1. 기본 정보 */
|
||||
{ name: 'sido', fieldName: '시도', header: { text: '시도' } },
|
||||
{ name: 'sgg', fieldName: '시군구', header: { text: '시군구' } },
|
||||
{ name: 'GBN', fieldName: '구분', header: { text: '구분' } },
|
||||
{ name: 'ADDR', fieldName: '소재지', header: { text: '소재지' } },
|
||||
{ name: 'FCT_NM_GROUP', fieldName: '시설명(500m3/일이상/미만)', header: { text: '시설명(500m3/일이상/미만)' } },
|
||||
{
|
||||
name: 'ctpvNm',
|
||||
fieldName: 'ctpvNm',
|
||||
header: { text: '시도' },
|
||||
footer: [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
],
|
||||
},
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{
|
||||
name: 'displayYear',
|
||||
fieldName: 'displayYear',
|
||||
header: { text: '구분' },
|
||||
footer: [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
],
|
||||
},
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '시설명(500m³/일 이상/미만)' } },
|
||||
{ name: 'impdFcltPstnCn', fieldName: 'impdFcltPstnCn', header: { text: '소재지' } },
|
||||
|
||||
/* 2. 시설용량 (㎥/일) */
|
||||
{ name: 'FCT_CPC_GROUP', fieldName: '시설용량(500m3/일이상/미만)_용량', header: { text: '시설용량(500m3/일이상/미만)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'FCT_CPC_PHY', fieldName: '물리적_용량', header: { text: '물리적' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'FCT_CPC_BIO', fieldName: '생물학적_용량', header: { text: '생물학적' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'FCT_CPC_ADV', fieldName: '고도_용량', header: { text: '고도' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '시설용량(500m³/일 이상/미만)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsFcltPhysCpct', fieldName: 'prcsFcltPhysCpct', header: { text: '물리적' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsFcltBoilCpct', fieldName: 'prcsFcltBoilCpct', header: { text: '생물학적' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsFcltAltdCpct', fieldName: 'prcsFcltAltdCpct', header: { text: '고도' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 3. 유입하수량 (㎥/일) */
|
||||
{ name: 'IN_QTY_GROUP', fieldName: '유입하수량(500m3/일이상/미만)', header: { text: '유입하수량(500m3/일이상/미만)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'IN_QTY_PHY', fieldName: '물리적_유입', header: { text: '물리적' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'IN_QTY_BIO', fieldName: '생물학적_유입', header: { text: '생물학적' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'IN_QTY_ADV', fieldName: '고도_유입', header: { text: '고도' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'inflwSwageQnt', fieldName: 'inflwSwageQnt', header: { text: '유입하수량(500m³/일 이상/미만)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'inflwSwagePhysQnt', fieldName: 'inflwSwagePhysQnt', header: { text: '물리적' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'inflwSwageBoilQnt', fieldName: 'inflwSwageBoilQnt', header: { text: '생물학적' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'inflwSwageAltdQnt', fieldName: 'inflwSwageAltdQnt', header: { text: '고도' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 4. 방류량 (㎥/일) */
|
||||
{ name: 'OUT_QTY_GROUP', fieldName: '방류량(500m3/일이상/미만)', header: { text: '방류량(500m3/일이상/미만)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OUT_QTY_PHY', fieldName: '물리적_방류', header: { text: '물리적' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OUT_QTY_BIO', fieldName: '생물학적_방류', header: { text: '생물학적' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OUT_QTY_ADV', fieldName: '고도_방류', header: { text: '고도' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsQnt', fieldName: 'prcsQnt', header: { text: '방류량(500m³/일 이상/미만)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsPhysCpct', fieldName: 'prcsPhysCpct', header: { text: '물리적' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsBoilCpct', fieldName: 'prcsBoilCpct', header: { text: '생물학적' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsAltdCpct', fieldName: 'prcsAltdCpct', header: { text: '고도' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 5. 수질 및 처리효율 */
|
||||
{ name: 'IN_BOD', fieldName: '유입BOD', header: { text: '유입BOD' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OUT_BOD', fieldName: '방류BOD', header: { text: '방류BOD' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'IN_TOC', fieldName: '유입TOC', header: { text: '유입TOC' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OUT_TOC', fieldName: '방류TOC', header: { text: '방류TOC' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'IN_SS', fieldName: '유입SS', header: { text: '유입SS' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OUT_SS', fieldName: '방류SS', header: { text: '방류SS' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'IN_TN', fieldName: '유입T-N', header: { text: '유입T-N' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'OUT_TN', fieldName: '방류T-N', header: { text: '방류T-N' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'IN_TP', fieldName: '유입T-P', header: { text: '유입T-P' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'OUT_TP', fieldName: '방류T-P', header: { text: '방류T-P' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'IN_COLI', fieldName: '유입총대장균군수', header: { text: '유입총대장균군수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'OUT_COLI', fieldName: '방대총대장균군수', header: { text: '방대총대장균군수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'TRT_EFF', fieldName: '처리효율(%)', header: { text: '처리효율(%)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'TRT_LOD', fieldName: '처리부하량(kgBOD/D)', header: { text: '처리부하량(kgBOD/D)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'TRT_MTHD', fieldName: '처리방법', header: { text: '처리방법' } },
|
||||
{ name: 'inflwBodVl', fieldName: 'inflwBodVl', header: { text: '유입BOD' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'dschgBodVl', fieldName: 'dschgBodVl', header: { text: '방류BOD' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'inflwTocQnt', fieldName: 'inflwTocQnt', header: { text: '유입TOC' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'dschgTocQnt', fieldName: 'dschgTocQnt', header: { text: '방류TOC' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'inflwSsQnt', fieldName: 'inflwSsQnt', header: { text: '유입SS' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'dschgSsQnt', fieldName: 'dschgSsQnt', header: { text: '방류SS' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'inflwTnVl', fieldName: 'inflwTnVl', header: { text: '유입T-N' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'dschgTnVl', fieldName: 'dschgTnVl', header: { text: '방류T-N' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'inflwTpVl', fieldName: 'inflwTpVl', header: { text: '유입T-P' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'dschgTpVl', fieldName: 'dschgTpVl', header: { text: '방류T-P' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'inflwGrssClfgVl', fieldName: 'inflwGrssClfgVl', header: { text: '유입총대장균군수' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'dschgGrssClfgVl', fieldName: 'dschgGrssClfgVl', header: { text: '방류총대장균군수' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsRt', fieldName: 'prcsRt', header: { text: '처리효율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsLodQnt', fieldName: 'prcsLodQnt', header: { text: '처리부하량(kgBOD/D)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prcsMthdExpln', fieldName: 'prcsMthdExpln', header: { text: '처리방법' } },
|
||||
|
||||
/* 6. 연계처리량 */
|
||||
{ name: 'LNK_EXC', fieldName: '분뇨', header: { text: '분뇨' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'LNK_LVS', fieldName: '축산', header: { text: '축산' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'LNK_LCH', fieldName: '침출수', header: { text: '침출수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'LNK_ETC', fieldName: '기타_연계', header: { text: '기타' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'wholHmnQnt', fieldName: 'wholHmnQnt', header: { text: '분뇨' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'wholLvstQnt', fieldName: 'wholLvstQnt', header: { text: '축산' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'wholEfntQnt', fieldName: 'wholEfntQnt', header: { text: '침출수' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'wholEtcQnt', fieldName: 'wholEtcQnt', header: { text: '기타' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
/* 7. 상세 시설 정보 */
|
||||
{ name: 'TEL_NO', fieldName: '전화번호', header: { text: '전화번호' } },
|
||||
{ name: 'COMP_DT', fieldName: '준공일', header: { text: '준공일' } },
|
||||
{ name: 'RN_DT', fieldName: '가동개시일', header: { text: '가동개시일' } },
|
||||
{ name: 'BSNS_CT', fieldName: '사업비(백만원)', header: { text: '사업비(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'DSCH_DSF_MTHD', fieldName: '방류수소독방법', header: { text: '방류수소독방법' } },
|
||||
{ name: 'telNo', fieldName: 'telNo', header: { text: '전화번호' } },
|
||||
{ name: 'cmptnStrtYmd', fieldName: 'cmptnStrtYmd', header: { text: '준공일' } },
|
||||
{ name: 'oprtngStrtYmd', fieldName: 'oprtngStrtYmd', header: { text: '가동개시일' } },
|
||||
{ name: 'wholBizVl', fieldName: 'wholBizVl', header: { text: '사업비' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'eflntDsnfMthdExpln', fieldName: 'eflntDsnfMthdExpln', header: { text: '방류수 소독방법' } },
|
||||
|
||||
/* 8. 방류수역 */
|
||||
{ name: 'WTR_SYS', fieldName: '수계', header: { text: '수계' } },
|
||||
{ name: 'STRM', fieldName: '지류', header: { text: '지류' } },
|
||||
{ name: 'DET_UNIT_ZONE', fieldName: '세부단위구역', header: { text: '세부단위구역' } },
|
||||
{ name: 'RGN_GBN', fieldName: '지역구분', header: { text: '지역구분' } },
|
||||
{ name: 'MID_ZONE_NM', fieldName: '명칭', header: { text: '명칭' } },
|
||||
{ name: 'MID_ZONE_BOD', fieldName: '목표수질(BOD)', header: { text: '목표수질(BOD)' } },
|
||||
{ name: 'wtsysNm', fieldName: 'wtsysNm', header: { text: '수계' } },
|
||||
{ name: 'trbtrNm', fieldName: 'trbtrNm', header: { text: '지류' } },
|
||||
{ name: 'dtlUnitZoneCn', fieldName: 'dtlUnitZoneCn', header: { text: '세부단위구역' } },
|
||||
{ name: 'rgnSeNm', fieldName: 'rgnSeNm', header: { text: '지역구분' } },
|
||||
{ name: 'msirbCn', fieldName: 'msirbCn', header: { text: '명칭' } },
|
||||
{ name: 'goalWqNo', fieldName: 'goalWqNo', header: { text: '목표수질(BOD)' } },
|
||||
|
||||
/* 9. 에너지 및 환경 */
|
||||
{ name: 'ENG_SLF_RT', fieldName: '에너지자립율(%)', header: { text: '에너지자립율(%)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ENG_SLF_QTY', fieldName: '에너지자체생산량(TOE)', header: { text: '에너지자체생산량(TOE)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ENG_TOT_SUM', fieldName: '계(TOE)', header: { text: '계(TOE)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ENG_TOT_PWR', fieldName: '총전력사용량(KWh)', header: { text: '총전력사용량(KWh)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ENG_TOT_SLF', fieldName: '자체에너지사용량(TOE)', header: { text: '자체에너지사용량(TOE)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'CO2_EMISS', fieldName: '하수처리량당CO2배출량(KgCO2/m3)', header: { text: '하수처리량당CO2배출량(KgCO2/m3)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
{ name: 'engySrlneRt', fieldName: 'engySrlneRt', header: { text: '에너지자립률(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'engyItslfPrdctnQnt', fieldName: 'engyItslfPrdctnQnt', header: { text: '에너지자체생산량(TOE)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'anlEngyWholUseQnt', fieldName: 'anlEngyWholUseQnt', header: { text: '계(TOE)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'anlElpwrUseQnt', fieldName: 'anlElpwrUseQnt', header: { text: '총 전력사용량(kWh)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'itslfEngyUseQnt', fieldName: 'itslfEngyUseQnt', header: { text: '자체 에너지사용량(TOE)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'swtrmtEmsnQnt', fieldName: 'swtrmtEmsnQnt', header: { text: '하수처리량당 CO2배출량(kgCO2/m³)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
|
||||
|
||||
/* 10. 운영 및 위탁 */
|
||||
{ name: 'OPR_MBY', fieldName: '운영주체', header: { text: '운영주체' } },
|
||||
{ name: 'ENTRPS_NM', fieldName: '위탁업체명', header: { text: '위탁업체명' } },
|
||||
{ name: 'ENTRPS_CT', fieldName: '위탁비용(백만원/년)', header: { text: '위탁비용(백만원/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'ENTRPS_TERM', fieldName: '위탁계약기간', header: { text: '위탁계약기간' } },
|
||||
{ name: 'CTR_TRT_FCT', fieldName: '중심처리시설', header: { text: '중심처리시설' } },
|
||||
{ name: 'EMP_CNT', fieldName: '직원총수(명)', header: { text: '직원총수(명)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'ACC_CNT', fieldName: '안전사고건수', header: { text: '안전사고건수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
|
||||
{ name: 'operMnbdCn', fieldName: 'operMnbdCn', header: { text: '운영주체(자체/공기업/민간대행)' } },
|
||||
{ name: 'prxyBzentyNmCn', fieldName: 'prxyBzentyNmCn', header: { text: '위탁업체명' } },
|
||||
{ name: 'cnsgnCstVl', fieldName: 'cnsgnCstVl', header: { text: '위탁비용(백만원/년)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'prxyCtrtCn', fieldName: 'prxyCtrtCn', header: { text: '위탁계약기간' } },
|
||||
{ name: 'intgPrcsFcltCn', fieldName: 'intgPrcsFcltCn', header: { text: '중심처리시설' } },
|
||||
{ name: 'wholNope', fieldName: 'wholNope', header: { text: '직원총수(명)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'sftyAcdntEvntcsCnt', fieldName: 'sftyAcdntEvntcsCnt', header: { text: '안전사고건수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' },
|
||||
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'groupA', header: { text: '행정구역' }, items: ['sido', 'sgg'] },
|
||||
'FCT_NM_GROUP', 'GBN', 'ADDR', 'FCT_CPC_GROUP',
|
||||
{
|
||||
name: 'groupA',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '행정구역' },
|
||||
items: [{ column: 'ctpvNm', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, 'sggNm'],
|
||||
},
|
||||
'displayYear', 'swtrmtFcltNm', 'impdFcltPstnCn', 'prcsFcltCpct',
|
||||
{
|
||||
name: 'groupB',
|
||||
header: { text: '시설용량(㎥/일)' },
|
||||
items: ['FCT_CPC_PHY', 'FCT_CPC_BIO', 'FCT_CPC_ADV'],
|
||||
items: ['prcsFcltPhysCpct', 'prcsFcltBoilCpct', 'prcsFcltAltdCpct'],
|
||||
},
|
||||
'IN_QTY_GROUP',
|
||||
'inflwSwageQnt',
|
||||
{
|
||||
name: 'groupC',
|
||||
header: { text: '유입하수량(㎥/일)' },
|
||||
items: ['IN_QTY_PHY', 'IN_QTY_BIO', 'IN_QTY_ADV'],
|
||||
items: ['inflwSwagePhysQnt', 'inflwSwageBoilQnt', 'inflwSwageAltdQnt'],
|
||||
},
|
||||
'OUT_QTY_GROUP',
|
||||
'prcsQnt',
|
||||
{
|
||||
name: 'groupD',
|
||||
header: { text: '방류량(㎥/일)' },
|
||||
items: ['OUT_QTY_PHY', 'OUT_QTY_BIO', 'OUT_QTY_ADV'],
|
||||
items: ['prcsPhysCpct', 'prcsBoilCpct', 'prcsAltdCpct'],
|
||||
},
|
||||
'IN_BOD', 'OUT_BOD', 'IN_TOC', 'OUT_TOC', 'IN_SS', 'OUT_SS', 'IN_TN', 'OUT_TN', 'IN_TP', 'OUT_TP', 'IN_COLI', 'OUT_COLI',
|
||||
'TRT_EFF', 'TRT_LOD', 'TRT_MTHD',
|
||||
'inflwBodVl', 'dschgBodVl', 'inflwTocQnt', 'dschgTocQnt', 'inflwSsQnt', 'dschgSsQnt', 'inflwTnVl', 'dschgTnVl', 'inflwTpVl', 'dschgTpVl', 'inflwGrssClfgVl', 'dschgGrssClfgVl',
|
||||
'prcsRt', 'prcsLodQnt', 'prcsMthdExpln',
|
||||
{
|
||||
name: 'groupE',
|
||||
header: { text: '연계처리량(㎥/일)' },
|
||||
items: ['LNK_EXC', 'LNK_LVS', 'LNK_LCH', 'LNK_ETC'],
|
||||
items: ['wholHmnQnt', 'wholLvstQnt', 'wholEfntQnt', 'wholEtcQnt'],
|
||||
},
|
||||
'TEL_NO', 'COMP_DT', 'RN_DT', 'BSNS_CT', 'DSCH_DSF_MTHD',
|
||||
'telNo', 'cmptnStrtYmd', 'oprtngStrtYmd', 'wholBizVl', 'eflntDsnfMthdExpln',
|
||||
{
|
||||
name: 'groupF',
|
||||
header: { text: '방류수역' },
|
||||
items: ['WTR_SYS', 'STRM', 'DET_UNIT_ZONE', 'RGN_GBN',
|
||||
items: ['wtsysNm', 'trbtrNm', 'dtlUnitZoneCn', 'rgnSeNm',
|
||||
{
|
||||
name: 'groupG',
|
||||
header: { text: '중권역' },
|
||||
items: ['MID_ZONE_NM', 'MID_ZONE_BOD'],
|
||||
items: ['msirbCn', 'goalWqNo'],
|
||||
},
|
||||
],
|
||||
},
|
||||
'ENG_SLF_RT', 'ENG_SLF_QTY',
|
||||
'engySrlneRt', 'engyItslfPrdctnQnt',
|
||||
{
|
||||
name: 'groupH',
|
||||
header: { text: '연간 총 에너지 사용량(TOE)' },
|
||||
items: ['ENG_TOT_SUM', 'ENG_TOT_PWR', 'ENG_TOT_SLF'],
|
||||
items: ['anlEngyWholUseQnt', 'anlElpwrUseQnt', 'itslfEngyUseQnt'],
|
||||
},
|
||||
'CO2_EMISS', 'OPR_MBY',
|
||||
'swtrmtEmsnQnt', 'operMnbdCn',
|
||||
{
|
||||
name: 'groupI',
|
||||
header: { text: '위탁현황' },
|
||||
items: ['ENTRPS_NM', 'ENTRPS_CT', 'ENTRPS_TERM'],
|
||||
items: ['prxyBzentyNmCn', 'cnsgnCstVl', 'prxyCtrtCn'],
|
||||
},
|
||||
'CTR_TRT_FCT', 'EMP_CNT', 'ACC_CNT', 'RMK'
|
||||
]
|
||||
'intgPrcsFcltCn', 'wholNope', 'sftyAcdntEvntcsCnt', 'rmrk'
|
||||
],
|
||||
fixedColumns: 3,
|
||||
rowStartNum: 3
|
||||
};
|
||||
|
||||
// 최종 Export 객체 (일괄 editable: false 적용)
|
||||
@@ -144,6 +192,7 @@ export const PublicSewageFacilityInfoList = {
|
||||
...rawPublicSewageFacilityInfoList,
|
||||
columns: rawPublicSewageFacilityInfoList.columns.map(col => ({
|
||||
...col,
|
||||
editable: false
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly"
|
||||
}))
|
||||
};
|
||||
@@ -30,6 +30,10 @@ import {
|
||||
import {
|
||||
type MediationPumpStationApiResponse,
|
||||
} from "../model/statistics-pumping-station-review-interface";
|
||||
import {
|
||||
type PublicSewageFacilityReviewApiResponse,
|
||||
type PublicSewageFacilityReviewRequestParams,
|
||||
} from "../model/statistics-public-sewage-facility-review-interface";
|
||||
|
||||
|
||||
export class StatisticsManagementApiService {
|
||||
@@ -61,6 +65,20 @@ export class StatisticsManagementApiService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 06. 공공하수처리시설 검토 목록 조회
|
||||
* @param params (sido, gugun, yr, bsnmCd, fctName)
|
||||
*/
|
||||
public async getPublicSewageFacilityReviewList(
|
||||
params: PublicSewageFacilityReviewRequestParams
|
||||
): Promise<PublicSewageFacilityReviewApiResponse> {
|
||||
const response = await stClient.get<PublicSewageFacilityReviewApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/public-sewage`,
|
||||
{ params }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 02. 하수관로 지역별 시설현황 목록 조회 (Tab1)
|
||||
* @param params (sido, gugun, yr, bsnmCd)
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type ReviewSubmitResponse,
|
||||
type SearchCondition,
|
||||
type Sgg,
|
||||
type StatisticsColumnGuideListResponse,
|
||||
type StatisticsLogDetailResponse,
|
||||
type StatisticsLogRequest,
|
||||
type StatisticsLogResponse,
|
||||
@@ -91,6 +92,17 @@ export class StatisticsApiService implements StatisticsService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 통계 입력 기간 수정 — 경로는 저장과 동일, 메서드는 PUT */
|
||||
public async updateStatsInputPeriod(
|
||||
param: StatsInputPeriodSaveRequest,
|
||||
): Promise<StatsInputPeriodSaveApiResponse> {
|
||||
const response = await stClient.put<StatsInputPeriodSaveApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info`,
|
||||
param,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 통계 입력 기간 삭제 — JSON body. HTTP 메서드·URL은 백엔드 스펙에 맞게 수정 */
|
||||
public async deleteStatsInputPeriod(
|
||||
param: StatsInputPeriodDeleteRequest,
|
||||
@@ -158,6 +170,17 @@ export class StatisticsApiService implements StatisticsService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 컬럼 가이드 목록 조회 — GET `.../common/column-guides?statsCd=` */
|
||||
public async getColumnGuides(
|
||||
param: string,
|
||||
): Promise<StatisticsColumnGuideListResponse> {
|
||||
const response = await stClient.get<StatisticsColumnGuideListResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUTCOMMON}/column-guides`,
|
||||
{ params: { statsCd: param } },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async saveReviewOpinion(
|
||||
param: ReviewOpinionSaveRequest,
|
||||
): Promise<ReviewOpinionSaveResponse> {
|
||||
@@ -181,9 +204,11 @@ export class StatisticsApiService implements StatisticsService {
|
||||
public async saveReviewReturn(
|
||||
param: ReviewReturnSaveRequest,
|
||||
): Promise<ReviewReturnSaveResponse> {
|
||||
const { type, ...body } = param;
|
||||
const response = await stClient.post<ReviewReturnSaveResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/return`,
|
||||
param,
|
||||
`${ENDPOINT.ST_SERVICE.STATS}/reject`,
|
||||
body,
|
||||
{ params: { type } },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,18 @@ export interface ApiResponse<T> {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/** 통계 컬럼 가이드(영문 컬럼명·한글명·유형·계산 여부·가이드 문구) 한 행 */
|
||||
export interface StatisticsColumnGuideItem {
|
||||
engColNm: string;
|
||||
korColNm: string;
|
||||
colType: string;
|
||||
isCalcCol: string;
|
||||
columnGuide: string;
|
||||
}
|
||||
|
||||
/** 컬럼 가이드 정보 조회 응답 (`data`: 컬럼 가이드 배열) */
|
||||
export type StatisticsColumnGuideListResponse = ApiResponse<StatisticsColumnGuideItem[]>;
|
||||
|
||||
/** 통계 입력 관리 등 — 연도·구분·분류(입력/검증)별 입력 기간 한 행 */
|
||||
export interface StatsInputPeriodItem {
|
||||
statsYr: string;
|
||||
@@ -42,6 +54,7 @@ export interface StatsInputPeriodItem {
|
||||
seType: "INPT" | "VRFC";
|
||||
inptBgngYmd: string;
|
||||
inptEndYmd: string;
|
||||
inptYmdSn: number | null;
|
||||
}
|
||||
|
||||
/** `data`가 `StatsInputPeriodItem[]` 인 조회 응답 */
|
||||
@@ -51,13 +64,14 @@ export type StatsInputPeriodListApiResponse = ApiResponse<StatsInputPeriodItem[]
|
||||
export interface StatsInputPeriodListRequest {
|
||||
statsYr?: string;
|
||||
/** 전체 / 입력 / 검증 (SearchForm `clsfCd`와 동일) */
|
||||
seType?: "ALL" | "INPT" | "VRFC";
|
||||
seType?: "" | "ALL" | "INPT" | "VRFC";
|
||||
}
|
||||
|
||||
/** 통계 입력 기간 등록·수정 등 요청 본문 */
|
||||
export interface StatsInputPeriodSaveRequest {
|
||||
statsYr: string;
|
||||
seType: string;
|
||||
inptYmdSn: number;
|
||||
seCd: number;
|
||||
/** 시작일 (백엔드 형식 문자열, 예: YYYYMMDD) */
|
||||
inptBgngYmd: string;
|
||||
@@ -215,6 +229,10 @@ export interface StatisticsLog {
|
||||
statsDataChgComCd?: string;
|
||||
rgtrId?: string;
|
||||
regDt?: string;
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsDataChgComNm?: string;
|
||||
statsYr?: string;
|
||||
}
|
||||
|
||||
export interface StatisticsLogRequest {
|
||||
@@ -300,6 +318,7 @@ export interface ReviewOpinionSaveResponse {
|
||||
}
|
||||
|
||||
export interface ReviewReturnSaveRequest {
|
||||
type: "REVIEW";
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
@@ -349,7 +368,10 @@ export interface StatisticsService {
|
||||
getStatsInputPeriodList(
|
||||
params?: StatsInputPeriodListRequest,
|
||||
): Promise<StatsInputPeriodListApiResponse>;
|
||||
/** 입력 기간 등록 (POST) */
|
||||
saveStatsInputPeriod(param: StatsInputPeriodSaveRequest): Promise<StatsInputPeriodSaveApiResponse>;
|
||||
/** 입력 기간 수정 (PUT) */
|
||||
updateStatsInputPeriod(param: StatsInputPeriodSaveRequest): Promise<StatsInputPeriodSaveApiResponse>;
|
||||
deleteStatsInputPeriod(param: StatsInputPeriodDeleteRequest): Promise<StatsInputPeriodDeleteApiResponse>;
|
||||
cancelSubmit(param: cancelSubmitRequest): Promise<cancelSubmitResponse>;
|
||||
callImrService(param: ImrRequest): Promise<ImrResponse>;
|
||||
@@ -363,4 +385,6 @@ export interface StatisticsService {
|
||||
getVerificationResultList(param: VerificationResultListRequest): Promise<VerificationResultListResponse>;
|
||||
getImrStatus(param: string): Promise<ImrStatusResponse>;
|
||||
SubmitVerifyException(param: SubmitVerifyExceptionRequest): Promise<SubmitVerifyExceptionResponse>;
|
||||
/** `INPUTCOMMON/column-guides` — 통계코드 등 단일 쿼리 문자열 */
|
||||
getColumnGuides(param: string): Promise<StatisticsColumnGuideListResponse>;
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
import type { StatsStatusInfo } from "./statistics-sewage-pipe-review-interface";
|
||||
|
||||
export interface PublicSewageFacilityReviewItem {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
statsYr: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string;
|
||||
swtrmtFcltNm: string;
|
||||
displayYear: string;
|
||||
rowType: "OLD" | "CURR";
|
||||
fcltPstnCn: string | null;
|
||||
prcsFcltCpct: number;
|
||||
prcsFcltPhysCpct: number;
|
||||
prcsFcltBoilCpct: number;
|
||||
prcsFcltAltdCpct: number;
|
||||
inflwSwageQnt: number;
|
||||
inflwSwagePhysQnt: number;
|
||||
inflwSwageBoilQnt: number;
|
||||
inflwSwageAltdQnt: number;
|
||||
prcsQnt: number;
|
||||
prcsPhysCpct: number;
|
||||
prcsBoilCpct: number;
|
||||
prcsAltdCpct: number;
|
||||
inflwBodVl: number;
|
||||
dschgBodVl: number;
|
||||
inflwTocQnt: number;
|
||||
dschgTocQnt: number;
|
||||
inflwSsQnt: number;
|
||||
dschgSsQnt: number;
|
||||
inflwTnVl: number;
|
||||
dschgTnVl: number;
|
||||
inflwTpVl: number;
|
||||
dschgTpVl: number;
|
||||
inflwGrssClfgVl: number;
|
||||
dschgGrssClfgVl: number;
|
||||
prcsRt: number;
|
||||
prcsLodQnt: number;
|
||||
prcsMthdExpln: string | null;
|
||||
wholHmnQnt: number;
|
||||
wholLvstQnt: number;
|
||||
wholEfntQnt: number;
|
||||
wholEtcQnt: number;
|
||||
telNo: string | null;
|
||||
cmptnStrtYmd: string | null;
|
||||
oprtngStrtYmd: string | null;
|
||||
wholBizVl: number;
|
||||
eflntDsnfMthdExpln: string | null;
|
||||
wtsysNm: string | null;
|
||||
trbtrNm: string | null;
|
||||
dtlUnitZoneCn: string | null;
|
||||
rgnSeNm: string | null;
|
||||
msirbCn: string | null;
|
||||
goalWqNo: string | null;
|
||||
engySrlneRt: number;
|
||||
engyItslfPrdctnQnt: number;
|
||||
anlEngyWholUseQnt: number;
|
||||
anlElpwrUseQnt: number;
|
||||
itslfEngyUseQnt: number;
|
||||
swtrmtEmsnQnt: number;
|
||||
operMnbdCn: string | null;
|
||||
prxyBzentyNmCn: string | null;
|
||||
cnsgnCstVl: number;
|
||||
prxyCtrtCn: string | null;
|
||||
intgPrcsFcltCn: string | null;
|
||||
wholNope: number;
|
||||
sftyAcdntEvntcsCnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface PublicSewageFacilityReviewResponseData {
|
||||
publicSewageList: PublicSewageFacilityReviewItem[];
|
||||
statsStatusInfo: StatsStatusInfo;
|
||||
}
|
||||
|
||||
export interface PublicSewageFacilityReviewRequestParams {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
fctName?: string;
|
||||
}
|
||||
|
||||
export type PublicSewageFacilityReviewApiResponse =
|
||||
ApiResponse<PublicSewageFacilityReviewResponseData>;
|
||||
|
||||
export interface PublicSewageFacilityReviewService {
|
||||
getPublicSewageFacilityReviewList(
|
||||
params: PublicSewageFacilityReviewRequestParams,
|
||||
): Promise<PublicSewageFacilityReviewApiResponse>;
|
||||
}
|
||||
@@ -70,6 +70,7 @@ export const ENDPOINT = {
|
||||
COMMON: "/st-management-service/api/v1/common",
|
||||
INPUT: "/st-input-service/api/v1/input",
|
||||
REVIEW: "/st-management-service/api/v1/review",
|
||||
STATS: "/st-management-service/api/stats",
|
||||
INPUTCOMMON: "/st-input-service/api/v1/common",
|
||||
INPUTVERIFY: "/st-input-service/api/v1/verify",
|
||||
TOTAL: "/st-management-service/api/v1/stats",
|
||||
|
||||
Reference in New Issue
Block a user