Merge remote-tracking branch 'origin/main' into iipbs/sn/dev

This commit is contained in:
dongsilove
2026-02-25 20:22:11 +09:00
200 changed files with 19840 additions and 783 deletions
@@ -118,7 +118,7 @@ export const linkFormSchema = z.object({
last_link_rslt_cd: z.string().max(4).nullable().optional(),
last_link_excn_dt: z.string().max(40).nullable().optional(),
next_link_excn_dt: z.string().max(40).nullable().optional(),
last_link_err_cd: z.string().max(10).nullable().optional(),
last_link_err_cd: z.string().max(50, { message: '마지막 연계 오류 코드는 50자 이하입니다.' }).nullable().optional(),
mdfcn_dt: z.string().nullable().optional(),
mdfr_id: z.string().nullable().optional(),
link_cycle_mi: z.coerce.number().nullable().optional().refine((val) => {
+2 -1
View File
@@ -38,4 +38,5 @@ yarn-error.log*
next-env.d.ts
.env*
!.env.example
!.env.example
*.md
+2 -1
View File
@@ -1,3 +1,4 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
"plugins": ["prettier-plugin-tailwindcss"],
"tabWidth": 4
}
+64 -5
View File
@@ -1,12 +1,13 @@
"use client";
import { useEffect, useCallback } from "react";
import { useEffect, useCallback, useMemo } from "react";
import { usePathname } from "next/navigation";
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";
@@ -29,10 +30,17 @@ export default function WithBaseLayout({
const queryClient = useQueryClient();
const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo);
const addTab = useTabStore((state) => state.addTab);
const setActiveTab = useTabStore((state) => state.setActiveTab);
const setMenuList = useTabStore((state) => state.setMenuList);
const activeTabId = useTabStore((state) => state.activeTabId);
const menuList = useTabStore((state) => state.menuList);
const tabs = useTabStore((state) => state.tabs);
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 +52,9 @@ export default function WithBaseLayout({
useEffect(() => {
if (menuListData) {
setMenuList(menuListData);
setMenuListForGrid(menuListData);
}
}, [menuListData, setMenuList]);
}, [menuListData, setMenuList, setMenuListForGrid]);
useEffect(() => {
if (!pathname) return;
@@ -55,23 +64,73 @@ 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]);
// 활성 탭의 메뉴를 찾아서 urlPath를 가져옴. 탭이 전부 닫히면 ''를 넘겨 LNB에서 메뉴 강조 제거
const currentPath = useMemo(() => {
if (tabs.length === 0) return "";
if (activeTabId && menuList) {
// menuList에서 활성 탭의 메뉴 찾기
const findMenuByEngNm = (items: MenuItem[]): MenuItem | null => {
for (const item of items) {
if (item.menuEngNm === activeTabId) {
return item;
}
if (item.children) {
const found = findMenuByEngNm(item.children);
if (found) return found;
}
}
return null;
};
const activeMenu = findMenuByEngNm(menuList);
if (activeMenu?.urlPath) {
return activeMenu.urlPath;
}
}
// 탭에서 직접 찾기
if (activeTabId) {
const currentTabs = getTabStoreState().tabs;
const activeTab = currentTabs.find((t) => t.id === activeTabId);
if (activeTab?.menuItem?.urlPath) {
return activeTab.menuItem.urlPath;
}
}
// currentMenuInfo에서 가져오기
if (currentMenuInfo?.urlPath) {
return currentMenuInfo.urlPath;
}
// 기본값으로 pathname 사용
return pathname ?? "";
}, [tabs.length, activeTabId, menuList, currentMenuInfo, pathname]);
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, alert]);
return (
<BaseLayout
queryClient={queryClient}
currentPath={pathname ?? ""}
currentPath={currentPath}
handleMenuClick={handleMenuClick}
>
<StatisticsPageLayout>{children}</StatisticsPageLayout>
@@ -1,3 +1,3 @@
import { statisticsPage } from "@/pages/statistics";
import { StatisticsPage } from "@/pages/statistics";
export default statisticsPage;
export default StatisticsPage;
+16 -6
View File
@@ -6,16 +6,26 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
rules: {
"react-hooks/exhaustive-deps": "off",
...compat.extends("next/core-web-vitals", "next/typescript"),
{
rules: {
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-wrapper-object-types": "warn",
},
},
},
];
export default eslintConfig;
+2 -2
View File
@@ -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*", "/"],
};
+2
View File
@@ -12,6 +12,7 @@
"@common/assets": "workspace:*",
"@common/components": "workspace:*",
"@common/config": "workspace:*",
"@faker-js/faker": "^9.9.0",
"@fontsource-variable/noto-sans-kr": "^5.2.5",
"@frontend-opensource/use-react-hooks": "^1.1.4",
"@fullcalendar/core": "^6.1.17",
@@ -25,6 +26,7 @@
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@rsone/components": "workspace:*",
"@tanstack/react-query": "^5.67.1",
"@tanstack/react-query-devtools": "^5.75.5",
"@tanstack/react-table": "^8.21.2",
@@ -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 />
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface AddArticleButtonProps {
addArticleFunc: () => void;
}
const AddArticleButton = ({ addArticleFunc }: AddArticleButtonProps) => {
return (
<Button className="h-[3.2rem] w-[96px]" onClick={addArticleFunc}>
<i className="ri-add-fill" />
<span>항목 추가</span>
</Button>
);
};
export default AddArticleButton;
@@ -6,9 +6,9 @@ interface ApproveButtonProps {
const ApproveButton = ({ approveFunc }: ApproveButtonProps) => {
return (
<Button color="emerald" className="px-6" onClick={approveFunc}>
<i className="diveicon di-check-circle translate-y-[1px]" />
<span className="translate-y-[-1px]">승인</span>
<Button className="h-[3.2rem] w-[96px]" onClick={approveFunc}>
<i className="diveicon di-check-circle" />
<span>승인</span>
</Button>
);
};
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface DeleteArticleButtonProps {
deleteArticleFunc: () => void;
}
const DeleteArticleButton = ({ deleteArticleFunc }: DeleteArticleButtonProps) => {
return (
<Button className="h-[3.2rem] w-[96px]" color="red" onClick={deleteArticleFunc}>
<i className="ri-subtract-fill"></i>
<span>항목 제거</span>
</Button>
);
};
export default DeleteArticleButton;
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface InstallationStatusButtonProps {
installationStatusFunc: () => void;
}
const InstallationStatusButton = ({ installationStatusFunc }: InstallationStatusButtonProps) => {
return (
<Button className="h-[3.2rem] w-[176px]" color="lightGray" onClick={installationStatusFunc}>
<i className="diveicon di-server-line"></i>
<span>시설별 설치 현황관리</span>
</Button>
);
};
export default InstallationStatusButton;
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface LastYearDataButtonProps {
getLastYearDataFunc: () => void;
}
const LastYearDataButton = ({ getLastYearDataFunc }: LastYearDataButtonProps) => {
return (
<Button color="emerald" className="h-[3.2rem] w-[176px]" onClick={getLastYearDataFunc}>
<i className="diveicon di-search" />
<span>지난연도 데이터 가져오기</span>
</Button>
);
};
export default LastYearDataButton;
@@ -0,0 +1,28 @@
import { Checkbox, buttonVariants, cn } from "@common/components";
import type React from "react";
interface NotApplicableCheckButtonProp {
isCheck: boolean;
setIsCheck: React.Dispatch<React.SetStateAction<boolean>>;
}
const NotApplicableCheckButton = ({ isCheck, setIsCheck }: NotApplicableCheckButtonProp) => {
const handleCheckChange = (value: boolean) => {
setIsCheck(value);
};
return (
<div className={cn(buttonVariants({ variant: "default", size: "default" }), "w-[124px] bg-[#ebebeb] border border-[#999] text-[rgb(146 146 146)] text-opacity-100")}>
<label htmlFor="notApplicable">해당사항 없음</label>
<Checkbox
id="notApplicable"
className="!border-[#999] data-[state=checked]:!bg-[#999]"
checked={isCheck}
onCheckedChange={handleCheckChange}
/>
</div>
);
};
export default NotApplicableCheckButton;
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface OperateDataButtonProps {
getOperateDataFunc: () => void;
}
const OperateDataButton = ({ getOperateDataFunc }: OperateDataButtonProps) => {
return (
<Button color="emerald" className="h-[3.2rem] w-[176px]" onClick={getOperateDataFunc}>
<i className="diveicon di-arrow-left-right" />
<span>운영 데이터 가져오기</span>
</Button>
);
};
export default OperateDataButton;
@@ -6,9 +6,9 @@ interface RejectButtonProps {
const RejectButton = ({ rejectFunc }: RejectButtonProps) => {
return (
<Button color="red" className="px-6" onClick={rejectFunc}>
<i className="diveicon di-exclamation-circle translate-y-[1px]" />
<span className="translate-y-[-1px]">보완 요청</span>
<Button color="red" className="h-[3.2rem] w-[96px]" onClick={rejectFunc}>
<i className="diveicon di-exclamation-circle" />
<span>보완 요청</span>
</Button>
);
};
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface ReturnButtonProps {
returnFunc: () => void;
}
const ReturnButton = ({ returnFunc }: ReturnButtonProps) => {
return (
<Button color="red" className="h-[3.2rem] w-[96px]" onClick={returnFunc}>
<i className="diveicon di-arrow-reset" />
<span>반려</span>
</Button>
);
};
export default ReturnButton;
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface ReviewOpinionButtonProps {
reviewOpinionFunc: () => void;
}
const ReviewOpinionButton = ({ reviewOpinionFunc }: ReviewOpinionButtonProps) => {
return (
<Button color="emerald" className="h-[3.2rem] w-[124px]" onClick={reviewOpinionFunc}>
<i className="diveicon di-pencil" />
<span>검토의견 작성</span>
</Button>
);
};
export default ReviewOpinionButton;
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface SaveButtonProps {
saveFunc: () => void;
}
const SaveButton = ({ saveFunc }: SaveButtonProps) => {
return (
<Button color="green" className="h-[3.2rem] w-[96px]" onClick={saveFunc}>
<i className="diveicon di-download" />
<span>저장</span>
</Button>
);
};
export default SaveButton;
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface SubmitExceptionButtonProps {
SubmitExceptionFunc: () => void;
}
const SubmitExceptionButton = ({ SubmitExceptionFunc }: SubmitExceptionButtonProps) => {
return (
<Button color="red" className="h-[3.2rem] w-[96px]" onClick={SubmitExceptionFunc}>
<i className="diveicon di-box-arrow-upload" />
<span>예외제출</span>
</Button>
);
};
export default SubmitExceptionButton;
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface SubmitButtonProps {
submitFunc: () => void;
}
const SubmitButton = ({ submitFunc }: SubmitButtonProps) => {
return (
<Button className="h-[3.2rem] w-[96px]" onClick={submitFunc}>
<i className="diveicon di-upload" />
<span>제출</span>
</Button>
);
};
export default SubmitButton;
@@ -0,0 +1,16 @@
import { Button } from "@common/components";
interface UpdateOperateDataButtonProps {
updateOperateDataButtonFunc: () => void;
}
const UpdateOperateDataButton = ({ updateOperateDataButtonFunc }: UpdateOperateDataButtonProps) => {
return (
<Button color="emerald" className="h-[3.2rem] w-[176px]" onClick={updateOperateDataButtonFunc}>
<i className="diveicon di-arrow-up-down" />
<span>운영 데이터 업데이트</span>
</Button>
);
};
export default UpdateOperateDataButton;
+52 -104
View File
@@ -1,105 +1,53 @@
/* 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;
@tailwind components;
@layer components {
.rg-grid .rg-header .rg-table .rg-header-group-cell {
@apply !bg-[#466995] !text-[#fff];
}
.rg-grid .rg-header .rg-table .rg-header-cell {
@apply !bg-[#5987bf] !text-[#fff];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
@apply !bg-white;
}
/* 셀 클래스가 있으면 위 기본색보다 우선 (같은 specificity + 나중 규칙) */
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly {
@apply !bg-[#f0f0f0] !text-[#666];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-verify-error {
@apply !bg-[#ffe6e6] !text-[#c00];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-total {
@apply !bg-[#e3eaf2];
}
[data-theme="black"] {
.rg-grid .rg-header .rg-table .rg-header-group-cell {
@apply !bg-[#3C4043] !text-[#fff];
}
.rg-grid .rg-header .rg-table .rg-header-cell {
@apply !bg-[#62686d] !text-[#fff];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
@apply !bg-[#545556] !text-[#fff];
}
}
[data-theme="red"] {
.rg-grid .rg-header .rg-table .rg-header-group-cell {
@apply !bg-[#646469] !text-[#fff];
}
.rg-grid .rg-header .rg-table .rg-header-cell {
@apply !bg-[#8d8d94] !text-[#fff];
}
}
[data-theme="green"] {
.rg-grid .rg-header .rg-table .rg-header-group-cell {
@apply !bg-[#518585] !text-[#fff];
}
.rg-grid .rg-header .rg-table .rg-header-cell {
@apply !bg-[#6bafae] !text-[#fff];
}
}
}
/* 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,295 @@
import { useRef, useEffect } from "react"
import { ValueType } from "realgrid"
import RealGrid, { GridFitStyle } from "realgrid"
import { RealGridReact, RGDataColumn } from "realgrid-react"
import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback } from "../type/DataGrid.type"
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
// RealGrid 타입 확장
declare module "realgrid" {
interface GridView {
setOptions(options: {
footer?: { visible?: boolean };
summaryMode?: 'aggregate' | 'statistical' | 'none';
checkBar?: { visible?: boolean };
}): void;
}
}
RealGrid.setLicenseKey(process.env.NEXT_PUBLIC_ST_REALGRID_KEY || '');
/** setDataComparer(field, comparer) - setFields 없이 숫자 정렬용. comparer(field, row1, row2) => -1|0|1 */
interface RealGridDataProviderComparer {
setDataComparer?(field: string | number, comparer: (field: string | number, row1: number, row2: number) => number): void;
getValue?(row: number, field: string | number): unknown;
}
/** columnInfo → RealGrid DataField 배열 (computed → valueCallback 포함, setFields용) */
function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueType; computed?: import("../type/DataGrid.type").ComputedFieldConfig }[]) {
return columns.map((col) => {
const field: { fieldName: string; dataType: string; valueCallback?: (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number } = {
fieldName: col.fieldName,
dataType: col.valueType === ValueType.NUMBER ? "number" : "text",
};
if (col.computed) {
const hasFields = "fields" in col.computed && col.computed.fields.length > 0;
const hasNumeratorDenominator = "numeratorFields" in col.computed && col.computed.numeratorFields.length > 0 && col.computed.denominatorFields.length > 0;
if (hasFields || hasNumeratorDenominator) {
field.valueCallback = getComputedValueCallback(col.computed) as (prod: unknown, dataRow: number, fieldName: string, fieldNames: string[], values: unknown[]) => number;
}
}
return field;
});
}
/** setDataComparer용 숫자 비교기. setFields 없이 숫자 정렬. */
function createNumberComparer(dp: RealGridDataProviderComparer): (field: string | number, row1: number, row2: number) => number {
return (field, row1, row2) => {
const get = dp.getValue?.bind(dp);
if (!get) return 0;
const val1 = get(row1, field);
const val2 = get(row2, field);
if (val1 == null && val2 == null) return 0;
if (val1 == null) return 1;
if (val2 == null) return -1;
const num1 = typeof val1 === "number" ? val1 : parseFloat(String(val1));
const num2 = typeof val2 === "number" ? val2 : parseFloat(String(val2));
if (isNaN(num1) && isNaN(num2)) return 0;
if (isNaN(num1)) return 1;
if (isNaN(num2)) return -1;
return num1 > num2 ? 1 : num1 < num2 ? -1 : 0;
};
}
const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, rowData, setRowData }: DataGridProps) => {
const { columns, columnLayout } = columnInfo;
const gridRef = useRef<RealGridReact>(null);
useEffect(() => {
if (!gridRef.current?.gridView || !gridRef.current?.dataProvider) return;
const gridView = gridRef.current.gridView;
const dataProvider = gridRef.current.dataProvider;
// setDataComparer로 숫자 정렬 (setFields 미사용 → 데이터 유실·필드 중복 오류 없음)
const dp = dataProvider as RealGridDataProviderComparer;
if (typeof dp.setDataComparer === "function" && typeof dp.getValue === "function") {
const numberComparer = createNumberComparer(dp);
const seen = new Set<string>();
columns.forEach((col) => {
if (col.valueType === ValueType.NUMBER && col.fieldName && !seen.has(col.fieldName)) {
seen.add(col.fieldName);
try {
dp.setDataComparer!(col.fieldName, numberComparer);
} catch (e) {
console.warn("setDataComparer:", e);
}
}
});
}
gridView.setDisplayOptions({
fitStyle: GridFitStyle.EVEN
});
gridView.header.height = 60;
// 헤더 체크박스 표시 여부 설정
gridView.setOptions({
checkBar: { visible: showHeaderCheckbox },
rowIndicator: { visible: false },
});
// footer가 있는 컬럼이 있으면 footer 높이 설정
const hasFooter = columns.some(col => col.footer);
if (hasFooter) {
// footer가 배열이거나 multiRow가 true인 footer가 있으면 멀티푸터 설정 (여러 행)
const hasArrayFooter = columns.some(col => col.footer && Array.isArray(col.footer));
const hasMultiRowFooter = columns.some(col => {
const footer = col.footer;
return footer && !Array.isArray(footer) && 'multiRow' in footer && footer.multiRow === true;
});
if (hasArrayFooter || hasMultiRowFooter) {
// footer 배열의 길이를 확인하여 행 수 결정
const arrayFooterLengths = columns
.filter(col => col.footer && Array.isArray(col.footer))
.map(col => (col.footer as unknown[]).length);
const maxFooterLength = arrayFooterLengths.length > 0
? Math.max(...arrayFooterLengths)
: 2; // 기본값 2
const footerHeights = Array.from({ length: maxFooterLength }, () => ({ height: 40 }));
gridView.setFooters?.(footerHeights);
} else {
gridView.footer.height = 40;
}
}
// 고정 컬럼 설정
if (fixedColumns !== undefined && fixedColumns > 0) {
gridView.setFixedOptions({
colCount: fixedColumns,
});
}
gridView.setOptions({
summaryMode: "aggregate"
});
// footerUserSpans가 있는 경우 setColumnLayout으로 설정
const checkFooterUserSpans = (layout: unknown[]): boolean => {
return layout.some(item => {
if (typeof item === 'string') return false;
if (item && typeof item === 'object' && 'footerUserSpans' in item) return true;
if (item && typeof item === 'object' && 'items' in item && Array.isArray(item.items)) {
return checkFooterUserSpans(item.items);
}
return false;
});
};
if (checkFooterUserSpans(columnLayout)) {
// setColumnLayout은 그리드가 완전히 렌더링된 후 호출해야 함
setTimeout(() => {
if (gridRef.current?.gridView && columnLayout) {
gridRef.current.gridView.setColumnLayout?.(columnLayout);
}
}, 100);
}
// footer 설정 (배열 또는 단일 객체 모두 처리)
// setFooters 호출 후 footer 배열 설정
setTimeout(() => {
const currentGridView = gridRef.current?.gridView;
if (!currentGridView || !currentGridView.columnByName) {
console.warn('GridView not available for footer setup');
return;
}
// footer가 배열인 컬럼이 있으면 setColumns를 다시 호출하여 footer 설정
const hasArrayFooter = columns.some(col => col.footer && Array.isArray(col.footer));
if (hasArrayFooter) {
// 모든 컬럼 정보를 다시 가져와서 footer 배열을 포함하여 setColumns 호출
const columnsWithFooter: RealGridColumnInfo[] = columns.map(col => {
const colInfo: RealGridColumnInfo = {
name: col.name,
fieldName: col.fieldName,
header: col.header,
editable: col.editable,
valueType: col.valueType,
dataFormat: col.dataFormat,
...(col.styleName != null && { styleName: col.styleName }),
...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }),
};
if (col.footer) {
if (Array.isArray(col.footer)) {
colInfo.footer = col.footer;
} else {
colInfo.footer = {
text: col.footer.text,
expression: col.footer.expression,
numberFormat: col.footer.numberFormat,
};
}
}
return colInfo;
});
try {
(currentGridView as GridViewWithSetColumns).setColumns?.(columnsWithFooter);
// setColumns 호출 후 footer 배열의 valueCallback을 다시 설정
setTimeout(() => {
columns.forEach(col => {
if (!col.footer || !Array.isArray(col.footer)) return;
try {
const column = currentGridView.columnByName(col.name);
if (!column) return;
// footer 배열의 각 요소에 valueCallback 설정
col.footer.forEach((footerItem, index) => {
if (footerItem.valueCallback && column.footer) {
// footer가 배열인 경우
if (Array.isArray(column.footer) && column.footer[index]) {
(column.footer[index] as FooterItemWithCallback).valueCallback = footerItem.valueCallback;
}
}
});
} catch (e) {
console.warn(`Error setting footer array valueCallback for column ${col.name}:`, e);
}
});
}, 50);
} catch (e) {
console.warn('Failed to set columns with footer array:', e);
}
}
// footer가 단일 객체이고 valueCallback이 있는 경우
columns.forEach(col => {
if (!col.footer || Array.isArray(col.footer)) return;
try {
const column = currentGridView.columnByName(col.name);
if (!column || !column.footer) return;
if (col.footer.valueCallback) {
(column.footer as { valueCallback?: typeof col.footer.valueCallback }).valueCallback = col.footer.valueCallback;
}
} catch (e) {
console.warn(`Error setting footer valueCallback for column ${col.name}:`, e);
}
});
}, 200);
}, [columns, columnLayout, fixedColumns, rowData, showHeaderCheckbox]);
return (
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem] w-1/2">
<div className="h-[65vh]">
<RealGridReact
ref={gridRef}
autoGenerateField={true}
layout={columnLayout}
rows={rowData}
onInitialized={(grid) => {
const dp = gridRef.current?.dataProvider as { setFields?: (fields: unknown[]) => void } | undefined;
if (dp?.setFields) {
dp.setFields(buildFieldsFromColumns(columns));
}
}}
>
{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}
{...(column.styleName != null
? { styleName: column.styleName }
: column.editable === false
? { styleName: 'rg-cell-readonly' as const }
: {})}
{...(column.visible === false && { visible: false })}
footer={column.footer && !Array.isArray(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 CompactDataGrid;
File diff suppressed because it is too large Load Diff
+65 -28
View File
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useState, useEffect } from "react";
import useSWR from 'swr';
import {
@@ -18,9 +18,9 @@ from "@common/components";
import { useAlertStore } from "@/shared/lib";
import { type SearchFormProps, type SearchFormValues, defaultSearchFormProps } from "../type/SearchForm.type";
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
import type { ApiResponse, Sido, Sgg } from "@/entities/statistics/model/statistics-interface";
import type { ApiResponse, SearchCondition, Sgg } from "@/entities/statistics/model/statistics-interface";
const getSidoListFetcher = async (): Promise<ApiResponse<Sido[]>> => {
const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
return statisticsAPi.getSidoList();
};
@@ -31,15 +31,11 @@ const getSggListFetcher = async (key: [string, string]): Promise<ApiResponse<Sgg
const SearchForm = (props: SearchFormProps = {}) => {
const { setMessage: alert } = useAlertStore((state) => state);
const mergedProps = { ...defaultSearchFormProps, ...props };
const [values, setValues] = useState<SearchFormValues>({
sido: props.defaultValues?.sido || "",
sgg: props.defaultValues?.sgg || "",
year: props.defaultValues?.year || "",
order: props.defaultValues?.order || "",
fctName: props.defaultValues?.fctName || "",
});
const { data: sidoList } = useSWR<ApiResponse<Sido[]>>(
const currentYear = new Date().getFullYear();
const defaultYear = currentYear - 1;
const { data: sidoList } = useSWR<ApiResponse<SearchCondition>>(
"sido-list",
getSidoListFetcher,
{
@@ -61,6 +57,15 @@ const SearchForm = (props: SearchFormProps = {}) => {
}
);
const [values, setValues] = useState<SearchFormValues>({
sido: props.defaultValues?.sido || '',
gugun: props.defaultValues?.gugun || '',
year: props.defaultValues?.year || defaultYear.toString(),
order: props.defaultValues?.order || '',
fctName: props.defaultValues?.fctName || '',
bsnmCd: props.defaultValues?.bsnmCd || '',
});
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
values.sido && values.sido !== "ALL" ? ["sgg-list", values.sido] : null,
getSggListFetcher,
@@ -83,6 +88,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
}
);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
props.onSubmit?.(values);
@@ -90,17 +96,44 @@ const SearchForm = (props: SearchFormProps = {}) => {
const handleReset = () => {
const resetValues: SearchFormValues = {
sido: "",
sgg: "",
year: "",
order: "",
fctName: "",
sido: '',
gugun: '',
year: String(defaultYear),
order: '',
fctName: '',
bsnmCd: '',
};
setValues(resetValues);
props.onReset?.();
props.onSubmit?.(resetValues);
};
const handleSidoChange = (value: string) => {
const selectedBsnm = sidoList?.data.sidoList.find((v) => v.sido === value)?.bsnmCd || '';
setValues((prev) => ({
...prev,
sido: value,
gugun: '',
bsnmCd: selectedBsnm,
}));
};
const handleSggChange = (value: string) => {
const selectedBsnm = value === 'ALL'
? (sidoList?.data.sidoList.find((v) => v.sido === values.sido)?.bsnmCd || '')
: (sggList?.data?.find((v) => v.gugun === value)?.bsnmCd || '');
setValues((prev) => ({
...prev,
gugun: value,
bsnmCd: selectedBsnm,
}));
};
useEffect(() => {
props.onValuesChange?.(values);
}, [values]);
return (
<div className="card card-border">
<form onSubmit={handleSubmit}>
@@ -110,13 +143,13 @@ const SearchForm = (props: SearchFormProps = {}) => {
<Label className="text-label">
시도
</Label>
<Select value={values.sido} onValueChange={(value) => setValues((prev) => ({ ...prev, sido: value, sgg: 'ALL' }))}>
<Select value={values.sido} onValueChange={handleSidoChange}>
<SelectTrigger className="w-[12rem]">
<SelectValue placeholder="전체"></SelectValue>
<SelectValue placeholder="선택"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
{sidoList?.data?.map((sido) => (
<SelectItem value="ALL">선택</SelectItem>
{sidoList?.data?.sidoList?.map((sido) => (
<SelectItem key={sido.adstDstrCd} value={sido.sido}>
{sido.sido}
</SelectItem>
@@ -125,12 +158,12 @@ const SearchForm = (props: SearchFormProps = {}) => {
</Select>
</div>
}
{mergedProps.sgg &&
{mergedProps.gugun &&
<div className="flex items-center gap-2">
<Label className="text-label">
시군구
</Label>
<Select value={values.sgg} onValueChange={(value) => setValues({ ...values, sgg: value })}>
<Select value={values.gugun} onValueChange={handleSggChange}>
<SelectTrigger className="w-[12rem]">
<SelectValue placeholder="전체"></SelectValue>
</SelectTrigger>
@@ -152,10 +185,14 @@ const SearchForm = (props: SearchFormProps = {}) => {
</Label>
<Select value={values.year} onValueChange={(value) => setValues({ ...values, year: value })}>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="전체"></SelectValue>
<SelectValue placeholder="선택"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
{sidoList?.data?.yearList.map((year) => (
<SelectItem key={year} value={String(year)}>
{year}년
</SelectItem>
))}
</SelectContent>
</Select>
</div>
@@ -176,10 +213,10 @@ const SearchForm = (props: SearchFormProps = {}) => {
</div>
}
<div className="flex items-center gap-2 ml-auto">
<Button color="green" className="h-[3.2rem]" type="button" onClick={handleReset}>
<Button color="green" className="h-[3.2rem] w-[96px]" type="button" onClick={handleReset}>
초기화
</Button>
<Button className="h-[3.2rem]" type="submit">
<Button className="h-[3.2rem] w-[96px]" type="submit">
조회
</Button>
</div>
@@ -202,7 +239,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
}
{mergedProps.userMenual &&
<div className="flex items-center gap-2 ml-auto">
<Button className="h-[3.2rem]" type="button">
<Button className="h-[3.2rem] w-[124px]" type="button">
<i className="diveicon di-person-fill" />
사용자 메뉴얼
</Button>
@@ -0,0 +1,35 @@
import CompactDataGrid from "./CompactDataGrid";
import type { SplitDataGridProps } from "../type/DataGrid.type";
const SplitDataGrid = ({
leftColumnInfo,
rightColumnInfo,
leftFixedColumns,
rightFixedColumns,
showHeaderCheckbox = false,
leftRowData,
rightRowData,
setLeftRowData,
setRightRowData,
}: SplitDataGridProps) => {
return (
<div className="flex gap-[0.6rem] mx-[0.6rem]">
<CompactDataGrid
columnInfo={leftColumnInfo}
fixedColumns={leftFixedColumns}
showHeaderCheckbox={showHeaderCheckbox}
rowData={leftRowData}
setRowData={setLeftRowData}
/>
<CompactDataGrid
columnInfo={rightColumnInfo}
fixedColumns={rightFixedColumns}
showHeaderCheckbox={showHeaderCheckbox}
rowData={rightRowData}
setRowData={setRightRowData}
/>
</div>
);
};
export default SplitDataGrid;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,80 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 AddArticleButton from "@/components/button/AddArticleButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
import { ExcretaTreatFactInpColumn } from "@/components/util/column/input/ExcretaTreatFactInpColumn"
// 하수도 통계 관리 > 07. 분뇨처리시설
const ExcretaTreatmentFacilityInp = () => {
const submitExceptionFunc = () => {
console.log('submit exception');
// 예외제출 로직
};
const getLastYearDataFunc = () => {
console.log('get last year data');
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
console.log('submit');
// 제출 로직
};
const saveFunc = () => {
console.log('save');
// 저장 로직
};
const addArticleFunc = () => {
console.log('save');
// 항목 추가 로직
};
const deleteArticleFunc = () => {
console.log('save');
// 항목 제외 로직
};
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isNotApplicable, setIsNotApplicable] = useState(false);
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={submitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SaveButton saveFunc={saveFunc} />
<SubmitButton submitFunc={submitFunc} />
</div>
</div>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div>
</div>
<DataGrid
columnInfo={ExcretaTreatFactInpColumn}
rowStartNum={2}
fixedColumns={2}
rowData={rowData}
setRowData={setRowData}
/>
</>
);
};
export default ExcretaTreatmentFacilityInp;
@@ -0,0 +1,62 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 { SewageTreatFactStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SewageTreatFactStatusInpColumn"
import { SepticTankStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SepticTankStatusInpColumn"
import { PrivateSewageDesigStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/PrivateSewageDesigStatusInpColumn"
// 하수도 통계 관리 > 08. 개인하수처리시설
const PrivateSewageFacilityInp = () => {
// tab list
const PrivateSewageFacilityInpTabList = [
SewageTreatFactStatusInpColumn,
SepticTankStatusInpColumn,
PrivateSewageDesigStatusInpColumn
];
const submitExceptionFunc = () => {
// 예외 제출 로직
};
const lastYearDataFunc = () => {
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
// 제출 로직
};
const saveFunc = () => {
// 저장 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={submitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={lastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid
columnInfo={PrivateSewageFacilityInpTabList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default PrivateSewageFacilityInp;
@@ -0,0 +1,73 @@
import { useState } from 'react';
import SearchForm from '@/components/module/SearchForm';
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 UpdateOperateDataButton from "@/components/button/UpdateOperateDataButton"
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
import { PublicSewageFactInpColumn } from '@/components/util/column/input/PublicSewageFactInpColumn';
// 하수도 통계 관리 > 06. 공공하수처리시설
const PublicSewageFacilityInp = () => {
const SubmitExceptionFunc = () => {
console.log('submitException');
// 예외제출 로직
};
const submitFunc = () => {
console.log('submit');
// 제출 로직
};
const saveFunc = () => {
console.log('save');
// 저장 로직
};
const getOperateDataFunc = () => {
console.log('get operate data');
// 운영 데이터 가져오기
};
const updateOperateDataButtonFunc = () => {
console.log('updatedOperateData');
// 운영 데이터 업데이트 로직
};
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isNotApplicable, setIsNotApplicable] = useState(false);
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />
<UpdateOperateDataButton updateOperateDataButtonFunc={updateOperateDataButtonFunc} />
<OperateDataButton getOperateDataFunc={getOperateDataFunc} />
</div>
</div>
<DataGrid
columnInfo={ PublicSewageFactInpColumn }
rowStartNum={3}
fixedColumns={2}
rowData={rowData}
setRowData={setRowData}
/>
</>
);
};
export default PublicSewageFacilityInp;
@@ -0,0 +1,80 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 AddArticleButton from "@/components/button/AddArticleButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
import { PumpingStationInpColumn } from "@/components/util/column/input/PumpingStationInpColumn"
// 하수도 통계 관리 > 05. 펌프장
const PumpingStationInp = () => {
const SubmitExceptionFunc = () => {
console.log('submit exception');
// 예외 제출 로직
};
const getLastYearDataFunc = () => {
console.log('get last year data');
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
console.log('submit');
// 제출 로직
};
const saveFunc = () => {
console.log('save');
// 저장 로직
};
const addArticleFunc = () => {
console.log('save');
// 항목 추가 로직
};
const deleteArticleFunc = () => {
console.log('save');
// 항목 제외 로직
};
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isNotApplicable, setIsNotApplicable] = useState(false);
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div>
</div>
<DataGrid
columnInfo={ PumpingStationInpColumn }
rowStartNum={3}
fixedColumns={3}
rowData={rowData}
setRowData={setRowData}
/>
</>
);
};
export default PumpingStationInp;
@@ -0,0 +1,81 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 AddArticleButton from "@/components/button/AddArticleButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn"
import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn"
import { ExcretaRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn";
import { PrivateSewageTreatRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn";
// 하수도 통계 관리 > 12. 하수 및 분뇨관련산업
const SewageExcretaRelatedInp = () => {
// tab list
const SewageExcretaRelatedInpTabList = [
PublicInstitutionSewerIndustryInpColumn,
PublicInstitutionExcretaIndustryInpColumn,
ExcretaRelatedIndustryInpColumn,
PrivateSewageTreatRelatedIndustryInpColumn
];
const submitExceptionFunc = () => {
// 예외 제출 로직
};
const lastYearDataFunc = () => {
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
// 제출 로직
};
const saveFunc = () => {
// 저장 로직
};
const addArticleFunc = () => {
// 항목 추가 로직
};
const deleteArticleFunc = () => {
console.log('save');
// 항목 제외 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={submitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={lastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div>
</div>
<TabDataGrid
columnInfo={SewageExcretaRelatedInpTabList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default SewageExcretaRelatedInp;
@@ -0,0 +1,84 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 AddArticleButton from "@/components/button/AddArticleButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
import { SlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn";
import { DigestorOperatingStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/DigestorOperatingStatusInpColumn";
import { SewageSlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SewageSlurgeProcessingFacStatusInpColumn";
import { ExcretaSlurgeProcessingStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/ExcretaSlurgeProcessingStatusInpColumn";
// 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리
const SewageExcretaSlurgeInp = () => {
// tab list
const SewageExcretaSlurgeInpTabList = [
SlurgeProcessingFacStatusInpColumn,
DigestorOperatingStatusInpColumn,
SewageSlurgeProcessingFacStatusInpColumn,
ExcretaSlurgeProcessingStatusInpColumn
];
const submitExceptionFunc = () => {
// 예외 제출 로직
};
const lastYearDataFunc = () => {
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
// 제출 로직
};
const saveFunc = () => {
// 저장 로직
};
const addArticleFunc = () => {
// 항목 추가 로직
};
const deleteArticleFunc = () => {
// 항목 제외 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const [isNotApplicable, setIsNotApplicable] = useState(false);
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={submitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={lastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div>
</div>
<TabDataGrid
columnInfo={SewageExcretaSlurgeInpTabList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default SewageExcretaSlurgeInp;
@@ -0,0 +1,72 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn";
import { FactInstallStatusInpColumn } from "@/components/util/column/input/sewagePipe/FactInstallStatusInpColumn";
import { PipeTypeStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeTypeStatusInpColumn";
import { PipeDiameterStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeDiameterStatusInpColumn";
import { ManholeStatusInpColumn } from "@/components/util/column/input/sewagePipe/ManholeStatusInpColumn";
import { ConnectionStatusInpColumn } from "@/components/util/column/input/sewagePipe/ConnectionStatusInpColumn";
// 하수도 통계 관리 > 02. 하수관로
const SewagePipeInp = () => {
// tab List
const SewagePipeInpTapList = [
RegionalFactStatusInpColumn,
FactInstallStatusInpColumn,
PipeTypeStatusInpColumn,
PipeDiameterStatusInpColumn,
ManholeStatusInpColumn,
ConnectionStatusInpColumn,
];
const SubmitExceptionFunc = () => {
console.log('submit exception');
// 예외 제출 로직
};
const getLastYearDataFunc = () => {
console.log('get last year data');
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
console.log('submit');
// 제출 로직
};
const saveFunc = () => {
console.log('save');
// 저장 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid
columnInfo={SewagePipeInpTapList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default SewagePipeInp;
@@ -0,0 +1,64 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 { RepairStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/RepairStatusInpColumn";
import { PipeDredgingStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/PipeDredgingStatusInpColumn";
// 하수도 통계 관리 > 03. 하수관로 유지관리
const SewagePipeMaintainInp = () => {
// tab List
const SewagePipeMaintainInpTabList = [
RepairStatusInpColumn,
PipeDredgingStatusInpColumn
];
const SubmitExceptionFunc = () => {
console.log('submit exception');
// 예외 제출 로직
};
const getLastYearDataFunc = () => {
console.log('get last year data');
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
console.log('submit');
// 제출 로직
};
const saveFunc = () => {
console.log('save');
// 저장 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid
columnInfo={SewagePipeMaintainInpTabList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default SewagePipeMaintainInp;
@@ -0,0 +1,85 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 AddArticleButton from "@/components/button/AddArticleButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
import { SewageReservoirInpColumn } from "@/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn";
import { RainfallPumpStationInpColumn } from "@/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn";
// 하수도 통계 관리 > 04. 하수저류 시설 및 빗물 펌프장
const SewageReservoirPumpInp = () => {
// tab List
const SewageReservoirPumpInpTabList = [
SewageReservoirInpColumn,
RainfallPumpStationInpColumn
];
const submitExceptionFunc = () => {
console.log('submit exception');
// 예외 제출 로직
};
const getLastYearDataFunc = () => {
console.log('get last year data');
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
console.log('submit');
// 제출 로직
};
const saveFunc = () => {
console.log('save');
// 저장 로직
};
const addArticleFunc = () => {
console.log('save');
// 항목 추가 로직
};
const deleteArticleFunc = () => {
console.log('save');
// 항목 제외 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const [isNotApplicable, setIsNotApplicable] = useState(false);
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={submitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div>
</div>
<TabDataGrid
columnInfo={SewageReservoirPumpInpTabList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default SewageReservoirPumpInp;
@@ -0,0 +1,64 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 { SewerageFinanceSettleInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageFinanceSettleInpColumn"
import { SewerageChargeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageChargeInpColumn"
import { SewerageUsageFeeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageUsageFeeInpColumn"
import { CauserBurdenInpColumn } from "@/components/util/column/input/sewerageFinance/CauserBurdenInpColumn"
// 하수도 통계 관리 > 09. 하수도재정
const SewerageFinanceInp = () => {
// tab list
const SewerageFinanceInpTabList = [
SewerageFinanceSettleInpColumn,
SewerageChargeInpColumn,
SewerageUsageFeeInpColumn,
CauserBurdenInpColumn
];
const submitExceptionFunc = () => {
// 예외 제출 로직
};
const lastYearDataFunc = () => {
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
// 제출 로직
};
const saveFunc = () => {
// 저장 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={submitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={lastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid
columnInfo={SewerageFinanceInpTabList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default SewerageFinanceInp;
@@ -0,0 +1,302 @@
import { useState, useMemo, useRef } from "react";
import SearchForm from "@/components/module/SearchForm";
// import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import SubmitButton from "@/components/button/SumitButton";
import SaveButton from "@/components/button/SaveButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
import { LoadingWithAnimation } from "@common/components";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { useAlertStore } from "@/shared/lib";
import { statisticsInputAPi } from "@/entities/statistics/api/statistics-input-service";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import type {
SewerageSupplyRateItem,
SewerageSupplyRateRequestParams,
SewerageSupplyRateSaveItem,
SewerageSupplyRateVerificationResult,
SewerageSupplyRateSaveRequest
} from "@/entities/statistics/model/statistics-input-interface";
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
import { getFooterSummary } from "@/components/util/grid/footerSummary";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
function getSewerageSupplyRateParams(
values: SearchFormValues,
overrides?: { yr?: string; demoYr?: string; mode?: string }
): SewerageSupplyRateRequestParams {
return {
sido: values.sido,
gugun: values.gugun === 'ALL' ? '' : values.gugun,
bsnmCd: values.bsnmCd,
yr: overrides?.yr ?? values.year,
...(overrides?.mode && { mode: overrides.mode }),
};
}
// 하수도 통계 관리 > 01. 하수도 보급률
const SeweragePenetrationRateInp = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
const [isEnterable, setIsEnterable] = useState<boolean>(true);
const [lastSearchedYear, setLastSearchedYear] = useState<string | null>(null);
const { confirm } = useConfirm();
const [footerFromApi, setFooterFromApi] = useState<{ totalPopl?: number; poplSm?: number; frgnrPoplSm?: number } | null>(null);
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const footerFieldNames = ['totalPopl', 'poplSm', 'frgnrPoplSm'] as const;
const footerWithApiDefault = (fieldName: (typeof footerFieldNames)[number], numberFormat: string) => ({
expression: 'sum' as const,
numberFormat,
valueCallback: (grid: Parameters<typeof getFooterSummary>[0], column: Parameters<typeof getFooterSummary>[1]) =>
(footerFromApi && footerFromApi[fieldName] != null ? footerFromApi[fieldName] : null) ?? getFooterSummary(grid, column),
});
// 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요.
const columnInfo = useMemo(() => {
const { columns, columnLayout } = SeweragePenetrationRateInpColumn;
const base = isEnterable ? columns : columns.map((col) => ({ ...col, editable: false as const }));
return {
columnLayout,
columns: base.map((col) => {
if (col.fieldName === 'totalPopl') return { ...col, footer: footerWithApiDefault('totalPopl', '#,##0') };
if (col.fieldName === 'poplSm') return { ...col, footer: footerWithApiDefault('poplSm', '#,##0') };
if (col.fieldName === 'frgnrPoplSm') return { ...col, footer: footerWithApiDefault('frgnrPoplSm', '#,##0') };
return col;
}),
};
}, [isEnterable, footerFromApi]);
const handleReset = () => {
setRowData([]);
setLastSearchedYear(null);
setFooterFromApi(null);
};
const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams) => {
try {
setIsLoading(true);
const response = await statisticsInputAPi.getSewerageSupplyRateList(params);
if (response.status === 'success' && response.data) {
const commonInfo = response.data.commonInfo;
const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d) => ({
...d,
sido: commonInfo.sido,
speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr,
piplSupprt: commonInfo.piplSupprt,
}));
setRowData(
convertedData.map((d) => {
const row = toGridRows([d])[0] as Record<string, string | number> & { verificationResult?: SewerageSupplyRateVerificationResult };
if (d.verificationResult) row.verificationResult = d.verificationResult;
return row;
})
);
setFooterFromApi(
commonInfo
? {
totalPopl: commonInfo.bsnmTotalPopl,
poplSm: commonInfo.bsnmPoplSm,
frgnrPoplSm: commonInfo.bsnmFrgnrPoplSm,
}
: null
);
} else {
alert(response.message);
}
} catch {
alert('데이터 조회 중 오류가 발생했습니다.');
setRowData([]);
setFooterFromApi(null);
} finally {
setIsLoading(false);
}
};
const handleSearch = async (values: SearchFormValues) => {
if (values.sido === '' || values.sido === 'ALL') {
alert('시도를 선택해주세요.');
return;
}
try {
const response = await statisticsInputAPi.checkInputPeriod(values.year || '');
if (response.status === 'success') {
setIsEnterable(response.data);
setLastSearchedYear(values.year || null);
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values));
} else {
alert(response.message);
}
} catch {
alert('데이터 조회 중 오류가 발생했습니다.');
}
};
const SubmitExceptionFunc = () => {
// 예외 제출 로직
};
const getLastYearDataFunc = async (values: SearchFormValues) => {
if (values.sido === '' || values.sido === 'ALL') {
alert('시도를 선택해주세요.');
return;
}
// 실제 조회된 연도가 없으면 실행 불가
if (!lastSearchedYear) {
alert('먼저 데이터를 조회해주세요.');
return;
}
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }));
};
const submitFunc = () => {
// 제출 로직
};
const saveFunc = async () => {
try {
if (await confirm('저장 하시겠습니까?')) {
setIsLoading(true);
const userInfo = await getUserInfo();
const currentData = getGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert('저장할 데이터가 없습니다.');
return;
}
// 컬럼 정의에서 dataFormat 추출 (소수점 자리수 맞추기 위해)
const columnFormatMap = new Map<string, string>();
SeweragePenetrationRateInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) {
columnFormatMap.set(col.fieldName, col.dataFormat);
}
});
// 숫자 포맷팅 함수 (dataFormat에 따라 소수점 자리수 맞춤)
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === '') return 0;
const num = typeof value === 'string' ? parseFloat(value.replace(/,/g, '')) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
// #,##0.000 → 소수점 3자리, #,##0.0 → 소수점 1자리, #,##0 → 정수
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
gugun: (d.gugun as string) || null,
adstCd: d.adstCd as string,
adstDong: d.adstDong as string,
bstrm: d.bstrm as string,
speclCntrplnAr: d.speclCntrplnAr === '해당없음' ? null : (d.speclCntrplnAr as string | null),
pblswrgPrmTrt: formatNumber(d.pblswrgPrmTrt, 'pblswrgPrmTrt'),
pblswrgScdTrt: formatNumber(d.pblswrgScdTrt, 'pblswrgScdTrt'),
pblswrgTertTrt: formatNumber(d.pblswrgTertTrt, 'pblswrgTertTrt'),
wstrPrmTrt: formatNumber(d.wstrPrmTrt, 'wstrPrmTrt'),
wstrScdTrt: formatNumber(d.wstrScdTrt, 'wstrScdTrt'),
wstrTertTrt: formatNumber(d.wstrTertTrt, 'wstrTertTrt'),
notConectPopltn: formatNumber(d.notConectPopltn, 'notConectPopltn'),
swgeProcZoneInnerAr: formatNumber(d.swgeProcZoneInnerAr, 'swgeProcZoneInnerAr'),
townUnprocPopltn: formatNumber(d.townUnprocPopltn, 'townUnprocPopltn'),
townSwttPopltn: formatNumber(d.townSwttPopltn, 'townSwttPopltn'),
townWrrtnPopltn: formatNumber(d.townWrrtnPopltn, 'townWrrtnPopltn'),
swgeProcZoneOuterAr: formatNumber(d.swgeProcZoneOuterAr, 'swgeProcZoneOuterAr'),
rmk: (d.rmk as string) ?? '',
popltnSm: formatNumber(d.popltnSm, 'popltnSm'),
arSm: formatNumber(d.arSm, 'arSm'),
swgeProcZoneInnerSm: formatNumber(d.swgeProcZoneInnerSm, 'swgeProcZoneInnerSm'),
pblswrgPopltnSm: formatNumber(d.pblswrgPopltnSm, 'pblswrgPopltnSm'),
wstrPopltnSm: formatNumber(d.wstrPopltnSm, 'wstrPopltnSm'),
swgeProcZoneOuterSm: formatNumber(d.swgeProcZoneOuterSm, 'swgeProcZoneOuterSm'),
pblswrgPopltnSuprt: formatNumber(d.pblswrgPopltnSuprt, 'pblswrgPopltnSuprt'),
adtrtPopltnSuprt: formatNumber(d.adtrtPopltnSuprt, 'adtrtPopltnSuprt'),
swrgInstlrt: formatNumber(d.swrgInstlrt, 'swrgInstlrt'),
}));
const requestData: SewerageSupplyRateSaveRequest = {
bsnmCd: searchValues?.bsnmCd ?? '',
sido: searchValues?.sido ?? '',
yr: searchValues?.year ?? '',
userId: userInfo.userName,
saveList: convertedData,
}
const response = await statisticsInputAPi.saveSewerageSupplyRate(requestData);
console.log(convertedData)
console.log(response);
} else {
// 저장 취소
}
} catch {
alert('데이터 저장 중 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
};
return (
<>
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} onValuesChange={setSearchValues} />
{rowData.length > 0 &&
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
{isEnterable && <SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />}
{isEnterable && <LastYearDataButton getLastYearDataFunc={() => getLastYearDataFunc(searchValues ?? {})} />}
{isEnterable && <SubmitButton submitFunc={submitFunc} />}
{
// 최고 관리자일땐 저장 가능하게
<SaveButton saveFunc={saveFunc} />
}
</div>
</div>
}
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>}
<DataGrid
key={isEnterable ? 'enterable' : 'readonly'}
columnInfo={columnInfo}
isExcelImport={(rowData.length > 0) && isEnterable}
rowStartNum={3}
fixedColumns={1}
rowData={rowData}
setRowData={setRowData}
getGridDataRef={getGridDataRef}
cellStyleCallback={({ row, fieldName }) => {
const verification = (row as any).verificationResult as SewerageSupplyRateVerificationResult | undefined;
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
return 'rg-cell-verify-error';
}
}}
/>
</div>
<ConfirmDialog isOpen={false} />
</>
);
};
export default SeweragePenetrationRateInp;
@@ -0,0 +1,67 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
import { ComplaintInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ComplaintInpColumn";
import { ResidentFriendlyFactInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ResidentFriendlyFactInpColumn";
// 하수도 통계 관리 > 13. 하수도 서비스 수준
const SewerageServiceLevelInp = () => {
// tab List
const SewerageServiceLevelInpTabList = [
ComplaintInpColumn,
ResidentFriendlyFactInpColumn
];
const submitExceptionFunc = () => {
// 예외 제출 로직
};
const getLastYearDataFunc = () => {
// 지난연도 데이터 가져오기 로직
};
const submitFunc = () => {
// 제출 로직
};
const saveFunc = () => {
// 저장 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const [isNotApplicable, setIsNotApplicable] = useState(false);
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={submitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />
</div>
</div>
<TabDataGrid
columnInfo={SewerageServiceLevelInpTabList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default SewerageServiceLevelInp;
@@ -0,0 +1,44 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
import { DataGrid } from "@rsone/components";
import ApproveButton from "@/components/button/ApproveButton";
import RejectButton from "@/components/button/RejectButton";
import { StatsInputStatusInpColumn } from "@/components/util/column/input/StatsInputStatusInpColumn";
// 하수도 통계 관리 > 00. 통계 입력 현황
const StatsInputStatusInp = () => {
const rejectFunc = () => {
// 보완 요청 로직
};
const approveFunc = () => {
// 승인 요청 로직
};
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<RejectButton rejectFunc={rejectFunc} />
<ApproveButton approveFunc={approveFunc} />
</div>
</div>
<DataGrid
columnInfo={ StatsInputStatusInpColumn }
rowStartNum={2}
fixedColumns={3}
rowData={rowData}
setRowData={setRowData}
isExcelImport={false}
/>
</>
);
};
export default StatsInputStatusInp;
@@ -0,0 +1,92 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
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 UpdateOperateDataButton from "@/components/button/UpdateOperateDataButton"
import AddArticleButton from "@/components/button/AddArticleButton";
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
import { PublicSewageTreatWaterReuseInpColumn } from "@/components/util/column/input/waterReuse/PublicSewageTreatWaterReuseInpColumn";
import { RecycledWaterStatusInpColumn } from "@/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn";
import { RainFallUtilFactStatusInpColumn } from "@/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn";
import { TreatWaterReuseDesignConstStatusInpColumn } from "@/components/util/column/input/waterReuse/TreatWaterReuseDesignConstStatusInpColumn";
import { TreatWaterReuseBusinessApprStatusInpColumn } from "@/components/util/column/input/waterReuse/TreatWaterReuseBusinessApprStatusInpColumn";
// 하수도 통계 관리 > 10. 물 재이용
const WaterReuseInp = () => {
// tab list
const WaterReuseInpTabList = [
PublicSewageTreatWaterReuseInpColumn,
RecycledWaterStatusInpColumn,
RainFallUtilFactStatusInpColumn,
TreatWaterReuseDesignConstStatusInpColumn,
TreatWaterReuseBusinessApprStatusInpColumn
];
const SubmitExceptionFunc = () => {
// 예외 제출 로직
};
const getLastYearDataFunc = () => {
// 전연도 데이터 가져오기 로직
};
const submitFunc = () => {
// 제출 로직
};
const saveFunc = () => {
// 저장 로직
};
const updateOperateDataButtonFunc = () => {
// 운영 데이터 업데이트 로직 - tab1. 공공하수처리수 재이용
};
const addArticleFunc = () => {
console.log('save');
// 항목 추가 로직
};
const deleteArticleFunc = () => {
console.log('save');
// 항목 제외 로직
};
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const [isNotApplicable, setIsNotApplicable] = useState(false);
return (
<>
<SearchForm />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />
<UpdateOperateDataButton updateOperateDataButtonFunc={updateOperateDataButtonFunc} />
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div>
</div>
<TabDataGrid
columnInfo={WaterReuseInpTabList}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
/>
</>
);
};
export default WaterReuseInp;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const ExcretaTreatmentFacilityMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default ExcretaTreatmentFacilityMng;
@@ -0,0 +1,41 @@
import { useState } from "react";
import { DataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnConfig } from "@/components/type/DataGrid.type";
const NotEnteredStatus = () => {
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const columns: ColumnConfig[] = [
{ name: 'sido', fieldName: '시도', header: { text: '시도' }, mergeCells: true, editable: false, sortdisable: true },
{ name: 'sgg', fieldName: '시군구', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true },
{ name: '1', fieldName: '1', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true },
{ name: '2-1', fieldName: '2-1', header: { text: '2-1' }, mergeCells: true, editable: false, sortdisable: true },
{ name: '2-2', fieldName: '2-2', header: { text: '2-2' }, mergeCells: true, editable: false, sortdisable: true },
{ name: '2-3', fieldName: '2-3', header: { text: '2-3' }, mergeCells: true, editable: false, sortdisable: true },
{ name: '2-4', fieldName: '2-4', header: { text: '2-4' }, mergeCells: true, editable: false, sortdisable: true },
{ name: '3-1', fieldName: '3-1', header: { text: '3-1' }, mergeCells: true, editable: false, sortdisable: true },
{ name: '3-2', fieldName: '3-2', header: { text: '3-2' }, mergeCells: true, editable: false, sortdisable: true },
];
const columnLayout = [
'sido',
'sgg',
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '미입력 하수도 통계 세부항목(번호)' },
items: ['1', '2-1', '2-2', '2-3', '2-4', '3-1', '3-2'],
},
];
return (
<>
<SearchForm />
<DataGrid columnInfo={{ columns, columnLayout }} rowData={rowData} setRowData={setRowData} isExcelImport={false} fixedColumns={2} rowStartNum={2} />
</>
);
};
export default NotEnteredStatus;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const PrivateSewageFacilityMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default PrivateSewageFacilityMng;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const PublicSewageFacilityMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default PublicSewageFacilityMng;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const PumpingStationMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default PumpingStationMng;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const SewageExcretaRelatedMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewageExcretaRelatedMng;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const SewageExcretaSlurgeMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewageExcretaSlurgeMng;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const SewagePipeMaintainMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewagePipeMaintainMng;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const SewageReservoirPumpMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewageReservoirPumpMng;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const SewerageFinanceMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewerageFinanceMng;
@@ -1,11 +0,0 @@
import SearchForm from "../../module/SearchForm";
const SewerageServiceLevelMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default SewerageServiceLevelMng;
@@ -0,0 +1,85 @@
import { useState } from "react";
import { DataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnConfig } from "@/components/type/DataGrid.type";
const StatisticsInputManagement = () => {
const [rowData, setRowData] = useState<Record<string, string | number>[]>(
[],
);
const columns: ColumnConfig[] = [
{
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: "광역자치단체" },
editable: false,
sortdisable: true,
},
{
name: "공단",
fieldName: "공단",
header: { text: "공단" },
editable: false,
sortdisable: true,
},
{
name: "처리내역",
fieldName: "처리내역",
header: { text: "처리내역" },
editable: false,
sortdisable: true,
},
];
const columnLayout = [
"sido",
"sgg",
"구분",
{
name: "groupA",
hideChildHeaders: false,
header: { text: "검토내역" },
items: ["광역자치단체", "공단", "처리내역"],
},
];
return (
<>
<SearchForm sido={false} />
<DataGrid
columnInfo={{ columns, columnLayout }}
rowData={rowData}
setRowData={setRowData}
isExcelImport={false}
isExcelExport={false}
/>
</>
);
};
export default StatisticsInputManagement;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const StatisticsPopulationManagement = () => {
return (
<>
<SearchForm sido={false} />
</>
);
};
export default StatisticsPopulationManagement;
@@ -0,0 +1,39 @@
import { useState } from "react";
import { DataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnConfig } from "@/components/type/DataGrid.type";
const StatisticsReviewHistory = () => {
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const columns: ColumnConfig[] = [
{ 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: '광역자치단체' }, editable: false, sortdisable: true },
{ name: '공단', fieldName: '공단', header: { text: '공단' }, editable: false, sortdisable: true },
{ name: '처리내역', fieldName: '처리내역', header: { text: '처리내역' }, editable: false, sortdisable: true },
];
const columnLayout = [
'sido',
'sgg',
'구분',
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '검토내역' },
items: ['광역자치단체', '공단', '처리내역'],
},
];
return (
<>
<SearchForm />
<DataGrid columnInfo={{ columns, columnLayout }} rowStartNum={2} fixedColumns={3} rowData={rowData} setRowData={setRowData} isExcelImport={false} />
</>
);
};
export default StatisticsReviewHistory;
@@ -1,55 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
import DataGrid from "@/components/module/DataGrid";
import ApproveButton from "@/components/button/ApproveButton";
import RejectButton from "@/components/button/RejectButton";
const StatsInputSatusMng = () => {
const columns = [
{ name: 'sido', fieldName: '시도', header: { text: '시도' } },
{ name: 'sgg', fieldName: '구군', header: { text: '구군' } },
{ name: 'menu_nm', fieldName: '구분', header: { text: '구분' } },
{ name: 'locg_status_nm', fieldName: '진행상황', header: { text: '진행상황' } },
{ name: 'locg_status', fieldName: '작성률', header: { text: '작성률' } },
{ name: 'locg_charger', fieldName: '작성자', header: { text: '작성자' } },
{ name: 'locg_cttpc', fieldName: '작성자 연락처', header: { text: '작성자 연락처' } },
{ name: 'locg_dept', fieldName: '작성자 부서', header: { text: '작성자 부서' } },
{ name: 'locg_job', fieldName: '작성자 직제', header: { text: '작성자 직제' } },
{ name: 'locg_cnfirm_nm', fieldName: '승인자', header: { text: '승인자' } },
{ name: 'locg_cnfirm_dept', fieldName: '승인자 부서', header: { text: '승인자 부서' } },
{ name: 'locg_cnfirm_job', fieldName: '승인자 직제', header: { text: '승인자 직제' } },
{ name: 'return_opinion_sm', fieldName: '반려 사유', header: { text: '반려 사유' } },
];
const columnLayout = [
'sido',
'sgg',
'menu_nm',
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '하수도사업자' },
items: columns.filter((c) => ['sido', 'sgg', 'menu_nm'].includes(c.name) === false).map((m) => m.name),
},
];
const approveFunc = () => {
console.log('appoved');
};
const rejectFunc = () => {
console.log('reject');
};
return (
<>
<SearchForm />
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<RejectButton rejectFunc={rejectFunc} />
<ApproveButton approveFunc={approveFunc} />
</div>
<DataGrid columnInfo={{columns, columnLayout}} rowStartNum={2} isExcelImport={true} fixedColumns={3} />
</>
);
};
export default StatsInputSatusMng;
@@ -1,11 +0,0 @@
import SearchForm from "../../module/SearchForm";
const WaterReuseMng = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
};
export default WaterReuseMng;
@@ -0,0 +1,38 @@
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { ExcretaTreatmentFacilityInfoList } from "@/components/util/column/review/ExcretaTreatmentFacilityRvwColumn";
// 하수도 통계 관리 07.분뇨처리 시설
const ExcretaTreatmentFacilityRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => console.log('approveFunc');
const saveFunc = () => console.log('submit');
const reviewOpinionFunc = () => console.log('reviewOpinionFunc');
const returnFunc = () => console.log('returnFunc');
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={ExcretaTreatmentFacilityInfoList} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
};
export default ExcretaTreatmentFacilityRvw;
@@ -0,0 +1,52 @@
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { PrivateSewageFacilityInfoListRvw } from "@/components/util/column/review/PrivateSewageFacilityColumn";
// 하수도 통계 관리 08.개인하수처리시설
const PrivateSewageFacilityRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
// 검토의견 작성
};
const returnFunc = () => {
console.log('returnFunc');
// 반려
};
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={PrivateSewageFacilityInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
};
export default PrivateSewageFacilityRvw;
@@ -0,0 +1,48 @@
import { useState } from "react";
import { DataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn";
// 하수도 통계 관리 06.공공하수처리시설
const PublicSewageFacilityRvw = () => {
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const approveFunc = () => {
console.log('approveFunc');
};
const saveFunc = () => {
console.log('submit');
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
};
const returnFunc = () => {
console.log('returnFunc');
};
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<DataGrid columnInfo={PublicSewageFacilityInfoList} isExcelImport={false} rowStartNum={3} fixedColumns={3} rowData={rowData} setRowData={setRowData} />
</>
);
};
export default PublicSewageFacilityRvw;
@@ -1,12 +1,37 @@
import SearchForm from "@/components/module/SearchForm";
import { useState } from "react";
import { DataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { PumpingStationInfoList } from "@/components/util/column/review/PumpingStationRvwColumn";
// 하수도 통계 관리 05.펌프장
const PumpingStationRvw = () => {
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const approveFunc = () => { console.log('approveFunc'); };
const saveFunc = () => { console.log('submit'); };
const reviewOpinionFunc = () => { console.log('reviewOpinionFunc'); };
const returnFunc = () => { console.log('returnFunc'); };
return (
<>
<SearchForm userMenual={true} fctName={true} />
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<DataGrid columnInfo={PumpingStationInfoList} isExcelImport={false} rowStartNum={3} fixedColumns={3} rowData={rowData} setRowData={setRowData} />
</>
);
}
};
export default PumpingStationRvw;
@@ -0,0 +1,53 @@
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { SewageExcretaRelatedInfoListRvw } from "@/components/util/column/review/SewageExcretaRelatedRvwColumn";
// 하수도 통계 관리 12. 하수 및 분뇨 관련 산업
const SewageExcretaRelatedRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
// 검토의견 작성
};
const returnFunc = () => {
console.log('returnFunc');
// 반려
};
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={SewageExcretaRelatedInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
};
export default SewageExcretaRelatedRvw;
@@ -0,0 +1,52 @@
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { SewageExcretaSludgeInfoListRvw } from "@/components/util/column/review/SewageExcretaSlurgeRvwColumn";
// 하수도 통계 관리 11. 하수 및 분뇨 찌꺼기(슬러지) 처리
const SewageExcretaSlurgeRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
// 검토의견 작성
};
const returnFunc = () => {
console.log('returnFunc');
// 반려
};
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={SewageExcretaSludgeInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
};
export default SewageExcretaSlurgeRvw;
@@ -1,11 +1,52 @@
import SearchForm from "@/components/module/SearchForm";
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn";
// 하수도 통계 관리 03. 하수관로 유지 관리
const SewagePipeMaintainRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
// 검토의견 작성
};
const returnFunc = () => {
console.log('returnFunc');
// 반려
};
return (
<>
<SearchForm userMenual={true} />
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={SewagePipeMaintainColumnInfoList} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
}
};
export default SewagePipeMaintainRvw;
@@ -1,11 +1,148 @@
import { useState, useMemo, useRef } from "react";
import TabDataGrid from "@/components/module/TabDataGrid";
import SearchForm from "@/components/module/SearchForm";
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 { LoadingWithAnimation } from "@common/components";
import { useAlertStore } from "@/shared/lib";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
import type { ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { toGridRows } from "@/components/util/grid/gridFunction";
// 하수도 통계 검토 02.하수관로
const SewagePipeRvw = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<string>('a');
const searchParamRef = useRef<SearchFormValues | null>(null);
const columnInfo = useMemo(() => {
return SewagePipeColumnInfoListRvw.map((tab: ColumnInfoList) => ({
...tab,
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
...col,
editable: false as const,
styleName: col.styleName ?? ("rg-cell-readonly" as const),
})),
}));
}, []);
const handleReset = () => {
setRowDataMap(new Map());
setActiveTab('a');
};
const handleSearch = async (values: SearchFormValues) => {
if (!values.sido || values.sido === 'ALL') {
alert('시도를 선택해주세요.');
return;
}
setActiveTab('a');
setRowDataMap(new Map());
try {
setIsLoading(true);
const searchParam = {
sido: values.sido,
gugun: values.gugun === 'ALL' ? '' : values.gugun,
bsnmCd: values.bsnmCd,
yr: values.year,
};
searchParamRef.current = searchParam;
const response = await statisticsManagementApi.getSewagePipeReviewRegionalList(searchParam);
if (response?.status === 'success' && response.data) {
const newRowDataMap = new Map<string, Record<string, string | number>[]>();
newRowDataMap.set('a', toGridRows(response.data as object[]));
setRowDataMap(newRowDataMap);
} else {
alert(response?.message || "데이터가 없습니다.");
}
} catch (error) {
alert('데이터 조회 중 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
};
const handleActiveTabChange = async (tabKey: string) => {
setActiveTab(tabKey);
const existingAData = rowDataMap.get('a');
const activeTabData = rowDataMap.get(tabKey);
const hasAData = Array.isArray(existingAData) && existingAData.length > 0;
const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0;
if (hasAData && !hasActiveTabData && searchParamRef.current) {
try {
setIsLoading(true);
let response;
if (tabKey === 'b') {
response = await statisticsManagementApi.getSewagePipeFacilityList(searchParamRef.current);
}
if (response?.status === 'success' && response.data) {
setRowDataMap(prev => {
const next = new Map(prev);
next.set(tabKey, toGridRows(response.data as object[]));
return next;
});
} else {
alert(response?.message || "데이터가 없습니다.");
}
} catch {
alert('데이터 조회 중 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
}
};
return (
<>
<SearchForm userMenual={true} />
</>
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={() => { }} />
<ApproveButton approveFunc={() => { }} />
<ReturnButton returnFunc={() => { }} />
<SaveButton saveFunc={() => { }} />
</div>
</div>
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<TabDataGrid
columnInfo={columnInfo}
isExcelImport={false}
rowDataMap={rowDataMap}
setRowDataMap={setRowDataMap}
activeTab={activeTab}
onActiveTabChange={handleActiveTabChange}
/>
</div>
);
}
};
export default SewagePipeRvw;
@@ -1,11 +1,52 @@
import SearchForm from "@/components/module/SearchForm";
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn";
// 하수도 통계 관리 04. 하수저류 시설 및 빗물 펌프장
const SewageReservoirPumpRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
// 검토의견 작성
};
const returnFunc = () => {
console.log('returnFunc');
// 반려
};
return (
<>
<SearchForm userMenual={true} />
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={SewageReservoirPumpInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
}
};
export default SewageReservoirPumpRvw;
export default SewageReservoirPumpRvw;
@@ -0,0 +1,54 @@
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { SewerageFinanceInfoListRvw } from "@/components/util/column/review/SewerageFinanceRvwColumn";
// 하수도 통계 관리 09. 하수도 재정
const SewerageFinanceRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
// 검토의견 작성
};
const returnFunc = () => {
console.log('returnFunc');
// 반려
};
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={SewerageFinanceInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
};
export default SewerageFinanceRvw;
@@ -1,11 +1,115 @@
import { useState, useMemo } from "react";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import SaveButton from "@/components/button/SaveButton";
import ApproveButton from "@/components/button/ApproveButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import ReturnButton from "@/components/button/ReturnButton";
import { LoadingWithAnimation } from "@common/components";
import { useAlertStore } from "@/shared/lib";
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
import type { SewerageSupplyRateReviewItem } from "@/entities/statistics/model/statistics-sewerage-penetration-review-interface";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { toGridRows } from "@/components/util/grid/gridFunction";
// 하수도 통계 검토 01.하수도 보급률
const SeweragePenetrationRateRvw = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [rowData, setRowData] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const columnInfo = useMemo(() => {
const { columns, columnLayout, fixedColumns, rowStartNum } = SeweragePenetrationRateInfoList;
return {
columnLayout,
fixedColumns,
rowStartNum,
columns: columns.map((col) => ({
...col,
editable: false as const,
styleName: col.styleName ?? ("rg-cell-readonly" as const),
})),
};
}, []);
const handleReset = () => {
setRowData([]);
};
const handleSearch = async (values: SearchFormValues) => {
if (values.sido === '' || values.sido === 'ALL') {
alert('시도를 선택해주세요.');
return;
}
try {
setIsLoading(true);
const response = await statisticsManagementApi.getSewerageSupplyRateReviewList({
sido: values.sido,
gugun: values.gugun === 'ALL' ? '' : values.gugun,
bsnmCd: values.bsnmCd,
yr: values.year,
});
if (response.status === 'success' && response.data) {
const rawList = response.data.sewerageSupplyRateList;
const convertedData: SewerageSupplyRateReviewItem[] = rawList.map((item) => ({
...item,
speclCntrplnAr: !item.speclCntrplnAr ? '해당없음' : item.speclCntrplnAr,
}));
setRowData(toGridRows(convertedData));
} else {
setRowData([]);
alert(response.message || "데이터가 없습니다.");
}
} catch (error) {
console.error("조회 중 에러 발생:", error);
alert('데이터 조회 중 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
};
const approveFunc = () => console.log('approveFunc');
const saveFunc = () => console.log('submit');
const reviewOpinionFunc = () => console.log('reviewOpinionFunc');
const returnFunc = () => console.log('returnFunc');
return (
<>
<SearchForm userMenual={true} />
</>
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
<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>
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<DataGrid
columnInfo={columnInfo}
isExcelImport={false}
fixedColumns={columnInfo.fixedColumns}
rowStartNum={columnInfo.rowStartNum}
rowData={rowData}
setRowData={setRowData}
/>
</div>
);
}
};
export default SeweragePenetrationRateRvw;
@@ -0,0 +1,53 @@
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { SewerageServiceLevelInfoListRvw } from "@/components/util/column/review/SewerageServiceLevelRvwColumn";
// 하수도 통계 관리 13. 하수도 서비스 수준
const SewerageServiceLevelRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
// 검토의견 작성
};
const returnFunc = () => {
console.log('returnFunc');
// 반려
};
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={SewerageServiceLevelInfoListRvw} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
};
export default SewerageServiceLevelRvw;
@@ -1,11 +0,0 @@
import SearchForm from "@/components/module/SearchForm";
const StatsInputSatusRvw = () => {
return (
<>
<SearchForm userMenual={true} />
</>
);
}
export default StatsInputSatusRvw;
@@ -0,0 +1,113 @@
import { useState, useMemo } from "react";
import { DataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { LoadingWithAnimation } from "@common/components";
import { useAlertStore } from "@/shared/lib";
import { StatsInputStatusInfoList } from "@/components/util/column/review/StatsInputStatusRvwColumn";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import type { StatsInputStatusReviewItem } from "@/entities/statistics/model/statistics-input-review-interface";
import { toGridRows } from "@/components/util/grid/gridFunction";
// 하수도 통계 검토 00. 통계 입력 현황
const StatsInputStatusRvw = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [rowData, setRowData] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const columnInfo = useMemo(() => {
const { columns, columnLayout } = StatsInputStatusInfoList;
return {
columnLayout,
columns: columns.map((col) => ({
...col,
editable: false as const,
})),
};
}, []);
const handleReset = () => {
setRowData([]);
};
const handleSearch = async (values: any) => {
if (!values.sido || values.sido === 'ALL') {
alert('시도를 선택해주세요.');
return;
}
try {
setIsLoading(true);
const response = await statisticsManagementApi.getStatsInputStatusList({
sido: values.sido,
gugun: values.gugun === 'ALL' ? '' : values.gugun,
yr: values.year || values.yr,
bsnmCd: values.bsnmCd
});
if (response.status === 'success' && response.data) {
const rawList = response.data.sewerageSupplyRateList || response.data;
const convertedData: StatsInputStatusReviewItem[] = rawList.map((item: any) => ({
...item,
status: item.status || '미확인',
}));
setRowData(toGridRows(convertedData));
} else {
setRowData([]);
alert(response.message || "조회된 데이터가 없습니다.");
}
} catch (error) {
console.error("입력현황 조회 중 에러 발생:", error);
alert('데이터 조회 중 오류가 발생했습니다.');
setRowData([]);
} finally {
setIsLoading(false);
}
};
const approveFunc = () => console.log('approveFunc');
const saveFunc = () => console.log('submit');
const reviewOpinionFunc = () => console.log('reviewOpinionFunc');
const returnFunc = () => console.log('returnFunc');
return (
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
<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>
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<DataGrid
columnInfo={columnInfo}
isExcelImport={false}
rowStartNum={2}
fixedColumns={2}
rowData={rowData}
setRowData={setRowData}
/>
</div>
);
};
export default StatsInputStatusRvw;
@@ -0,0 +1,51 @@
import { useState } from "react";
import { TabDataGrid } from "@rsone/components";
import SearchForm from "@/components/module/SearchForm";
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 { WaterReuseInfoList } from "@/components/util/column/review/WaterReuseRvwColumn";
const WaterReuseRvw = () => {
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
const approveFunc = () => {
console.log('approveFunc');
// 승인 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const reviewOpinionFunc = () => {
console.log('reviewOpinionFunc');
// 검토의견 작성
};
const returnFunc = () => {
console.log('returnFunc');
// 반려
};
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
<ApproveButton approveFunc={approveFunc} />
<ReturnButton returnFunc={returnFunc} />
<SaveButton saveFunc={saveFunc} />
</div>
</div>
<TabDataGrid columnInfo={WaterReuseInfoList} isExcelImport={false} rowDataMap={rowDataMap} setRowDataMap={setRowDataMap} />
</>
);
};
export default WaterReuseRvw;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const ExcretaTreatmentFacilityTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default ExcretaTreatmentFacilityTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const PrivateSewageFacilityTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default PrivateSewageFacilityTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const PublicSewageFacilityTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default PublicSewageFacilityTot;
@@ -1,11 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewagePipeMng = () => {
const PumpingStationTot = () => {
return (
<>
<SearchForm userMenual={true} />
<SearchForm />
</>
);
};
export default SewagePipeMng;
export default PumpingStationTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewageExcretaRelatedTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default SewageExcretaRelatedTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewageExcretaSlurgeTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default SewageExcretaSlurgeTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewagePipeMaintainTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default SewagePipeMaintainTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewagePipeTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default SewagePipeTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewageReservoirPumpTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default SewageReservoirPumpTot;
@@ -0,0 +1,11 @@
import SearchForm from "@/components/module/SearchForm";
const SewerageFinanceTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default SewerageFinanceTot;
@@ -1,10 +1,15 @@
import SearchForm from "@/components/module/SearchForm";
import DataGrid from "@/components/module/DataGrid";
import SubmitButton from "@/components/button/SumitButton";
import SaveButton from "@/components/button/SaveButton";
import LastYearDataButton from "@/components/button/LastYearDataButton";
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
import { useState } from "react";
const SeweragePenetrationRate = () => {
const SeweragePenetrationRateTot = () => {
const columns = [
{ name: 'sido', fieldName: '시도', header: { text: '시도' } },
{ name: 'sgg', fieldName: '구군', header: { text: '구군' } },
{ name: 'sgg', fieldName: '시군구', header: { text: '시군구' } },
{ name: 'rvbsn', fieldName: '수계', header: { text: '수계' } },
{ name: 'bstrm', fieldName: '지류', header: { text: '지류' } },
{ name: 'mdbsn', fieldName: '세부단위유역', header: { text: '세부단위유역' } },
@@ -98,12 +103,40 @@ const SeweragePenetrationRate = () => {
'rmk',
];
const submitFunc = () => {
console.log('submit');
// 제출 로직
};
const saveFunc = () => {
console.log('submit');
// 저장 로직
};
const getLastYearDataFunc = () => {
console.log('get last year data');
// 지난연도 데이터 가져오기 로직
};
const SubmitExceptionFunc = () => {
console.log('submit exception');
// 예외 제출 로직
};
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
return (
<>
<SearchForm userMenual={true} />
<DataGrid columnInfo={{columns, columnLayout}} rowStartNum={2} isExcelImport={true} fixedColumns={1} />
<SearchForm />
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
<LastYearDataButton getLastYearDataFunc={getLastYearDataFunc} />
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
</div>
<DataGrid columnInfo={{columns, columnLayout}} rowStartNum={2} isExcelImport={true} fixedColumns={1} rowData={rowData} setRowData={setRowData} />
</>
);
};
export default SeweragePenetrationRate;
export default SeweragePenetrationRateTot;
@@ -0,0 +1,11 @@
import SearchForm from "../../module/SearchForm";
const SewerageServiceLevelTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default SewerageServiceLevelTot;
@@ -0,0 +1,11 @@
import SearchForm from "../../module/SearchForm";
const WaterReuseTot = () => {
return (
<>
<SearchForm />
</>
);
};
export default WaterReuseTot;
@@ -0,0 +1,483 @@
"use client";
import { useState } from "react";
// import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import {
Button,
Input,
Label,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
Checkbox,
} from "@common/components";
import { verificationHistoryColumnInfo } from "@/entities/statistics/model/verification-history-column";
import { verificationHistoryMockData } from "@/entities/statistics/model/verification-history-mock";
interface VerificationFormValues {
mu: string;
percentile: string;
a: string;
c: string;
verificationYear: string;
checkboxOptions: {
sidoDistribution: boolean;
countyDistribution: boolean;
countyDistributionUserManagement: boolean;
lowerDistribution: boolean;
sidoServiceAvailability: boolean;
individualService: boolean;
legalServiceUsage: boolean;
excessUse: boolean;
lowerOrgStatistics: boolean;
studentStatistics: boolean;
developmentStatistics: boolean;
lowerManagementEnterprise: boolean;
};
}
const HbStatisticsVerification = () => {
const [verificationHistoryRowData, setVerificationHistoryRowData] =
useState<Record<string, string | number>[]>(
verificationHistoryMockData,
);
const [formValues, setFormValues] = useState<VerificationFormValues>({
mu: "0",
percentile: "0",
a: "0",
c: "0",
verificationYear: "2025",
checkboxOptions: {
sidoDistribution: false,
countyDistribution: false,
countyDistributionUserManagement: false,
lowerDistribution: false,
sidoServiceAvailability: false,
individualService: false,
legalServiceUsage: false,
excessUse: false,
lowerOrgStatistics: false,
studentStatistics: false,
developmentStatistics: false,
lowerManagementEnterprise: false,
},
});
const handleCheckboxChange = (
key: keyof VerificationFormValues["checkboxOptions"],
) => {
setFormValues((prev) => ({
...prev,
checkboxOptions: {
...prev.checkboxOptions,
[key]: !prev.checkboxOptions[key],
},
}));
};
const handleInputChange = (
key: keyof Omit<VerificationFormValues, "checkboxOptions">,
value: string,
) => {
setFormValues((prev) => ({
...prev,
[key]: value,
}));
};
const handleSubmit = () => {
console.log("검증 실행:", formValues);
};
const handleReset = () => {
console.log("초기화");
};
return (
<div className="card card-border mt-4">
<div className="p-2">
{/* 통계 검증 섹션 */}
<div className="mb-6">
<h3 className="px-2 text-[1.5rem] font-semibold text-title">
통계 검증
</h3>
<div className="flex gap-2">
{/* 왼쪽: 검증 변수 */}
<div className="card card-border flex-[0.5] rounded p-4">
<h4 className="mb-3 rounded bg-gray-100 p-2 text-[1.4rem] font-medium">
검증 변수
</h4>
<div className="space-y-3">
<div className="flex items-center gap-4">
<Label className="w-32 text-label">μ</Label>
<Input
type="number"
value={formValues.mu}
onChange={(e) =>
handleInputChange(
"mu",
e.target.value,
)
}
className="flex-1"
/>
</div>
<div className="flex items-center gap-4">
<Label className="w-32 text-label">
Percentile
</Label>
<Input
type="number"
value={formValues.percentile}
onChange={(e) =>
handleInputChange(
"percentile",
e.target.value,
)
}
className="flex-1"
/>
</div>
<div className="flex items-center gap-4">
<Label className="w-32 text-label">A</Label>
<Input
type="number"
value={formValues.a}
onChange={(e) =>
handleInputChange(
"a",
e.target.value,
)
}
className="flex-1"
/>
</div>
<div className="flex items-center gap-4">
<Label className="w-32 text-label">C</Label>
<Input
type="number"
value={formValues.c}
onChange={(e) =>
handleInputChange(
"c",
e.target.value,
)
}
className="flex-1"
/>
</div>
<div className="flex items-center gap-4">
<Label className="w-32 text-label">
검증 년도
</Label>
<Select
value={formValues.verificationYear}
onValueChange={(value) =>
handleInputChange(
"verificationYear",
value,
)
}
>
<SelectTrigger className="flex-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="2025">
2025년
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
{/* 오른쪽: 검증 대상 */}
<div className="card card-border flex-1 rounded p-4">
<h4 className="mb-3 rounded bg-gray-100 p-2 text-[1.4rem] font-medium">
검증 대상
</h4>
<div className="space-y-4 p-2">
<div className="grid grid-cols-4 gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.sidoDistribution
}
onCheckedChange={() =>
handleCheckboxChange(
"sidoDistribution",
)
}
/>
<Label className="cursor-pointer text-label">
하수도 보급률
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.countyDistribution
}
onCheckedChange={() =>
handleCheckboxChange(
"countyDistribution",
)
}
/>
<Label className="cursor-pointer text-label">
하수관로
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.countyDistributionUserManagement
}
onCheckedChange={() =>
handleCheckboxChange(
"countyDistributionUserManagement",
)
}
/>
<Label className="cursor-pointer text-label">
하수관로 유지관리
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.lowerDistribution
}
onCheckedChange={() =>
handleCheckboxChange(
"lowerDistribution",
)
}
/>
<Label className="cursor-pointer text-label">
하수저류시설 및 빗물펌프장
</Label>
</div>
</div>
<div className="grid grid-cols-4 gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.sidoServiceAvailability
}
onCheckedChange={() =>
handleCheckboxChange(
"sidoServiceAvailability",
)
}
/>
<Label className="cursor-pointer text-label">
펌프장
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.individualService
}
onCheckedChange={() =>
handleCheckboxChange(
"individualService",
)
}
/>
<Label className="cursor-pointer text-label">
공공하수처리시설
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.sidoServiceAvailability
}
onCheckedChange={() =>
handleCheckboxChange(
"sidoServiceAvailability",
)
}
/>
<Label className="cursor-pointer text-label">
분뇨처리시설
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.individualService
}
onCheckedChange={() =>
handleCheckboxChange(
"individualService",
)
}
/>
<Label className="cursor-pointer text-label">
개인하수처리시설
</Label>
</div>
</div>
<div className="grid grid-cols-4 gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.lowerOrgStatistics
}
onCheckedChange={() =>
handleCheckboxChange(
"lowerOrgStatistics",
)
}
/>
<Label className="cursor-pointer text-label">
하수도재정
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.studentStatistics
}
onCheckedChange={() =>
handleCheckboxChange(
"studentStatistics",
)
}
/>
<Label className="cursor-pointer text-label">
물 재이용
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.lowerOrgStatistics
}
onCheckedChange={() =>
handleCheckboxChange(
"lowerOrgStatistics",
)
}
/>
<Label className="cursor-pointer text-label">
하수 및 분뇨찌꺼기(슬러지) 처리
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.studentStatistics
}
onCheckedChange={() =>
handleCheckboxChange(
"studentStatistics",
)
}
/>
<Label className="cursor-pointer text-label">
하수 및 분뇨관련산업
</Label>
</div>
</div>
<div className="grid grid-cols-4 gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={
formValues.checkboxOptions
.lowerManagementEnterprise
}
onCheckedChange={() =>
handleCheckboxChange(
"lowerManagementEnterprise",
)
}
/>
<Label className="cursor-pointer text-label">
하수도 서비스 수준
</Label>
</div>
</div>
</div>
</div>
</div>
</div>
{/* 버튼 */}
<div className="mt-6 flex justify-center gap-4 pt-4">
<Button
color="green"
className="h-[3.6rem] w-[120px]"
onClick={handleSubmit}
>
검증 실행
</Button>
<Button
color="gray"
className="h-[3.6rem] w-[120px]"
onClick={handleReset}
>
초기화
</Button>
</div>
{/* 검증 이력 섹션 */}
<div className="mt-8 border-t border-border pt-6">
<h3 className="mb-4 px-2 text-[1.5rem] font-semibold text-title">
검증 이력
</h3>
<DataGrid
columnInfo={verificationHistoryColumnInfo}
rowData={verificationHistoryRowData}
setRowData={setVerificationHistoryRowData}
isExcelImport={false}
isExcelExport={false}
showHeaderCheckbox={false}
/>
</div>
</div>
</div>
);
};
export default HbStatisticsVerification;
@@ -0,0 +1,262 @@
"use client";
import DataGrid from "@/components/module/DataGrid";
import {
hbArticleColumnInfo,
imrArticleColumnInfo,
validationArticleColumnInfo,
} from "@/entities/statistics/model/verification-article-column";
import {
Button,
Input,
Label,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@common/components";
import { CodeSelect } from "@/entities/code/ui/code-select";
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { ENDPOINT } from "@/shared/config";
import type {
VerificationTargetManagementSearchParams,
VerificationTargetManagementResponse,
} from "@/entities/statistics/model/verification-target-management-interface";
import { VerificationTargetDialog } from "./VerificationTargetDialog";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
const TAB_CONFIG = {
HB: { value: "HB", label: "H-B", columnInfo: hbArticleColumnInfo },
IMR: { value: "IMR", label: "I-MR", columnInfo: imrArticleColumnInfo },
VV: {
value: "VV",
label: "Validation",
columnInfo: validationArticleColumnInfo,
},
} as const;
export type TabValue = keyof typeof TAB_CONFIG;
function VerificationArticleManagement() {
const queryClient = useQueryClient();
const { confirm } = useConfirm();
const [activeTab, setActiveTab] = useState<TabValue>("HB");
const [statsCd, setStatsCd] = useState("");
const [vrfcTrgtNm, setVrfcTrgtNm] = useState("");
const [searchParams, setSearchParams] =
useState<VerificationTargetManagementSearchParams>({
vrfcMthdCd: activeTab,
statsCd,
vrfcTrgtNm,
});
const [dialogOpen, setDialogOpen] = useState(false);
const [selectedData, setSelectedData] =
useState<VerificationTargetManagementResponse | null>(null);
const { data: verificationData } = useQuery({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG, searchParams],
queryFn: async () => {
const response =
await verificationTargetManagementApi.getVerificationTargetManagementList(
{
vrfcMthdCd: searchParams.vrfcMthdCd,
statsCd: searchParams.statsCd || undefined,
vrfcTrgtNm: searchParams.vrfcTrgtNm || undefined,
},
);
return response;
},
});
const rowData = (verificationData?.content || []) as unknown as Record<
string,
string | number
>[];
const columnInfo = TAB_CONFIG[activeTab].columnInfo;
function handleSearch() {
setSearchParams({
vrfcMthdCd: activeTab,
statsCd,
vrfcTrgtNm,
});
}
function handleInitialize() {
setStatsCd("");
setVrfcTrgtNm("");
setSearchParams({
vrfcMthdCd: activeTab,
statsCd: "",
vrfcTrgtNm: "",
});
}
function handleTabChange(value: string) {
const newTab = value as TabValue;
setActiveTab(newTab);
setSearchParams({
vrfcMthdCd: newTab,
statsCd,
vrfcTrgtNm,
});
}
const updateMutation = useMutation({
mutationFn: (data: VerificationTargetManagementResponse) =>
verificationTargetManagementApi.updateVerificationTargetManagement(
data.vrfcTrgtSn,
{
vrfcMthdCd: data.vrfcMthdCd,
statsCd: data.statsCd,
schmNm: data.schmNm,
tblNm: data.tblNm,
colNm: data.colNm,
pkColNm: data.pkColNm,
ipcrCntVl: data.ipcrCntVl,
pctlCrtrVl: data.pctlCrtrVl,
scopCntCntVl: data.scopCntCntVl,
thrsCntVl: data.thrsCntVl,
useYn: data.useYn,
vrfcTrgtNm: data.vrfcTrgtNm,
},
),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG],
});
setDialogOpen(false);
},
});
const deleteMutation = useMutation({
mutationFn: (vrfcTrgtSn: number) =>
verificationTargetManagementApi.deleteVerificationTargetManagement(
vrfcTrgtSn,
),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG],
});
setDialogOpen(false);
},
});
function handleRowClick(rowData: Record<string, string | number>) {
setSelectedData(
rowData as unknown as VerificationTargetManagementResponse,
);
setDialogOpen(true);
}
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
handleSearch();
}
async function handleSave(data: VerificationTargetManagementResponse) {
if (await confirm("저장하시겠습니까?")) {
updateMutation.mutate(data);
}
}
async function handleDelete(vrfcTrgtSn: number) {
if (await confirm("정말 삭제하시겠습니까?")) {
deleteMutation.mutate(vrfcTrgtSn);
}
}
return (
<Tabs
defaultValue="HB"
value={activeTab}
onValueChange={handleTabChange}
>
<TabsList>
{Object.entries(TAB_CONFIG).map(([key, config]) => (
<TabsTrigger
key={key}
value={config.value}
className="!px-[2rem] !py-[0.8rem] !text-[1.2rem]"
>
{config.label}
</TabsTrigger>
))}
</TabsList>
<div className="card card-border !mx-0">
<form onSubmit={handleSubmit}>
<div className="flex items-center gap-4">
<div className="flex items-center gap-4">
<Label className="whitespace-nowrap px-4 text-label">
통계항목
</Label>
<CodeSelect
upCd="STITEM"
value={statsCd}
onValueChange={setStatsCd}
placeholder="선택"
className="w-[16rem]"
/>
</div>
<div className="flex items-center gap-4">
<Label className="whitespace-nowrap px-4 text-label">
검증명
</Label>
<Input
value={vrfcTrgtNm}
onChange={(e) => setVrfcTrgtNm(e.target.value)}
placeholder="검증명"
className="w-[20rem]"
/>
</div>
<div className="ml-auto flex gap-2">
<Button
type="submit"
color="green"
className="h-[3.6rem] w-[80px]"
>
조회
</Button>
<Button
type="button"
color="gray"
className="h-[3.6rem] w-[80px]"
onClick={handleInitialize}
>
초기화
</Button>
</div>
</div>
</form>
</div>
{Object.entries(TAB_CONFIG).map(([key, config]) => (
<TabsContent key={key} value={config.value}>
<DataGrid
columnInfo={columnInfo}
rowData={rowData}
setRowData={() => {}}
onRowClick={handleRowClick}
isExcelImport={false}
/>
</TabsContent>
))}
<VerificationTargetDialog
tabValue={activeTab}
open={dialogOpen}
onOpenChange={setDialogOpen}
data={selectedData}
onSave={handleSave}
onDelete={handleDelete}
/>
</Tabs>
);
}
export default VerificationArticleManagement;
@@ -0,0 +1,376 @@
"use client";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Input,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "@common/components";
import { CodeSelect } from "@/entities/code/ui/code-select";
import type { VerificationTargetManagementResponse } from "@/entities/statistics/model/verification-target-management-interface";
import {
verificationTargetManagementSchema,
type VerificationTargetManagementFormData,
} from "@/entities/statistics/model/verification-target-management-schema";
import { useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import type { TabValue } from "./VerificationArticleManagement";
interface VerificationTargetDialogProps {
tabValue: TabValue;
open: boolean;
onOpenChange: (open: boolean) => void;
data: VerificationTargetManagementResponse | null;
onSave: (data: VerificationTargetManagementResponse) => void;
onDelete: (vrfcTrgtSn: number) => void;
}
export function VerificationTargetDialog({
tabValue,
open,
onOpenChange,
data,
onSave,
onDelete,
}: VerificationTargetDialogProps) {
const {
control,
handleSubmit,
reset,
formState: { errors },
} = useForm<VerificationTargetManagementFormData>({
resolver: zodResolver(verificationTargetManagementSchema),
defaultValues: data || undefined,
});
useEffect(() => {
if (data && open) reset(data);
}, [data, open, reset]);
function onSubmit(formData: VerificationTargetManagementFormData) {
if (!data) return;
onSave({
...data,
...formData,
} as VerificationTargetManagementResponse);
}
function handleDelete() {
if (data?.vrfcTrgtSn) {
onDelete(data.vrfcTrgtSn);
}
}
if (!data) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[800px]">
<DialogHeader>
<DialogTitle>검증 항목 상세</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)}>
<Table variant="secondary">
<TableBody>
<TableRow>
<TableHead
className="w-[150px] bg-muted font-medium"
data-required
>
통계항목
</TableHead>
<TableCell>
<Controller
name="statsCd"
control={control}
render={({ field }) => (
<div className="space-y-1">
<CodeSelect
upCd="STITEM"
value={field.value}
onValueChange={
field.onChange
}
placeholder="선택"
/>
</div>
)}
/>
</TableCell>
<TableHead
className="w-[150px] bg-muted font-medium"
data-required
>
검증명
</TableHead>
<TableCell>
<Controller
name="vrfcTrgtNm"
control={control}
render={({ field }) => (
<Input
{...field}
value={field.value || ""}
placeholder="검증명"
/>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead
className="bg-muted font-medium"
data-required
>
스키마명
</TableHead>
<TableCell>
<Controller
name="schmNm"
control={control}
render={({ field }) => (
<div className="space-y-1">
<Input
{...field}
placeholder="스키마명"
/>
</div>
)}
/>
</TableCell>
<TableHead
className="bg-muted font-medium"
data-required
>
테이블명
</TableHead>
<TableCell>
<Controller
name="tblNm"
control={control}
render={({ field }) => (
<div className="space-y-1">
<Input
{...field}
placeholder="테이블명"
/>
</div>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead
className="bg-muted font-medium"
data-required
>
컬럼명
</TableHead>
<TableCell>
<Controller
name="colNm"
control={control}
render={({ field }) => (
<div className="space-y-1">
<Input
{...field}
placeholder="컬럼명"
/>
</div>
)}
/>
</TableCell>
<TableHead
className="bg-muted font-medium"
data-required
>
PK 키
</TableHead>
<TableCell>
<Controller
name="pkColNm"
control={control}
render={({ field }) => (
<div className="space-y-1">
<Input
{...field}
placeholder="PK 키"
/>
</div>
)}
/>
</TableCell>
</TableRow>
{tabValue !== "IMR" && (
<TableRow>
<TableHead className="bg-muted font-medium">
중요도 계수(μ)
</TableHead>
<TableCell>
<Controller
name="ipcrCntVl"
control={control}
render={({ field }) => (
<div className="space-y-1">
<Input
type="number"
min="0"
max="1"
step="0.1"
value={field.value ?? ""}
onChange={(e) =>
field.onChange(
e.target.value
? Number(
e.target
.value,
)
: null,
)
}
placeholder="(0 ~ 1, 0.1 단위)"
/>
</div>
)}
/>
</TableCell>
<TableHead className="bg-muted font-medium">
백분위수 기준값(pct)
</TableHead>
<TableCell>
<Controller
name="pctlCrtrVl"
control={control}
render={({ field }) => (
<div className="space-y-1">
<Input
type="number"
min="0"
max="0.5"
step="0.1"
value={field.value ?? ""}
onChange={(e) =>
field.onChange(
e.target.value
? Number(
e.target
.value,
)
: null,
)
}
placeholder="(0 ~ 0.5, 0.1 단위)"
/>
</div>
)}
/>
</TableCell>
</TableRow>
)}
{tabValue !== "IMR" && (
<TableRow>
<TableHead className="bg-muted font-medium">
스케일 조정 계수(A)
</TableHead>
<TableCell>
<Controller
name="scopCntCntVl"
control={control}
render={({ field }) => (
<div className="space-y-1">
<Input
type="number"
min="0"
max="1"
step="0.01"
value={field.value ?? ""}
onChange={(e) =>
field.onChange(
e.target.value
? Number(
e.target
.value,
)
: null,
)
}
placeholder="(0 ~ 1, 0.01 단위)"
/>
</div>
)}
/>
</TableCell>
<TableHead className="bg-muted font-medium">
임계 계수(c)
</TableHead>
<TableCell>
<Controller
name="thrsCntVl"
control={control}
render={({ field }) => (
<div className="space-y-1">
<Input
type="number"
min="0"
max="100"
step="1"
value={field.value ?? ""}
onChange={(e) =>
field.onChange(
e.target.value
? Number(
e.target
.value,
)
: null,
)
}
placeholder="(0 ~ 100, 1 단위)"
/>
</div>
)}
/>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<DialogFooter className="mt-6">
<Button
type="button"
color="gray"
onClick={() => onOpenChange(false)}
>
취소
</Button>
<Button
type="button"
color="red"
onClick={handleDelete}
>
삭제
</Button>
<Button type="submit" color="green">
저장
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}
+250 -9
View File
@@ -1,26 +1,267 @@
import React from "react";
import { ValueType } from "realgrid";
// RealGrid 타입 정의
/** setColumns에 전달하는 컬럼 정보 (footer 배열/단일 객체 포함) */
export type RealGridColumnInfo = {
name: string;
fieldName: string;
header: { text: string };
editable?: boolean;
valueType?: import("realgrid").ValueType;
dataFormat?: string;
styleName?: string;
mergeRule?: { criteria: "value" };
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;
}
| Array<{
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;
}>;
};
/** setColumns 메서드가 있는 GridView */
export type GridViewWithSetColumns = GridView & {
setColumns?: (columns: RealGridColumnInfo[]) => void;
};
/** footer 배열 요소에 valueCallback 할당용 */
export type FooterItemWithCallback = {
valueCallback?: (
grid: GridView,
column: DataColumn,
footerIndex: number,
columnFooter: ColumnFooter,
value: number | string | undefined,
) => number | string;
};
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;
};
/** 정렬 시 같은 그룹 내에서는 orderByField 순서 유지, 그룹 간에는 정렬 필드로 비교. */
export type SortPreserveGroupOrder = {
groupField: string;
orderByField?: string;
};
/** RealGrid valueCallback 시그니처 (계산 필드용) */
export type DataFieldValueCallback = (
prod: unknown,
dataRow: number,
fieldName: string,
fieldNames: string[],
values: unknown[],
) => number | string | undefined;
/** 계산 필드 설정 (computedFieldCallbacks.ts 참고) */
export type ComputedFieldConfig =
| { fields: string[]; type: "sum" | "avg" }
| {
numeratorFields: string[];
denominatorFields: string[];
type: "pblswrgPopltnSuprtRate";
}
| {
numeratorFields: string[];
denominatorFields: string[];
type: "adtrtPopltnSuprtRate";
}
| {
numeratorFields: string[];
denominatorFields: string[];
thirdFields: string[];
type: "swrgInstlrtRate";
};
export type ColumnConfig = {
name: string;
fieldName: string;
header: { text: string };
/** 계산 필드: fields 합계/평균 등. valueCallback으로 dataProvider.setFields 시 전달 */
computed?: ComputedFieldConfig;
editable?: boolean; // 셀 편집 가능 여부
/** 그리드에서 컬럼 숨김 (수식용 필드 등) */
visible?: boolean;
/** 셀에 적용할 CSS 클래스명 (예: 'rg-cell-readonly', 'rg-cell-total') */
styleName?: string;
mergeCells?: boolean;
sortdisable?: boolean;
sortPreserveGroupOrder?: SortPreserveGroupOrder;
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;
multiRow?: boolean; // 여러 행 footer 사용 여부
}
| Array<{
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;
}>; // RealGrid에서 footer를 배열로 설정할 수 있음
};
export type ColumnLayoutItem = string | {
name: string;
hideChildHeaders?: boolean;
header: { text: string };
items: (string | ColumnLayoutItem)[];
export type FooterUserSpan = {
rowspan?: number; // 병합할 행 수
colspan?: number; // 병합할 컬럼 수
text?: string; // 표시할 텍스트
};
export type ColumnLayoutItem =
| string
| {
name: string;
hideChildHeaders?: boolean;
header: { text: string };
items: (string | ColumnLayoutItem)[];
footerUserSpans?: FooterUserSpan[]; // footer 병합 설정
}
| {
column: string; // 컬럼 이름 (footerUserSpans 사용 시)
footerUserSpans?: FooterUserSpan[]; // footer 병합 설정
};
export type ColumnInfo = {
columns: ColumnConfig[];
columnLayout: ColumnLayoutItem[];
};
export type ColumnInfoList = {
tabKey: string; // 탭을 식별하는 고유 키 (필수), server(api)에 전송할 변수 값
columns?: ColumnConfig[]; // 일반 그리드용 (isSplitGrid가 false일 때)
columnLayout?: ColumnLayoutItem[]; // 일반 그리드용 (isSplitGrid가 false일 때)
rowStartNum?: number;
fixedColumns?: number;
tabLabel?: string; // 탭에 표시될 레이블
isSplitGrid?: boolean; // 5:5 분리형 그리드 유무
left?: ColumnInfoList; // SplitDataGrid용 왼쪽 그리드 정보
right?: ColumnInfoList; // SplitDataGrid용 오른쪽 그리드 정보
};
// 개별 셀 스타일을 동적으로 결정하기 위한 콜백 (예: 검증 오류 셀 강조)
export type CellStyleCallbackParams = {
rowIndex: number;
fieldName: string;
row: Record<string, string | number>;
};
export type CellStyleCallback = (params: CellStyleCallbackParams) => string | undefined;
export type DataGridProps = {
columnInfo: ColumnInfo;
rows?: Record<string, string>[];
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
isExcelImport?: boolean; // 엑셀 가져오기 유무무
onRowsChange?: (rows: Record<string, string>[]) => void;
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>[]>
>;
/** 셀 단위로 styleName을 동적으로 결정 (예: 검증 오류 셀) */
cellStyleCallback?: CellStyleCallback;
/** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */
getGridDataRef?: {
current: (() => Record<string, string | number>[]) | null;
};
onRowClick?: (rowData: Record<string, string | number>) => void; // 행 클릭 이벤트
};
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>[]>>
>;
isSplitGrid?: boolean; // 5:5 분리형 그리드 유무
/** 활성 탭 키 (제어 모드). 부모에서 넘기면 해당 탭이 선택됨 */
activeTab?: string;
/** 활성 탭 변경 시 호출 (제어 모드). activeTab과 함께 넘기면 부모가 탭 상태를 가짐 */
onActiveTabChange?: (tabKey: string) => void;
};
export type SplitDataGridProps = {
leftColumnInfo: ColumnInfo;
rightColumnInfo: ColumnInfo;
leftFixedColumns?: number;
rightFixedColumns?: number;
showHeaderCheckbox?: boolean;
leftRowData: Record<string, string | number>[];
rightRowData: Record<string, string | number>[];
setLeftRowData: React.Dispatch<
React.SetStateAction<Record<string, string | number>[]>
>;
setRightRowData: React.Dispatch<
React.SetStateAction<Record<string, string | number>[]>
>;
};
@@ -1,26 +1,28 @@
export type SearchFormValues = {
sido?: string;
sgg?: string;
gugun?: string;
year?: string;
order?: string;
fctName?: string;
bsnmCd?: string;
};
export type SearchFormProps = {
sido?: boolean;
sgg?: boolean;
gugun?: boolean;
year?: boolean;
order?: boolean;
fctName?: boolean;
userMenual?: boolean;
onSubmit?: (values: SearchFormValues) => void;
onReset?: () => void;
onValuesChange?: (values: SearchFormValues) => void;
defaultValues?: SearchFormValues;
};
export const defaultSearchFormProps: Required<Omit<SearchFormProps, 'onSubmit' | 'onReset' | 'defaultValues'>> = {
export const defaultSearchFormProps: Required<Omit<SearchFormProps, 'onSubmit' | 'onReset' | 'onValuesChange' | 'defaultValues'>> = {
sido: true,
sgg: true,
gugun: true,
year: true,
order: false,
fctName: false,
@@ -0,0 +1,107 @@
import { footerInt, footerDecimal1, footerDecimal3 } from "@/components/util/grid/footerSummary";
import { ValueType } from "realgrid";
/**
* 하수도 통계 관리 > 07. 분뇨처리시설
* 단일 Tab
*/
export const ExcretaTreatFactInpColumn = {
columns: [
// 행정구역
{ name: 'sido', fieldName: '시도', header: { text: '시도' }, editable: false },
{ name: 'gugun', fieldName: '시군구', header: { text: '시군구' }, footer: { text: '합계' } },
// 기본 정보
{ name: 'ecscs_nm', fieldName: '시설명', header: { text: '시설명' } },
{ name: 'locplcs', fieldName: '소재지', header: { text: '소재지' } },
{ name: 'telno', fieldName: '전화번호', header: { text: '전화번호' } },
// 날짜 정보
{ name: 'comp_dt', fieldName: '준공일 yyyy-mm-dd', header: { text: '준공일 yyyy-mm-dd' } },
{ name: 'rn_dt', fieldName: '가동개시일 yyyy-mm-dd', header: { text: '가동개시일 yyyy-mm-dd' } },
// 사업비/용량 정보
{ name: 'wct', fieldName: '사업비(백만원)', header: { text: '사업비(백만원)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'ecscs_cpcty', fieldName: '시설용량(m³/일)', header: { text: '시설용량(m³/일)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'process_cpcty', fieldName: '처리량(m³/일)', header: { text: '처리량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'opr_daycnt', fieldName: '가동일수(일/년)', header: { text: '가동일수(일/년)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 처리 공법 및 연계
{ name: 'trt_msrc', fieldName: '처리공법', header: { text: '처리공법' } },
{ name: 'cntc_prcplt', fieldName: '연계처리장명', header: { text: '연계처리장명' } },
{ name: 'assct_trt_cpc', fieldName: '연계처리량(m³/일)', header: { text: '연계처리량(m³/일)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 유입수질
{ name: 'infl_wqlt_bod', fieldName: 'BOD', header: { text: 'BOD' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'infl_wqlt_toc', fieldName: 'TOC', header: { text: 'TOC' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'infl_wqlt_ss', fieldName: 'SS', header: { text: 'SS' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'infl_wqlt_tn', fieldName: 'T-N', header: { text: 'T-N' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'infl_wqlt_tp', fieldName: 'T-P', header: { text: 'T-P' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'infl_wqlt_clfgrpcnt', fieldName: '대장균군수', header: { text: '대장균군수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 방류수질
{ name: 'effl_wqlt_bod', fieldName: 'BOD', header: { text: 'BOD' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'effl_wqlt_toc', fieldName: 'TOC', header: { text: 'TOC' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'effl_wqlt_ss', fieldName: 'SS', header: { text: 'SS' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'effl_wqlt_tn', fieldName: 'T-N', header: { text: 'T-N' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'effl_wqlt_tp', fieldName: 'T-P', header: { text: 'T-P' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'effl_wqlt_clfgrpcnt', fieldName: '대장균군수', header: { text: '대장균군수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 방류수역
{ name: 'rvbsn', fieldName: '수계', header: { text: '수계' } },
{ name: 'bstrm', fieldName: '지류', header: { text: '지류' } },
{ name: 'lgbsn', fieldName: '중권역', header: { text: '중권역' } },
{ name: 'mdmbsn', fieldName: '소권역', header: { text: '소권역' } },
// 운영 정보
{ name: 'oper_mth', fieldName: '운영주체(자체/공기업/민간위탁)', header: { text: '운영주체(자체/공기업/민간위탁)' } },
{ name: 'cntc_prcplt_cpc', fieldName: '직원총수(명)', header: { text: '직원총수(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'safe_acdnt_co', fieldName: '안전사고건수(건/인/년)', header: { text: '안전사고건수(건/인/년)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 비고
{ name: 'rmk', fieldName: '비고', header: { text: '비고' } },
],
columnLayout: [
{
name: 'adminGroup',
hideChildHeaders: false,
header: { text: '행정구역' },
items: ['sido', 'gugun'],
},
'ecscs_nm',
'locplcs',
'telno',
'comp_dt',
'rn_dt',
'wct',
'ecscs_cpcty',
'process_cpcty',
'opr_daycnt',
'trt_msrc',
'cntc_prcplt',
'assct_trt_cpc',
{
name: 'influenceGroup',
hideChildHeaders: false,
header: { text: '유입수질(mg/L, 개/mL)' },
items: ['infl_wqlt_bod', 'infl_wqlt_toc', 'infl_wqlt_ss', 'infl_wqlt_tn', 'infl_wqlt_tp', 'infl_wqlt_clfgrpcnt'],
},
{
name: 'effluentGroup',
hideChildHeaders: false,
header: { text: '방류수질(mg/L, 개/mL)' },
items: ['effl_wqlt_bod', 'effl_wqlt_toc', 'effl_wqlt_ss', 'effl_wqlt_tn', 'effl_wqlt_tp', 'effl_wqlt_clfgrpcnt'],
},
{
name: 'dischargeGroup',
hideChildHeaders: false,
header: { text: '방류수역' },
items: ['rvbsn', 'bstrm', 'lgbsn', 'mdmbsn'],
},
'oper_mth',
'cntc_prcplt_cpc',
'safe_acdnt_co',
'rmk',
]
};
@@ -0,0 +1,211 @@
import { footerInt, footerDecimal1, footerDecimal3 } from "@/components/util/grid/footerSummary";
import { ValueType } from "realgrid";
/**
* 하수도 통계 관리 > 06. 공공하수처리시설
* 단일 Tab
*/
export const PublicSewageFactInpColumn = {
columns: [
// 행정구역
{ name: 'sidoCd', fieldName: '시도', header: { text: '시도' }, editable: false },
{ name: 'gugun', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } },
// 시설명
{ name: 'bscFctNm', fieldName: '시설명(500m³/일 이상/미만)', header: { text: '시설명(500m³/일 이상/미만)' }, editable: false },
// 소재지
{ name: 'addr', fieldName: '소재지', header: { text: '소재지' }, editable: false },
// 시설용량
{ name: 'fctCpc', fieldName: '시설용량(500m³/일 이상/미만)', header: { text: '시설용량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// (m³/일)
{ name: 'fctCpc1', fieldName: '물리적', header: { text: '물리적' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'fctCpc2', fieldName: '생물학적', header: { text: '생물학적' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'fctCpc3', fieldName: '고도', header: { text: '고도' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 유입하수량
{ name: 'totInflAmntAve', fieldName: '유입하수량(500m³/일 이상/미만)', header: { text: '유입하수량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// (m³/일)
{ name: 'pInflAmntAve', fieldName: '물리적', header: { text: '물리적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'oInflAmntAve', fieldName: '생물학적', header: { text: '생물학적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'aInflAmntAve', fieldName: '고도', header: { text: '고도' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 방류량
{ name: 'totTputAve', fieldName: '방류량(500m³/일 이상/미만)', header: { text: '방류량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 방류량 - (m³/일)
{ name: 'pTputAve', fieldName: '물리적', header: { text: '물리적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'oTputAve', fieldName: '생물학적', header: { text: '생물학적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'aTputAve', fieldName: '고도', header: { text: '고도' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 수질
{ name: 'inflAssctBfrWqltBod', fieldName: '유입BOD', header: { text: '유입BOD' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'efflWqltBod', fieldName: '방류BOD', header: { text: '방류BOD' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'inflAssctBfrWqltToc', fieldName: '유입TOC', header: { text: '유입TOC' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'efflWqltToc', fieldName: '방류TOC', header: { text: '방류TOC' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'inflAssctBfrWqltSs', fieldName: '유입SS', header: { text: '유입SS' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'efflWqltSs', fieldName: '방류SS', header: { text: '방류SS' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'inflAssctBfrWqltTn', fieldName: '유입T-N', header: { text: '유입T-N' }, editable: false, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'efflWqltTn', fieldName: '방류T-N', header: { text: '방류T-N' }, editable: false, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'inflAssctBfrWqltTp', fieldName: '유입T-P', header: { text: '유입T-P' }, editable: false, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'efflWqltTp', fieldName: '방류T-P', header: { text: '방류T-P' }, editable: false, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
{ name: 'inflAssctBfrWqltClfgrpcnt', fieldName: '유입총대장균군수', header: { text: '유입총대장균군수' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'efflWqltClfgrpcnt', fieldName: '방류총대장균군수', header: { text: '방류총대장균군수' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 처리특성
{ name: 'tmEfficiency', fieldName: '처리효율(%)', header: { text: '처리효율(%)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'pcLoad', fieldName: '처리부하량(kgBOD/D)', header: { text: '처리부하량(kgBOD/D)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'trtFrfHanNm', fieldName: '처리방법', header: { text: '처리방법' }, editable: false },
// 연계처리량
{ name: 'excretionsTot', fieldName: '분뇨', header: { text: '분뇨' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'stkrsngWstwtrTot', fieldName: '축산', header: { text: '축산' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'lchtTot', fieldName: '침출수', header: { text: '침출수' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'etcTot', fieldName: '기타', header: { text: '기타' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 시설정보
{ name: 'tel', fieldName: '전화번호', header: { text: '전화번호' }, editable: false },
{ name: 'rnDt', fieldName: '준공일', header: { text: '준공일' }, editable: false },
{ name: 'compDt', fieldName: '가동개시일', header: { text: '가동개시일' }, editable: false },
{ name: 'tot', fieldName: '사업비', header: { text: '사업비' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 방류수 소독방법
{ name: 'dsfctNm', fieldName: '방류수 소독방법', header: { text: '방류수 소독방법' }, editable: false },
// 방류수역
{ name: 'wbsnNm', fieldName: '수계', header: { text: '수계' }, editable: false },
{ name: 'rvNm', fieldName: '지류', header: { text: '지류' }, editable: false },
{ name: 'mdmbsn', fieldName: '세부단위구역', header: { text: '세부단위구역' }, editable: false },
{ name: 'efflWqltStndrdNm', fieldName: '지역구분', header: { text: '지역구분' }, editable: false },
// 방류수역 > 중권역
{ name: 'lgbsn', fieldName: '명칭', header: { text: '명칭' }, editable: false },
{ name: 'lgbsnBod', fieldName: '목표수질(BOD)', header: { text: '목표수질(BOD)' }, editable: false },
// 에너지 자립률
{ name: 'toeRt', fieldName: '에너지자립률(%)', header: { text: '에너지자립률(%)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 에너지 자체 생산량
{ name: 'useQnty', fieldName: '에너지자체생산량(TOE)', header: { text: '에너지자체생산량(TOE)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 연간 총 에너지 사용량
{ name: 'toeSum', fieldName: '계(TOE)', header: { text: '계(TOE)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'elcpwAmnt', fieldName: '총 전력사용량(kWh)', header: { text: '총 전력사용량(kWh)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'toeQnty', fieldName: '자체 에너지사용량(TOE)', header: { text: '자체 에너지사용량(TOE)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 하수처리량당 CO2배출량
{ name: 'co2Emissions', fieldName: '하수처리량당 CO2배출량(kgCO2/m³)', header: { text: '하수처리량당 CO2배출량(kgCO2/m³)' }, editable: false, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
// 운영주체
{ name: 'oprClsNm', fieldName: '운영주체(자체/공기업/민간대행)', header: { text: '운영주체(자체/공기업/민간대행)' }, editable: false },
// 위탁현황
{ name: 'trstOprEtpNm', fieldName: '위탁업체명', header: { text: '위탁업체명' } },
{ name: 'yrstpTrstCst', fieldName: '위탁비용(백만원/년)', header: { text: '위탁비용(백만원/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'trstOprDt', fieldName: '위탁계약기간(yyyy-mm-dd~yyyy-mm-dd)', header: { text: '위탁계약기간(yyyy-mm-dd~yyyy-mm-dd)' } },
// 기타
{ name: 'cntrTrtFctNm', fieldName: '중심처리시설', header: { text: '중심처리시설' } },
{ name: 'totWtrQrm', fieldName: '직원총수(명)', header: { text: '직원총수(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'safeAcdntCo', fieldName: '안전사고건수', header: { text: '안전사고건수' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rmk', fieldName: '비고', header: { text: '비고' } },
],
columnLayout: [
{
name: 'administrative',
hideChildHeaders: false,
header: { text: '행정구역' },
items: ['sidoCd', 'gugun'],
},
'bscFctNm',
'addr',
'fctCpc',
{
name: 'facilityCapacityDetail',
hideChildHeaders: false,
header: { text: '(m³/일)' },
items: ['fctCpc1', 'fctCpc2', 'fctCpc3'],
},
'totInflAmntAve',
{
name: 'influentWaterDetail',
hideChildHeaders: false,
header: { text: '(m³/일)' },
items: ['pInflAmntAve', 'oInflAmntAve', 'aInflAmntAve'],
},
'totTputAve',
{
name: 'effluentWaterDetail',
hideChildHeaders: false,
header: { text: '(m³/일)' },
items: ['pTputAve', 'oTputAve', 'aTputAve'],
},
'inflAssctBfrWqltBod',
'efflWqltBod',
'inflAssctBfrWqltToc',
'efflWqltToc',
'inflAssctBfrWqltSs',
'efflWqltSs',
'inflAssctBfrWqltTn',
'efflWqltTn',
'inflAssctBfrWqltTp',
'efflWqltTp',
'inflAssctBfrWqltClfgrpcnt',
'efflWqltClfgrpcnt',
'tmEfficiency',
'pcLoad',
'trtFrfHanNm',
{
name: 'linkedTreatment',
hideChildHeaders: false,
header: { text: '연계처리량(m³/일) (500m³/일 이상/미만)' },
items: ['excretionsTot', 'stkrsngWstwtrTot', 'lchtTot', 'etcTot'],
},
'tel',
'rnDt',
'compDt',
'tot',
'dsfctNm',
{
name: 'dischargeArea',
hideChildHeaders: false,
header: { text: '방류수역' },
items: [
'wbsnNm',
'rvNm',
'mdmbsn',
'efflWqltStndrdNm',
{
name: 'mediumWatershed',
hideChildHeaders: false,
header: { text: '중권역' },
items: ['lgbsn', 'lgbsnBod'],
},
],
},
'toeRt',
'useQnty',
{
name: 'energy',
hideChildHeaders: false,
header: { text: '연간 총 에너지 사용량(TOE)' },
items: ['toeSum', 'elcpwAmnt', 'toeQnty'],
},
'co2Emissions',
'oprClsNm',
{
name: 'consignmentStatus',
hideChildHeaders: false,
header: { text: '위탁현황' },
items: ['trstOprEtpNm', 'yrstpTrstCst', 'trstOprDt'],
},
'cntrTrtFctNm',
'totWtrQrm',
'safeAcdntCo',
'rmk',
]
};
@@ -0,0 +1,143 @@
import { footerInt, footerDecimal1, footerDecimal3 } from "@/components/util/grid/footerSummary";
import { ValueType } from "realgrid";
/**
* 하수도 통계 관리 > 05. 펌프장
* 단일 Tab
*/
export const PumpingStationInpColumn = {
columns: [
// 행정구역
{ name: 'sido', fieldName: '시도', header: { text: '시도' }, editable: false },
{ name: 'sgg', fieldName: '시군구', header: { text: '시군구' }, footer: { text: '합계' } },
// 기본 정보
{ name: 'treatmentFacilityName', fieldName: '처리장명', header: { text: '처리장명' } },
{ name: 'facilityName', fieldName: '시설명', header: { text: '시설명' } },
{ name: 'classification', fieldName: '구분(합류식 분류식)', header: { text: '구분(합류식 분류식)' } },
{ name: 'location', fieldName: '위치', header: { text: '위치' } },
{ name: 'operationDate', fieldName: '가동 년월일 yyyy-mm-dd', header: { text: '가동 년월일 yyyy-mm-dd' } },
{ name: 'planDrainageArea', fieldName: '계획배수면적(㎢)', header: { text: '계획배수면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
// 계획시간 대 최대배수량
{ name: 'fairWeatherFlow', fieldName: '청천시(A)', header: { text: '청천시(A)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'rainyWeatherFlow', fieldName: '우천시(B)', header: { text: '우천시(B)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'rainwaterFlow', fieldName: '우수량(B-A)', header: { text: '우수량(B-A)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 배수능력 > 총 양수량 (오수)
{ name: 'sewageTotalPumpCapacity', fieldName: '총양수량(m³/min)', header: { text: '총양수량(m³/min)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 배수능력 > 오수펌프(1)
{ name: 'sewagePump1Diameter', fieldName: '오수펌프(1) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sewagePump1Capacity', fieldName: '오수펌프(1) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'sewagePump1Count', fieldName: '오수펌프(1) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sewagePump1Reserve', fieldName: '오수펌프(1) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 배수능력 > 오수펌프(2)
{ name: 'sewagePump2Diameter', fieldName: '오수펌프(2) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sewagePump2Capacity', fieldName: '오수펌프(2) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'sewagePump2Count', fieldName: '오수펌프(2) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sewagePump2Reserve', fieldName: '오수펌프(2) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 배수능력 > 오수펌프(3)
{ name: 'sewagePump3Diameter', fieldName: '오수펌프(3) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sewagePump3Capacity', fieldName: '오수펌프(3) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'sewagePump3Count', fieldName: '오수펌프(3) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sewagePump3Reserve', fieldName: '오수펌프(3) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 배수능력 > 총 양수량 (우수)
{ name: 'rainwaterTotalPumpCapacity', fieldName: '총양수량(m³/min)', header: { text: '총양수량(m³/min)' }, footer: footerDecimal1, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 배수능력 > 우수펌프(1)
{ name: 'rainwaterPump1Diameter', fieldName: '우수펌프(1) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rainwaterPump1Capacity', fieldName: '우수펌프(1) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'rainwaterPump1Count', fieldName: '우수펌프(1) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rainwaterPump1Reserve', fieldName: '우수펌프(1) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 배수능력 > 우수펌프(2)
{ name: 'rainwaterPump2Diameter', fieldName: '우수펌프(2) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rainwaterPump2Capacity', fieldName: '우수펌프(2) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'rainwaterPump2Count', fieldName: '우수펌프(2) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rainwaterPump2Reserve', fieldName: '우수펌프(2) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 배수능력 > 우수펌프(3)
{ name: 'rainwaterPump3Diameter', fieldName: '우수펌프(3) 구경(mm)', header: { text: '구경(mm)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rainwaterPump3Capacity', fieldName: '우수펌프(3) 대당 양수량(m³/min·대)', header: { text: '대당 양수량(m³/min·대)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'rainwaterPump3Count', fieldName: '우수펌프(3) 펌프대수', header: { text: '펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rainwaterPump3Reserve', fieldName: '우수펌프(3) 예비펌프대수', header: { text: '예비펌프대수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 연간전력 사용량
{ name: 'annualPowerUsage', fieldName: '연간전력사용량(kWh/년)', header: { text: '연간전력사용량(kWh/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 비고
{ name: 'remarks', fieldName: '비고', header: { text: '비고' } },
],
columnLayout: [
{
name: 'administrativeArea',
hideChildHeaders: false,
header: { text: '행정구역' },
items: ['sido', 'sgg'],
},
'treatmentFacilityName',
'facilityName',
'classification',
'location',
'operationDate',
'planDrainageArea',
{
name: 'planFlowGroup',
hideChildHeaders: false,
header: { text: '계획시간대최대배수량(m³/hr)' },
items: ['fairWeatherFlow', 'rainyWeatherFlow', 'rainwaterFlow'],
},
{
name: 'drainageCapability',
hideChildHeaders: false,
header: { text: '배수능력' },
items: [
'sewageTotalPumpCapacity',
{
name: 'sewagePump1Group',
hideChildHeaders: false,
header: { text: '오수펌프(1)' },
items: ['sewagePump1Diameter', 'sewagePump1Capacity', 'sewagePump1Count', 'sewagePump1Reserve'],
},
{
name: 'sewagePump2Group',
hideChildHeaders: false,
header: { text: '오수펌프(2)' },
items: ['sewagePump2Diameter', 'sewagePump2Capacity', 'sewagePump2Count', 'sewagePump2Reserve'],
},
{
name: 'sewagePump3Group',
hideChildHeaders: false,
header: { text: '오수펌프(3)' },
items: ['sewagePump3Diameter', 'sewagePump3Capacity', 'sewagePump3Count', 'sewagePump3Reserve'],
},
'rainwaterTotalPumpCapacity',
{
name: 'rainwaterPump1Group',
hideChildHeaders: false,
header: { text: '우수펌프(1)' },
items: ['rainwaterPump1Diameter', 'rainwaterPump1Capacity', 'rainwaterPump1Count', 'rainwaterPump1Reserve'],
},
{
name: 'rainwaterPump2Group',
hideChildHeaders: false,
header: { text: '우수펌프(2)' },
items: ['rainwaterPump2Diameter', 'rainwaterPump2Capacity', 'rainwaterPump2Count', 'rainwaterPump2Reserve'],
},
{
name: 'rainwaterPump3Group',
hideChildHeaders: false,
header: { text: '우수펌프(3)' },
items: ['rainwaterPump3Diameter', 'rainwaterPump3Capacity', 'rainwaterPump3Count', 'rainwaterPump3Reserve'],
},
],
},
'annualPowerUsage',
'remarks',
]
};
@@ -0,0 +1,256 @@
import { footerInt, footerDecimal3, footerAvgDecimal } from "@/components/util/grid/footerSummary";
import { ValueType } from "realgrid";
/**
* 하수도 통계 관리 > 01. 하수도 보급률
* 단일 Tab
*/
export const SeweragePenetrationRateInpColumn = {
columns: [
// 행정구역
{ name: 'sido', fieldName: 'sido', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
// 수계 및 권역
{ name: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' } },
// 수계영향권별분류
{ name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'lgbsn', fieldName: 'lgbsn', header: { text: '중권역' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'mdmbsn', fieldName: 'mdmbsn', header: { text: '소권역' }, editable: false, styleName: 'rg-cell-readonly' },
// 특별대책지역
{ name: 'speclCntrplnAr', fieldName: 'speclCntrplnAr', header: { text: '특별대책지역' } },
// 행정참고자료인구
{ name: 'totalPopl', fieldName: 'totalPopl', header: { text: '총인구' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'poplSm', fieldName: 'poplSm', header: { text: '내국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'frgnrPoplSm', fieldName: 'frgnrPoplSm', header: { text: '외국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
// 행정참고자료면적
{ name: 'area', fieldName: 'area', header: { text: '행정참고자료면적(㎢)' }, footer: footerDecimal3, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000', styleName: 'rg-cell-readonly' },
// 총인구
{
name: 'popltnSm',
fieldName: 'popltnSm',
header: { text: '총인구(명)' },
footer: footerInt,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'sum' as const },
styleName: 'rg-cell-total',
},
// 총면적
{
name: 'arSm',
fieldName: 'arSm',
header: { text: '총면적(㎢)' },
footer: footerDecimal3,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.000',
computed: { fields: ['swgeProcZoneInnerAr', 'swgeProcZoneOuterAr'], type: 'sum' as const },
styleName: 'rg-cell-total',
},
// 하수처리구역 내 > 합계
{
name: 'swgeProcZoneInnerSm',
fieldName: 'swgeProcZoneInnerSm',
header: { text: '합계' },
footer: footerInt,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], type: 'sum' as const },
styleName: 'rg-cell-total',
},
// 하수처리구역 내 > 공공하수처리인구
{
name: 'pblswrgPopltnSm',
fieldName: 'pblswrgPopltnSm',
header: { text: '소계' },
footer: footerInt,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'], type: 'sum' as const },
styleName: 'rg-cell-total',
},
{ name: 'pblswrgPrmTrt', fieldName: 'pblswrgPrmTrt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'pblswrgScdTrt', fieldName: 'pblswrgScdTrt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'pblswrgTertTrt', fieldName: 'pblswrgTertTrt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 내 > 폐수처리인구
{
name: 'wstrPopltnSm',
fieldName: 'wstrPopltnSm',
header: { text: '소계' },
footer: footerInt,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
computed: { fields: ['wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], type: 'sum' as const },
styleName: 'rg-cell-total',
},
{ name: 'wstrPrmTrt', fieldName: 'wstrPrmTrt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'wstrScdTrt', fieldName: 'wstrScdTrt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'wstrTertTrt', fieldName: 'wstrTertTrt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 내 > 미접속인구
{ name: 'notConectPopltn', fieldName: 'notConectPopltn', header: { text: '미접속인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 내 > 면적
{ name: 'swgeProcZoneInnerAr', fieldName: 'swgeProcZoneInnerAr', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
// 하수처리구역 외
{
name: 'swgeProcZoneOuterSm',
fieldName: 'swgeProcZoneOuterSm',
header: { text: '합계' },
footer: footerInt,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0',
computed: { fields: ['townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'sum' as const },
styleName: 'rg-cell-total',
},
{ name: 'townUnprocPopltn', fieldName: 'townUnprocPopltn', header: { text: '미처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'townSwttPopltn', fieldName: 'townSwttPopltn', header: { text: '오수처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'townWrrtnPopltn', fieldName: 'townWrrtnPopltn', header: { text: '정화조인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'swgeProcZoneOuterAr', fieldName: 'swgeProcZoneOuterAr', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' },
// 공공하수처리구역 인구보급률 (하수처리구역 내 인구 / 전체 인구 * 100)
{
name: 'pblswrgPopltnSuprt',
fieldName: 'pblswrgPopltnSuprt',
header: { text: '공공하수처리구역인구보급률(%)' },
footer: footerAvgDecimal,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.0',
computed: {
numeratorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'],
denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'],
type: 'pblswrgPopltnSuprtRate' as const
},
styleName: 'rg-cell-total',
},
// 고도처리 인구보급률 (고도처리 인구 / 전체 인구 * 100)
{
name: 'adtrtPopltnSuprt',
fieldName: 'adtrtPopltnSuprt',
header: { text: '고도처리인구보급률(%)' },
footer: footerAvgDecimal,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.0',
computed: {
numeratorFields: ['pblswrgTertTrt', 'wstrTertTrt'],
denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'],
type: 'adtrtPopltnSuprtRate' as const
},
styleName: 'rg-cell-total',
},
// 하수도 설치율
{
name: 'swrgInstlrt',
fieldName: 'swrgInstlrt',
header: { text: '하수도설치율(%)' },
footer: footerAvgDecimal,
editable: false,
valueType: ValueType.NUMBER,
dataFormat: '#,##0.0',
computed: {
numeratorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'swgeProcZoneOuterSm'],
denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'],
thirdFields: ['piplSupprt'],
type: 'swrgInstlrtRate' as const
},
styleName: 'rg-cell-total',
},
// 비고
{ name: 'rmk', fieldName: 'rmk', header: { text: '비고' } },
{ name: 'piplSupprt', fieldName: 'piplSupprt', header: { text: '하수관로보급률(%)' }, visible: false },
{ name: 'adstCd', fieldName: 'adstCd', header: { text: '행정코드' }, visible: false },
],
columnLayout: [
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '행정구역' },
items: [
{ column: 'sido', footerUserSpans: [{ colspan: 3 }] },
'gugun',
'adstDong',
],
},
'rvbsn',
'bstrm',
{
name: 'groupB',
hideChildHeaders: false,
header: { text: '수계영향권별분류' },
items: ['mdbsn', 'lgbsn', 'mdmbsn'],
},
'speclCntrplnAr',
{
name: 'groupC',
hideChildHeaders: false,
header: { text: '행정참고자료인구' },
items: ['totalPopl', 'poplSm', 'frgnrPoplSm'],
},
'area',
'popltnSm',
'arSm',
{
name: 'groupD',
hideChildHeaders: false,
header: { text: '하수처리구역내' },
items: [
'swgeProcZoneInnerSm',
{
name: 'pblswrgGroup',
hideChildHeaders: false,
header: { text: '공공하수처리인구(명)' },
items: ['pblswrgPopltnSm', 'pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'],
},
{
name: 'wstrGroup',
hideChildHeaders: false,
header: { text: '폐수처리인구(명)' },
items: ['wstrPopltnSm', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'],
},
'notConectPopltn',
'swgeProcZoneInnerAr'
],
},
{
name: 'groupE',
hideChildHeaders: false,
header: { text: '하수처리구역외' },
items: [
'swgeProcZoneOuterSm',
'townUnprocPopltn',
'townSwttPopltn',
'townWrrtnPopltn',
'swgeProcZoneOuterAr'
],
},
'pblswrgPopltnSuprt',
'adtrtPopltnSuprt',
'swrgInstlrt',
'rmk',
'piplSupprt',
'adstCd',
]
};
@@ -0,0 +1,46 @@
import { ValueType } from "realgrid";
/**
* 하수도 통계 관리 > 00. 통계 입력 현황
* 단일 Tab
*/
export const StatsInputStatusInpColumn = {
columns: [
{ name: 'sido', fieldName: '시도', header: { text: '시도' } },
{ name: 'sgg', fieldName: '시군구', header: { text: '시군구' } },
{ name: 'menu_nm', fieldName: '구분', header: { text: '구분' } },
{ name: 'locg_status_nm', fieldName: '진행상황', header: { text: '진행상황' } },
{ name: 'locg_status', fieldName: '작성률', header: { text: '작성률' }, valueType: ValueType.NUMBER },
{ name: 'locg_charger', fieldName: '작성자', header: { text: '작성자' } },
{ name: 'locg_cttpc', fieldName: '작성자연락처', header: { text: '작성자연락처' } },
{ name: 'locg_dept', fieldName: '부서', header: { text: '부서' } },
{ name: 'locg_job', fieldName: '직제', header: { text: '직제' } },
{ name: 'locg_cnfirm_nm', fieldName: '승인자', header: { text: '승인자' } },
{ name: 'locg_cnfirm_dept', fieldName: '부서', header: { text: '부서' } },
{ name: 'locg_cnfirm_job', fieldName: '직제', header: { text: '직제' } },
{ name: 'return_opinion_sm', fieldName: '반려사유', header: { text: '반려사유' } },
],
columnLayout: [
'sido',
'sgg',
'menu_nm',
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '하수도사업자' },
items: [
'locg_status_nm',
'locg_status',
'locg_charger',
'locg_cttpc',
'locg_dept',
'locg_job',
'locg_cnfirm_nm',
'locg_cnfirm_dept',
'locg_cnfirm_job',
'return_opinion_sm'
],
},
]
};
@@ -0,0 +1,73 @@
import { footerInt, footerDecimal1 } from "@/components/util/grid/footerSummary";
import { ValueType } from "realgrid";
// 하수도 통계 관리 > 08.개인하수처리시설 > tab 3. 개인하수도 관리지역 지정공고현황
export const PrivateSewageDesigStatusInpColumn = {
columns: [
// 행정구역
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' }, editable: false },
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } },
// 개인하수도관리지역공고현황
{ name: 'MGMT_AR', fieldName: '공고지역(개소)', header: { text: '공고지역(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'APPN_SCOPE', fieldName: '지정범위(㎢)', header: { text: '지정범위(㎢)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 개인하수도관리지역공고현황 > 정화조
{ name: 'STTY_CNT', fieldName: '개소수', header: { text: '개소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_CPC', fieldName: '처리용량(인/일)', header: { text: '처리용량(인/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 개인하수도관리지역공고현황 > 오수시설
{ name: 'WRRTN_CNT', fieldName: '개소수', header: { text: '개소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'WRRTN_CPC', fieldName: '처리용량(m³/일)', header: { text: '처리용량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
// 비고
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
// 검증 필드 (hidden)
{ name: 'CHK_MGMT_AR', fieldName: 'CHK_MGMT_AR', header: { text: '' } },
{ name: 'CHK_APPN_SCOPE', fieldName: 'CHK_APPN_SCOPE', header: { text: '' } },
{ name: 'CHK_STTY_CNT', fieldName: 'CHK_STTY_CNT', header: { text: '' } },
{ name: 'CHK_STTY_CPC', fieldName: 'CHK_STTY_CPC', header: { text: '' } },
{ name: 'CHK_WRRTN_CNT', fieldName: 'CHK_WRRTN_CNT', header: { text: '' } },
{ name: 'CHK_WRRTN_CPC', fieldName: 'CHK_WRRTN_CPC', header: { text: '' } },
{ name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' } },
{ name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' } },
],
columnLayout: [
{
name: 'group_admin',
header: { text: '행정구역' },
items: ['SIDO', 'GUGUN']
},
{
name: 'group_notice',
header: { text: '개인하수도관리지역공고현황 (하수도법제34조의2)' },
items: [
'MGMT_AR',
'APPN_SCOPE',
{
name: 'group_septic',
header: { text: '정화조' },
items: [
'STTY_CNT',
'STTY_CPC'
]
},
{
name: 'group_sewage',
header: { text: '오수시설' },
items: [
'WRRTN_CNT',
'WRRTN_CPC'
]
}
]
},
'RMK'
],
rowStartNum: 3,
fixedColumns: 1,
tabKey: 'privateSewageDesigStatusInp',
tabLabel: '개인하수도 관리지역 지정공고 현황'
};
@@ -0,0 +1,235 @@
import { footerInt } from "@/components/util/grid/footerSummary";
import { ValueType } from "realgrid";
// 하수도 통계 관리 > 08.개인하수처리시설 > tab 2. 정화조 현황
export const SepticTankStatusInpColumn = {
columns: [
// 행정구역
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' }, editable: false },
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } },
// 총 시설 수
{ name: 'TOT_FCLTY_CNT', fieldName: '총 시설 수 (A+D)', header: { text: '총 시설 수 (A+D)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 내 > 총계
{ name: 'SPZI_SM', fieldName: '총계 A=(B+C)/2', header: { text: '총계 A=(B+C)/2' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 내 > 처리방식별
{ name: 'SPZI_MTHD_SM', fieldName: '계(B)', header: { text: '계(B)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 내 > 처리방식별 > 부패탱크
{ name: 'SPZI_ROT_NPFL_CNT', fieldName: '자연유하', header: { text: '자연유하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_ROT_ENFCT_PUMP_1', fieldName: '강제펌핑식', header: { text: '강제펌핑식' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 내 > 처리방식별
{ name: 'SPZI_WTROT_NPFL_CNT', fieldName: '살수여상', header: { text: '살수여상' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_TFM', fieldName: '폭기방법', header: { text: '폭기방법' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_ARTM', fieldName: '접촉폭기', header: { text: '접촉폭기' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_CTARM', fieldName: '기타', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 내 > 처리용량별
{ name: 'SPZI_PROC_CPC_SM', fieldName: '계(C)', header: { text: '계(C)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_PROC_CPC_1', fieldName: '10인조이하', header: { text: '10인조이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_PROC_CPC_2', fieldName: '11~20', header: { text: '11~20' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_PROC_CPC_3', fieldName: '21~30', header: { text: '21~30' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_PROC_CPC_4', fieldName: '31~50', header: { text: '31~50' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_PROC_CPC_5', fieldName: '51~199', header: { text: '51~199' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_PROC_CPC_6', fieldName: '200~499', header: { text: '200~499' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_PROC_CPC_7', fieldName: '500~999', header: { text: '500~999' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZI_PROC_CPC_8', fieldName: '1,000 이상', header: { text: '1,000 이상' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 외 > 총계
{ name: 'SPZO_SM', fieldName: '총계 D=(E+F)/2', header: { text: '총계 D=(E+F)/2' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 외 > 처리방식별
{ name: 'SPZO_MTHD_SM', fieldName: '계(E)', header: { text: '계(E)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 외 > 처리방식별 > 부패탱크
{ name: 'SPZO_ROT_NPFL_CNT', fieldName: '자연유하', header: { text: '자연유하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_ROT_ENFCT_PUMP_1', fieldName: '강제펌핑식', header: { text: '강제펌핑식' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 외 > 처리방식별
{ name: 'SPZO_WTROT_NPFL_CNT', fieldName: '살수여상', header: { text: '살수여상' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_TFM', fieldName: '폭기방법', header: { text: '폭기방법' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_ARTM', fieldName: '접촉폭기', header: { text: '접촉폭기' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_CTARM', fieldName: '기타', header: { text: '기타' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 하수처리구역 외 > 처리용량별
{ name: 'SPZO_PROC_CPC_SM', fieldName: '계(F)', header: { text: '계(F)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_PROC_CPC_1', fieldName: '10인조이하', header: { text: '10인조이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_PROC_CPC_2', fieldName: '11~20', header: { text: '11~20' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_PROC_CPC_3', fieldName: '21~30', header: { text: '21~30' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_PROC_CPC_4', fieldName: '31~50', header: { text: '31~50' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_PROC_CPC_5', fieldName: '51~199', header: { text: '51~199' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_PROC_CPC_6', fieldName: '200~499', header: { text: '200~499' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_PROC_CPC_7', fieldName: '500~999', header: { text: '500~999' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'SPZO_PROC_CPC_8', fieldName: '1,000 이상', header: { text: '1,000 이상' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 비고
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
// hidden
{ name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' } },
{ name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' } },
// 검증 필드 (검증 여부 표시용 - 빨간색 표시)
{ name: 'CHK_TOT_FCLTY_CNT', fieldName: 'CHK_TOT_FCLTY_CNT', header: { text: '' } },
{ name: 'CHK_SPZI_SM', fieldName: 'CHK_SPZI_SM', header: { text: '' } },
{ name: 'CHK_SPZI_MTHD_SM', fieldName: 'CHK_SPZI_MTHD_SM', header: { text: '' } },
{ name: 'CHK_SPZI_ROT_NPFL_CNT', fieldName: 'CHK_SPZI_ROT_NPFL_CNT', header: { text: '' } },
{ name: 'CHK_SPZI_ROT_ENFCT_PUMP_1', fieldName: 'CHK_SPZI_ROT_ENFCT_PUMP_1', header: { text: '' } },
{ name: 'CHK_SPZI_ROT_ENFCT_PUMP_2', fieldName: 'CHK_SPZI_ROT_ENFCT_PUMP_2', header: { text: '' } },
{ name: 'CHK_SPZI_ROT_ENFCT_PUMP_3', fieldName: 'CHK_SPZI_ROT_ENFCT_PUMP_3', header: { text: '' } },
{ name: 'CHK_SPZI_ROT_TANK_SM', fieldName: 'CHK_SPZI_ROT_TANK_SM', header: { text: '' } },
{ name: 'CHK_SPZI_IMHF_NPFL_CNT', fieldName: 'CHK_SPZI_IMHF_NPFL_CNT', header: { text: '' } },
{ name: 'CHK_SPZI_IMHF_ENFCT_PUMP_1', fieldName: 'CHK_SPZI_IMHF_ENFCT_PUMP_1', header: { text: '' } },
{ name: 'CHK_SPZI_IMHF_ENFCT_PUMP_2', fieldName: 'CHK_SPZI_IMHF_ENFCT_PUMP_2', header: { text: '' } },
{ name: 'CHK_SPZI_IMHF_ENFCT_PUMP_3', fieldName: 'CHK_SPZI_IMHF_ENFCT_PUMP_3', header: { text: '' } },
{ name: 'CHK_SPZI_IMHF_TANK_SM', fieldName: 'CHK_SPZI_IMHF_TANK_SM', header: { text: '' } },
{ name: 'CHK_SPZI_WTROT_NPFL_CNT', fieldName: 'CHK_SPZI_WTROT_NPFL_CNT', header: { text: '' } },
{ name: 'CHK_SPZI_WTROT_ENFCT_PUMP_1', fieldName: 'CHK_SPZI_WTROT_ENFCT_PUMP_1', header: { text: '' } },
{ name: 'CHK_SPZI_WTROT_ENFCT_PUMP_2', fieldName: 'CHK_SPZI_WTROT_ENFCT_PUMP_2', header: { text: '' } },
{ name: 'CHK_SPZI_WTROT_ENFCT_PUMP_3', fieldName: 'CHK_SPZI_WTROT_ENFCT_PUMP_3', header: { text: '' } },
{ name: 'CHK_SPZI_WTROT_TANK_SM', fieldName: 'CHK_SPZI_WTROT_TANK_SM', header: { text: '' } },
{ name: 'CHK_SPZI_TFM', fieldName: 'CHK_SPZI_TFM', header: { text: '' } },
{ name: 'CHK_SPZI_ARTM', fieldName: 'CHK_SPZI_ARTM', header: { text: '' } },
{ name: 'CHK_SPZI_CTARM', fieldName: 'CHK_SPZI_CTARM', header: { text: '' } },
{ name: 'CHK_SPZI_NPFL', fieldName: 'CHK_SPZI_NPFL', header: { text: '' } },
{ name: 'CHK_SPZI_ENFCT_PUMP', fieldName: 'CHK_SPZI_ENFCT_PUMP', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_SM', fieldName: 'CHK_SPZI_PROC_CPC_SM', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_1', fieldName: 'CHK_SPZI_PROC_CPC_1', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_2', fieldName: 'CHK_SPZI_PROC_CPC_2', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_3', fieldName: 'CHK_SPZI_PROC_CPC_3', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_4', fieldName: 'CHK_SPZI_PROC_CPC_4', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_5', fieldName: 'CHK_SPZI_PROC_CPC_5', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_6', fieldName: 'CHK_SPZI_PROC_CPC_6', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_7', fieldName: 'CHK_SPZI_PROC_CPC_7', header: { text: '' } },
{ name: 'CHK_SPZI_PROC_CPC_8', fieldName: 'CHK_SPZI_PROC_CPC_8', header: { text: '' } },
{ name: 'CHK_SPZO_SM', fieldName: 'CHK_SPZO_SM', header: { text: '' } },
{ name: 'CHK_SPZO_MTHD_SM', fieldName: 'CHK_SPZO_MTHD_SM', header: { text: '' } },
{ name: 'CHK_SPZO_ROT_NPFL_CNT', fieldName: 'CHK_SPZO_ROT_NPFL_CNT', header: { text: '' } },
{ name: 'CHK_SPZO_ROT_ENFCT_PUMP_1', fieldName: 'CHK_SPZO_ROT_ENFCT_PUMP_1', header: { text: '' } },
{ name: 'CHK_SPZO_ROT_ENFCT_PUMP_2', fieldName: 'CHK_SPZO_ROT_ENFCT_PUMP_2', header: { text: '' } },
{ name: 'CHK_SPZO_ROT_ENFCT_PUMP_3', fieldName: 'CHK_SPZO_ROT_ENFCT_PUMP_3', header: { text: '' } },
{ name: 'CHK_SPZO_ROT_TANK_SM', fieldName: 'CHK_SPZO_ROT_TANK_SM', header: { text: '' } },
{ name: 'CHK_SPZO_IMHF_NPFL_CNT', fieldName: 'CHK_SPZO_IMHF_NPFL_CNT', header: { text: '' } },
{ name: 'CHK_SPZO_IMHF_ENFCT_PUMP_1', fieldName: 'CHK_SPZO_IMHF_ENFCT_PUMP_1', header: { text: '' } },
{ name: 'CHK_SPZO_IMHF_ENFCT_PUMP_2', fieldName: 'CHK_SPZO_IMHF_ENFCT_PUMP_2', header: { text: '' } },
{ name: 'CHK_SPZO_IMHF_ENFCT_PUMP_3', fieldName: 'CHK_SPZO_IMHF_ENFCT_PUMP_3', header: { text: '' } },
{ name: 'CHK_SPZO_IMHF_TANK_SM', fieldName: 'CHK_SPZO_IMHF_TANK_SM', header: { text: '' } },
{ name: 'CHK_SPZO_WTROT_NPFL_CNT', fieldName: 'CHK_SPZO_WTROT_NPFL_CNT', header: { text: '' } },
{ name: 'CHK_SPZO_WTROT_ENFCT_PUMP_1', fieldName: 'CHK_SPZO_WTROT_ENFCT_PUMP_1', header: { text: '' } },
{ name: 'CHK_SPZO_WTROT_ENFCT_PUMP_2', fieldName: 'CHK_SPZO_WTROT_ENFCT_PUMP_2', header: { text: '' } },
{ name: 'CHK_SPZO_WTROT_ENFCT_PUMP_3', fieldName: 'CHK_SPZO_WTROT_ENFCT_PUMP_3', header: { text: '' } },
{ name: 'CHK_SPZO_WTROT_TANK_SM', fieldName: 'CHK_SPZO_WTROT_TANK_SM', header: { text: '' } },
{ name: 'CHK_SPZO_TFM', fieldName: 'CHK_SPZO_TFM', header: { text: '' } },
{ name: 'CHK_SPZO_ARTM', fieldName: 'CHK_SPZO_ARTM', header: { text: '' } },
{ name: 'CHK_SPZO_CTARM', fieldName: 'CHK_SPZO_CTARM', header: { text: '' } },
{ name: 'CHK_SPZO_NPFL', fieldName: 'CHK_SPZO_NPFL', header: { text: '' } },
{ name: 'CHK_SPZO_ENFCT_PUMP', fieldName: 'CHK_SPZO_ENFCT_PUMP', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_SM', fieldName: 'CHK_SPZO_PROC_CPC_SM', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_1', fieldName: 'CHK_SPZO_PROC_CPC_1', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_2', fieldName: 'CHK_SPZO_PROC_CPC_2', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_3', fieldName: 'CHK_SPZO_PROC_CPC_3', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_4', fieldName: 'CHK_SPZO_PROC_CPC_4', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_5', fieldName: 'CHK_SPZO_PROC_CPC_5', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_6', fieldName: 'CHK_SPZO_PROC_CPC_6', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_7', fieldName: 'CHK_SPZO_PROC_CPC_7', header: { text: '' } },
{ name: 'CHK_SPZO_PROC_CPC_8', fieldName: 'CHK_SPZO_PROC_CPC_8', header: { text: '' } },
],
columnLayout: [
{
name: 'group_admin',
header: { text: '행정구역' },
items: ['SIDO', 'GUGUN']
},
'TOT_FCLTY_CNT',
{
name: 'group_spzi',
header: { text: '하수처리구역 내' },
items: [
'SPZI_SM',
{
name: 'group_spzi_mthd',
header: { text: '처리방식별' },
items: [
'SPZI_MTHD_SM',
{
name: 'group_spzi_rot',
header: { text: '부패탱크' },
items: [
'SPZI_ROT_NPFL_CNT',
'SPZI_ROT_ENFCT_PUMP_1'
]
},
'SPZI_WTROT_NPFL_CNT',
'SPZI_TFM',
'SPZI_ARTM',
'SPZI_CTARM'
]
},
{
name: 'group_spzi_proc',
header: { text: '처리용량별' },
items: [
'SPZI_PROC_CPC_SM',
'SPZI_PROC_CPC_1',
'SPZI_PROC_CPC_2',
'SPZI_PROC_CPC_3',
'SPZI_PROC_CPC_4',
'SPZI_PROC_CPC_5',
'SPZI_PROC_CPC_6',
'SPZI_PROC_CPC_7',
'SPZI_PROC_CPC_8'
]
}
]
},
{
name: 'group_spzo',
header: { text: '하수처리구역 외' },
items: [
'SPZO_SM',
{
name: 'group_spzo_mthd',
header: { text: '처리방식별' },
items: [
'SPZO_MTHD_SM',
{
name: 'group_spzo_rot',
header: { text: '부패탱크' },
items: [
'SPZO_ROT_NPFL_CNT',
'SPZO_ROT_ENFCT_PUMP_1'
]
},
'SPZO_WTROT_NPFL_CNT',
'SPZO_TFM',
'SPZO_ARTM',
'SPZO_CTARM'
]
},
{
name: 'group_spzo_proc',
header: { text: '처리용량별' },
items: [
'SPZO_PROC_CPC_SM',
'SPZO_PROC_CPC_1',
'SPZO_PROC_CPC_2',
'SPZO_PROC_CPC_3',
'SPZO_PROC_CPC_4',
'SPZO_PROC_CPC_5',
'SPZO_PROC_CPC_6',
'SPZO_PROC_CPC_7',
'SPZO_PROC_CPC_8'
]
}
]
},
'RMK'
],
rowStartNum: 4,
fixedColumns: 1,
tabKey: 'septicTankStatusInp',
tabLabel: '정화조 현황'
};
@@ -0,0 +1,107 @@
import { footerInt } from "@/components/util/grid/footerSummary";
import { ValueType } from "realgrid";
// 하수도 통계 관리 > 08.개인하수처리시설 > tab 1. 처리용량별 오수처리시설현황
export const SewageTreatFactStatusInpColumn = {
columns: [
// 행정구역
{ name: 'SIDO', fieldName: '시도', header: { text: '시도' }, editable: false },
{ name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' }, editable: false, footer: { text: '합계' } },
// 총계
{ name: 'STTY_SM', fieldName: '총계', header: { text: '총계' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 오수처리시설 > 하수처리구역 내(m³/일)
{ name: 'STTY_ZONE_INNER_SM', fieldName: '계', header: { text: '계' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_INNER_2', fieldName: '2이하', header: { text: '2이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_INNER_3', fieldName: '3~10', header: { text: '3~10' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_INNER_11', fieldName: '11~20', header: { text: '11~20' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_INNER_21', fieldName: '21~50', header: { text: '21~50' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_INNER_51', fieldName: '51~100', header: { text: '51~100' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_INNER_101', fieldName: '101~500', header: { text: '101~500' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_INNER_501', fieldName: '501이상', header: { text: '501이상' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 오수처리시설 > 하수처리구역 외(m³/일)
{ name: 'STTY_ZONE_OUTER_SM', fieldName: '계', header: { text: '계' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_OUTER_2', fieldName: '2이하', header: { text: '2이하' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_OUTER_3', fieldName: '3~10', header: { text: '3~10' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_OUTER_11', fieldName: '11~20', header: { text: '11~20' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_OUTER_21', fieldName: '21~50', header: { text: '21~50' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_OUTER_51', fieldName: '51~100', header: { text: '51~100' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_OUTER_101', fieldName: '101~500', header: { text: '101~500' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'STTY_ZONE_OUTER_501', fieldName: '501이상', header: { text: '501이상' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 비고
{ name: 'RMK', fieldName: '비고', header: { text: '비고' } },
// hidden
{ name: 'WRTER_OPINION', fieldName: '사용자의견', header: { text: '사용자의견' } },
{ name: 'CHCKER_OPINION', fieldName: '검토자의견', header: { text: '검토자의견' } },
// 검증 필드 (검증 여부 표시용, 빨간색 표시)
{ name: 'CHK_STTY_SM', fieldName: 'CHK_STTY_SM', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_INNER_SM', fieldName: 'CHK_STTY_ZONE_INNER_SM', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_INNER_2', fieldName: 'CHK_STTY_ZONE_INNER_2', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_INNER_3', fieldName: 'CHK_STTY_ZONE_INNER_3', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_INNER_11', fieldName: 'CHK_STTY_ZONE_INNER_11', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_INNER_21', fieldName: 'CHK_STTY_ZONE_INNER_21', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_INNER_51', fieldName: 'CHK_STTY_ZONE_INNER_51', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_INNER_101', fieldName: 'CHK_STTY_ZONE_INNER_101', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_INNER_501', fieldName: 'CHK_STTY_ZONE_INNER_501', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_OUTER_SM', fieldName: 'CHK_STTY_ZONE_OUTER_SM', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_OUTER_2', fieldName: 'CHK_STTY_ZONE_OUTER_2', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_OUTER_3', fieldName: 'CHK_STTY_ZONE_OUTER_3', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_OUTER_11', fieldName: 'CHK_STTY_ZONE_OUTER_11', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_OUTER_21', fieldName: 'CHK_STTY_ZONE_OUTER_21', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_OUTER_51', fieldName: 'CHK_STTY_ZONE_OUTER_51', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_OUTER_101', fieldName: 'CHK_STTY_ZONE_OUTER_101', header: { text: '' } },
{ name: 'CHK_STTY_ZONE_OUTER_501', fieldName: 'CHK_STTY_ZONE_OUTER_501', header: { text: '' } },
],
columnLayout: [
{
name: 'group_admin',
header: { text: '행정구역' },
items: ['SIDO', 'GUGUN']
},
'STTY_SM',
{
name: 'group_stty',
header: { text: '오수처리시설' },
items: [
{
name: 'group_inner',
header: { text: '하수처리구역 내(m³/일)' },
items: [
'STTY_ZONE_INNER_SM',
'STTY_ZONE_INNER_2',
'STTY_ZONE_INNER_3',
'STTY_ZONE_INNER_11',
'STTY_ZONE_INNER_21',
'STTY_ZONE_INNER_51',
'STTY_ZONE_INNER_101',
'STTY_ZONE_INNER_501'
]
},
{
name: 'group_outer',
header: { text: '하수처리구역 외(m³/일)' },
items: [
'STTY_ZONE_OUTER_SM',
'STTY_ZONE_OUTER_2',
'STTY_ZONE_OUTER_3',
'STTY_ZONE_OUTER_11',
'STTY_ZONE_OUTER_21',
'STTY_ZONE_OUTER_51',
'STTY_ZONE_OUTER_101',
'STTY_ZONE_OUTER_501'
]
}
]
},
'RMK'
],
rowStartNum: 3,
fixedColumns: 1,
tabKey: 'sewageTreatFactStatusInp',
tabLabel: '처리용량별 오수처리시설 현황'
};

Some files were not shown because too many files have changed in this diff Show More