feat: 그리드 텝 형식 컴포넌트 신규 생성

TabDataGrid.tsx(탭 형석 그리드) & SewagePipeColumn.ts(컬럼정보)
예시 화면 - 하수도 통계관리/하수관로
This commit is contained in:
2025-12-23 12:05:03 +09:00
parent 019415f124
commit ba4062f826
19 changed files with 798 additions and 4 deletions
@@ -0,0 +1,352 @@
import { useRef, useEffect, useState } from "react"
import RealGrid, { GridFitStyle } from "realgrid"
import { RealGridReact, RGDataColumn } from "realgrid-react"
import type { TabDataGridProps } from "../type/DataGrid.type"
import JSZip from "jszip"
import * as XLSX from "xlsx"
import { useAlertStore } from "@/shared/lib";
import { useTabStore } from "@/store/tab-store"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components";
import { Button } from "@common/components";
// Window 인터페이스 확장
declare global {
interface Window {
JSZip: typeof JSZip;
}
}
// RealGrid가 jszip을 사용할 수 있도록 전역에 등록
if (typeof window !== "undefined") {
window.JSZip = JSZip;
}
RealGrid.setLicenseKey(process.env.NEXT_PUBLIC_ST_REALGRID_KEY || '');
const TabDataGrid = ({ columnInfo, isExcelImport = true, rows = [] }: TabDataGridProps) => {
// 각 탭별로 그리드 ref와 데이터를 관리
const gridRefs = useRef<Map<string, RealGridReact>>(new Map());
const fileInputRefs = useRef<Map<string, HTMLInputElement>>(new Map());
const { setMessage: alert } = useAlertStore((state) => state);
const activeTabId = useTabStore((state) => state.activeTabId);
const menuList = useTabStore((state) => state.menuList);
// 각 탭별로 데이터를 관리 (key: tabKey)
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string>[]>>(new Map());
const [activeTab, setActiveTab] = useState<string>(() => {
// 초기 활성 탭을 첫 번째 tabKey로 설정
const firstTab = columnInfo[0];
return firstTab?.tabKey || '';
});
const exportExcel = (tabKey: string) => {
const currentRowData = rowDataMap.get(tabKey) || [];
if (currentRowData.length === 0) {
alert('데이터가 없습니다.');
return;
}
const gridRef = gridRefs.current.get(tabKey);
if (!gridRef?.gridView) return;
const gridView = gridRef.gridView;
const menuName = menuList
?.flatMap((m) => m.children || [])
.flatMap((subM) => subM || [])
.find((s) => s.menuEngNm === activeTabId)
?.menuKornNm || 'export';
const columnInfoItem = columnInfo.find(info => info.tabKey === tabKey);
const tabLabel = columnInfoItem?.tabLabel || tabKey;
gridView.exportGrid({
type: 'excel',
target: 'local',
fileName: `${menuName}_${tabLabel}.xlsx`,
footer: 'hidden',
done: () => {
alert('내보내기가 완료되었습니다.');
},
});
};
const importExcel = (tabKey: string) => {
const fileInput = fileInputRefs.current.get(tabKey);
fileInput?.click();
};
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>, tabKey: string) => {
const file = e.target.files?.[0];
if (!file) return;
const columnInfoItem = columnInfo.find(info => info.tabKey === tabKey);
if (!columnInfoItem) return;
const rowStartNum = columnInfoItem.rowStartNum || 1;
try {
const reader = new FileReader();
reader.onload = (event) => {
try {
const data = event.target?.result;
if (!data) {
alert('파일을 읽을 수 없습니다.');
return;
}
// SheetJS로 엑셀 파일을 JSON으로 변환
const workbook = XLSX.read(data, { type: 'array', cellDates: true });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName || ''];
if (!worksheet || !worksheet['!ref']) {
alert('워크시트를 읽을 수 없습니다.');
return;
}
const range = XLSX.utils.decode_range(worksheet['!ref']);
const headerRowCount = rowStartNum || 1; // 기본값 1
const headerStartRow = Math.max(0, headerRowCount - 1); // 0-based index
// 각 컬럼별로 헤더 찾기 (rowStartNum 행부터 위로 올라가면서)
const headers: string[] = [];
const headerRowIndexes: number[] = [];
for (let col = range.s.c; col <= range.e.c; col++) {
let headerValue = '';
let headerRowIndex = headerStartRow;
// rowStartNum 행부터 시작해서 위로 올라가면서 헤더 찾기
for (let rowIndex = headerStartRow; rowIndex >= 0; rowIndex--) {
const cell = worksheet[XLSX.utils.encode_cell({ r: rowIndex, c: col })];
if (cell && cell.v !== undefined && cell.v !== null && cell.v !== '') {
headerValue = String(cell.v);
headerRowIndex = rowIndex;
break;
}
}
// 헤더를 찾지 못한 경우 기본값 사용
if (!headerValue) {
headerValue = `Column${col + 1}`;
headerRowIndex = headerStartRow;
}
headers.push(headerValue);
headerRowIndexes.push(headerRowIndex);
}
// 실제 데이터가 시작하는 행 찾기 (헤더가 있는 최대 행 번호 + 1)
const maxHeaderRowIndex = headerRowIndexes.length > 0 ? Math.max(...headerRowIndexes) : headerStartRow;
const dataStartRow = maxHeaderRowIndex + 1;
// 데이터 행 읽기
const dataRows: Record<string, string>[] = [];
for (let row = dataStartRow; row <= range.e.r; row++) {
const rowData: Record<string, string> = {};
let hasData = false;
for (let colIndex = 0; colIndex < headers.length; colIndex++) {
const col = range.s.c + colIndex;
const cell = worksheet[XLSX.utils.encode_cell({ r: row, c: col })];
const headerName = headers[colIndex] || `Column${colIndex + 1}`;
const cellValue = cell && cell.v !== undefined && cell.v !== null
? String(cell.v)
: '';
if (cellValue !== '') {
hasData = true;
}
rowData[headerName] = cellValue;
}
// 데이터가 하나라도 있는 행만 추가
if (hasData) {
dataRows.push(rowData);
}
}
setRowDataMap(prev => {
const newMap = new Map(prev);
newMap.set(tabKey, dataRows);
return newMap;
});
} catch (error) {
console.error('엑셀 파일 파싱 오류:', error);
alert('엑셀 파일 파싱 중 오류가 발생했습니다.');
} finally {
const fileInput = fileInputRefs.current.get(tabKey);
if (fileInput) {
fileInput.value = '';
}
}
};
reader.onerror = () => {
alert('파일 읽기 중 오류가 발생했습니다.');
const fileInput = fileInputRefs.current.get(tabKey);
if (fileInput) {
fileInput.value = '';
}
};
reader.readAsArrayBuffer(file);
} catch (error) {
console.error('엑셀 파일 가져오기 오류:', error);
alert('엑셀 파일 가져오기 중 오류가 발생했습니다.');
const fileInput = fileInputRefs.current.get(tabKey);
if (fileInput) {
fileInput.value = '';
}
}
};
// 초기 데이터 설정
useEffect(() => {
if (rows.length > 0) {
columnInfo.forEach((info) => {
const tabKey = info.tabKey;
if (!rowDataMap.has(tabKey)) {
setRowDataMap(prev => {
const newMap = new Map(prev);
newMap.set(tabKey, rows);
return newMap;
});
}
});
}
}, [rows, columnInfo]);
// 활성 탭이 변경될 때마다 fitStyle 재적용 (그리드가 보일 때)
useEffect(() => {
const gridRef = gridRefs.current.get(activeTab);
if (!gridRef?.gridView) return;
const gridView = gridRef.gridView;
// 약간의 지연을 두고 설정 (그리드 렌더링 완료 후)
const timer = setTimeout(() => {
gridView.setDisplayOptions({
fitStyle: GridFitStyle.EVEN
});
gridView.refresh();
}, 50);
return () => clearTimeout(timer);
}, [activeTab]);
if (columnInfo.length === 0) {
return <div>컬럼 정보가 없습니다.</div>;
}
return (
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full" variant="outline">
<TabsList className="mb-4">
{columnInfo.map((info) => {
const tabKey = info.tabKey;
const tabLabel = info.tabLabel || tabKey;
return (
<TabsTrigger key={tabKey} value={tabKey}>
{tabLabel}
</TabsTrigger>
);
})}
</TabsList>
{columnInfo.map((info) => {
const tabKey = info.tabKey;
const { columns, columnLayout } = info;
const currentRowData = rowDataMap.get(tabKey) || [];
return (
<TabsContent key={tabKey} value={tabKey}>
<div className="flex flex-col gap-[0.6rem]">
<input
ref={(el) => {
if (el) {
fileInputRefs.current.set(tabKey, el);
} else {
fileInputRefs.current.delete(tabKey);
}
}}
type="file"
accept=".xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel"
style={{ display: 'none' }}
onChange={(e) => handleFileChange(e, tabKey)}
/>
<div className="flex justify-end items-center gap-[0.6rem]">
{isExcelImport && (
<Button
className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover"
type="button"
onClick={() => importExcel(tabKey)}
>
<i className="diveicon di-file-excel" />
<span>가져오기</span>
</Button>
)}
<Button
className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover"
type="button"
onClick={() => exportExcel(tabKey)}
>
<i className="diveicon di-file-excel" />
<span>내보내기</span>
</Button>
</div>
<div className="h-[60vh]">
<RealGridReact
ref={(el) => {
if (el) {
gridRefs.current.set(tabKey, el);
// 그리드가 생성된 직후 fitStyle 설정
if (el.gridView) {
setTimeout(() => {
const gridView = el.gridView;
if (!gridView) return;
gridView.setDisplayOptions({
fitStyle: GridFitStyle.EVEN
});
gridView.header.height = 60;
// 고정 컬럼 설정
const currentInfo = columnInfo.find(info => info.tabKey === tabKey);
if (currentInfo?.fixedColumns !== undefined && currentInfo.fixedColumns > 0) {
gridView.setFixedOptions({
colCount: currentInfo.fixedColumns,
});
}
}, 0);
}
} else {
gridRefs.current.delete(tabKey);
}
}}
autoGenerateField={true}
layout={columnLayout}
rows={currentRowData}
>
{columns.map((column) => (
<RGDataColumn
key={column.name}
name={column.name}
fieldName={column.fieldName}
header={column.header}
/>
))}
</RealGridReact>
</div>
</div>
</TabsContent>
);
})}
</Tabs>
</div>
);
};
export default TabDataGrid;
@@ -1,9 +1,30 @@
import SearchForm from "@/components/module/SearchForm";
import TabDataGrid from "@/components/module/TabDataGrid";
import ApproveButton from "@/components/button/ApproveButton";
import RejectButton from "@/components/button/RejectButton"
import { SewagePipeColumnInfoList } from "@/components/util/column/SewagePipeColumn";
const SewagePipeMng = () => {
const approveFunc = () => {
console.log('appoved');
// 승인 요청 로직
};
const rejectFunc = () => {
console.log('reject');
// 보완 요청 로직
};
return (
<>
<SearchForm userMenual={true} />
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<RejectButton rejectFunc={rejectFunc} />
<ApproveButton approveFunc={approveFunc} />
</div>
<TabDataGrid columnInfo={SewagePipeColumnInfoList}/>
</>
);
};
@@ -5,7 +5,7 @@ import SaveButton from "@/components/button/SaveButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
const SeweragePenetrationRate = () => {
const SeweragePenetrationRateMng = () => {
const columns = [
{ name: 'sido', fieldName: '시도', header: { text: '시도' } },
{ name: 'sgg', fieldName: '구군', header: { text: '구군' } },
@@ -136,4 +136,4 @@ const SeweragePenetrationRate = () => {
);
};
export default SeweragePenetrationRate;
export default SeweragePenetrationRateMng;
@@ -65,7 +65,7 @@ const StatsInputSatusMng = () => {
<RejectButton rejectFunc={rejectFunc} />
<ApproveButton approveFunc={approveFunc} />
</div>
<DataGrid columnInfo={{ columns, columnLayout }} rowStartNum={2} isExcelImport={true} fixedColumns={3} />
<DataGrid columnInfo={{ columns, columnLayout }} rowStartNum={2} fixedColumns={3} />
</>
);
};
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const ExcretaTreatmentFacilityTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default ExcretaTreatmentFacilityTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const PrivateSewageFacilityTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default PrivateSewageFacilityTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const PublicSewageFacilityTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default PublicSewageFacilityTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const PumpingStationTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default PumpingStationTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewageExcretaRelatedTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewageExcretaRelatedTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewageExcretaSlurgeTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewageExcretaSlurgeTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewagePipeMaintainTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewagePipeMaintainTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewagePipeTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewagePipeTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewageReservoirPumpTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewageReservoirPumpTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewerageFinanceTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewerageFinanceTot;
@@ -0,0 +1,139 @@
import SearchForm from "@/components/module/SearchForm";
import DataGrid from "@/components/module/DataGrid";
import SubmitButton from "@/components/button/SumitButton";
import SaveButton from "@/components/button/SaveButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
const SeweragePenetrationRateTot = () => {
const columns = [
{ name: 'sido', fieldName: '시도', header: { text: '시도' } },
{ name: 'sgg', fieldName: '구군', header: { text: '구군' } },
{ name: 'rvbsn', fieldName: '수계', header: { text: '수계' } },
{ name: 'bstrm', fieldName: '지류', header: { text: '지류' } },
{ name: 'mdbsn', fieldName: '세부단위유역', header: { text: '세부단위유역' } },
{ name: 'lgbsn', fieldName: '중권역', header: { text: '중권역' } },
{ name: 'mdmbsn', fieldName: '소권역', header: { text: '소권역' } },
{ name: 'specl_cntrpln_ar', fieldName: '특별대책지역', header: { text: '특별대책지역' } },
{ name: 'total_popl', fieldName: '총인구', header: { text: '총인구' } },
{ name: 'popl_sm', fieldName: '내국인', header: { text: '내국인' } },
{ name: 'frgnr_popl_sm', fieldName: '외국인', header: { text: '외국인' } },
{ name: 'area', fieldName: '행정참고자료면적(㎢)', header: { text: '행정참고자료면적(㎢)' } },
{ name: 'popltn_sm', fieldName: '총인구(명)', header: { text: '총인구(명)' } },
{ name: 'ar_sm', fieldName: '총면적(㎢)', header: { text: '총면적(㎢)' } },
{ name: 'swge_proc_zone_inner_sm', fieldName: '합계', header: { text: '합계' } },
{ name: 'pblswrg_popltn_sm', fieldName: '소계', header: { text: '소계' } },
{ name: 'pblswrg_prm_trt', fieldName: '물리적처리', header: { text: '물리적처리' } },
{ name: 'pblswrg_scd_trt', fieldName: '생물학적처리', header: { text: '생물학적처리' } },
{ name: 'pblswrg_tert_trt', fieldName: '고도처리', header: { text: '고도처리' } },
{ name: 'wstr_popltn_sm', fieldName: '소계', header: { text: '소계' } },
{ name: 'wstr_prm_trt', fieldName: '물리적처리', header: { text: '물리적처리' } },
{ name: 'wstr_scd_trt', fieldName: '생물학적처리', header: { text: '생물학적처리' } },
{ name: 'wstr_tert_trt', fieldName: '고도처리', header: { text: '고도처리' } },
{ name: 'not_conect_popltn', fieldName: '미접속인구', header: { text: '미접속인구' } },
{ name: 'swge_proc_zone_inner_ar', fieldName: '면적(㎢)', header: { text: '면적(㎢)' } },
{ name: 'swge_proc_zone_outer_sm', fieldName: '합계', header: { text: '합계' } },
{ name: 'town_unproc_popltn', fieldName: '미처리인구', header: { text: '미처리인구' } },
{ name: 'town_swtt_popltn', fieldName: '오수처리인구', header: { text: '오수처리인구' } },
{ name: 'town_wrrtn_popltn', fieldName: '정화조인구', header: { text: '정화조인구' } },
{ name: 'swge_proc_zone_outer_ar', fieldName: '면적(㎢)', header: { text: '면적(㎢)' } },
{ name: 'pblswrg_popltn_suprt', fieldName: '공공하수처리구역인구보급율(%)', header: { text: '공공하수처리구역인구보급율(%)' } },
{ name: 'adtrt_popltn_suprt', fieldName: '고도처리인구보급율(%)', header: { text: `고도처리인구보급율(%)` } },
{ name: 'swrg_instlrt', fieldName: '하수도설치율(%)', header: { text: '하수도설치율(%)' } },
{ name: 'rmk', fieldName: '비고', header: { text: '비고' } },
];
const columnLayout = [
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '행정구역' },
items: ['sido', 'sgg'],
},
'rvbsn',
'bstrm',
{
name: 'groupB',
hideChildHeaders: false,
header: { text: '수계영향권별분류류' },
items: ['mdbsn', 'lgbsn', 'mdmbsn'],
},
'specl_cntrpln_ar',
{
name: 'groupC',
hideChildHeaders: false,
header: { text: '행정참고자료인구' },
items: ['total_popl', 'popl_sm', 'frgnr_popl_sm'],
},
'area',
'popltn_sm',
'ar_sm',
{
name: 'groupD',
hideChildHeaders: false,
header: { text: '하수처리구역내' },
items: [
'swge_proc_zone_inner_sm',
{
name: 'pblswrgGroup',
hideChildHeaders: false,
header: { text: '공공하수처리인구(명)' },
items: ['pblswrg_popltn_sm', 'pblswrg_prm_trt', 'pblswrg_scd_trt', 'pblswrg_tert_trt'],
},
{
name: 'wstrGroup',
hideChildHeaders: false,
header: { text: '폐수처리인구(명)' },
items: ['wstr_popltn_sm', 'wstr_prm_trt', 'wstr_scd_trt', 'wstr_tert_trt'],
},
'not_conect_popltn',
'swge_proc_zone_inner_ar'
],
},
{
name: 'groupE',
hideChildHeaders: false,
header: { text: '하수처리구역외' },
items: ['swge_proc_zone_outer_sm', 'town_swtt_popltn', 'town_unproc_popltn', 'town_wrrtn_popltn', 'swge_proc_zone_outer_ar'],
},
'pblswrg_popltn_suprt',
'adtrt_popltn_suprt',
'swrg_instlrt',
'rmk',
];
const submitFunc = () => {
console.log('submit');
// 제출 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const getLastYearDataFunc = () => {
console.log('get last year data');
// 지난연도 데이터 가져오기 로직
};
const SubmitExceptionFunc = () => {
console.log('submit exception');
// 예외 제출 로직
};
return (
<>
<SearchForm userMenual={true} />
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
</div>
<DataGrid columnInfo={{columns, columnLayout}} rowStartNum={2} isExcelImport={true} fixedColumns={1} />
</>
);
};
export default SeweragePenetrationRateTot;
@@ -0,0 +1,11 @@
import SearchForm from "../../module/SearchForm";
const SewerageServiceLevelTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewerageServiceLevelTot;
@@ -0,0 +1,11 @@
import SearchForm from "../../module/SearchForm";
const WaterReuseTot = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default WaterReuseTot;
+17 -1
View File
@@ -16,11 +16,27 @@ export type ColumnInfo = {
columnLayout: ColumnLayoutItem[];
};
export type ColumnInfoList = {
tabKey: string; // 탭을 식별하는 고유 키 (필수), server(api)에 전송할 변수 값
columns: ColumnConfig[];
columnLayout: ColumnLayoutItem[];
rowStartNum?: number;
fixedColumns?: number;
tabLabel?: string; // 탭에 표시될 레이블
}
export type DataGridProps = {
columnInfo: ColumnInfo;
rows?: Record<string, string>[];
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
isExcelImport?: boolean; // 엑셀 가져오기 유무무
isExcelImport?: boolean; // 엑셀 가져오기 유무
onRowsChange?: (rows: Record<string, string>[]) => void;
fixedColumns?: number; // 고정할 컬럼 개수 (좌측부터)
};
export type TabDataGridProps = {
columnInfo: ColumnInfoList[];
rows?: Record<string, string>[];
isExcelImport?: boolean; // 엑셀 가져오기 유무
onRowsChange?: (rows: Record<string, string>[]) => void;
};
@@ -0,0 +1,134 @@
export const SewagePipeColumnInfoList = [
{
columns: [
{ name: 'sido', fieldName: '시도', header: { text: '시도' } },
{ name: 'sgg', fieldName: '구군', header: { text: '구군' } },
{ name: 'menu_nm', fieldName: '구분', header: { text: '구분' } },
{ name: 'locg_status_nm', fieldName: '진행상황', header: { text: '진행상황' } },
{ name: 'locg_status', fieldName: '작성률', header: { text: '작성률' } },
{ name: 'locg_charger', fieldName: '작성자', header: { text: '작성자' } },
{ name: 'locg_cttpc', fieldName: '작성자 연락처', header: { text: '작성자 연락처' } },
{ name: 'locg_dept', fieldName: '작성자 부서', header: { text: '작성자 부서' } },
{ name: 'locg_job', fieldName: '작성자 직제', header: { text: '작성자 직제' } },
{ name: 'locg_cnfirm_nm', fieldName: '승인자', header: { text: '승인자' } },
{ name: 'locg_cnfirm_dept', fieldName: '승인자 부서', header: { text: '승인자 부서' } },
{ name: 'locg_cnfirm_job', fieldName: '승인자 직제', header: { text: '승인자 직제' } },
{ name: 'return_opinion_sm', fieldName: '반려 사유', header: { text: '반려 사유' } },
],
columnLayout: [
'sido',
'sgg',
'menu_nm',
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '하수도사업자' },
items: ['locg_status_nm', 'locg_status', 'locg_charger', 'locg_cttpc', 'locg_dept', 'locg_job', 'locg_cnfirm_nm', 'locg_cnfirm_dept', 'locg_cnfirm_job', 'return_opinion_sm'],
},
],
rowStartNum: 2,
fixedColumns: 3,
tabKey: 'a',
tabLabel: '1번째 텝'
},
{
columns: [
{ name: 'sido', fieldName: '시도', header: { text: '시도' } },
{ name: 'sgg', fieldName: '구군', header: { text: '구군' } },
{ name: 'rvbsn', fieldName: '수계', header: { text: '수계' } },
{ name: 'bstrm', fieldName: '지류', header: { text: '지류' } },
{ name: 'mdbsn', fieldName: '세부단위유역', header: { text: '세부단위유역' } },
{ name: 'lgbsn', fieldName: '중권역', header: { text: '중권역' } },
{ name: 'mdmbsn', fieldName: '소권역', header: { text: '소권역' } },
{ name: 'specl_cntrpln_ar', fieldName: '특별대책지역', header: { text: '특별대책지역' } },
{ name: 'total_popl', fieldName: '총인구', header: { text: '총인구' } },
{ name: 'popl_sm', fieldName: '내국인', header: { text: '내국인' } },
{ name: 'frgnr_popl_sm', fieldName: '외국인', header: { text: '외국인' } },
{ name: 'area', fieldName: '행정참고자료면적(㎢)', header: { text: '행정참고자료면적(㎢)' } },
{ name: 'popltn_sm', fieldName: '총인구(명)', header: { text: '총인구(명)' } },
{ name: 'ar_sm', fieldName: '총면적(㎢)', header: { text: '총면적(㎢)' } },
{ name: 'swge_proc_zone_inner_sm', fieldName: '합계', header: { text: '합계' } },
{ name: 'pblswrg_popltn_sm', fieldName: '소계', header: { text: '소계' } },
{ name: 'pblswrg_prm_trt', fieldName: '물리적처리', header: { text: '물리적처리' } },
{ name: 'pblswrg_scd_trt', fieldName: '생물학적처리', header: { text: '생물학적처리' } },
{ name: 'pblswrg_tert_trt', fieldName: '고도처리', header: { text: '고도처리' } },
{ name: 'wstr_popltn_sm', fieldName: '소계', header: { text: '소계' } },
{ name: 'wstr_prm_trt', fieldName: '물리적처리', header: { text: '물리적처리' } },
{ name: 'wstr_scd_trt', fieldName: '생물학적처리', header: { text: '생물학적처리' } },
{ name: 'wstr_tert_trt', fieldName: '고도처리', header: { text: '고도처리' } },
{ name: 'not_conect_popltn', fieldName: '미접속인구', header: { text: '미접속인구' } },
{ name: 'swge_proc_zone_inner_ar', fieldName: '면적(㎢)', header: { text: '면적(㎢)' } },
{ name: 'swge_proc_zone_outer_sm', fieldName: '합계', header: { text: '합계' } },
{ name: 'town_unproc_popltn', fieldName: '미처리인구', header: { text: '미처리인구' } },
{ name: 'town_swtt_popltn', fieldName: '오수처리인구', header: { text: '오수처리인구' } },
{ name: 'town_wrrtn_popltn', fieldName: '정화조인구', header: { text: '정화조인구' } },
{ name: 'swge_proc_zone_outer_ar', fieldName: '면적(㎢)', header: { text: '면적(㎢)' } },
{ name: 'pblswrg_popltn_suprt', fieldName: '공공하수처리구역인구보급율(%)', header: { text: '공공하수처리구역인구보급율(%)' } },
{ name: 'adtrt_popltn_suprt', fieldName: '고도처리인구보급율(%)', header: { text: `고도처리인구보급율(%)` } },
{ name: 'swrg_instlrt', fieldName: '하수도설치율(%)', header: { text: '하수도설치율(%)' } },
{ name: 'rmk', fieldName: '비고', header: { text: '비고' } },
],
columnLayout: [
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '행정구역' },
items: ['sido', 'sgg'],
},
'rvbsn',
'bstrm',
{
name: 'groupB',
hideChildHeaders: false,
header: { text: '수계영향권별분류류' },
items: ['mdbsn', 'lgbsn', 'mdmbsn'],
},
'specl_cntrpln_ar',
{
name: 'groupC',
hideChildHeaders: false,
header: { text: '행정참고자료인구' },
items: ['total_popl', 'popl_sm', 'frgnr_popl_sm'],
},
'area',
'popltn_sm',
'ar_sm',
{
name: 'groupD',
hideChildHeaders: false,
header: { text: '하수처리구역내' },
items: [
'swge_proc_zone_inner_sm',
{
name: 'pblswrgGroup',
hideChildHeaders: false,
header: { text: '공공하수처리인구(명)' },
items: ['pblswrg_popltn_sm', 'pblswrg_prm_trt', 'pblswrg_scd_trt', 'pblswrg_tert_trt'],
},
{
name: 'wstrGroup',
hideChildHeaders: false,
header: { text: '폐수처리인구(명)' },
items: ['wstr_popltn_sm', 'wstr_prm_trt', 'wstr_scd_trt', 'wstr_tert_trt'],
},
'not_conect_popltn',
'swge_proc_zone_inner_ar'
],
},
{
name: 'groupE',
hideChildHeaders: false,
header: { text: '하수처리구역외' },
items: ['swge_proc_zone_outer_sm', 'town_swtt_popltn', 'town_unproc_popltn', 'town_wrrtn_popltn', 'swge_proc_zone_outer_ar'],
},
'pblswrg_popltn_suprt',
'adtrt_popltn_suprt',
'swrg_instlrt',
'rmk',
],
rowStartNum: 2,
fixedColumns: 1,
tabKey: 'b',
tabLabel: '2번째 텝'
},
]