feat: 그리드 텝 형식 컴포넌트 신규 생성
TabDataGrid.tsx(탭 형석 그리드) & SewagePipeColumn.ts(컬럼정보) 예시 화면 - 하수도 통계관리/하수관로
This commit is contained in:
@@ -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;
|
||||
@@ -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번째 텝'
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user