Merge branch 'iipbs/st/dev' of https://onasys.co.kr:3000/ONAANDALL/frontend into feature/management-skeleton-eunsun
This commit is contained in:
@@ -7,6 +7,7 @@ import { useQueryClient, useQuery } from "@tanstack/react-query";
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { useMenuStore, useAlertStore } from "@/shared/lib";
|
||||
import { useTabStore, getTabStoreState } from "@/store/tab-store";
|
||||
import { useTabStore as gridTabStore } from "@rsone/components";
|
||||
import { getConfig } from "@common/config";
|
||||
import { getCookie } from "@/shared/lib";
|
||||
import { getMenuList } from "../../../../packages/common/components/src/api";
|
||||
@@ -33,6 +34,9 @@ export default function WithBaseLayout({
|
||||
const addTab = useTabStore((state) => state.addTab);
|
||||
const setActiveTab = useTabStore((state) => state.setActiveTab);
|
||||
const setMenuList = useTabStore((state) => state.setMenuList);
|
||||
const addTabForGrid = gridTabStore((state) => state.addTab);
|
||||
const setActiveTabForGrid = gridTabStore((state) => state.setActiveTab);
|
||||
const setMenuListForGrid = gridTabStore((state) => state.setMenuList);
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const { data: menuListData } = useQuery({
|
||||
@@ -44,8 +48,9 @@ export default function WithBaseLayout({
|
||||
useEffect(() => {
|
||||
if (menuListData) {
|
||||
setMenuList(menuListData);
|
||||
setMenuListForGrid(menuListData);
|
||||
}
|
||||
}, [menuListData, setMenuList]);
|
||||
}, [menuListData, setMenuList, setMenuListForGrid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pathname) return;
|
||||
@@ -55,18 +60,24 @@ export default function WithBaseLayout({
|
||||
const tab = currentTabs.find((t) => t.path === pathname);
|
||||
if (tab) {
|
||||
setActiveTab(tab.id);
|
||||
setActiveTabForGrid(tab.id);
|
||||
}
|
||||
}, [pathname, setActiveTab]);
|
||||
}, [pathname, setActiveTab, setActiveTabForGrid]);
|
||||
|
||||
const handleMenuClick = useCallback((item: MenuItem) => {
|
||||
setCurrentMenuInfo(item);
|
||||
const addTabResult: boolean = addTab(item);
|
||||
|
||||
// packages/rsone의 스토어에도 동기화
|
||||
if (addTabResult) {
|
||||
addTabForGrid(item);
|
||||
}
|
||||
|
||||
if (!addTabResult) {
|
||||
alert(`최대 추가 할 수 있는 화면 수를 초과 하였습니다.
|
||||
기존 화면을 닫은 후 이용해주세요.`);
|
||||
}
|
||||
}, [setCurrentMenuInfo, addTab]);
|
||||
}, [setCurrentMenuInfo, addTab, addTabForGrid]);
|
||||
|
||||
return (
|
||||
<BaseLayout
|
||||
|
||||
@@ -32,7 +32,7 @@ export function middleware(req: NextRequest) {
|
||||
`[Request]\n Original Pathname: ${pathname}`,
|
||||
];
|
||||
|
||||
if (pathname.startsWith("/st-service")) {
|
||||
if (pathname.startsWith("/st-management-service") || pathname.startsWith("/st-input-service")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
const token = req.cookies.get(CLAIM_NAME)?.value;
|
||||
const userId = req.cookies.get(AUTH_USER_ID)?.value;
|
||||
@@ -138,5 +138,5 @@ export function middleware(req: NextRequest) {
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/server/:path*", "/attach-service/:path*", "/api/link/:path*", "/st-service/:path*", "/"],
|
||||
matcher: ["/server/:path*", "/attach-service/:path*", "/api/link/:path*", "/st-management-service/:path*", "/st-input-service/:path*", "/"],
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"@common/assets": "workspace:*",
|
||||
"@common/components": "workspace:*",
|
||||
"@common/config": "workspace:*",
|
||||
"@rsone/components": "workspace:*",
|
||||
"@fontsource-variable/noto-sans-kr": "^5.2.5",
|
||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||
"@fullcalendar/core": "^6.1.17",
|
||||
|
||||
@@ -13,6 +13,7 @@ import { PromptDialog } from "@/shared/ui/prompt-dialog";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { SelectPromptDialog } from "@/shared/ui/select-prompt-dialog";
|
||||
import { PromptProvider, StoreProvider } from "@/shared/providers";
|
||||
import { AlertDialog as RsoneAlertDialog } from "@rsone/components";
|
||||
|
||||
const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||
const queryClient = new QueryClient();
|
||||
@@ -31,6 +32,7 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||
</DndProvider>
|
||||
</MenuWrapper>
|
||||
<AlertDialog />
|
||||
<RsoneAlertDialog />
|
||||
<PromptDialog />
|
||||
<ConfirmDialog />
|
||||
<SelectPromptDialog />
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/* RealGrid2 폰트 설정 - 루트 폰트와 일치 */
|
||||
.rg-root,
|
||||
.rg-grid,
|
||||
.rg-body,
|
||||
.rg-cell,
|
||||
.rg-header,
|
||||
.rg-head,
|
||||
.rg-rowbarcontainer {
|
||||
font-family: "Noto Sans KR", sans-serif !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
/* RealGrid2 컨테이너 스타일 */
|
||||
.rg-root {
|
||||
border-radius: 0.5rem !important;
|
||||
overflow: hidden !important;
|
||||
border: 1px solid #dbe2e6 !important;
|
||||
}
|
||||
|
||||
.rg-grid {
|
||||
border-radius: 0.5rem !important;
|
||||
/* box-shadow: 0 0 10px -1px rgba(0, 0, 0, 0.06); */
|
||||
}
|
||||
|
||||
/* 헤더 스타일 */
|
||||
.rg-header,
|
||||
.rg-head,
|
||||
.rg-foot,
|
||||
.rg-footer,
|
||||
.rg-fixed-footer {
|
||||
background-color: #f5f7fa !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
/* 헤더 셀 스타일 */
|
||||
.rg-header .rg-cell,
|
||||
.rg-head .rg-cell {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 헤더 필터링 영역 위치 조정 - 오른쪽 여백 추가 */
|
||||
.rg-header-filtering {
|
||||
left: auto !important;
|
||||
right: 8px !important;
|
||||
}
|
||||
|
||||
/* 그룹 헤더 스타일 */
|
||||
.rg-header-group,
|
||||
.rg-header-group-cell {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 첫 번째 헤더 셀 - 왼쪽 상단 모서리 */
|
||||
.rg-header .rg-cell:first-child,
|
||||
.rg-head .rg-cell:first-child {
|
||||
border-top-left-radius: 8px !important;
|
||||
}
|
||||
|
||||
/* 마지막 헤더 셀 - 오른쪽 상단 모서리 */
|
||||
.rg-header .rg-cell:last-child,
|
||||
.rg-head .rg-cell:last-child {
|
||||
border-top-right-radius: 8px !important;
|
||||
}
|
||||
|
||||
/* border color 통일 */
|
||||
.rg-header .rg-table tr td,
|
||||
.rg-head .rg-table tr td:not(:first-child),
|
||||
.rg-body .rg-table tr td,
|
||||
.rg-rowbarcontainer .rg-table tr td:not(:first-child) {
|
||||
border-left: 1px solid #dbe2e6 !important;
|
||||
border-bottom: 1px solid #dbe2e6 !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
.rg-rowindicator-head,
|
||||
.rg-rowbarcontainer .rg-table tr td:first-child {
|
||||
border-right: none !important;
|
||||
border-bottom: 1px solid #dbe2e6 !important;
|
||||
border-left: none !important;
|
||||
}
|
||||
.rg-footer .rg-table tr td,
|
||||
.rg-fixed-footer .rg-table tr td,
|
||||
.rg-foot .rg-table tr td {
|
||||
border-right: 1px solid #dbe2e6 !important;
|
||||
border-top: 1px solid #dbe2e6 !important;
|
||||
}
|
||||
|
||||
.rg-rowbarcontainer .rg-table tr td {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 푸터 마지막 요소 - 오른쪽 border 제거 */
|
||||
.rg-footer .rg-table tr td:last-child {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
/* 고정 컬럼 바 */
|
||||
.rg-fixed-column-bar {
|
||||
background-color: #ccc !important;
|
||||
border-right: 1px solid #ccc !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRef, useEffect } from "react"
|
||||
import { ValueType } from "realgrid"
|
||||
|
||||
import RealGrid, { GridFitStyle, type DisplayOptions, SortDirection } from "realgrid"
|
||||
import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid"
|
||||
import { RealGridReact, RGDataColumn } from "realgrid-react"
|
||||
import type { DataGridProps } from "../type/DataGrid.type"
|
||||
import JSZip from "jszip"
|
||||
@@ -312,10 +312,12 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport
|
||||
<span>가져오기</span>
|
||||
</Button>
|
||||
}
|
||||
{isExcelExport && <Button className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover w-[96px]" type="button" onClick={exportExcel}>
|
||||
<i className="diveicon di-file-excel" />
|
||||
<span>내보내기</span>
|
||||
</Button>}
|
||||
{isExcelExport &&
|
||||
<Button className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover w-[96px]" type="button" onClick={exportExcel}>
|
||||
<i className="diveicon di-file-excel" />
|
||||
<span>내보내기</span>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
<div className="h-[65vh]">
|
||||
<RealGridReact
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRef, useEffect, useState } from "react"
|
||||
import { ValueType } from "realgrid"
|
||||
|
||||
import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid"
|
||||
import RealGrid, { GridFitStyle } from "realgrid"
|
||||
import { RealGridReact, RGDataColumn } from "realgrid-react"
|
||||
import type { TabDataGridProps } from "../type/DataGrid.type"
|
||||
import JSZip from "jszip"
|
||||
@@ -15,7 +15,7 @@ import { Button } from "@common/components";
|
||||
// RealGrid 타입 확장
|
||||
declare module "realgrid" {
|
||||
interface GridView {
|
||||
setOptions(options: {
|
||||
setOptions(options: {
|
||||
footer?: { visible?: boolean };
|
||||
summaryMode?: 'aggregate' | 'statistical' | 'none';
|
||||
checkBar?: { visible?: boolean };
|
||||
@@ -161,7 +161,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
|
||||
const col = range.s.c + colIndex;
|
||||
const cell = worksheet[XLSX.utils.encode_cell({ r: row, c: col })];
|
||||
const headerName = headers[colIndex] || `Column${colIndex + 1}`;
|
||||
|
||||
|
||||
let cellValue: string | number = '';
|
||||
if (cell && cell.v !== undefined && cell.v !== null) {
|
||||
// 엑셀 셀 타입이 숫자이거나 값이 숫자인 경우 숫자로 변환
|
||||
@@ -301,24 +301,26 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
|
||||
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 w-[96px]"
|
||||
type="button"
|
||||
{isExcelImport &&
|
||||
<Button
|
||||
className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover w-[96px]"
|
||||
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 w-[96px]"
|
||||
type="button"
|
||||
onClick={() => exportExcel(tabKey)}
|
||||
>
|
||||
<i className="diveicon di-file-excel" />
|
||||
<span>내보내기</span>
|
||||
</Button>
|
||||
}
|
||||
{isExcelExport &&
|
||||
<Button
|
||||
className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover w-[96px]"
|
||||
type="button"
|
||||
onClick={() => exportExcel(tabKey)}
|
||||
>
|
||||
<i className="diveicon di-file-excel" />
|
||||
<span>내보내기</span>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
<div className="h-[60vh]">
|
||||
<RealGridReact
|
||||
@@ -330,31 +332,31 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
|
||||
setTimeout(() => {
|
||||
const gridView = el.gridView;
|
||||
if (!gridView) return;
|
||||
|
||||
|
||||
const currentInfo = columnInfo.find(info => info.tabKey === tabKey);
|
||||
if (!currentInfo) return;
|
||||
|
||||
|
||||
gridView.setDisplayOptions({
|
||||
fitStyle: GridFitStyle.EVEN
|
||||
});
|
||||
gridView.header.height = 60;
|
||||
|
||||
|
||||
// 헤더 체크박스 표시 여부 설정
|
||||
gridView.setOptions({
|
||||
checkBar: { visible: showHeaderCheckbox },
|
||||
rowIndicator: { visible: false },
|
||||
});
|
||||
|
||||
|
||||
// footer가 있는 컬럼이 있으면 footer 높이 설정
|
||||
const hasFooter = currentInfo.columns.some(col => col.footer);
|
||||
if (hasFooter) {
|
||||
gridView.footer.height = 40;
|
||||
}
|
||||
|
||||
|
||||
gridView.setOptions({
|
||||
summaryMode: "aggregate"
|
||||
});
|
||||
|
||||
|
||||
// footer의 valueCallback 설정
|
||||
currentInfo.columns.forEach(col => {
|
||||
if (col.footer && col.footer.valueCallback) {
|
||||
@@ -364,21 +366,21 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 고정 컬럼 설정
|
||||
if (currentInfo.fixedColumns !== undefined && currentInfo.fixedColumns > 0) {
|
||||
gridView.setFixedOptions({
|
||||
colCount: currentInfo.fixedColumns,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// valueType이 설정된 컬럼에 대해 필드의 dataType을 명시적으로 설정
|
||||
// autoGenerateField={true}일 때 valueType prop만으로는 필드의 dataType이 설정되지 않을 수 있음
|
||||
if (el.dataProvider) {
|
||||
const dataProvider = el.dataProvider;
|
||||
const fieldsToSet: Array<{ fieldName: string; dataType: string }> = [];
|
||||
const fieldMap = new Map<string, string>();
|
||||
|
||||
|
||||
currentInfo.columns.forEach(col => {
|
||||
if (col.valueType && col.fieldName && !fieldMap.has(col.fieldName)) {
|
||||
fieldMap.set(col.fieldName, col.valueType);
|
||||
@@ -395,13 +397,13 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
|
||||
// 기존 필드 정보 가져오기
|
||||
const existingFields = (dataProvider as { getFields?: () => Array<{ fieldName: string; dataType?: string }> }).getFields?.() || [];
|
||||
const existingFieldMap = new Map(existingFields.map(f => [f.fieldName, f.dataType || 'text']));
|
||||
|
||||
|
||||
// valueType이 설정된 필드 중 dataType이 다른 필드만 업데이트
|
||||
const fieldsToUpdate = fieldsToSet.filter(field => {
|
||||
const existingDataType = existingFieldMap.get(field.fieldName);
|
||||
return existingDataType !== field.dataType;
|
||||
});
|
||||
|
||||
|
||||
if (fieldsToUpdate.length > 0) {
|
||||
// 기존 필드 정보를 유지하면서 dataType만 업데이트
|
||||
// 컬럼 정의에 valueType이 있으면 그것을 우선 사용, 없으면 기존 필드의 dataType 사용
|
||||
@@ -409,14 +411,14 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
|
||||
fieldName: f.fieldName,
|
||||
dataType: fieldMap.get(f.fieldName) || f.dataType || 'text'
|
||||
}));
|
||||
|
||||
|
||||
// 새로 추가할 필드가 있으면 추가
|
||||
fieldsToSet.forEach(field => {
|
||||
if (!existingFieldMap.has(field.fieldName)) {
|
||||
allFields.push({ fieldName: field.fieldName, dataType: field.dataType });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// setFields를 호출하여 필드의 dataType 업데이트
|
||||
(dataProvider as { setFields: (fields: Array<{ fieldName: string; dataType: string }>) => void })
|
||||
.setFields(allFields);
|
||||
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { DataGrid } from "@rsone/components";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ExcretaTreatmentFacilityMngColumn } from "@/components/util/column/management/ExcretaTreatmentFacilityMngColumn"
|
||||
import { ExcretaTreatmentFacilityMngColumn } from "@/components/util/column/input/ExcretaTreatmentFacilityMngColumn"
|
||||
|
||||
// 하수도 통계 관리 > 07. 분뇨처리시설
|
||||
const ExcretaTreatmentFacilityMng = () => {
|
||||
const ExcretaTreatmentFacilityInp = () => {
|
||||
|
||||
const submitExceptionFunc = () => {
|
||||
console.log('submit exception');
|
||||
@@ -47,4 +47,4 @@ const ExcretaTreatmentFacilityMng = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ExcretaTreatmentFacilityMng;
|
||||
export default ExcretaTreatmentFacilityInp;
|
||||
+12
-12
@@ -1,25 +1,25 @@
|
||||
import { useState } from "react";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import { TabDataGrid } from "@rsone/components";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
|
||||
import { SewageTreatmentFacilityStatusMngColumn } from "@/components/util/column/management/privateSewageFacility/SewageTreatmentFacilityStatusMngColumn"
|
||||
import { SepticTankStatusMngColumn } from "@/components/util/column/management/privateSewageFacility/SepticTankStatusMngColumn"
|
||||
import { PrivateSewageDesignationStatusMngColumn } from "@/components/util/column/management/privateSewageFacility/PrivateSewageDesignationStatusMngColumn"
|
||||
import { SewageTreatmentFacilityStatusMngColumn } from "@/components/util/column/input/privateSewageFacility/SewageTreatmentFacilityStatusMngColumn"
|
||||
import { SepticTankStatusMngColumn } from "@/components/util/column/input/privateSewageFacility/SepticTankStatusMngColumn"
|
||||
import { PrivateSewageDesignationStatusMngColumn } from "@/components/util/column/input/privateSewageFacility/PrivateSewageDesignationStatusMngColumn"
|
||||
|
||||
// 하수도 통계 관리 > 08. 개인하수처리시설
|
||||
const PrivateSewageFacilityMng = () => {
|
||||
const PrivateSewageFacilityInp = () => {
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
|
||||
// tab list
|
||||
const PrivateSewageFacilityMngColumnList = [
|
||||
SewageTreatmentFacilityStatusMngColumn,
|
||||
SepticTankStatusMngColumn,
|
||||
PrivateSewageDesignationStatusMngColumn
|
||||
];
|
||||
const PrivateSewageFacilityInpColumnList = [
|
||||
SewageTreatmentFacilityStatusMngColumn,
|
||||
SepticTankStatusMngColumn,
|
||||
PrivateSewageDesignationStatusMngColumn
|
||||
];
|
||||
|
||||
const submitExceptionFunc = () => {
|
||||
// TODO: 예외 제출 로직 구현
|
||||
@@ -51,9 +51,9 @@ const PrivateSewageFacilityMng = () => {
|
||||
<SaveButton saveFunc={saveFunc} />
|
||||
</div>
|
||||
|
||||
<TabDataGrid columnInfo={ PrivateSewageFacilityMngColumnList } rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
<TabDataGrid columnInfo={PrivateSewageFacilityInpColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrivateSewageFacilityMng;
|
||||
export default PrivateSewageFacilityInp;
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
import SearchForm from '@/components/module/SearchForm';
|
||||
import DataGrid from '@/components/module/DataGrid';
|
||||
import { DataGrid } from '@rsone/components';
|
||||
import SubmitButton from '@/components/button/SumitButton';
|
||||
import SaveButton from '@/components/button/SaveButton';
|
||||
import SubmitExceptionButton from '@/components/button/SubmitExceptionButton';
|
||||
import OperateDataButton from '@/components/button/OperateDataButton';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { PublicSewageFacilityMngColumn } from '@/components/util/column/management/PublicSewageFacilityMngColumn';
|
||||
import { PublicSewageFacilityMngColumn } from '@/components/util/column/input/PublicSewageFacilityMngColumn';
|
||||
|
||||
// 하수도 통계 관리 > 06. 공공하수처리시설
|
||||
const PublicSewageFacilityMng = () => {
|
||||
const PublicSewageFacilityInp = () => {
|
||||
|
||||
const SubmitExceptionFunc = () => {
|
||||
console.log('submitException');
|
||||
@@ -47,4 +47,4 @@ const PublicSewageFacilityMng = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicSewageFacilityMng;
|
||||
export default PublicSewageFacilityInp;
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { DataGrid } from '@rsone/components';
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
import { useState } from "react";
|
||||
|
||||
import { PumpingStationMngColumn } from "@/components/util/column/management/PumpingStationMngColumn"
|
||||
import { PumpingStationMngColumn } from "@/components/util/column/input/PumpingStationMngColumn"
|
||||
// 하수도 통계 관리 > 05. 펌프장
|
||||
const PumpingStationMng = () => {
|
||||
const PumpingStationInp = () => {
|
||||
|
||||
const SubmitExceptionFunc = () => {
|
||||
console.log('submit exception');
|
||||
@@ -46,4 +46,4 @@ const PumpingStationMng = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default PumpingStationMng;
|
||||
export default PumpingStationInp;
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
|
||||
// 하수도 통계 관리 > 12. 하수 및 분뇨관련산업
|
||||
const SewageExcretaRelatedMng = () => {
|
||||
const SewageExcretaRelatedInp = () => {
|
||||
return (
|
||||
<>
|
||||
<SearchForm userMenual={true} />
|
||||
@@ -9,4 +9,4 @@ const SewageExcretaRelatedMng = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SewageExcretaRelatedMng;
|
||||
export default SewageExcretaRelatedInp;
|
||||
+3
-6
@@ -13,8 +13,8 @@ import { ExcretaSlurgeProcessingStatusMngColumn } from "@/components/util/column
|
||||
|
||||
|
||||
// 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리
|
||||
const SewageExcretaSlurgeMng = () => {
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const SewageExcretaSlurgeInp = () => {
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
|
||||
// tab list
|
||||
const SewageExcretaSlurgeMngList = [
|
||||
@@ -40,7 +40,6 @@ const SewageExcretaSlurgeMng = () => {
|
||||
// TODO: 저장 로직 구현
|
||||
console.log('저장 로직 실행');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm userMenual={true} fctName={true}/>
|
||||
@@ -57,6 +56,4 @@ const SewageExcretaSlurgeMng = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SewageExcretaSlurgeMng;
|
||||
|
||||
|
||||
export default SewageExcretaSlurgeInp;
|
||||
+11
-11
@@ -1,28 +1,28 @@
|
||||
import { useState } from "react";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import { TabDataGrid } from '@rsone/components';
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
import OperateDateButton from "@/components/button/OperateDataButton";
|
||||
|
||||
import { RegionalFacilityStatusMngColumn } from "@/components/util/column/management/sewagePipe/RegionalFacilityStatusMngColumn";
|
||||
import { FacilityInstallStatusMngColumn } from "@/components/util/column/management/sewagePipe/FacilityInstallStatusMngColumn";
|
||||
import { PipeTypeStatusMngColumn } from "@/components/util/column/management/sewagePipe/PipeTypeStatusMngColumn";
|
||||
import { PipeDiameterStatusMngColumn } from "@/components/util/column/management/sewagePipe/PipeDiameterStatusMngColumn";
|
||||
import { ManholeStatusMngColumn } from "@/components/util/column/management/sewagePipe/ManholeStatusMngColumn";
|
||||
import { RegionalFacilityStatusMngColumn } from "@/components/util/column/input/sewagePipe/RegionalFacilityStatusMngColumn";
|
||||
import { FacilityInstallStatusMngColumn } from "@/components/util/column/input/sewagePipe/FacilityInstallStatusMngColumn";
|
||||
import { PipeTypeStatusMngColumn } from "@/components/util/column/input/sewagePipe/PipeTypeStatusMngColumn";
|
||||
import { PipeDiameterStatusMngColumn } from "@/components/util/column/input/sewagePipe/PipeDiameterStatusMngColumn";
|
||||
import { ManholeStatusMngColumn } from "@/components/util/column/input/sewagePipe/ManholeStatusMngColumn";
|
||||
import {
|
||||
AdminAreaConnectionMngColumn,
|
||||
TreatmentPlantConnectionMngColumn
|
||||
} from "@/components/util/column/management/sewagePipe/ConnectionStatusMngColumn";
|
||||
} from "@/components/util/column/input/sewagePipe/ConnectionStatusMngColumn";
|
||||
|
||||
// 하수도 통계 관리 > 02. 하수관로
|
||||
const SewagePipeMng = () => {
|
||||
const SewagePipeInp = () => {
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
|
||||
// tab List
|
||||
const SewagePipeMngColumnList = [
|
||||
const SewagePipeInpColumnList = [
|
||||
RegionalFacilityStatusMngColumn,
|
||||
FacilityInstallStatusMngColumn,
|
||||
PipeTypeStatusMngColumn,
|
||||
@@ -69,9 +69,9 @@ const SewagePipeMng = () => {
|
||||
<SaveButton saveFunc={saveFunc} />
|
||||
</div>
|
||||
</div>
|
||||
<TabDataGrid columnInfo={SewagePipeMngColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
<TabDataGrid columnInfo={SewagePipeInpColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SewagePipeMng;
|
||||
export default SewagePipeInp;
|
||||
+7
-7
@@ -1,21 +1,21 @@
|
||||
import { useState } from "react";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import { TabDataGrid } from '@rsone/components';
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
|
||||
import { RepairStatusMngColumn } from "@/components/util/column/management/sewagePipeMaintain/RepairStatusMngColumn";
|
||||
import { PipeDredgingStatusMngColumn } from "@/components/util/column/management/sewagePipeMaintain/PipeDredgingStatusMngColumn";
|
||||
import { RepairStatusMngColumn } from "@/components/util/column/input/sewagePipeMaintain/RepairStatusMngColumn";
|
||||
import { PipeDredgingStatusMngColumn } from "@/components/util/column/input/sewagePipeMaintain/PipeDredgingStatusMngColumn";
|
||||
|
||||
// 하수도 통계 관리 > 03. 하수관로 유지관리
|
||||
const SewagePipeMaintainMng = () => {
|
||||
const SewagePipeMaintainInp = () => {
|
||||
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
|
||||
// tab List
|
||||
const SewagePipeMaintainMngColumnList = [
|
||||
const SewagePipeMaintainInpColumnList = [
|
||||
RepairStatusMngColumn,
|
||||
PipeDredgingStatusMngColumn
|
||||
];
|
||||
@@ -51,9 +51,9 @@ const SewagePipeMaintainMng = () => {
|
||||
<SaveButton saveFunc={saveFunc} />
|
||||
</div>
|
||||
|
||||
<TabDataGrid columnInfo={SewagePipeMaintainMngColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
<TabDataGrid columnInfo={SewagePipeMaintainInpColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SewagePipeMaintainMng;
|
||||
export default SewagePipeMaintainInp;
|
||||
+7
-7
@@ -1,20 +1,20 @@
|
||||
import { useState } from "react";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import { TabDataGrid } from '@rsone/components';
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
|
||||
import { SewageReservoirMngColumn } from "@/components/util/column/management/sewageReservoirPump/SewageReservoirMngColumn";
|
||||
import { RainfallPumpStationMngColumn } from "@/components/util/column/management/sewageReservoirPump/RainfallPumpStationMngColumn";
|
||||
import { SewageReservoirMngColumn } from "@/components/util/column/input/sewageReservoirPump/SewageReservoirMngColumn";
|
||||
import { RainfallPumpStationMngColumn } from "@/components/util/column/input/sewageReservoirPump/RainfallPumpStationMngColumn";
|
||||
|
||||
// 하수도 통계 관리 > 04. 하수저류 시설 및 빗물 펌프장
|
||||
const SewageReservoirPumpMng = () => {
|
||||
const SewageReservoirPumpInp = () => {
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
|
||||
// tab List
|
||||
const SewageReservoirPumpMngColumnList = [
|
||||
const SewageReservoirPumpInpColumnList = [
|
||||
SewageReservoirMngColumn,
|
||||
RainfallPumpStationMngColumn
|
||||
];
|
||||
@@ -48,9 +48,9 @@ const SewageReservoirPumpMng = () => {
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SaveButton saveFunc={saveFunc} />
|
||||
</div>
|
||||
<TabDataGrid columnInfo={SewageReservoirPumpMngColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
<TabDataGrid columnInfo={SewageReservoirPumpInpColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SewageReservoirPumpMng;
|
||||
export default SewageReservoirPumpInp;
|
||||
+9
-9
@@ -1,22 +1,22 @@
|
||||
import { useState } from "react";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import { TabDataGrid } from '@rsone/components';
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
|
||||
import { SewerageFinanceSettlementMngColumn } from "@/components/util/column/management/sewerageFinance/SewerageFinanceSettlementMngColumn"
|
||||
import { SewerageChargeMngColumn } from "@/components/util/column/management/sewerageFinance/SewerageChargeMngColumn"
|
||||
import { SewerageUsageFeeMngColumn } from "@/components/util/column/management/sewerageFinance/SewerageUsageFeeMngColumn"
|
||||
import { CauserBurdenMngColumn } from "@/components/util/column/management/sewerageFinance/CauserBurdenMngColumn"
|
||||
import { SewerageFinanceSettlementMngColumn } from "@/components/util/column/input/sewerageFinance/SewerageFinanceSettlementMngColumn"
|
||||
import { SewerageChargeMngColumn } from "@/components/util/column/input/sewerageFinance/SewerageChargeMngColumn"
|
||||
import { SewerageUsageFeeMngColumn } from "@/components/util/column/input/sewerageFinance/SewerageUsageFeeMngColumn"
|
||||
import { CauserBurdenMngColumn } from "@/components/util/column/input/sewerageFinance/CauserBurdenMngColumn"
|
||||
|
||||
// 하수도 통계 관리 > 09. 하수도재정
|
||||
const SewerageFinanceMng = () => {
|
||||
const SewerageFinanceInp = () => {
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
|
||||
// tab list
|
||||
const SewerageFinanceMngList = [
|
||||
const SewerageFinanceInpList = [
|
||||
SewerageFinanceSettlementMngColumn,
|
||||
SewerageChargeMngColumn,
|
||||
SewerageUsageFeeMngColumn,
|
||||
@@ -54,9 +54,9 @@ const SewerageFinanceMng = () => {
|
||||
<SaveButton saveFunc={saveFunc} />
|
||||
</div>
|
||||
|
||||
<TabDataGrid columnInfo={SewerageFinanceMngList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
<TabDataGrid columnInfo={SewerageFinanceInpList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SewerageFinanceMng;
|
||||
export default SewerageFinanceInp;
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { DataGrid } from "@rsone/components";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
@@ -7,10 +7,10 @@ import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { SeweragePenetrationRateMngColumn } from "@/components/util/column/management/SeweragePenetrationRateMngColumn";
|
||||
import { SeweragePenetrationRateMngColumn } from "@/components/util/column/input/SeweragePenetrationRateMngColumn";
|
||||
|
||||
// 하수도 통계 관리 > 01. 하수도 보급률
|
||||
const SeweragePenetrationRateMng = () => {
|
||||
const SeweragePenetrationRateInp = () => {
|
||||
|
||||
const SubmitExceptionFunc = () => {
|
||||
console.log('submit exception');
|
||||
@@ -57,4 +57,4 @@ const SeweragePenetrationRateMng = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SeweragePenetrationRateMng;
|
||||
export default SeweragePenetrationRateInp;
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import SearchForm from "../../module/SearchForm";
|
||||
|
||||
// 하수도 통계 관리 > 13. 하수도 서비스 수준
|
||||
const SewerageServiceLevelMng = () => {
|
||||
const SewerageServiceLevelInp = () => {
|
||||
return (
|
||||
<>
|
||||
<SearchForm userMenual={true} />
|
||||
@@ -9,4 +9,4 @@ const SewerageServiceLevelMng = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SewerageServiceLevelMng;
|
||||
export default SewerageServiceLevelInp;
|
||||
+3
-3
@@ -4,10 +4,10 @@ import ApproveButton from "@/components/button/ApproveButton";
|
||||
import RejectButton from "@/components/button/RejectButton";
|
||||
import { useState } from "react";
|
||||
|
||||
import { StatsInputSatusMngColumn } from "@/components/util/column/management/StatsInputSatusMngColumn";
|
||||
import { StatsInputSatusMngColumn } from "@/components/util/column/input/StatsInputSatusMngColumn";
|
||||
|
||||
// 하수도 통계 관리 > 00. 통계 입력 현황
|
||||
const StatsInputSatusMng = () => {
|
||||
const StatsInputSatusInp = () => {
|
||||
|
||||
const rejectFunc = () => {
|
||||
console.log('reject');
|
||||
@@ -35,4 +35,4 @@ const StatsInputSatusMng = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default StatsInputSatusMng;
|
||||
export default StatsInputSatusInp;
|
||||
+10
-10
@@ -1,25 +1,25 @@
|
||||
import { useState } from "react";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import { TabDataGrid } from '@rsone/components';
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
import OperateDataButton from "@/components/button/OperateDataButton"
|
||||
|
||||
import { PublicSewageTreatmentWaterReuseMngColumn } from "@/components/util/column/management/waterReuse/PublicSewageTreatmentWaterReuseMngColumn";
|
||||
import { RecycledWaterStatusMngColumn } from "@/components/util/column/management/waterReuse/RecycledWaterStatusMngColumn";
|
||||
import { RainfallUtilizationFacilityStatusMngColumn } from "@/components/util/column/management/waterReuse/RainfallUtilizationFacilityStatusMngColumn";
|
||||
import { TreatmentWaterReuseDesignConstructionStatusMngColumn } from "@/components/util/column/management/waterReuse/TreatmentWaterReuseDesignConstructionStatusMngColumn";
|
||||
import { TreatmentWaterReuseBussinessApprovalStatusMngColumn } from "@/components/util/column/management/waterReuse/TreatmentWaterReuseBussinessApprovalStatusMngColumn";
|
||||
import { PublicSewageTreatmentWaterReuseMngColumn } from "@/components/util/column/input/waterReuse/PublicSewageTreatmentWaterReuseMngColumn";
|
||||
import { RecycledWaterStatusMngColumn } from "@/components/util/column/input/waterReuse/RecycledWaterStatusMngColumn";
|
||||
import { RainfallUtilizationFacilityStatusMngColumn } from "@/components/util/column/input/waterReuse/RainfallUtilizationFacilityStatusMngColumn";
|
||||
import { TreatmentWaterReuseDesignConstructionStatusMngColumn } from "@/components/util/column/input/waterReuse/TreatmentWaterReuseDesignConstructionStatusMngColumn";
|
||||
import { TreatmentWaterReuseBussinessApprovalStatusMngColumn } from "@/components/util/column/input/waterReuse/TreatmentWaterReuseBussinessApprovalStatusMngColumn";
|
||||
|
||||
// 하수도 통계 관리 > 10. 물 재이용
|
||||
const WaterReuseMng = () => {
|
||||
const WaterReuseInp = () => {
|
||||
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
|
||||
// tab list
|
||||
const WaterReuseMngColumnList = [
|
||||
const WaterReuseInpColumnList = [
|
||||
PublicSewageTreatmentWaterReuseMngColumn,
|
||||
RecycledWaterStatusMngColumn,
|
||||
RainfallUtilizationFacilityStatusMngColumn,
|
||||
@@ -63,9 +63,9 @@ const WaterReuseMng = () => {
|
||||
<OperateDataButton getOperateDataFunc={getOperateDataFunc} />
|
||||
</div>
|
||||
|
||||
<TabDataGrid columnInfo={WaterReuseMngColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
<TabDataGrid columnInfo={WaterReuseInpColumnList} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WaterReuseMng;
|
||||
export default WaterReuseInp;
|
||||
+2
-4
@@ -1,11 +1,9 @@
|
||||
import { useState } from "react";
|
||||
// import { useState } from "react";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnConfig } from "@/components/type/DataGrid.type";
|
||||
|
||||
const StatisticsPopulationManagement = () => {
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
// const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1,112 +1,16 @@
|
||||
import { ValueType } from "realgrid";
|
||||
import { DataGrid } from "@rsone/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import type { ColumnConfig } from "@/components/type/DataGrid.type";
|
||||
|
||||
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateColumnRvw";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
const SeweragePenetrationRateRvw = () => {
|
||||
const columns: ColumnConfig[] = [
|
||||
{ name: 'sn', fieldName: '순번', header: { text: '순번' }, mergeCells: true, editable: false, sortdisable: true },
|
||||
{ name: 'sido', fieldName: '시도', header: { text: '시도' }, mergeCells: true, editable: false, sortdisable: true },
|
||||
{ name: 'sgg', fieldName: '시군구', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true },
|
||||
{ name: '구분', fieldName: '구분', header: { text: '구분' }, editable: false, sortdisable: true },
|
||||
{ name: '수계', 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: '총인구' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ 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: '총면적(㎢)' }, valueType: ValueType.NUMBER },
|
||||
{ name: '계', fieldName: '계', header: { text: '계' } },
|
||||
//공공하수처리인구
|
||||
{ name: '합계', fieldName: '합계', header: { text: '합계' } },
|
||||
{ name: '1차처리', fieldName: '1차처리', header: { text: '1차처리' } },
|
||||
{ name: '2차처리', fieldName: '2차처리', header: { text: '2차처리' } },
|
||||
{ name: '3차처리', fieldName: '3차처리', header: { text: '3차처리' } },
|
||||
//폐수처리인구(명)
|
||||
{ name: '(폐수처리인구)합계', fieldName: '(폐수처리인구)합계', header: { text: '합계' } },
|
||||
{ name: '(폐수처리인구)1차처리', fieldName: '(폐수처리인구)1차처리', header: { text: '1차처리' } },
|
||||
{ name: '(폐수처리인구)2차처리', fieldName: '(폐수처리인구)2차처리', header: { text: '2차처리' } },
|
||||
{ name: '(폐수처리인구)3차처리', fieldName: '(폐수처리인구)3차처리', header: { text: '3차처리' } },
|
||||
|
||||
{ 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 = [
|
||||
'sn',
|
||||
{
|
||||
name: 'groupA',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '행정구역' },
|
||||
items: ['sido', 'sgg'],
|
||||
},
|
||||
'구분',
|
||||
'수계',
|
||||
'bstrm',
|
||||
{
|
||||
name: 'groupB',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '수계영향권별분류류' },
|
||||
items: ['mdbsn', 'lgbsn', 'mdmbsn'],
|
||||
},
|
||||
'specl_cntrpln_ar',
|
||||
'popltn_sm',
|
||||
'ar_sm',
|
||||
{
|
||||
name: 'groupD',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '하수처리구역내' },
|
||||
items: [
|
||||
'계',
|
||||
{
|
||||
name: 'pblswrgGroup',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '공공하수처리인구(명)' },
|
||||
items: ['합계', '1차처리', '2차처리', '3차처리'],
|
||||
},
|
||||
{
|
||||
name: 'wstrGroup',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '폐수처리인구(명)' },
|
||||
items: ['(폐수처리인구)합계', '(폐수처리인구)1차처리', '(폐수처리인구)2차처리', '(폐수처리인구)3차처리'],
|
||||
},
|
||||
'not_conect_popltn',
|
||||
'swge_proc_zone_inner_ar'
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'groupE',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '하수처리구역외' },
|
||||
items: ['swge_proc_zone_outer_sm', 'town_unproc_popltn', 'town_swtt_popltn', 'town_wrrtn_popltn', 'swge_proc_zone_outer_ar'],
|
||||
},
|
||||
'pblswrg_popltn_suprt',
|
||||
'adtrt_popltn_suprt',
|
||||
'swrg_instlrt',
|
||||
'rmk',
|
||||
];
|
||||
|
||||
const approveFunc = () => {
|
||||
console.log('approveFunc');
|
||||
@@ -133,13 +37,22 @@ const SeweragePenetrationRateRvw = () => {
|
||||
return (
|
||||
<>
|
||||
<SearchForm userMenual={true} />
|
||||
<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="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>
|
||||
</div>
|
||||
<DataGrid columnInfo={{ columns, columnLayout}} isExcelImport={false} rowData={rowData} setRowData={setRowData} />
|
||||
<DataGrid
|
||||
columnInfo={SeweragePenetrationRateInfoList}
|
||||
isExcelImport={false}
|
||||
fixedColumns={SeweragePenetrationRateInfoList.fixedColumns}
|
||||
rowStartNum={SeweragePenetrationRateInfoList.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
|
||||
const ManagementVerificationRule = () => {
|
||||
return (
|
||||
<>
|
||||
<SearchForm userMenual={true} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManagementVerificationRule;
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { GridView, DataColumn } from "@/components/type/DataGrid.type";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
|
||||
const rawPrivateSewageList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { GridView, DataColumn } from "@/components/type/DataGrid.type";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
|
||||
const rawExcretaRelatedList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { GridView, DataColumn } from "@/components/type/DataGrid.type";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
|
||||
const rawSludgeList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { GridView, DataColumn } from "@/components/type/DataGrid.type";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
|
||||
const baseYear = new Date().getFullYear();
|
||||
|
||||
const rawList = [
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
|
||||
import type { GridView, DataColumn } from "@/components/type/DataGrid.type";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
|
||||
const rawReservoirList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { GridView, DataColumn } from "@/components/type/DataGrid.type";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
|
||||
const rawFinanceList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
|
||||
@@ -1,95 +1,99 @@
|
||||
export const SeweragePenetrationRateInfoList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
tabLabel: '하수도 보급률 현황',
|
||||
rowStartNum: 3, // 수계영향권 및 처리구역 내/외 3단 헤더 구조
|
||||
fixedColumns: 2, // 순번, 시도, 구군까지 고정
|
||||
columns: [
|
||||
/* 1. 행정 및 수계 정보 */
|
||||
{ name: 'sido', fieldName: 'SIDO', header: { text: '시도' }, editable: false },
|
||||
{ name: 'sgg', fieldName: 'GUGUN', header: { text: '구군' }, editable: false },
|
||||
{ name: 'GBN', fieldName: 'GBN', header: { text: '구분' }, editable: false },
|
||||
{ name: 'WTR_SYS', fieldName: 'WTR_SYS', header: { text: '수계' }, editable: false },
|
||||
{ name: 'bstrm', fieldName: 'BSTRM', header: { text: '지류' }, editable: false },
|
||||
{ name: 'mdbsn', fieldName: 'MDBSN', header: { text: '세부단위유역' }, editable: false },
|
||||
{ name: 'lgbsn', fieldName: 'LGBSN', header: { text: '중권역' }, editable: false },
|
||||
{ name: 'mdmbsn', fieldName: 'MDMBSN', header: { text: '소권역' }, editable: false },
|
||||
{ name: 'specl_cntrpln_ar', fieldName: 'SPECL_CNTRPLN_AR', header: { text: '특별대책지역' }, editable: false },
|
||||
import { ValueType } from "realgrid";
|
||||
import type { ColumnConfig } from "@rsone/components";
|
||||
|
||||
/* 2. 인구 및 면적 총계 */
|
||||
{ name: 'popltn_sm', fieldName: 'POPLTN_SM', header: { text: '총인구(명)' }, editable: false },
|
||||
{ name: 'ar_sm', fieldName: 'AR_SM', header: { text: '총면적(㎢)' }, editable: false },
|
||||
export const SeweragePenetrationRateInfoList = {
|
||||
columns: [
|
||||
{ name: 'sido', fieldName: '시도', header: { text: '시도' }, mergeCells: true, editable: false, sortdisable: true },
|
||||
{ name: 'sgg', fieldName: '시군구', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true },
|
||||
{ name: '구분', fieldName: '구분', header: { text: '구분' }, editable: false, sortdisable: true },
|
||||
{ name: '수계', 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: '총인구' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ 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: '총면적(㎢)' }, valueType: ValueType.NUMBER },
|
||||
{ name: '계', fieldName: '계', header: { text: '계' } },
|
||||
//공공하수처리인구
|
||||
{ name: '합계', fieldName: '합계', header: { text: '합계' } },
|
||||
{ name: '1차처리', fieldName: '1차처리', header: { text: '1차처리' } },
|
||||
{ name: '2차처리', fieldName: '2차처리', header: { text: '2차처리' } },
|
||||
{ name: '3차처리', fieldName: '3차처리', header: { text: '3차처리' } },
|
||||
//폐수처리인구(명)
|
||||
{ name: '(폐수처리인구)합계', fieldName: '(폐수처리인구)합계', header: { text: '합계' } },
|
||||
{ name: '(폐수처리인구)1차처리', fieldName: '(폐수처리인구)1차처리', header: { text: '1차처리' } },
|
||||
{ name: '(폐수처리인구)2차처리', fieldName: '(폐수처리인구)2차처리', header: { text: '2차처리' } },
|
||||
{ name: '(폐수처리인구)3차처리', fieldName: '(폐수처리인구)3차처리', header: { text: '3차처리' } },
|
||||
|
||||
/* 3. 하수처리구역 내 상세 */
|
||||
{ name: 'inner_sm', fieldName: 'INNER_SM', header: { text: '계' }, editable: false },
|
||||
// 공공하수처리인구
|
||||
{ name: 'pbl_sm', fieldName: 'PBL_SM', header: { text: '합계' }, editable: false },
|
||||
{ name: 'pbl_1ch', fieldName: 'PBL_1CH', header: { text: '1차처리' }, editable: false },
|
||||
{ name: 'pbl_2ch', fieldName: 'PBL_2CH', header: { text: '2차처리' }, editable: false },
|
||||
{ name: 'pbl_3ch', fieldName: 'PBL_3CH', header: { text: '3차처리' }, editable: false },
|
||||
// 폐수처리인구
|
||||
{ name: 'wstr_sm', fieldName: 'WSTR_SM', header: { text: '합계' }, editable: false },
|
||||
{ name: 'wstr_1ch', fieldName: 'WSTR_1CH', header: { text: '1차처리' }, editable: false },
|
||||
{ name: 'wstr_2ch', fieldName: 'WSTR_2CH', header: { text: '2차처리' }, editable: false },
|
||||
{ name: 'wstr_3ch', fieldName: 'WSTR_3CH', header: { text: '3차처리' }, editable: false },
|
||||
|
||||
{ name: 'not_conect_popltn', fieldName: 'NOT_CONECT_POPLTN', header: { text: '미접속인구' }, editable: false },
|
||||
{ name: 'swge_proc_zone_inner_ar', fieldName: 'SWGE_PROC_ZONE_INNER_AR', header: { text: '면적(㎢)' }, editable: false },
|
||||
|
||||
/* 4. 하수처리구역 외 상세 */
|
||||
{ name: 'swge_proc_zone_outer_sm', fieldName: 'SWGE_PROC_ZONE_OUTER_SM', header: { text: '계' }, editable: false },
|
||||
{ name: 'town_unproc_popltn', fieldName: 'TOWN_UNPROC_POPLTN', header: { text: '미처리인구' }, editable: false },
|
||||
{ name: 'town_swtt_popltn', fieldName: 'TOWN_SWTT_POPLTN', header: { text: '오수처리인구' }, editable: false },
|
||||
{ name: 'town_wrrtn_popltn', fieldName: 'TOWN_WRRTN_POPLTN', header: { text: '정화조인구' }, editable: false },
|
||||
{ name: 'swge_proc_zone_outer_ar', fieldName: 'SWGE_PROC_ZONE_OUTER_AR', header: { text: '면적(㎢)' }, editable: false },
|
||||
|
||||
/* 5. 보급율 및 지표 */
|
||||
{ name: 'pblswrg_popltn_suprt', fieldName: 'PBLSWRG_POPLTN_SUPRT', header: { text: '공공하수처리구역\n인구보급율(%)' }, editable: false },
|
||||
{ name: 'adtrt_popltn_suprt', fieldName: 'ADTRT_POPLTN_SUPRT', header: { text: '고도처리\n인구보급율(%)' }, editable: false },
|
||||
{ name: 'swrg_instlrt', fieldName: 'SWRG_INSTLRT', header: { text: '하수도설치율(%)' }, editable: false },
|
||||
{ name: 'rmk', fieldName: 'RMK', header: { text: '비고' }, editable: false },
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: 'groupA', header: { text: '행정구역' }, items: ['sido', 'sgg'] },
|
||||
'GBN',
|
||||
'WTR_SYS',
|
||||
'bstrm',
|
||||
{
|
||||
name: 'groupB',
|
||||
header: { text: '수계영향권별분류' },
|
||||
items: ['mdbsn', 'lgbsn', 'mdmbsn'],
|
||||
},
|
||||
'specl_cntrpln_ar',
|
||||
'popltn_sm',
|
||||
'ar_sm',
|
||||
{
|
||||
name: 'groupD',
|
||||
header: { text: '하수처리구역내' },
|
||||
items: [
|
||||
'inner_sm',
|
||||
{
|
||||
name: 'pblswrgGroup',
|
||||
header: { text: '공공하수처리인구(명)' },
|
||||
items: ['pbl_sm', 'pbl_1ch', 'pbl_2ch', 'pbl_3ch'],
|
||||
},
|
||||
{
|
||||
name: 'wstrGroup',
|
||||
header: { text: '폐수처리인구(명)' },
|
||||
items: ['wstr_sm', 'wstr_1ch', 'wstr_2ch', 'wstr_3ch'],
|
||||
},
|
||||
'not_conect_popltn',
|
||||
'swge_proc_zone_inner_ar'
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'groupE',
|
||||
header: { text: '하수처리구역외' },
|
||||
items: ['swge_proc_zone_outer_sm', 'town_unproc_popltn', 'town_swtt_popltn', 'town_wrrtn_popltn', 'swge_proc_zone_outer_ar'],
|
||||
},
|
||||
'pblswrg_popltn_suprt',
|
||||
'adtrt_popltn_suprt',
|
||||
'swrg_instlrt',
|
||||
'rmk',
|
||||
]
|
||||
}
|
||||
];
|
||||
{ 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: '비고' } },
|
||||
] as ColumnConfig[],
|
||||
columnLayout: [
|
||||
{
|
||||
name: 'groupA',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '행정구역' },
|
||||
items: ['sido', 'sgg'],
|
||||
},
|
||||
'구분',
|
||||
'수계',
|
||||
'bstrm',
|
||||
{
|
||||
name: 'groupB',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '수계영향권별분류류' },
|
||||
items: ['mdbsn', 'lgbsn', 'mdmbsn'],
|
||||
},
|
||||
'specl_cntrpln_ar',
|
||||
'popltn_sm',
|
||||
'ar_sm',
|
||||
{
|
||||
name: 'groupD',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '하수처리구역내' },
|
||||
items: [
|
||||
'계',
|
||||
{
|
||||
name: 'pblswrgGroup',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '공공하수처리인구(명)' },
|
||||
items: ['합계', '1차처리', '2차처리', '3차처리'],
|
||||
},
|
||||
{
|
||||
name: 'wstrGroup',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '폐수처리인구(명)' },
|
||||
items: ['(폐수처리인구)합계', '(폐수처리인구)1차처리', '(폐수처리인구)2차처리', '(폐수처리인구)3차처리'],
|
||||
},
|
||||
'not_conect_popltn',
|
||||
'swge_proc_zone_inner_ar'
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'groupE',
|
||||
hideChildHeaders: false,
|
||||
header: { text: '하수처리구역외' },
|
||||
items: ['swge_proc_zone_outer_sm', 'town_unproc_popltn', 'town_swtt_popltn', 'town_wrrtn_popltn', 'swge_proc_zone_outer_ar'],
|
||||
},
|
||||
'pblswrg_popltn_suprt',
|
||||
'adtrt_popltn_suprt',
|
||||
'swrg_instlrt',
|
||||
'rmk',
|
||||
],
|
||||
fixedColumns: 2,
|
||||
rowStartNum: 3
|
||||
};
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { GridView, DataColumn } from "@/components/type/DataGrid.type";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
|
||||
const rawServiceLevelList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { GridView, DataColumn } from "@/components/type/DataGrid.type";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
|
||||
const rawWaterReuseList = [
|
||||
{
|
||||
tabKey: 'a',
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { CodeSelect } from "../ui/code-select";
|
||||
@@ -0,0 +1,14 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { CodeService, ICode } from "../model/code-interface";
|
||||
|
||||
export class CodeApiService implements CodeService {
|
||||
public async getCode(upCd: string): Promise<ICode[]> {
|
||||
const response = await client.get<ICode[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.CODE}/${upCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const CodeApi = new CodeApiService();
|
||||
@@ -0,0 +1,73 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import {
|
||||
LwpoCodeFormSchema,
|
||||
type LwpoCodeService,
|
||||
type LwpoCodeList,
|
||||
type LwpoCodeDetail,
|
||||
type LwpoCodeParams,
|
||||
type LwpoCodeFormData,
|
||||
} from "../model/lwpocode-interface";
|
||||
|
||||
export class LwpoCodeApiService implements LwpoCodeService {
|
||||
public async getLwpoCodeList(params?: LwpoCodeParams): Promise<LwpoCodeList> {
|
||||
const response = await client.get<LwpoCodeList>(
|
||||
`${ENDPOINT.CMS_SERVICE.LWPO_CODE}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getUpCodeLwpoCodeList(upCd: string): Promise<LwpoCodeList> {
|
||||
const response = await client.get<LwpoCodeList>(
|
||||
`${ENDPOINT.CMS_SERVICE.LWPO_CODE}/up-cd/${upCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async lwpoCodeDetail(
|
||||
upCd: string,
|
||||
lwpoCd: string,
|
||||
): Promise<LwpoCodeDetail> {
|
||||
const response = await client.get<LwpoCodeDetail>(
|
||||
`${ENDPOINT.CMS_SERVICE.LWPO_CODE}/${lwpoCd}/up-cd/${upCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createLwpoCode(data: LwpoCodeFormData): Promise<void> {
|
||||
const validatedData = LwpoCodeFormSchema.parse(data);
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.LWPO_CODE}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateLwpoCode(
|
||||
upCd: string,
|
||||
lwpoCd: string,
|
||||
data: LwpoCodeFormData,
|
||||
): Promise<void> {
|
||||
const validatedData = LwpoCodeFormSchema.parse(data);
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.LWPO_CODE}/${lwpoCd}/up-cd/${upCd}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async deleteLwpoCode(upCd: string, lwpoCd: string): Promise<void> {
|
||||
await client.delete<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.LWPO_CODE}/${lwpoCd}/up-cd/${upCd}`,
|
||||
);
|
||||
}
|
||||
|
||||
public async codeRefreshAll(): Promise<void> {
|
||||
await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.LWPO_CODE}/cache/refresh-all`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const LwpoCodeApi = new LwpoCodeApiService();
|
||||
@@ -0,0 +1,47 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import {
|
||||
upCodeFormSchema,
|
||||
type UpCodeService,
|
||||
type UpCodeList,
|
||||
type UpCodeDetail,
|
||||
type UpCodeParams,
|
||||
type UpCodeFormData,
|
||||
} from "../model/upcode-interface";
|
||||
|
||||
export class UpCodeApiService implements UpCodeService {
|
||||
public async getUpCodeList(params?: UpCodeParams): Promise<UpCodeList> {
|
||||
const response = await client.get<UpCodeList>(
|
||||
`${ENDPOINT.CMS_SERVICE.UP_CODE}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async upCodeDetail(upCd: string): Promise<UpCodeDetail> {
|
||||
const response = await client.get<UpCodeDetail>(
|
||||
`${ENDPOINT.CMS_SERVICE.UP_CODE}/${upCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createUpCode(data: UpCodeFormData): Promise<void> {
|
||||
const validatedData = upCodeFormSchema.parse(data);
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.UP_CODE}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateUpCode(upCd: string, data: UpCodeFormData): Promise<void> {
|
||||
const validatedData = upCodeFormSchema.parse(data);
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.UP_CODE}/${upCd}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const UpCodeApi = new UpCodeApiService();
|
||||
@@ -0,0 +1,19 @@
|
||||
export { CodeApi } from "./api/code-service";
|
||||
export type { ICode } from "./model/code-interface";
|
||||
export { CodeSelect } from "./ui/code-select";
|
||||
|
||||
export { UpCodeApi } from "./api/upcode-service";
|
||||
export {
|
||||
upCodeFormSchema,
|
||||
type UpCode,
|
||||
type UpCodeFormData,
|
||||
type UpCodeParams,
|
||||
} from "./model/upcode-interface";
|
||||
|
||||
export { LwpoCodeApi } from "./api/lwpocode-service";
|
||||
export {
|
||||
LwpoCodeFormSchema,
|
||||
type LwpoCode,
|
||||
type LwpoCodeFormData,
|
||||
type LwpoCodeParams,
|
||||
} from "./model/lwpocode-interface";
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface ICode {
|
||||
lwpoCd: string;
|
||||
upCd: string;
|
||||
lwpoCdNm: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface CodeService {
|
||||
getCode(upCd: string): Promise<ICode[]>;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { z } from "zod";
|
||||
import type { PageableParams, PageableList } from "@/shared/model";
|
||||
|
||||
export interface LwpoCodeParams extends PageableParams {
|
||||
upCd?: string;
|
||||
lwpoCd?: string;
|
||||
lwpoCdNm?: string;
|
||||
useYn?: string;
|
||||
}
|
||||
|
||||
export interface LwpoCode {
|
||||
lwpoCd: string;
|
||||
upCd: string;
|
||||
lwpoCdNm: string;
|
||||
lwpoCdExpln: string;
|
||||
unit: string;
|
||||
prntLwpoCd: string;
|
||||
useYn: string;
|
||||
sortSeq: number;
|
||||
regDt: string;
|
||||
rgTr: string;
|
||||
rgTrNm: string;
|
||||
mdfcnDt: string;
|
||||
mdFr: string;
|
||||
}
|
||||
|
||||
export interface LwpoCodeDetail extends LwpoCodeFormData {
|
||||
lwpoCd: string;
|
||||
upCd: string;
|
||||
lwpoCdNm: string;
|
||||
lwpoCdExpln: string;
|
||||
unit: string;
|
||||
prntLwpoCd: string;
|
||||
useYn: string;
|
||||
sortSeq: 0;
|
||||
}
|
||||
|
||||
export const LwpoCodeFormSchema = z.object({
|
||||
lwpoCd: z.string().max(10).nonempty(),
|
||||
upCd: z.string().max(10).nonempty(),
|
||||
lwpoCdNm: z.string().max(100).nonempty(),
|
||||
lwpoCdExpln: z.string().max(1000).nullish(),
|
||||
unit: z.string().max(20).nullish(),
|
||||
prntLwpoCd: z.string().max(10).nullish(),
|
||||
useYn: z.string(),
|
||||
sortSeq: z.number(),
|
||||
delYn: z.string(),
|
||||
});
|
||||
|
||||
export type LwpoCodeFormData = z.infer<typeof LwpoCodeFormSchema>;
|
||||
export type LwpoCodeList = PageableList<LwpoCode>;
|
||||
|
||||
export interface LwpoCodeService {
|
||||
getLwpoCodeList(params: LwpoCodeParams): Promise<LwpoCodeList>;
|
||||
getUpCodeLwpoCodeList(upCd: string): Promise<LwpoCodeList>;
|
||||
lwpoCodeDetail(upCd: string, lwpoCd: string): Promise<LwpoCodeDetail>;
|
||||
updateLwpoCode(
|
||||
upCd: string,
|
||||
lwpoCd: string,
|
||||
data: LwpoCodeFormData,
|
||||
): Promise<void>;
|
||||
createLwpoCode(data: LwpoCodeFormData): Promise<void>;
|
||||
deleteLwpoCode(
|
||||
upCd: string,
|
||||
lwpoCd: string,
|
||||
data: LwpoCodeFormData,
|
||||
): Promise<void>;
|
||||
codeRefreshAll(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { z } from "zod";
|
||||
import type { PageableParams, PageableList } from "@/shared/model";
|
||||
|
||||
export interface UpCodeParams extends PageableParams {
|
||||
keyword?: string;
|
||||
upCd?: string;
|
||||
cdSe?: string;
|
||||
useYn?: string;
|
||||
siteId?: string;
|
||||
}
|
||||
|
||||
export interface UpCode {
|
||||
upCd: string;
|
||||
upCdNm: string;
|
||||
upCdExpln: string;
|
||||
cdSe: string;
|
||||
siteId: string;
|
||||
useYn: string;
|
||||
regDt: string;
|
||||
}
|
||||
|
||||
export interface UpCodeDetail extends UpCodeFormData {
|
||||
upCd: string;
|
||||
upCdNm: string;
|
||||
upCdExpln: string;
|
||||
cdSe: string;
|
||||
siteId: string;
|
||||
useYn: string;
|
||||
delYn: string;
|
||||
regDt: string;
|
||||
rgTrNm: string;
|
||||
}
|
||||
|
||||
export const upCodeFormSchema = z.object({
|
||||
upCd: z.string().max(10).nonempty(),
|
||||
upCdNm: z.string().max(100).nonempty(),
|
||||
upCdExpln: z.string().max(1000).nullish(),
|
||||
cdSe: z.string().max(10).nonempty(),
|
||||
siteId: z.string().max(2).nullish(),
|
||||
useYn: z.string(),
|
||||
delYn: z.string(),
|
||||
});
|
||||
|
||||
export type UpCodeFormData = z.infer<typeof upCodeFormSchema>;
|
||||
export type UpCodeList = PageableList<UpCode>;
|
||||
|
||||
export interface UpCodeService {
|
||||
getUpCodeList(params: UpCodeParams): Promise<UpCodeList>;
|
||||
upCodeDetail(upCodeId: string): Promise<UpCodeDetail>;
|
||||
updateUpCode(upCodeId: string, data: UpCodeFormData): Promise<void>;
|
||||
createUpCode(data: UpCodeFormData): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
cn,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { CodeApi } from "../api/code-service";
|
||||
|
||||
type CodeSelectProps = {
|
||||
upCd: string;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
} & React.ComponentPropsWithoutRef<typeof Select>;
|
||||
|
||||
function CodeSelect({
|
||||
upCd,
|
||||
placeholder = "선택",
|
||||
className,
|
||||
...props
|
||||
}: CodeSelectProps) {
|
||||
const { data: code } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CODE, upCd],
|
||||
queryFn: () => CodeApi.getCode(upCd),
|
||||
enabled: !!upCd,
|
||||
});
|
||||
|
||||
return (
|
||||
<Select {...props}>
|
||||
<SelectTrigger className={cn("w-[12rem]", className)}>
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{code?.map((item) => (
|
||||
<SelectItem key={item.lwpoCd} value={item.lwpoCd}>
|
||||
{item.lwpoCdNm}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export { CodeSelect };
|
||||
@@ -6,6 +6,8 @@ import { useTabStore } from "@/store/tab-store";
|
||||
import Dashboard from "@/components/Dashboard";
|
||||
import { SkeletonPage } from "@/shared/ui/skeleton-page";
|
||||
|
||||
import "@rsone/components/index.css";
|
||||
|
||||
const StatisticsPage = () => {
|
||||
const { tabs, activeTabId } = useTabStore();
|
||||
|
||||
@@ -38,7 +40,7 @@ const StatisticsPage = () => {
|
||||
{tabs.map((tab) => {
|
||||
// 렌더링 시점에 컴포넌트가 없으면 바로 로드
|
||||
if (!componentCache.current.has(tab.id)) {
|
||||
const componentName = tab.id;
|
||||
const componentName = tab.urlPath;
|
||||
const parentComponentName = tab.parentEngNm;
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
|
||||
@@ -65,7 +65,7 @@ export const ENDPOINT = {
|
||||
POST_EVALUATION: "/board-service/api/v1/pstEvl",
|
||||
},
|
||||
ST_SERVICE: {
|
||||
COMMON: "/st-service/api/v1/common",
|
||||
COMMON: "/st-management-service/api/v1/common",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import type { MenuItem } from "@/entities/menu";
|
||||
export interface Tab {
|
||||
id: string; // 고유 ID (menuEngNm)
|
||||
label: string; // 탭에 표시될 이름
|
||||
path: string; // URL 경로
|
||||
path: string; // 메뉴 경로
|
||||
urlPath: string; // url 경로
|
||||
parentEngNm: string; // 부모 menuEngNm
|
||||
menuItem: MenuItem; // 메뉴 정보
|
||||
}
|
||||
@@ -43,6 +44,7 @@ const useTabStoreHook = create<TabStore>((set, get) => ({
|
||||
id: tabId,
|
||||
label: menuItem.menuKornNm,
|
||||
path: menuItem.menuPath,
|
||||
urlPath: menuItem.urlPath,
|
||||
parentEngNm: menuList?.find((m) => m.menuCd === menuItem.parentId)?.menuEngNm || '',
|
||||
menuItem,
|
||||
};
|
||||
@@ -112,5 +114,4 @@ const useTabStoreHook = create<TabStore>((set, get) => ({
|
||||
}));
|
||||
|
||||
export const useTabStore = useTabStoreHook;
|
||||
export const getTabStoreState = () => useTabStoreHook.getState();
|
||||
|
||||
export const getTabStoreState = () => useTabStoreHook.getState();
|
||||
@@ -27,7 +27,8 @@
|
||||
"@/*": ["./*"],
|
||||
"@common/components": ["../../../packages/common/components/src/"],
|
||||
"@common/config": ["../../../packages/common/config/dist/"],
|
||||
"@common/network": ["../../../packages/common/network/"]
|
||||
"@common/network": ["../../../packages/common/network/"],
|
||||
"@rsone/components": ["../../../packages/rsone/dist/"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.env*
|
||||
@@ -0,0 +1,352 @@
|
||||
import { useRef, useEffect } from "react"
|
||||
import { ValueType } from "realgrid"
|
||||
|
||||
import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid"
|
||||
import { RealGridReact, RGDataColumn } from "realgrid-react"
|
||||
import type { DataGridProps } from "../type/DataGrid.type"
|
||||
import JSZip from "jszip"
|
||||
import * as XLSX from "xlsx"
|
||||
import { Button } from "@common/components";
|
||||
import { useAlertStore } from "../store/alert-store";
|
||||
import { useTabStore } from "../store/tab-store";
|
||||
|
||||
// RealGrid 타입 확장
|
||||
declare module "realgrid" {
|
||||
interface GridView {
|
||||
setOptions(options: {
|
||||
footer?: { visible?: boolean };
|
||||
summaryMode?: 'aggregate' | 'statistical' | 'none';
|
||||
checkBar?: { visible?: boolean };
|
||||
}): void;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport = true, fixedColumns, showHeaderCheckbox = false, rowData, setRowData }: DataGridProps) => {
|
||||
const { columns, columnLayout } = columnInfo;
|
||||
const gridRef = useRef<RealGridReact>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const activeTabId = useTabStore((state) => state.activeTabId);
|
||||
const menuList = useTabStore((state) => state.menuList);
|
||||
|
||||
const exportExcel = () => {
|
||||
if (rowData.length === 0) {
|
||||
alert('데이터가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gridRef.current?.gridView) return;
|
||||
|
||||
const gridView = gridRef.current.gridView;
|
||||
|
||||
const menuName = menuList
|
||||
?.flatMap((m) => m.children || [])
|
||||
.flatMap((subM) => subM || [])
|
||||
.find((s) => s.menuEngNm === activeTabId)
|
||||
?.menuKornNm || 'export';
|
||||
|
||||
gridView.exportGrid({
|
||||
type: 'excel',
|
||||
target: 'local',
|
||||
fileName: `${menuName}.xlsx`,
|
||||
footer: 'hidden',
|
||||
done: () => {
|
||||
alert('내보내기가 완료되었습니다.');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const importExcel = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
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 | number>[] = [];
|
||||
for (let row = dataStartRow; row <= range.e.r; row++) {
|
||||
const rowData: Record<string, string | number> = {};
|
||||
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}`;
|
||||
|
||||
let cellValue: string | number = '';
|
||||
if (cell && cell.v !== undefined && cell.v !== null) {
|
||||
// 엑셀 셀 타입이 숫자이거나 값이 숫자인 경우 숫자로 변환
|
||||
if (cell.t === 'n' || (typeof cell.v === 'number' && !isNaN(cell.v))) {
|
||||
cellValue = cell.v as number;
|
||||
} else {
|
||||
const stringValue = String(cell.v);
|
||||
// 문자열이지만 숫자로 변환 가능한 경우 숫자로 변환
|
||||
const numericValue = Number(stringValue.replace(/,/g, ''));
|
||||
if (!isNaN(numericValue) && stringValue.trim() !== '') {
|
||||
cellValue = numericValue;
|
||||
} else {
|
||||
cellValue = stringValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cellValue !== '' && cellValue !== null && cellValue !== undefined) {
|
||||
hasData = true;
|
||||
}
|
||||
|
||||
rowData[headerName] = cellValue;
|
||||
}
|
||||
|
||||
// 데이터가 하나라도 있는 행만 추가
|
||||
if (hasData) {
|
||||
dataRows.push(rowData);
|
||||
}
|
||||
}
|
||||
setRowData(dataRows);
|
||||
} catch (error) {
|
||||
console.error('엑셀 파일 파싱 오류:', error);
|
||||
alert('엑셀 파일 파싱 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
alert('파일 읽기 중 오류가 발생했습니다.');
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
} catch (error) {
|
||||
console.error('엑셀 파일 가져오기 오류:', error);
|
||||
alert('엑셀 파일 가져오기 중 오류가 발생했습니다.');
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!gridRef.current?.gridView || !gridRef.current?.dataProvider) return;
|
||||
|
||||
const gridView = gridRef.current.gridView;
|
||||
const dataProvider = gridRef.current.dataProvider;
|
||||
|
||||
const fieldsToSet: Array<{ fieldName: string; dataType: string }> = [];
|
||||
const fieldMap = new Map<string, string>();
|
||||
|
||||
columns.forEach(col => {
|
||||
if (col.valueType && col.fieldName && !fieldMap.has(col.fieldName)) {
|
||||
fieldMap.set(col.fieldName, col.valueType);
|
||||
fieldsToSet.push({
|
||||
fieldName: col.fieldName,
|
||||
dataType: col.valueType
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 필드가 이미 존재하는지 확인하고, valueType이 설정된 필드만 업데이트
|
||||
if (fieldsToSet.length > 0) {
|
||||
try {
|
||||
// 기존 필드 정보 가져오기
|
||||
const existingFields = (dataProvider as { getFields?: () => Array<{ fieldName: string; dataType?: string }> }).getFields?.() || [];
|
||||
const existingFieldMap = new Map(existingFields.map(f => [f.fieldName, f.dataType || 'text']));
|
||||
|
||||
// valueType이 설정된 필드 중 dataType이 다른 필드만 업데이트
|
||||
const fieldsToUpdate = fieldsToSet.filter(field => {
|
||||
const existingDataType = existingFieldMap.get(field.fieldName);
|
||||
return existingDataType !== field.dataType;
|
||||
});
|
||||
|
||||
if (fieldsToUpdate.length > 0) {
|
||||
// 기존 필드 정보를 유지하면서 dataType만 업데이트
|
||||
// 컬럼 정의에 valueType이 있으면 그것을 우선 사용, 없으면 기존 필드의 dataType 사용
|
||||
const allFields = existingFields.map(f => ({
|
||||
fieldName: f.fieldName,
|
||||
dataType: fieldMap.get(f.fieldName) || f.dataType || 'text'
|
||||
}));
|
||||
|
||||
// 새로 추가할 필드가 있으면 추가
|
||||
fieldsToSet.forEach(field => {
|
||||
if (!existingFieldMap.has(field.fieldName)) {
|
||||
allFields.push({ fieldName: field.fieldName, dataType: field.dataType });
|
||||
}
|
||||
});
|
||||
|
||||
// setFields를 호출하여 필드의 dataType 업데이트
|
||||
(dataProvider as { setFields: (fields: Array<{ fieldName: string; dataType: string }>) => void })
|
||||
.setFields(allFields);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to set field dataType:', error);
|
||||
}
|
||||
}
|
||||
|
||||
(gridView.displayOptions as DisplayOptions).fitStyle = GridFitStyle.EVEN;
|
||||
gridView.header.height = 60;
|
||||
|
||||
// 헤더 체크박스 표시 여부 설정
|
||||
gridView.setOptions({
|
||||
checkBar: { visible: showHeaderCheckbox },
|
||||
});
|
||||
|
||||
// footer가 있는 컬럼이 있으면 footer 높이 설정
|
||||
const hasFooter = columns.some(col => col.footer);
|
||||
if (hasFooter) {
|
||||
gridView.footer.height = 40;
|
||||
}
|
||||
|
||||
// 고정 컬럼 설정
|
||||
if (fixedColumns !== undefined && fixedColumns > 0) {
|
||||
gridView.setFixedOptions({
|
||||
colCount: fixedColumns,
|
||||
});
|
||||
}
|
||||
|
||||
gridView.setOptions({
|
||||
summaryMode: "aggregate"
|
||||
});
|
||||
|
||||
// footer의 valueCallback 설정
|
||||
columns.forEach(col => {
|
||||
if (col.footer && col.footer.valueCallback) {
|
||||
const column = gridView.columnByName(col.name);
|
||||
if (column && column.footer) {
|
||||
(column.footer as { valueCallback?: typeof col.footer.valueCallback }).valueCallback = col.footer.valueCallback;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}, [columns, columnLayout, fixedColumns, rowData, showHeaderCheckbox]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<div className="flex justify-end items-center gap-[0.6rem] px-[1.2rem]">
|
||||
{isExcelImport &&
|
||||
<Button className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover w-[96px]" type="button" onClick={importExcel}>
|
||||
<i className="diveicon di-file-excel" />
|
||||
<span>가져오기</span>
|
||||
</Button>
|
||||
}
|
||||
{isExcelExport &&
|
||||
<Button className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover w-[96px]" type="button" onClick={exportExcel}>
|
||||
<i className="diveicon di-file-excel" />
|
||||
<span>내보내기</span>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
<div className="h-[65vh]">
|
||||
<RealGridReact
|
||||
ref={gridRef}
|
||||
autoGenerateField={true}
|
||||
layout={columnLayout}
|
||||
rows={rowData}
|
||||
>
|
||||
{columns.map((column) => (
|
||||
<RGDataColumn
|
||||
key={column.name}
|
||||
name={column.name}
|
||||
fieldName={column.fieldName}
|
||||
valueType={column.valueType ? column.valueType : ValueType.TEXT}
|
||||
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })}
|
||||
header={column.header}
|
||||
editable={column.editable}
|
||||
footer={column.footer ? {
|
||||
text: column.footer.text,
|
||||
expression: column.footer.expression,
|
||||
numberFormat: column.footer.numberFormat,
|
||||
} : undefined}
|
||||
mergeRule={column.mergeCells ? { criteria: 'value' } : undefined}
|
||||
sortable={!column.sortdisable}
|
||||
/>
|
||||
))}
|
||||
</RealGridReact>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataGrid;
|
||||
@@ -0,0 +1,470 @@
|
||||
import { useRef, useEffect, useState } from "react"
|
||||
import { ValueType } from "realgrid"
|
||||
|
||||
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 "../store/alert-store";
|
||||
import { useTabStore } from "../store/tab-store";
|
||||
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
// RealGrid 타입 확장
|
||||
declare module "realgrid" {
|
||||
interface GridView {
|
||||
setOptions(options: {
|
||||
footer?: { visible?: boolean };
|
||||
summaryMode?: 'aggregate' | 'statistical' | 'none';
|
||||
checkBar?: { visible?: boolean };
|
||||
}): void;
|
||||
}
|
||||
}
|
||||
|
||||
// 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, isExcelExport = true, rows = [], showHeaderCheckbox = false, rowDataMap, setRowDataMap }: 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);
|
||||
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 | number>[] = [];
|
||||
for (let row = dataStartRow; row <= range.e.r; row++) {
|
||||
const rowData: Record<string, string | number> = {};
|
||||
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}`;
|
||||
|
||||
let cellValue: string | number = '';
|
||||
if (cell && cell.v !== undefined && cell.v !== null) {
|
||||
// 엑셀 셀 타입이 숫자이거나 값이 숫자인 경우 숫자로 변환
|
||||
if (cell.t === 'n' || (typeof cell.v === 'number' && !isNaN(cell.v))) {
|
||||
cellValue = cell.v as number;
|
||||
} else {
|
||||
const stringValue = String(cell.v);
|
||||
// 문자열이지만 숫자로 변환 가능한 경우 숫자로 변환
|
||||
const numericValue = Number(stringValue.replace(/,/g, ''));
|
||||
if (!isNaN(numericValue) && stringValue.trim() !== '') {
|
||||
cellValue = numericValue;
|
||||
} else {
|
||||
cellValue = stringValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cellValue !== '' && cellValue !== null && cellValue !== undefined) {
|
||||
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 w-[96px]"
|
||||
type="button"
|
||||
onClick={() => importExcel(tabKey)}
|
||||
>
|
||||
<i className="diveicon di-file-excel" />
|
||||
<span>가져오기</span>
|
||||
</Button>
|
||||
}
|
||||
{isExcelExport &&
|
||||
<Button
|
||||
className="h-[3.2rem] px-6 bg-gradient-green hover:bg-gradient-green-hover w-[96px]"
|
||||
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;
|
||||
|
||||
const currentInfo = columnInfo.find(info => info.tabKey === tabKey);
|
||||
if (!currentInfo) return;
|
||||
|
||||
gridView.setDisplayOptions({
|
||||
fitStyle: GridFitStyle.EVEN
|
||||
});
|
||||
gridView.header.height = 60;
|
||||
|
||||
// 헤더 체크박스 표시 여부 설정
|
||||
gridView.setOptions({
|
||||
checkBar: { visible: showHeaderCheckbox },
|
||||
rowIndicator: { visible: false },
|
||||
});
|
||||
|
||||
// footer가 있는 컬럼이 있으면 footer 높이 설정
|
||||
const hasFooter = currentInfo.columns.some(col => col.footer);
|
||||
if (hasFooter) {
|
||||
gridView.footer.height = 40;
|
||||
}
|
||||
|
||||
gridView.setOptions({
|
||||
summaryMode: "aggregate"
|
||||
});
|
||||
|
||||
// footer의 valueCallback 설정
|
||||
currentInfo.columns.forEach(col => {
|
||||
if (col.footer && col.footer.valueCallback) {
|
||||
const column = gridView.columnByName(col.name);
|
||||
if (column && column.footer) {
|
||||
(column.footer as { valueCallback?: typeof col.footer.valueCallback }).valueCallback = col.footer.valueCallback;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 고정 컬럼 설정
|
||||
if (currentInfo.fixedColumns !== undefined && currentInfo.fixedColumns > 0) {
|
||||
gridView.setFixedOptions({
|
||||
colCount: currentInfo.fixedColumns,
|
||||
});
|
||||
}
|
||||
|
||||
// valueType이 설정된 컬럼에 대해 필드의 dataType을 명시적으로 설정
|
||||
// autoGenerateField={true}일 때 valueType prop만으로는 필드의 dataType이 설정되지 않을 수 있음
|
||||
if (el.dataProvider) {
|
||||
const dataProvider = el.dataProvider;
|
||||
const fieldsToSet: Array<{ fieldName: string; dataType: string }> = [];
|
||||
const fieldMap = new Map<string, string>();
|
||||
|
||||
currentInfo.columns.forEach(col => {
|
||||
if (col.valueType && col.fieldName && !fieldMap.has(col.fieldName)) {
|
||||
fieldMap.set(col.fieldName, col.valueType);
|
||||
fieldsToSet.push({
|
||||
fieldName: col.fieldName,
|
||||
dataType: col.valueType
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 필드가 이미 존재하는지 확인하고, valueType이 설정된 필드만 업데이트
|
||||
if (fieldsToSet.length > 0) {
|
||||
try {
|
||||
// 기존 필드 정보 가져오기
|
||||
const existingFields = (dataProvider as { getFields?: () => Array<{ fieldName: string; dataType?: string }> }).getFields?.() || [];
|
||||
const existingFieldMap = new Map(existingFields.map(f => [f.fieldName, f.dataType || 'text']));
|
||||
|
||||
// valueType이 설정된 필드 중 dataType이 다른 필드만 업데이트
|
||||
const fieldsToUpdate = fieldsToSet.filter(field => {
|
||||
const existingDataType = existingFieldMap.get(field.fieldName);
|
||||
return existingDataType !== field.dataType;
|
||||
});
|
||||
|
||||
if (fieldsToUpdate.length > 0) {
|
||||
// 기존 필드 정보를 유지하면서 dataType만 업데이트
|
||||
// 컬럼 정의에 valueType이 있으면 그것을 우선 사용, 없으면 기존 필드의 dataType 사용
|
||||
const allFields = existingFields.map(f => ({
|
||||
fieldName: f.fieldName,
|
||||
dataType: fieldMap.get(f.fieldName) || f.dataType || 'text'
|
||||
}));
|
||||
|
||||
// 새로 추가할 필드가 있으면 추가
|
||||
fieldsToSet.forEach(field => {
|
||||
if (!existingFieldMap.has(field.fieldName)) {
|
||||
allFields.push({ fieldName: field.fieldName, dataType: field.dataType });
|
||||
}
|
||||
});
|
||||
|
||||
// setFields를 호출하여 필드의 dataType 업데이트
|
||||
(dataProvider as { setFields: (fields: Array<{ fieldName: string; dataType: string }>) => void })
|
||||
.setFields(allFields);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to set field dataType:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 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}
|
||||
valueType={column.valueType ? column.valueType : ValueType.TEXT}
|
||||
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.00' })}
|
||||
header={column.header}
|
||||
editable={column.editable}
|
||||
footer={column.footer ? {
|
||||
text: column.footer.text,
|
||||
expression: column.footer.expression,
|
||||
numberFormat: column.footer.numberFormat,
|
||||
} : undefined}
|
||||
mergeRule={column.mergeCells ? { criteria: 'value' } : undefined}
|
||||
sortable={!column.sortdisable}
|
||||
/>
|
||||
))}
|
||||
</RealGridReact>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabDataGrid;
|
||||
@@ -0,0 +1,105 @@
|
||||
/* RealGrid2 폰트 설정 - 루트 폰트와 일치 */
|
||||
.rg-root,
|
||||
.rg-grid,
|
||||
.rg-body,
|
||||
.rg-cell,
|
||||
.rg-header,
|
||||
.rg-head,
|
||||
.rg-rowbarcontainer {
|
||||
font-family: "Noto Sans KR", sans-serif !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
/* RealGrid2 컨테이너 스타일 */
|
||||
.rg-root {
|
||||
border-radius: 0.5rem !important;
|
||||
overflow: hidden !important;
|
||||
border: 1px solid #dbe2e6 !important;
|
||||
}
|
||||
|
||||
.rg-grid {
|
||||
border-radius: 0.5rem !important;
|
||||
/* box-shadow: 0 0 10px -1px rgba(0, 0, 0, 0.06); */
|
||||
}
|
||||
|
||||
/* 헤더 스타일 */
|
||||
.rg-header,
|
||||
.rg-head,
|
||||
.rg-foot,
|
||||
.rg-footer,
|
||||
.rg-fixed-footer {
|
||||
background-color: #f5f7fa !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
/* 헤더 셀 스타일 */
|
||||
.rg-header .rg-cell,
|
||||
.rg-head .rg-cell {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 헤더 필터링 영역 위치 조정 - 오른쪽 여백 추가 */
|
||||
.rg-header-filtering {
|
||||
left: auto !important;
|
||||
right: 8px !important;
|
||||
}
|
||||
|
||||
/* 그룹 헤더 스타일 */
|
||||
.rg-header-group,
|
||||
.rg-header-group-cell {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 첫 번째 헤더 셀 - 왼쪽 상단 모서리 */
|
||||
.rg-header .rg-cell:first-child,
|
||||
.rg-head .rg-cell:first-child {
|
||||
border-top-left-radius: 8px !important;
|
||||
}
|
||||
|
||||
/* 마지막 헤더 셀 - 오른쪽 상단 모서리 */
|
||||
.rg-header .rg-cell:last-child,
|
||||
.rg-head .rg-cell:last-child {
|
||||
border-top-right-radius: 8px !important;
|
||||
}
|
||||
|
||||
/* border color 통일 */
|
||||
.rg-header .rg-table tr td,
|
||||
.rg-head .rg-table tr td:not(:first-child),
|
||||
.rg-body .rg-table tr td,
|
||||
.rg-rowbarcontainer .rg-table tr td:not(:first-child) {
|
||||
border-left: 1px solid #dbe2e6 !important;
|
||||
border-bottom: 1px solid #dbe2e6 !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
.rg-rowindicator-head,
|
||||
.rg-rowbarcontainer .rg-table tr td:first-child {
|
||||
border-right: none !important;
|
||||
border-bottom: 1px solid #dbe2e6 !important;
|
||||
border-left: none !important;
|
||||
}
|
||||
.rg-footer .rg-table tr td,
|
||||
.rg-fixed-footer .rg-table tr td,
|
||||
.rg-foot .rg-table tr td {
|
||||
border-right: 1px solid #dbe2e6 !important;
|
||||
border-top: 1px solid #dbe2e6 !important;
|
||||
}
|
||||
|
||||
.rg-rowbarcontainer .rg-table tr td {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 푸터 마지막 요소 - 오른쪽 border 제거 */
|
||||
.rg-footer .rg-table tr td:last-child {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
/* 고정 컬럼 바 */
|
||||
.rg-fixed-column-bar {
|
||||
background-color: #ccc !important;
|
||||
border-right: 1px solid #ccc !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import "./grid/css/DataGrid.css";
|
||||
|
||||
export { default as DataGrid } from "./grid/DataGrid";
|
||||
export { default as TabDataGrid } from "./grid/TabDataGrid";
|
||||
export * from "./type/DataGrid.type";
|
||||
export * from "./store/alert-store";
|
||||
export * from "./store/tab-store";
|
||||
export { AlertDialog } from "./ui/alert-dialog";
|
||||
@@ -0,0 +1,25 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
export type AlertState = {
|
||||
message: string;
|
||||
callback?: () => void;
|
||||
};
|
||||
|
||||
export type AlertActions = {
|
||||
setMessage: (message: string, callback?: () => void) => void;
|
||||
};
|
||||
|
||||
export type AlertStore = AlertState & AlertActions;
|
||||
|
||||
interface AlertStoreState extends AlertStore {}
|
||||
|
||||
const useAlertStoreHook = create<AlertStoreState>((set) => ({
|
||||
message: "",
|
||||
callback: undefined,
|
||||
setMessage: (message: string, callback?: () => void) =>
|
||||
set(() => ({ message, callback })),
|
||||
}));
|
||||
|
||||
export const useAlertStore = useAlertStoreHook;
|
||||
export const getAlertStoreState = () => useAlertStoreHook.getState();
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
// MenuItem 타입 정의 (필요한 필드만 포함)
|
||||
export interface MenuItem {
|
||||
menuCd: number;
|
||||
menuKornNm: string;
|
||||
menuEngNm: string;
|
||||
parentId: number | null;
|
||||
sortSeq: number;
|
||||
iconNm?: string;
|
||||
children: MenuItem[] | null;
|
||||
levelNo: number;
|
||||
urlPath: string;
|
||||
menuLnkgTypeCd: string;
|
||||
menuLnkgSn: number;
|
||||
menuPath: string;
|
||||
qrcd: string;
|
||||
useYn: string;
|
||||
otptYn: string;
|
||||
newWndwYn: string;
|
||||
requiredUrlPath: boolean;
|
||||
menuHlpCn: string;
|
||||
}
|
||||
|
||||
export interface Tab {
|
||||
id: string; // 고유 ID (menuEngNm)
|
||||
label: string; // 탭에 표시될 이름
|
||||
path: string; // 메뉴 경로
|
||||
urlPath: string; // url 경로
|
||||
parentEngNm: string; // 부모 menuEngNm
|
||||
menuItem: MenuItem; // 메뉴 정보
|
||||
}
|
||||
|
||||
interface TabStore {
|
||||
tabs: Tab[];
|
||||
activeTabId: string | null;
|
||||
menuList: MenuItem[] | null;
|
||||
addTab: (menuItem: MenuItem) => boolean;
|
||||
removeTab: (tabId: string) => void;
|
||||
setActiveTab: (tabId: string) => void;
|
||||
setMenuList: (menuList: MenuItem[] | null) => void;
|
||||
closeAllTabs: () => void;
|
||||
closeOtherTabs: (tabId: string) => void;
|
||||
}
|
||||
|
||||
const useTabStoreHook = create<TabStore>((set, get) => ({
|
||||
tabs: [],
|
||||
activeTabId: null,
|
||||
menuList: null,
|
||||
addTab: (menuItem: MenuItem) => {
|
||||
const { tabs, menuList } = get();
|
||||
const tabId = menuItem.menuEngNm || '';
|
||||
|
||||
// 이미 존재하는 탭인지 확인
|
||||
const existingTab = tabs.find((tab) => tab.id === tabId);
|
||||
|
||||
if (existingTab) {
|
||||
// 이미 존재하면 활성화만
|
||||
set({ activeTabId: tabId });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tabs.length < 100) {
|
||||
const newTab: Tab = {
|
||||
id: tabId,
|
||||
label: menuItem.menuKornNm,
|
||||
path: menuItem.menuPath,
|
||||
urlPath: menuItem.urlPath,
|
||||
parentEngNm: menuList?.find((m) => m.menuCd === menuItem.parentId)?.menuEngNm || '',
|
||||
menuItem,
|
||||
};
|
||||
|
||||
set({
|
||||
tabs: [...tabs, newTab],
|
||||
activeTabId: tabId,
|
||||
});
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
removeTab: (tabId: string): void => {
|
||||
const { tabs, activeTabId } = get();
|
||||
const newTabs = tabs.filter((tab) => tab.id !== tabId);
|
||||
|
||||
// 닫는 탭이 현재 활성 탭이면 다른 탭으로 전환
|
||||
let newActiveTabId = activeTabId;
|
||||
if (activeTabId === tabId) {
|
||||
const closedTabIndex = tabs.findIndex((tab) => tab.id === tabId);
|
||||
if (newTabs.length > 0) {
|
||||
// 닫힌 탭의 이전 탭을 활성화, 없으면 다음 탭
|
||||
if (closedTabIndex > 0) {
|
||||
const prevTab = tabs[closedTabIndex - 1];
|
||||
newActiveTabId = prevTab ? prevTab.id : null;
|
||||
} else {
|
||||
const firstTab = newTabs[0];
|
||||
newActiveTabId = firstTab ? firstTab.id : null;
|
||||
}
|
||||
} else {
|
||||
newActiveTabId = null;
|
||||
}
|
||||
}
|
||||
|
||||
set({
|
||||
tabs: newTabs,
|
||||
activeTabId: newActiveTabId,
|
||||
});
|
||||
},
|
||||
setActiveTab: (tabId: string) => {
|
||||
set({ activeTabId: tabId });
|
||||
},
|
||||
setMenuList: (menuList: MenuItem[] | null) => {
|
||||
set({ menuList });
|
||||
},
|
||||
closeAllTabs: () => {
|
||||
set({
|
||||
tabs: [],
|
||||
activeTabId: null,
|
||||
});
|
||||
},
|
||||
closeOtherTabs: (tabId: string) => {
|
||||
const { tabs } = get();
|
||||
const tabToKeep = tabs.find((tab) => tab.id === tabId);
|
||||
|
||||
if (tabToKeep) {
|
||||
set({
|
||||
tabs: [tabToKeep],
|
||||
activeTabId: tabId,
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
export const useTabStore = useTabStoreHook;
|
||||
export const getTabStoreState = () => useTabStoreHook.getState();
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from "react";
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// RealGrid 타입 정의
|
||||
export type GridView = {
|
||||
getSummary(field: string, summary: 'sum' | 'avg' | 'count' | 'max' | 'min'): number | undefined;
|
||||
getDataSource(): {
|
||||
getRowCount(): number;
|
||||
getValue(rowIndex: number, field: string): number | string;
|
||||
getDataRowId(dataRow: number): number | string;
|
||||
};
|
||||
getDataRow(itemIndex: number): number;
|
||||
};
|
||||
|
||||
export type MergeCallback = (grid: GridView, column: DataColumn, itemIndex: number, value: number | string | null | undefined) => number | null;
|
||||
|
||||
export type DataColumn = {
|
||||
name: string;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type ColumnFooter = {
|
||||
text?: string;
|
||||
expression?: string;
|
||||
numberFormat?: string;
|
||||
};
|
||||
|
||||
export type ColumnConfig = {
|
||||
name: string;
|
||||
fieldName: string;
|
||||
header: { text: string };
|
||||
editable?: boolean; // 셀 편집 가능 여부
|
||||
mergeCells?: boolean;
|
||||
sortdisable?: boolean;
|
||||
valueType?: ValueType;
|
||||
dataFormat?: string;
|
||||
footer?: {
|
||||
text?: string;
|
||||
expression?: 'sum' | 'avg' | 'count' | 'max' | 'min';
|
||||
numberFormat?: string;
|
||||
valueCallback?: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => number | string;
|
||||
},
|
||||
};
|
||||
|
||||
export type ColumnLayoutItem = string | {
|
||||
name: string;
|
||||
hideChildHeaders?: boolean;
|
||||
header: { text: string };
|
||||
items: (string | ColumnLayoutItem)[];
|
||||
};
|
||||
|
||||
export type ColumnInfo = {
|
||||
columns: ColumnConfig[];
|
||||
columnLayout: ColumnLayoutItem[];
|
||||
};
|
||||
|
||||
export type ColumnInfoList = {
|
||||
tabKey: string; // 탭을 식별하는 고유 키 (필수), server(api)에 전송할 변수 값
|
||||
columns: ColumnConfig[];
|
||||
columnLayout: ColumnLayoutItem[];
|
||||
rowStartNum?: number;
|
||||
fixedColumns?: number;
|
||||
tabLabel?: string; // 탭에 표시될 레이블
|
||||
}
|
||||
|
||||
export type DataGridProps = {
|
||||
columnInfo: ColumnInfo;
|
||||
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
|
||||
isExcelImport?: boolean; // 엑셀 가져오기 유무
|
||||
isExcelExport?: boolean;
|
||||
onRowsChange?: (rows: Record<string, string | number>[]) => void;
|
||||
fixedColumns?: number; // 고정할 컬럼 개수 (좌측부터)
|
||||
showHeaderCheckbox?: boolean; // 헤더 체크박스 표시 여부 (기본값: true)
|
||||
rowData: Record<string, string | number>[];
|
||||
setRowData: React.Dispatch<React.SetStateAction<Record<string, string | number>[]>>;
|
||||
};
|
||||
|
||||
export type TabDataGridProps = {
|
||||
columnInfo: ColumnInfoList[];
|
||||
rows?: Record<string, string | number>[];
|
||||
isExcelImport?: boolean; // 엑셀 가져오기 유무
|
||||
isExcelExport?: boolean;
|
||||
onRowsChange?: (rows: Record<string, string | number>[]) => void;
|
||||
showHeaderCheckbox?: boolean; // 헤더 체크박스 표시 여부 (기본값: true)
|
||||
rowDataMap: Map<string, Record<string, string | number>[]>;
|
||||
setRowDataMap: React.Dispatch<React.SetStateAction<Map<string, Record<string, string | number>[]>>>;
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
Button,
|
||||
} from "@common/components";
|
||||
import { useAlertStore } from "../store/alert-store";
|
||||
|
||||
function AlertDialog() {
|
||||
const { message, callback, setMessage } = useAlertStore((state) => state);
|
||||
const handleOpenChange = () => {
|
||||
setMessage("", undefined);
|
||||
callback?.();
|
||||
};
|
||||
return (
|
||||
<Dialog open={!!message} onOpenChange={handleOpenChange}>
|
||||
<DialogContent aria-describedby={undefined}>
|
||||
<div className="py-10">
|
||||
<p className="whitespace-pre-line text-center text-[1.4rem] text-foreground">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter className="!justify-center">
|
||||
<DialogClose asChild>
|
||||
<Button>확인</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { AlertDialog };
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@rsone/components",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./index.css": "./dist/index.css"
|
||||
},
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@common/components": "workspace:*",
|
||||
"realgrid": "^2.9.5",
|
||||
"realgrid-react": "^0.9.7",
|
||||
"jszip": "^3.10.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.1",
|
||||
"react-dom": "^19.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "8.3.6",
|
||||
"typescript": "~5.6.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": false,
|
||||
"incremental": true,
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"components/*"
|
||||
],
|
||||
"@common/components": [
|
||||
"../common/components/src"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"**/node_modules",
|
||||
"**/.*/*",
|
||||
"dist"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import path from "path";
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig((options) => ({
|
||||
entry: ["components/index.ts"],
|
||||
splitting: true,
|
||||
treeshake: true,
|
||||
clean: true,
|
||||
dts: true,
|
||||
format: ["esm"],
|
||||
external: ["react", "react-dom", "realgrid", "realgrid-react"],
|
||||
platform: "browser",
|
||||
target: ["es2020"],
|
||||
tsconfig: path.resolve(__dirname, "tsconfig.build.json"),
|
||||
sourcemap: !options.watch,
|
||||
minify: !options.watch,
|
||||
}));
|
||||
Generated
+37
@@ -936,6 +936,9 @@ importers:
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.1.8
|
||||
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rsone/components':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rsone
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.67.1
|
||||
version: 5.90.12(react@19.2.3)
|
||||
@@ -1626,6 +1629,40 @@ importers:
|
||||
specifier: ^5.3.3
|
||||
version: 5.9.3
|
||||
|
||||
packages/rsone:
|
||||
dependencies:
|
||||
'@common/components':
|
||||
specifier: workspace:*
|
||||
version: link:../common/components
|
||||
jszip:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1
|
||||
react:
|
||||
specifier: ^19.2.1
|
||||
version: 19.2.3
|
||||
react-dom:
|
||||
specifier: ^19.2.1
|
||||
version: 19.2.3(react@19.2.3)
|
||||
realgrid:
|
||||
specifier: ^2.9.5
|
||||
version: 2.9.6
|
||||
realgrid-react:
|
||||
specifier: ^0.9.7
|
||||
version: 0.9.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
xlsx:
|
||||
specifier: ^0.18.5
|
||||
version: 0.18.5
|
||||
zustand:
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.9(@types/react@19.2.7)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
|
||||
devDependencies:
|
||||
tsup:
|
||||
specifier: 8.3.6
|
||||
version: 8.3.6(jiti@2.6.1)(postcss@8.5.6)(typescript@5.6.3)
|
||||
typescript:
|
||||
specifier: ~5.6.2
|
||||
version: 5.6.3
|
||||
|
||||
packages:
|
||||
|
||||
'@adobe/css-tools@4.4.4':
|
||||
|
||||
Reference in New Issue
Block a user