st: feat: 그리드 해더 색상 통일, 통계입력현황 조회 기능 추가 & 검색폼 컴포넌트 로직 수정(통계입력현황 조회시 도를 제외한 시는 행정구역(sggNm)을 조회하지 않도록)

This commit is contained in:
2026-02-26 15:13:56 +09:00
parent 690bca4c06
commit c957c2ff8d
15 changed files with 289 additions and 133 deletions
+34 -1
View File
@@ -4,9 +4,24 @@
.rg-grid .rg-header .rg-table .rg-header-group-cell {
@apply !bg-[#466995] !text-[#fff];
}
.rg-grid .rg-header .rg-table .rg-header-cell {
.rg-grid .rg-header .rg-table {
@apply !bg-[#5987bf] !text-[#fff];
}
/* 상태바·체크바 헤더 */
.rg-grid .rg-head .rg-table tr:first-child td:first-child,
.rg-grid .rg-head .rg-table tr:first-child td:nth-child(2),
.rg-grid .rg-header .rg-table tr:first-child td:first-child,
.rg-grid .rg-header .rg-table tr:first-child td:nth-child(2) {
@apply !bg-[#5987bf] !text-[#fff];
}
.rg-grid .rg-header .rg-table tr td,
.rg-grid .rg-head .rg-table tr td {
border: none;
border-bottom: 1px solid #e5e7eb;
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
@apply !bg-white;
}
@@ -40,6 +55,12 @@
.rg-grid .rg-header .rg-table .rg-header-cell {
@apply !bg-[#62686d] !text-[#fff];
}
.rg-grid .rg-head .rg-table tr:first-child td:first-child,
.rg-grid .rg-head .rg-table tr:first-child td:nth-child(2),
.rg-grid .rg-header .rg-table tr:first-child td:first-child,
.rg-grid .rg-header .rg-table tr:first-child td:nth-child(2) {
@apply !bg-[#62686d] !text-[#fff];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
@apply !bg-[#545556] !text-[#fff];
}
@@ -52,6 +73,12 @@
.rg-grid .rg-header .rg-table .rg-header-cell {
@apply !bg-[#8d8d94] !text-[#fff];
}
.rg-grid .rg-head .rg-table tr:first-child td:first-child,
.rg-grid .rg-head .rg-table tr:first-child td:nth-child(2),
.rg-grid .rg-header .rg-table tr:first-child td:first-child,
.rg-grid .rg-header .rg-table tr:first-child td:nth-child(2) {
@apply !bg-[#8d8d94] !text-[#fff];
}
}
[data-theme="green"] {
@@ -61,5 +88,11 @@
.rg-grid .rg-header .rg-table .rg-header-cell {
@apply !bg-[#6bafae] !text-[#fff];
}
.rg-grid .rg-head .rg-table tr:first-child td:first-child,
.rg-grid .rg-head .rg-table tr:first-child td:nth-child(2),
.rg-grid .rg-header .rg-table tr:first-child td:first-child,
.rg-grid .rg-header .rg-table tr:first-child td:nth-child(2) {
@apply !bg-[#6bafae] !text-[#fff];
}
}
}
@@ -449,6 +449,7 @@ const DataGrid = ({
target: "local",
fileName: `${menuName}.xlsx`,
footer: "hidden",
allColumns: true, // 숨김(visible: false) 컬럼도 엑셀에 포함
done: () => {
// 헤더 원복
columns.forEach((col) => {
@@ -852,6 +853,7 @@ const DataGrid = ({
editable: col.editable,
valueType: col.valueType,
dataFormat: col.dataFormat,
...(col.width != null && { width: col.width }),
...(col.styleName != null && {
styleName: col.styleName,
}),
@@ -1146,6 +1148,7 @@ const DataGrid = ({
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })}
header={column.header}
editable={column.editable}
{...(column.width != null && { width: column.width })}
{...(baseStyleName != null && { styleName: baseStyleName })}
{...(column.visible === false && { visible: false })}
{...(cellTooltipCallback && { renderer: { showTooltip: true } })}
+16 -6
View File
@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useRef } from "react";
import useSWR from 'swr';
import {
@@ -19,6 +19,7 @@ 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, SearchCondition, Sgg } from "@/entities/statistics/model/statistics-interface";
import { doList } from "../util/column/input/DoList";
const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
return statisticsAPi.getSidoList();
@@ -66,8 +67,13 @@ const SearchForm = (props: SearchFormProps = {}) => {
bsnmCd: props.defaultValues?.bsnmCd || '',
});
// isStatusMenu면 bsnmCd가 doList에 있을 때만 시군구 조회. 선택 후 옵션 유지를 위해 마지막 목록 ref 사용
const sggKey: [string, string] | null =
values.sido && values.sido !== "ALL" && (!mergedProps.isStatusMenu || doList.includes(values.bsnmCd ?? ""))
? ["sgg-list", values.sido]
: null;
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
values.sido && values.sido !== "ALL" ? ["sgg-list", values.sido] : null,
sggKey,
getSggListFetcher,
{
revalidateOnFocus: false,
@@ -87,7 +93,9 @@ const SearchForm = (props: SearchFormProps = {}) => {
},
}
);
const lastSggListRef = useRef<Sgg[]>([]);
if (sggList?.data) lastSggListRef.current = sggList.data;
const sggOptions = sggList?.data ?? (values.sido && values.sido !== "ALL" ? lastSggListRef.current : []);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@@ -95,6 +103,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
};
const handleReset = () => {
lastSggListRef.current = [];
const resetValues: SearchFormValues = {
sido: '',
gugun: '',
@@ -109,7 +118,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
const handleSidoChange = (value: string) => {
const selectedBsnm = sidoList?.data.sidoList.find((v) => v.sido === value)?.bsnmCd || '';
lastSggListRef.current = []; // 시도 변경 시 이전 시군구 목록 제거(조건 안 맞는 시도로 바꿀 때 옵션 안 남도록)
setValues((prev) => ({
...prev,
sido: value,
@@ -119,9 +128,10 @@ const SearchForm = (props: SearchFormProps = {}) => {
};
const handleSggChange = (value: string) => {
const listForBsnm = sggList?.data ?? lastSggListRef.current;
const selectedBsnm = value === 'ALL'
? (sidoList?.data.sidoList.find((v) => v.sido === values.sido)?.bsnmCd || '')
: (sggList?.data?.find((v) => v.gugun === value)?.bsnmCd || '');
: (listForBsnm.find((v) => v.gugun === value)?.bsnmCd || '');
setValues((prev) => ({
...prev,
@@ -169,7 +179,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
{sggList?.data?.map((sgg) => (
{sggOptions?.map((sgg) => (
<SelectItem key={sgg.adstDstrCd} value={sgg.gugun}>
{sgg.gugun}
</SelectItem>
@@ -419,6 +419,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
target: 'local',
fileName: `${menuName}_${tabLabel}.xlsx`,
footer: 'hidden',
allColumns: true, // 숨김(visible: false) 컬럼도 엑셀에 포함
done: () => {
// 헤더 원복
columns.forEach((col) => {
@@ -772,7 +773,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r
return (
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full" variant="outline">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full" variant="default">
<TabsList className="mb-4">
{columnInfo.map((info) => {
const tabKey = info.tabKey;
@@ -13,7 +13,7 @@ import SubmitDailog from "./SubmitDailog";
import { useAlertStore } from "@/shared/lib";
import { useAdminStore } from "@/store/admin-store";
import { statisticsInputAPi } from "@/entities/statistics/api/statistics-input-service";
import { StatisticsInputSewSupplyRateApi } from "@/entities/statistics/api/statistics-input-sew-supply-rate-service";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import type {
SewerageSupplyRateItem,
@@ -22,7 +22,7 @@ import type {
SewerageSupplyRateVerificationResult,
SewerageSupplyRateSaveRequest,
SewerageSupplyRateStatusInfo,
} from "@/entities/statistics/model/statistics-input-interface";
} from "@/entities/statistics/model/statistics-sewage-supply-rate-input-interface";
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
@@ -101,7 +101,7 @@ const SeweragePenetrationRateInp = () => {
const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams, isEnterable: boolean) => {
try {
setIsLoading(true);
const response = await statisticsInputAPi.getSewerageSupplyRateList(params);
const response = await StatisticsInputSewSupplyRateApi.getSewerageSupplyRateList(params);
if (response.status === 'success' && response.data) {
const commonInfo = response.data.commonInfo;
const statusInfo = response.data.statusInfo;
@@ -126,7 +126,7 @@ const SeweragePenetrationRateInp = () => {
totalPopl: commonInfo.sewerBzmnTotalPopl,
poplSm: commonInfo.sewerBzmnPoplSm,
frgnrPoplSm: commonInfo.sewerBzmnFrgnrPoplSm,
refArea: commonInfo.sewerBzmnArea,
refArea: commonInfo.sewerBzmnArea,
}
: null
);
@@ -161,7 +161,7 @@ const SeweragePenetrationRateInp = () => {
}
try {
const response = await statisticsInputAPi.checkInputPeriod(values.year || '');
const response = await StatisticsInputSewSupplyRateApi.checkInputPeriod(values.year || '');
if (response.status === 'success') {
const checkEnterable = response.data;
@@ -199,7 +199,8 @@ const SeweragePenetrationRateInp = () => {
const submitFunc = async () => {
const isError = rowData.filter((d) => d.verificationResult);
if (isError.length > 0) {
alert('검증 오류 데이터가 있습니다.');
alert(`데이터에 오류가 있습니다.
수정 후 제출해주세요.`);
return;
}
if (await confirm('제출 하시겠습니까?')) {
@@ -276,7 +277,8 @@ const SeweragePenetrationRateInp = () => {
locgCnfirmDept: submitInfo.locgCnfirmDept,
locgCnfirmPh: submitInfo.locgCnfirmPh,
};
const response = await statisticsInputAPi.submitSewerageSupplyRate(requestData);
const response = await StatisticsInputSewSupplyRateApi.submitSewerageSupplyRate(requestData);
if (response.status === 'success') {
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false);
alert('제출되었습니다.');
@@ -294,15 +296,15 @@ const SeweragePenetrationRateInp = () => {
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) => {
@@ -316,10 +318,10 @@ const SeweragePenetrationRateInp = () => {
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);
@@ -370,8 +372,12 @@ const SeweragePenetrationRateInp = () => {
sewerBzmnFrgnrPoplSm: footerFromApi?.frgnrPoplSm // 행정참고자료 외국인 합계
};
const response = await statisticsInputAPi.saveSewerageSupplyRate(requestData);
if (isAdminInfo) {
requestData.superAdmin = 'superAdmin';
}
const response = await StatisticsInputSewSupplyRateApi.saveSewerageSupplyRate(requestData);
if (response.status === 'success') {
fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
alert('저장되었습니다.');
@@ -390,57 +396,57 @@ const SeweragePenetrationRateInp = () => {
return (
<>
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} onValuesChange={setSearchValues} />
{rowData.length > 0 && (isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) || isAdminInfo) && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
{isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) && (
<>
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={() => getLastYearDataFunc(searchValues ?? {})} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{(isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) || isAdminInfo) && <SaveButton saveFunc={saveFunc} />}
<div className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} onValuesChange={setSearchValues} />
{rowData.length > 0 && (isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) || isAdminInfo) && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
{isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) && (
<>
<SubmitExceptionButton SubmitExceptionFunc={SubmitExceptionFunc} />
<LastYearDataButton getLastYearDataFunc={() => getLastYearDataFunc(searchValues ?? {})} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{(isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) || isAdminInfo) && <SaveButton saveFunc={saveFunc} />}
</div>
</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 || isAdminInfo) ? 'enterable' : 'readonly'}
columnInfo={columnInfo}
isExcelImport={(rowData.length > 0) && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true)}
rowStartNum={3}
fixedColumns={1}
rowData={rowData}
setRowData={setRowData}
statusInfo={statusInfo}
getGridDataRef={getGridDataRef}
cellStyleCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
return 'rg-cell-verify-error';
}
}}
cellTooltipCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
return errors.map((e) => e.rsnExpln).filter(Boolean).join('\n');
}
}}
/>
</div>
<ConfirmDialog isOpen={false} />
<SubmitDailog open={showDailog} onOpenChange={setShowDailog} statusInfo={statusInfo} onConfirm={executeSubmit} />
)}
{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 || isAdminInfo) ? 'enterable' : 'readonly'}
columnInfo={columnInfo}
isExcelImport={(rowData.length > 0) && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true)}
rowStartNum={3}
fixedColumns={1}
rowData={rowData}
setRowData={setRowData}
statusInfo={statusInfo}
getGridDataRef={getGridDataRef}
cellStyleCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
return 'rg-cell-verify-error';
}
}}
cellTooltipCallback={({ row, fieldName }) => {
const verification = (row as GridRowWithVerification).verificationResult;
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
return errors.map((e) => e.rsnExpln).filter(Boolean).join('\n');
}
}}
/>
</div>
<ConfirmDialog isOpen={false} />
<SubmitDailog open={showDailog} onOpenChange={setShowDailog} statusInfo={statusInfo} onConfirm={executeSubmit} />
</>
);
};
@@ -1,14 +1,59 @@
import { useState } from "react";
import SearchForm from "@/components/module/SearchForm";
import { DataGrid } from "@rsone/components";
// import { DataGrid } from "@rsone/components";
import DataGrid from "@/components/module/DataGrid";
import ApproveButton from "@/components/button/ApproveButton";
import RejectButton from "@/components/button/RejectButton";
import { LoadingWithAnimation } from "@common/components";
import { StatsInputStatusInpColumn } from "@/components/util/column/input/StatsInputStatusInpColumn";
import { StatisticsInputStatusApi } from "@/entities/statistics/api/statistics-input-status-service";
import type { StatsInputStatusListRequestParams } from "@/entities/statistics/model/statistics-input-status-interface";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { useAlertStore } from "@/shared/lib";
import { toGridRows } from "@/components/util/grid/gridFunction";
// 하수도 통계 관리 > 00. 통계 입력 현황
const StatsInputStatusInp = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleSearch = async (values: SearchFormValues) => {
if (values.sido === '' || values.sido === 'ALL') {
alert('시도를 선택해주세요.');
return;
}
const searchParam: StatsInputStatusListRequestParams = {
ctpvNm: values.sido,
sewerBzmnCd: values.bsnmCd,
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
statsYr: values.year,
};
try {
setIsLoading(true);
const response = await StatisticsInputStatusApi.getStatisticsInputStatusList(searchParam);
if (response.status === 'success' && response.data) {
setRowData(toGridRows(response.data));
} else {
alert(response.message);
}
} catch {
alert('데이터 조회 중 오류가 발생했습니다.');
setRowData([]);
} finally {
setIsLoading(false);
}
};
const handleReset = () => {
setRowData([]);
};
const rejectFunc = () => {
// 보완 요청 로직
@@ -18,25 +63,31 @@ const StatsInputStatusInp = () => {
// 승인 요청 로직
};
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 className="relative">
<SearchForm onSubmit={handleSearch} onReset={handleReset} isStatusMenu={true} />
<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>
{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={ StatsInputStatusInpColumn }
rowStartNum={2}
fixedColumns={3}
rowData={rowData}
setRowData={setRowData}
isExcelImport={false}
showHeaderCheckbox={true}
/>
</div>
<DataGrid
columnInfo={ StatsInputStatusInpColumn }
rowStartNum={2}
fixedColumns={3}
rowData={rowData}
setRowData={setRowData}
isExcelImport={false}
/>
</>
);
};
@@ -18,7 +18,7 @@ import { useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { statisticsInputSubmitInfoSchema, type StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
import { type SewerageSupplyRateStatusInfo } from "@/entities/statistics/model/statistics-input-interface";
import { type SewerageSupplyRateStatusInfo } from "@/entities/statistics/model/statistics-sewage-supply-rate-input-interface";
/** 숫자만 추출 후 하이픈 포맷 (010-1234-5678, 02-123-4567 등) */
function formatPhoneWithHyphen(value: string): string {
+4 -1
View File
@@ -1,6 +1,6 @@
import React from "react";
import { ValueType } from "realgrid";
import { type SewerageSupplyRateStatusInfo } from "@/entities/statistics/model/statistics-input-interface";
import { type SewerageSupplyRateStatusInfo } from "@/entities/statistics/model/statistics-sewage-supply-rate-input-interface";
// RealGrid 타입 정의
/** setColumns에 전달하는 컬럼 정보 (footer 배열/단일 객체 포함) */
@@ -11,6 +11,7 @@ export type RealGridColumnInfo = {
editable?: boolean;
valueType?: import("realgrid").ValueType;
dataFormat?: string;
width?: number;
styleName?: string;
mergeRule?: { criteria: "value" | string };
sortable?: boolean;
@@ -132,6 +133,8 @@ export type ColumnConfig = {
editable?: boolean; // 셀 편집 가능 여부
/** 그리드에서 컬럼 숨김 (수식용 필드 등) */
visible?: boolean;
/** 컬럼 너비 (px). 지정 시 ... 생략 없이 텍스트 표시에 유리 */
width?: number;
/** 셀에 적용할 CSS 클래스명 (예: 'rg-cell-readonly', 'rg-cell-total') */
styleName?: string;
mergeCells?: boolean;
@@ -18,6 +18,7 @@ export type SearchFormProps = {
onReset?: () => void;
onValuesChange?: (values: SearchFormValues) => void;
defaultValues?: SearchFormValues;
isStatusMenu?: boolean;
};
export const defaultSearchFormProps: Required<Omit<SearchFormProps, 'onSubmit' | 'onReset' | 'onValuesChange' | 'defaultValues'>> = {
@@ -27,4 +28,5 @@ export const defaultSearchFormProps: Required<Omit<SearchFormProps, 'onSubmit' |
order: false,
fctName: false,
userMenual: false,
isStatusMenu: false,
};
@@ -6,40 +6,42 @@ import { ValueType } from "realgrid";
*/
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: '반려사유' } },
{ name: 'sido', fieldName: 'sido', header: { text: '시도' }, editable: false },
{ name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, editable: false },
{ name: 'menuCd', fieldName: 'menuCd', header: { text: '메뉴코드' }, visible: false, editable: false },
{ name: 'menuNm', fieldName: 'menuNm', header: { text: '구분' }, editable: false, width: 220 },
{ name: 'locgStatusNm', fieldName: 'locgStatusNm', header: { text: '진행상황' }, editable: false },
{ name: 'inputRate', fieldName: 'inputRate', header: { text: '작성률' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'locgCharger', fieldName: 'locgCharger', header: { text: '작성자' }, editable: false },
{ name: 'locgCttpc', fieldName: 'locgCttpc', header: { text: '작성자연락처' }, editable: false },
{ name: 'locgDept', fieldName: 'locgDept', header: { text: '부서' }, editable: false },
{ name: 'locgJob', fieldName: 'locgJob', header: { text: '직제' }, editable: false },
{ name: 'locgCnfirmNm', fieldName: 'locgCnfirmNm', header: { text: '승인자' }, editable: false },
{ name: 'locgCnfirmDept', fieldName: 'locgCnfirmDept', header: { text: '부서' }, editable: false },
{ name: 'locgCnfirmJob', fieldName: 'locgCnfirmJob', header: { text: '직제' }, editable: false },
{ name: 'returnOpinionSm', fieldName: 'returnOpinionSm', header: { text: '반려사유' }, editable: false },
],
columnLayout: [
'sido',
'sgg',
'menu_nm',
'gugun',
'menuCd',
'menuNm',
{
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'
'locgStatusNm',
'inputRate',
'locgCharger',
'locgCttpc',
'locgDept',
'locgJob',
'locgCnfirmNm',
'locgCnfirmDept',
'locgCnfirmJob',
'returnOpinionSm'
],
},
]
@@ -2,15 +2,15 @@ import axios from "axios";
import { ENDPOINT } from "@/shared/config";
import {
type StatisticsInputService,
type StatisticsInputSewSupplyRateService,
type SewerageSupplyRateRequestParams,
type SewerageSupplyRateApiResponse,
type CheckInputPeriodResponse,
type SewerageSupplyRateSaveRequest,
type SewerageSupplyRateSaveApiResponse,
} from "../model/statistics-input-interface";
} from "../model/statistics-sewage-supply-rate-input-interface";
export class StatisticsInputAPiService implements StatisticsInputService {
export class StatisticsInputSewSupplyRateApiService implements StatisticsInputSewSupplyRateService {
public async getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise<SewerageSupplyRateApiResponse> {
const response = await axios.get<SewerageSupplyRateApiResponse> (
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`,
@@ -49,4 +49,4 @@ export class StatisticsInputAPiService implements StatisticsInputService {
}
}
export const statisticsInputAPi = new StatisticsInputAPiService();
export const StatisticsInputSewSupplyRateApi = new StatisticsInputSewSupplyRateApiService();
@@ -0,0 +1,23 @@
import axios from "axios";
import { ENDPOINT } from "@/shared/config";
import type {
StatisticsInputStatusService,
StatsInputStatusListRequestParams,
StatsInputStatusListApiResponse,
} from "../model/statistics-input-status-interface";
export class StatisticsInputStatusApiService implements StatisticsInputStatusService {
public async getStatisticsInputStatusList(param: StatsInputStatusListRequestParams): Promise<StatsInputStatusListApiResponse> {
const response = await axios.get<StatsInputStatusListApiResponse> (
`${ENDPOINT.ST_SERVICE.INPUT}/stats-input-status`,
{
params: param
}
);
return response.data;
};
}
export const StatisticsInputStatusApi = new StatisticsInputStatusApiService();
@@ -2,7 +2,6 @@ import axios from "axios";
import { ENDPOINT } from "@/shared/config";
import { type CheckInputPeriodResponse } from "../model/statistics-input-interface";
import {
type StatsInputStatusReviewApiResponse
} from "../model/statistics-input-review-interface";
@@ -46,18 +45,6 @@ export class StatisticsManagementApiService {
return response.data;
}
/**
* 입력 기간 확인 (공통)
* @param year 연도
*/
public async checkInputPeriod(year: string | number): Promise<CheckInputPeriodResponse> {
const response = await axios.get<CheckInputPeriodResponse>(
`${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period`,
{ params: { year } }
);
return response.data;
}
/**
* 01. 하수도 보급률 검토 목록 조회
* @param params (sido, gugun, yr, bsnmCd)
@@ -0,0 +1,34 @@
import type { ApiResponse } from "./statistics-interface";
export interface StatsInputStatusListRequestParams {
ctpvNm?: string;
sggNm?: string;
sewerBzmnCd?: string;
statsYr?: string;
mode?: string;
}
export interface StatsInputStatusList {
sido?: string;
gugun?: string;
menuCd?: string;
menuNm?: string;
locgStatusNm?: string;
inputRate?: number;
locgCharger?: string;
locgCttpc?: string;
locgDept?: string;
locgJob?: string;
locgCnfirmNm?: string;
locgCnfirmDept?: string;
locgCnfirmJob?: string;
returnOpinionSm?: string;
}
export type StatsInputStatusListApiResponse = ApiResponse<StatsInputStatusList[]> & {
message: string;
}
export interface StatisticsInputStatusService {
getStatisticsInputStatusList(param: StatsInputStatusListRequestParams): Promise<StatsInputStatusListApiResponse>;
}
@@ -146,6 +146,7 @@ export interface SewerageSupplyRateSaveRequest {
locgCnfirmJob?: string;
locgCnfirmDept?: string;
locgCnfirmPh?: string;
superAdmin?: string;
}
export interface SewerageSupplyRateSaveData {
@@ -162,7 +163,7 @@ export interface CheckInputPeriodResponse {
data: boolean;
}
export interface StatisticsInputService {
export interface StatisticsInputSewSupplyRateService {
getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise<SewerageSupplyRateApiResponse>;
checkInputPeriod(params: string): Promise<CheckInputPeriodResponse>;
saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse>;