diff --git a/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx b/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx
index 16947f896..7746febae 100644
--- a/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx
+++ b/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx
@@ -1,125 +1,184 @@
"use client";
-import { useMemo, useState } from 'react';
+import { useState, useEffect, useMemo } from 'react';
-import { CodeSelect } from '@/entities/code';
+import { CodeSelectWithAll } from '@/entities/code';
import {
Button,
Label,
- // Table,
- // TableBody,
- // TableCell,
- // TableHead,
- // TableRow,
+ Select,
+ SelectTrigger,
+ SelectContent,
+ SelectValue,
+ SelectItem,
DataTable,
} from "@common/components";
+import { LoadingWithAnimation } from '@common/components';
+import { NoData } from '@/shared/ui/no-data';
+import DetailComponent from './generalVerification/DetailComponent';
+import RegistDialog from './generalVerification/RegistDialog';
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
+import { useForm } from "react-hook-form";
+import { zodResolver } from '@hookform/resolvers/zod';
+import { generalVerificationMasterSubmitSchema, type GeneralVerificationMasterSubmitFormData } from '@/entities/statistics/model/general-verification-master-submit-schema';
// import { useDataTable } from "@/shared/lib";
-import { GeneralVerificationColumn } from '@/components/util/column/management/GeneralVerificationColumn';
+import { getGeneralVerificationColumns } from '@/components/util/column/management/GeneralVerificationColumn';
import type { GeneralverificationList } from '@/entities/statistics/model/general-verification-interface';
+import { generalverificationApiService } from '@/entities/statistics/api/general-verification-service';
+
+import { useAlertStore } from '@/shared/lib';
+
const GeneralVerificationManagement = () => {
- const [statsCd, setStatsCd] = useState('');
- // const [rowSelection, setRowSelection] = useState
({});
- // const [pagination, setPagination] = useState({
- // pageIndex: 0,
- // pageSize: 100,
- // });
+ const [searchValues, setSearchValues] = useState<{
+ statsCd: string;
+ useYn: string;
+ vrfcClsfCd: string;
+ }>({
+ statsCd: '',
+ useYn: '',
+ vrfcClsfCd: '',
+ });
+ const [vrfcMasterData, setVrfcMasterData] = useState([]);
+ const [rowSelection, setRowSelection] = useState>({});
+ const [isLoading, setIsLoading] = useState(false);
+ const [selectedVrfcRow, setSelectedVrfcRow] = useState(null);
+ const [showRegistForm, setShowRegistForm] = useState(false);
- // const { tableComponent } = useDataTable({
- // tableOptions: {
- // GeneralVerificationColumn,
- // state: {
- // pagination,
- // rowSelection,
- // },
- // manualPagination: true,
- // enableMultiRowSelection: true,
- // getCoreRowModel: getCoreRowModel(),
- // onPaginationChange: setPagination,
- // onRowSelectionChange: setRowSelection,
- // },
- // // dataTableOptions: {
- // // onRowClick: handleRowClick,
- // // viewPagination: true,
- // // },
- // });
+ const { setMessage: alert } = useAlertStore((state) => state);
- const mockData = useMemo(
- () => [
- {
- vrfSn: 1,
- tabNm: '하수도보급률',
- vrfNm: '인구수 검증',
- vrfClsCd: '시',
- statsCd: 'ST000001',
- useYn: 'Y',
- rgtrId: '관리자',
- regDt: '20260102',
- },
- {
- vrfSn: 2,
- tabNm: '하수도보급률',
- vrfNm: '인구수 검증',
- vrfClsCd: '시',
- statsCd: 'ST000001',
- useYn: 'Y',
- rgtrId: '관리자',
- regDt: '20260102',
- },
- {
- vrfSn: 3,
- tabNm: '하수도보급률',
- vrfNm: '인구수 검증',
- vrfClsCd: '시',
- statsCd: 'ST000001',
- useYn: 'Y',
- rgtrId: '관리자',
- regDt: '20260102',
- },
- {
- vrfSn: 4,
- tabNm: '하수도보급률',
- vrfNm: '인구수 검증',
- vrfClsCd: '시',
- statsCd: 'ST000001',
- useYn: 'Y',
- rgtrId: '관리자',
- regDt: '20260102',
- },
- ],
- []
+ const handleSearch = async () => {
+ try {
+ setIsLoading(true);
+
+ const params = Object.fromEntries(
+ Object.entries({
+ trgtStatsCd: searchValues.statsCd,
+ vrfcClsfCd: searchValues.vrfcClsfCd,
+ useYn: searchValues.useYn,
+ }).filter(([, value]) => value !== '' && value !== 'ALL')
+ );
+
+ const response = await generalverificationApiService.getGeneralverificationList(params);
+
+ if (response.status === 'success') {
+ setVrfcMasterData(response.data);
+ setRowSelection({});
+ setSelectedVrfcRow(null);
+ setShowRegistForm(false);
+ } else {
+ alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
+ }
+ } catch {
+ alert('데이터 조회 중 오류가 발생했습니다.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const columns = useMemo(
+ () => getGeneralVerificationColumns(handleSearch),
+ [searchValues.statsCd, searchValues.vrfcClsfCd, searchValues.useYn],
);
const table = useReactTable({
- data: mockData,
- columns: GeneralVerificationColumn,
+ data: vrfcMasterData,
+ columns,
getCoreRowModel: getCoreRowModel(),
+ getRowId: (row) => row.vrfcSn,
+ state: { rowSelection },
+ onRowSelectionChange: setRowSelection,
});
- const handleSearch = () => {
- console.log(statsCd);
+ const form = useForm({
+ resolver: zodResolver(generalVerificationMasterSubmitSchema),
+ defaultValues: {},
+ });
+
+ const handleVrfcMasterRowClick = async (row: GeneralverificationList) => {
+ try {
+ const response = await generalverificationApiService.getGeneralverificationInfo(row.vrfcSn);
+
+ if (response.status === 'success') {
+ setRowSelection({ [row.vrfcSn]: true });
+ setSelectedVrfcRow(row);
+ setShowRegistForm(false);
+
+ form.reset(response.data);
+ } else {
+ alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
+ form.reset({});
+ }
+ } catch {
+ alert('데이터 조회 중 오류가 발생했습니다.');
+ }
};
+ const handleReset = () => {
+ setRowSelection({});
+ setSelectedVrfcRow(null);
+ handleSearch();
+ setShowRegistForm(false);
+ }
+
+ const handleClickRegist = () => {
+ // setRowSelection({});
+ // setSelectedVrfcRow(null);
+ };
+
+ useEffect(() => {
+ handleSearch();
+ }, []);
+
return (
-
+
- setSearchValues({ ...searchValues, statsCd: value })}
placeholder="전체"
className="w-[16rem]"
/>
+
+
+
+
+
+
+
+
-
-
-
-
-
일반 검증 목록
-
-
-
-
+
+
+
+
+ 등록
+
+
-
-
+
+
+
+
일반 검증 목록
+
+
+ {isLoading && (
+
+ )}
+ {isLoading ? null
+ :
+ vrfcMasterData.length > 0 ?
+
handleVrfcMasterRowClick(value)} />
+ :
+
+
+
+ }
+
+
+
diff --git a/apps/st/src/components/statistics/verification/generalVerification/DetailComponent.tsx b/apps/st/src/components/statistics/verification/generalVerification/DetailComponent.tsx
new file mode 100644
index 000000000..752c6ff6b
--- /dev/null
+++ b/apps/st/src/components/statistics/verification/generalVerification/DetailComponent.tsx
@@ -0,0 +1,93 @@
+import { NoData } from '@/shared/ui/no-data';
+import {
+ // Button,
+ // Label,
+ // Select,
+ // SelectTrigger,
+ // SelectContent,
+ // SelectValue,
+ // SelectItem,
+ Input,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+} from "@common/components";
+import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form";
+
+import { type UseFormReturn } from "react-hook-form";
+
+import type { GeneralverificationList } from '@/entities/statistics/model/general-verification-interface';
+import { type GeneralVerificationMasterSubmitFormData } from '@/entities/statistics/model/general-verification-master-submit-schema';
+
+interface DetailComponentProps {
+ selectedVrfcRow: GeneralverificationList | null;
+ form: UseFormReturn
;
+}
+
+const DetailComponent = ({ selectedVrfcRow, form }: DetailComponentProps) => {
+ return (
+
+
+
검증 상세 정보
+
+
+ {selectedVrfcRow ?
+ <>
+
+ >
+ :
+
+ }
+
+
+ );
+};
+
+export default DetailComponent;
\ No newline at end of file
diff --git a/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx b/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx
new file mode 100644
index 000000000..3a5bbc01e
--- /dev/null
+++ b/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx
@@ -0,0 +1,614 @@
+"use client";
+
+import { useEffect, memo, useRef, useState } from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useQuery } from "@tanstack/react-query";
+
+import {
+ Button,
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+ DialogClose,
+ Input,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ RadioGroup,
+ RadioGroupItem,
+ Label,
+ Select,
+ SelectTrigger,
+ SelectContent,
+ SelectValue,
+ SelectItem,
+ Skeleton,
+} from "@common/components";
+import { Form, FormField, FormItem, FormControl } from "@/shared/ui/form";
+import { ENDPOINT } from "@/shared/config";
+import { CodeSelect } from "@/entities/code";
+
+import { type GeneralVerificationMasterSubmitFormData, generalVerificationMasterSubmitSchema } from '@/entities/statistics/model/general-verification-master-submit-schema';
+import type { UpdateVrfcMasterRequest, RegistVrfcMasterRequest } from "@/entities/statistics/model/general-verification-interface";
+import { generalverificationApiService } from '@/entities/statistics/api/general-verification-service';
+import { UserApi } from "@/entities/user/api/user-service";
+import { useConfirm } from "@frontend-opensource/use-react-hooks";
+import { useAlertStore } from "@/shared/lib";
+import { getUserInfo } from "../../../../../../../packages/common/components/src/api";
+
+interface RegistDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ children?: React.ReactNode;
+ vrfcSn?: string;
+ /** 수정/등록 성공 시 부모에서 목록 재조회 등 실행 */
+ onSuccess?: () => void;
+}
+
+const RegistDialog = memo(
+ ({
+ children,
+ open,
+ onOpenChange,
+ vrfcSn,
+ onSuccess,
+ ...props
+ }: RegistDialogProps) => {
+
+ const isVrfcSn = vrfcSn ? true : false;
+ const [checkVrfcNm, setCheckVrfcNm] = useState(false);
+ const { confirm } = useConfirm();
+ const alert = useAlertStore((state) => state.setMessage);
+
+ const { data: detailData } = useQuery({
+ queryKey: [`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/masters`, vrfcSn],
+ queryFn: () => generalverificationApiService.getGeneralverificationInfo(vrfcSn!),
+ enabled: open && !!vrfcSn,
+ });
+
+ const form = useForm({
+ resolver: zodResolver(generalVerificationMasterSubmitSchema),
+ values: {
+ vrfcSn: detailData?.data.vrfcSn ?? '',
+ vrfcNm: detailData?.data.vrfcNm ?? '',
+ vrfcClsfCd: detailData?.data.vrfcClsfCd ?? '',
+ trgtStatsCd: detailData?.data.trgtStatsCd ?? '',
+ trgtStatsNm: detailData?.data.trgtStatsNm ?? '',
+ dcptDigtCnt: detailData?.data.dcptDigtCnt ?? 0,
+ dcptPrcsMthCd: detailData?.data.dcptPrcsMthCd ?? '',
+ useYn: detailData?.data.useYn ?? 'Y',
+ rgtrId: detailData?.data.rgtrId ?? '',
+ regDt: detailData?.data.regDt ?? '',
+ mdfrId: detailData?.data.mdfrId ?? '',
+ mdfcnDt: detailData?.data.mdfcnDt ?? '',
+ },
+ });
+
+ const dcptDigtCnt = form.watch('dcptDigtCnt');
+ const rgtrId = form.watch('rgtrId');
+ const mdfrId = form.watch('mdfrId');
+ const trgtStatsCd = form.watch('trgtStatsCd');
+ const vrfcNm = form.watch('vrfcNm');
+
+ const { data: registrarUser } = useQuery({
+ queryKey: ['user', 'rgtr', rgtrId],
+ queryFn: () => UserApi.findByUserUnqId(rgtrId!),
+ enabled: open && !!rgtrId,
+ });
+
+ const { data: modifierUser } = useQuery({
+ queryKey: ['user', 'mdfr', mdfrId],
+ queryFn: () => UserApi.findByUserUnqId(mdfrId!),
+ enabled: open && !!mdfrId,
+ });
+
+ const originalVrfcNmRef = useRef(null);
+
+ useEffect(() => {
+ if (detailData?.data.vrfcNm && originalVrfcNmRef.current == null) {
+ originalVrfcNmRef.current = detailData.data.vrfcNm;
+ }
+ }, [detailData]);
+
+ /** 중복 여부 반환: true = 중복, false = 사용 가능 */
+ const runVerificationNameCheck = async (trgtStatsCdVal: string, vrfcNmVal: string, vrfcClsfCd: string): Promise => {
+ try {
+ const response = await generalverificationApiService.checkVerificationName({
+ trgtStatsCd: trgtStatsCdVal,
+ vrfcNm: vrfcNmVal,
+ vrfcClsfCd: vrfcClsfCd,
+ });
+ if (response.status === 'success') {
+ const isDuplicate = response.data;
+ setCheckVrfcNm(isDuplicate);
+ return isDuplicate;
+ }
+ alert('중복검사 중 오류가 발생했습니다.');
+ setCheckVrfcNm(true);
+ return true;
+ } catch {
+ alert('중복검사 중 오류가 발생했습니다.');
+ setCheckVrfcNm(true);
+ return true;
+ }
+ };
+
+ const handleUpdateVrfcMaster = async (data: GeneralVerificationMasterSubmitFormData) => {
+ try {
+ const response = await generalverificationApiService.UpdateVrfcMaster(data as UpdateVrfcMasterRequest);
+
+ if (response.status === 'success') {
+ alert('수정되었습니다.');
+ onOpenChange(false);
+ onSuccess?.();
+ } else {
+ alert(response.message ?? '수정 중 오류가 발생했습니다.');
+ }
+ } catch {
+ alert('수정 중 오류가 발생했습니다.');
+ }
+ };
+
+ const handleRegistVrfcMaster = async (data: GeneralVerificationMasterSubmitFormData) => {
+ try {
+ const response = await generalverificationApiService.RegistVrfcMaster(data as RegistVrfcMasterRequest);
+
+ if (response.status === 'success') {
+ alert('등록되었습니다.');
+ onOpenChange(false);
+ onSuccess?.();
+ } else {
+ alert(response.message ?? '등록 중 오류가 발생했습니다.');
+ }
+ } catch {
+ alert('등록 중 오류가 발생했습니다.');
+ }
+ };
+
+ const handleDeleteVrfcMaster = async () => {
+ const deleteMsg = `${detailData?.data.vrfcNm} 검증룰을
+ 삭제하시겠습니까?`;
+
+ if (await confirm(deleteMsg)) {
+ try {
+ const response = await generalverificationApiService.DeleteVrfcMaster(vrfcSn || '');
+
+ if (response.status === 'success') {
+ alert('삭제되었습니다.');
+ onOpenChange(false);
+ onSuccess?.();
+ } else {
+ alert(response.message ?? '삭제 중 오류가 발생했습니다.');
+ }
+ } catch {
+ alert('삭제 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (!open) return;
+ if (!vrfcNm) return;
+
+ const original = originalVrfcNmRef.current;
+ // 수정 모드: 기준값이 아직 안 세팅됐으면 검사 안 함(다이얼로그 막 열었을 때)
+ if (isVrfcSn && original === null) return;
+ // 수정 모드: 사용자가 검증명을 바꾸지 않았으면 검사 안 함
+ if (original !== null && original === vrfcNm) {
+ setCheckVrfcNm(false);
+ return;
+ }
+
+ // const timer = setTimeout(() => {
+ // runVerificationNameCheck(trgtStatsCd ?? '', vrfcNm, vrfcClsfCd);
+ // }, 500);
+
+ // return () => clearTimeout(timer);
+ }, [vrfcNm, open, isVrfcSn]);
+
+ const onSubmit = async (data: GeneralVerificationMasterSubmitFormData) => {
+ const currentVrfcNm = (data.vrfcNm ?? '').trim();
+ const originalVrfcNm = originalVrfcNmRef.current;
+
+ // 수정 모드인데 기존값과 동일하면 중복검사 완전 생략
+ const skipNameCheck =
+ isVrfcSn &&
+ (originalVrfcNm === null || currentVrfcNm === originalVrfcNm);
+
+ if (!skipNameCheck && currentVrfcNm) {
+ // 제출 시점에 중복검사 실행
+ const isDuplicate = await runVerificationNameCheck(
+ data.trgtStatsCd ?? '',
+ currentVrfcNm,
+ data.vrfcClsfCd,
+ );
+ if (isDuplicate) {
+ return;
+ }
+ }
+
+ const confirmMessage = isVrfcSn
+ ? "수정 하시겠습니까?"
+ : "등록 하시겠습니까?";
+ if (await confirm(confirmMessage)) {
+ const userInfo = await getUserInfo();
+ if (isVrfcSn) {
+ const updateData = {
+ vrfcSn: data.vrfcSn,
+ vrfcNm: data.vrfcNm,
+ vrfcClsfCd: data.vrfcClsfCd,
+ dcptDigtCnt: data.dcptDigtCnt,
+ ...(data.dcptDigtCnt === 0
+ ? {}
+ : { dcptPrcsMthCd: data.dcptPrcsMthCd }),
+ trgtStatsCd: data.trgtStatsCd,
+ mdfrId: userInfo.userUnqId,
+ };
+ await handleUpdateVrfcMaster(updateData);
+ } else {
+ const registData = {
+ vrfcNm: data.vrfcNm,
+ vrfcClsfCd: data.vrfcClsfCd,
+ dcptDigtCnt: data.dcptDigtCnt,
+ dcptPrcsMthCd: data.dcptPrcsMthCd,
+ trgtStatsCd: data.trgtStatsCd,
+ rgtrId: userInfo.userUnqId
+ };
+ await handleRegistVrfcMaster(registData);
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (!open) {
+ form.reset();
+ setCheckVrfcNm(false);
+ originalVrfcNmRef.current = null;
+ }
+ }, [open, form]);
+
+ return (
+
+ );
+ })
+
+RegistDialog.displayName = 'RegistDialog';
+
+export default RegistDialog;
\ No newline at end of file
diff --git a/apps/st/src/components/util/column/management/GeneralVerificationColumn.tsx b/apps/st/src/components/util/column/management/GeneralVerificationColumn.tsx
index b61ad9ea3..4ed1b8825 100644
--- a/apps/st/src/components/util/column/management/GeneralVerificationColumn.tsx
+++ b/apps/st/src/components/util/column/management/GeneralVerificationColumn.tsx
@@ -1,10 +1,14 @@
import dayjs from "dayjs";
import { useState } from "react";
import type { ColumnDef } from "@tanstack/react-table";
-import { Button } from "@common/components";
import type { GeneralverificationList } from "@/entities/statistics/model/general-verification-interface";
-export const GeneralVerificationColumn: ColumnDef[] = [
+import { Button } from "@common/components";
+import RegistDialog from "@/components/statistics/verification/generalVerification/RegistDialog";
+
+export const getGeneralVerificationColumns = (
+ onUpdateSuccess?: () => void,
+): ColumnDef[] => [
{
id: "no",
header: "NO",
@@ -14,21 +18,20 @@ export const GeneralVerificationColumn: ColumnDef[] = [
return totalRows - (pageIndex * pageSize + row.index);
},
},
+ // {
+ // accessorKey: "vrfcSn",
+ // header: "일련번호",
+ // },
{
- accessorKey: "vrfSn",
- header: "일련번호",
- enableHiding: true,
- },
- {
- accessorKey: "tabNm",
+ accessorKey: "trgtStatsNm",
header: "통계항목",
},
{
- accessorKey: "vrfNm",
+ accessorKey: "vrfcNm",
header: "검증명",
},
{
- accessorKey: "vrfClsCd",
+ accessorKey: "vrfcClsfNm",
header: "검증분류",
},
{
@@ -48,15 +51,31 @@ export const GeneralVerificationColumn: ColumnDef[] = [
header: "관리",
cell: ({ row }) => (
e.stopPropagation()}>
-
+
),
},
];
-const GeneralVerificationManageButton = ({ vrfSn }: { vrfSn: number }) => {
+const GeneralVerificationManageButton = ({
+ vrfcSn,
+ onSuccess,
+}: {
+ vrfcSn: string;
+ onSuccess?: () => void;
+}) => {
const [dialogOpen, setDialogOpen] = useState(false);
return (
- 관리
+
+ 관리
+
);
};
diff --git a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts
index 3c22c517c..54dbfcedd 100644
--- a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts
+++ b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts
@@ -88,10 +88,10 @@ export const rawPumpingStationInfoList = {
]
},
{ name: 'pumpstNm', fieldName: 'pumpstNm', header: { text: '펌프장명' }, mergeCells: true },
- { name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' } }, // bscFctNm -> swtrmtFcltNm
- { name: 'pumpSeNm', fieldName: 'pumpSeNm', header: { text: '구분\n(합류/분류)' } }, // pumpCls -> pumpSeNm
- { name: 'impdFcltPstnCn', fieldName: 'impdFcltPstnCn', header: { text: '위치' } }, // addr -> impdFcltPstnCn
- { name: 'oprtngStrtYmd', fieldName: 'oprtngStrtYmd', header: { text: '가동년월일' } }, // rnDt -> oprtngStrtYmd
+ { name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' } },
+ { name: 'pumpSeNm', fieldName: 'pumpSeNm', header: { text: '구분\n(합류/분류)' } },
+ { name: 'impdFcltPstnCn', fieldName: 'impdFcltPstnCn', header: { text: '위치' } },
+ { name: 'oprtngStrtYmd', fieldName: 'oprtngStrtYmd', header: { text: '가동년월일' } },
{
name: 'planMltvArea', fieldName: 'planMltvArea', header: { text: '계획배수면적\n(km2)' },
footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000'
diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts
index ae445b22d..1adb4eadf 100644
--- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts
+++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts
@@ -1,8 +1,13 @@
import { ValueType } from "realgrid";
-const baseYear = new Date().getFullYear();
+import dayjs from 'dayjs';
import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary";
import type { GridView } from "@/components/type/DataGrid.type";
+
+const currentYear = dayjs().year() - 1;
+const startYear = 1996;
+const totalCount = currentYear - startYear + 1 + 1;
+
// 하수도 통계 관리 02. 하수관로
const rawList = [
{
@@ -248,7 +253,7 @@ const rawList = [
}
]
},
- { name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리\n시설용량\n(㎥/일)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
+ { name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리\n시설용량\n(㎥/일)' },valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
{ name: 'prcsZoneArea', fieldName: 'prcsZoneArea', header: { text: '합계' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
{ name: 'cbsysPrcsZoneArea', fieldName: 'cbsysPrcsZoneArea', header: { text: '합류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
{ name: 'sprsysPrcsZoneArea', fieldName: 'sprsysPrcsZoneArea', header: { text: '분류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' },
@@ -273,19 +278,19 @@ const rawList = [
{
name: 'addrGroup', header: { text: '행정구역' },
items: [
- { column: 'ctpvNm', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] },
+ { column: 'ctpvNm', footerUserSpans: [{ colspan: 6 }, { colspan: 6 }] },
'sggNm', 'admdstNm'
]
},
'swtrmtFcltNm','prcsFcltCpct', 'displayYear',
- { name: 'areaGroup', header: { text: '처리구역면적(㎢)' }, items: ['prcsZoneArea', 'cbsysPrcsZoneArea', 'sprsysPrcsZoneArea'] }, // name 변경됨
+ { name: 'areaGroup', header: { text: '처리구역면적(㎢)' }, items: ['prcsZoneArea', 'cbsysPrcsZoneArea', 'sprsysPrcsZoneArea'] },
'sprsysVartnRt',
- { name: 'drainGroup', header: { text: '배수설비(개소)' }, items: ['mltvEqpmCnt', 'cbsysMltvEqpmCnt', 'sprsysMltvEqpmCnt'] }, // name 변경됨
+ { name: 'drainGroup', header: { text: '배수설비(개소)' }, items: ['mltvEqpmCnt', 'cbsysMltvEqpmCnt', 'sprsysMltvEqpmCnt'] },
{
name: 'pipeGroup', header: { text: '하수관로' },
items: [
'wholFcltPrlg',
- { name: 'trunkGroup', header: { text: '차집/오수간선(m)' }, items: ['wholIcptgAtlfcPrlg', 'icptgPrlg', 'atlfcsPrlg'] }, // name 변경됨
+ { name: 'trunkGroup', header: { text: '차집/오수간선(m)' }, items: ['wholIcptgAtlfcPrlg', 'icptgPrlg', 'atlfcsPrlg'] },
{
name: 'siteGroup', header: { text: '토실/토구(개소)' },
items: [
@@ -383,18 +388,18 @@ const rawList = [
{ name: 'lyngPrlgSum', fieldName: 'lyngPrlg', header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
{ name: 'dmltnPrlgSum', fieldName: 'dmltnPrlg', header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
- /* 동적 연도 컬럼 (1996년이전[yy30] ~ 2025년[yy1]) */
- ...Array.from({ length: 30 }, (_, i) => {
- const dtoNum = 30 - i; // 30, 29, ..., 1
+
+ ...Array.from({ length: totalCount }, (_, i) => {
+ const dtoNum = totalCount - i;
- const wholField = `yy${dtoNum}PrlgWhol`;
+ const wholField = `yy${dtoNum}PrlgWhol`;
const lyngField = `yy${dtoNum}LyngPrlg`;
const dmltnField = `yy${dtoNum}DmltnPrlg`;
-
+
return [
- { name: `${wholField}_col`, fieldName: wholField, header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
- { name: `${lyngField}_col`, fieldName: lyngField, header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
- { name: `${dmltnField}_col`, fieldName: dmltnField, header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }
+ { name: wholField, fieldName: wholField, header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
+ { name: lyngField, fieldName: lyngField, header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
+ { name: dmltnField, fieldName: dmltnField, header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }
];
}).flat(),
@@ -404,15 +409,17 @@ const rawList = [
'displayYear',
{ name: 'summaryGroup', header: { text: '총계' }, items: ['pipeByPrstWholSum','lyngPrlgSum', 'dmltnPrlgSum'] },
- ...Array.from({ length: 30 }, (_, i) => {
- const dtoNum = 30 - i;
- const yearLabel = i === 0 ? '1996년이전' : `${1995 + i}년`;
- const wholField = `yy${dtoNum}PrlgWhol`;
+ ...Array.from({ length: totalCount }, (_, i) => {
+
+ const dtoNum = totalCount - i;
+ const yearLabel = i === 0
+ ? '1996년이전'
+ : `${startYear + (i - 1)}년`;
return {
name: `group_${yearLabel}`,
header: { text: yearLabel },
- items: [`${wholField}_col`, `yy${dtoNum}LyngPrlg_col`, `yy${dtoNum}DmltnPrlg_col`]
+ items: [`yy${dtoNum}PrlgWhol`, `yy${dtoNum}LyngPrlg`, `yy${dtoNum}DmltnPrlg`]
};
}),
'rmrk'
@@ -512,7 +519,7 @@ const rawList = [
name: 'addrGroup',
header: { text: '행정구역' },
items: [
- { column: 'ctpvNm', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] },
+ { column: 'ctpvNm', footerUserSpans: [{ colspan: 6 }, { colspan: 6 }] },
'sggNm',
'admdstNm',
]
diff --git a/apps/st/src/entities/code/index.ts b/apps/st/src/entities/code/index.ts
index e166e2c29..cd4a3ea80 100644
--- a/apps/st/src/entities/code/index.ts
+++ b/apps/st/src/entities/code/index.ts
@@ -1,6 +1,7 @@
export { CodeApi } from "./api/code-service";
export type { ICode } from "./model/code-interface";
export { CodeSelect } from "./ui/code-select";
+export { CodeSelectWithAll } from "./ui/code-select-with-all";
export { UpCodeApi } from "./api/upcode-service";
export {
diff --git a/apps/st/src/entities/code/ui/code-select-with-all.tsx b/apps/st/src/entities/code/ui/code-select-with-all.tsx
new file mode 100644
index 000000000..b6e62e4f0
--- /dev/null
+++ b/apps/st/src/entities/code/ui/code-select-with-all.tsx
@@ -0,0 +1,48 @@
+import { useQuery } from "@tanstack/react-query";
+import {
+ cn,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@common/components";
+import { ENDPOINT } from "@/shared/config";
+import { CodeApi } from "../api/code-service";
+
+type CodeSelectProps = {
+ upCd: string;
+ placeholder?: string;
+ className?: string;
+} & React.ComponentPropsWithoutRef;
+
+function CodeSelectWithAll({
+ upCd,
+ placeholder = "선택",
+ className,
+ ...props
+}: CodeSelectProps) {
+ const { data: code } = useQuery({
+ queryKey: [ENDPOINT.CMS_SERVICE.CODE, upCd],
+ queryFn: () => CodeApi.getCode(upCd),
+ enabled: !!upCd,
+ });
+
+ return (
+
+ );
+}
+
+export { CodeSelectWithAll };
diff --git a/apps/st/src/entities/statistics/api/general-verification-service.ts b/apps/st/src/entities/statistics/api/general-verification-service.ts
new file mode 100644
index 000000000..e8128ebbe
--- /dev/null
+++ b/apps/st/src/entities/statistics/api/general-verification-service.ts
@@ -0,0 +1,66 @@
+import { stClient } from "@/shared/api/client";
+import { ENDPOINT } from "@/shared/config";
+
+import type {
+ GeneralverificationRequest,
+ GeneralverificationResponse,
+ GeneralverificationService,
+ GeneralverificationInfoResponse,
+ CheckVerificationNameRequest,
+ CheckVerificationNameResponse,
+ UpdateVrfcMasterRequest,
+ UpdateVrfcMasterResponse,
+ RegistVrfcMasterRequest,
+ RegistVrfcMasterResponse,
+ DeleteVrfcMasterResponse,
+} from "../model/general-verification-interface";
+
+export class GeneralverificationApiService implements GeneralverificationService {
+ public async getGeneralverificationList(param: GeneralverificationRequest): Promise {
+ const response = await stClient.get (
+ `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/masters`,
+ { params: param },
+ );
+ return response.data;
+ }
+
+ public async getGeneralverificationInfo(param: string): Promise {
+ const response = await stClient.get (
+ `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/masters/${param}`,
+ );
+ return response.data;
+ }
+
+ public async checkVerificationName(param: CheckVerificationNameRequest): Promise {
+ const response = await stClient.get (
+ `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/check-name`,
+ { params: param },
+ );
+ return response.data;
+ }
+
+ public async UpdateVrfcMaster(param: UpdateVrfcMasterRequest): Promise {
+ const response = await stClient.put (
+ `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/masters/${param.vrfcSn}`,
+ param,
+ );
+ return response.data;
+ }
+
+ public async RegistVrfcMaster(param: RegistVrfcMasterRequest): Promise {
+ const response = await stClient.post (
+ `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/master`,
+ param,
+ );
+ return response.data;
+ }
+
+ public async DeleteVrfcMaster(param: string): Promise {
+ const response = await stClient.delete (
+ `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${param}`,
+ );
+ return response.data;
+ }
+}
+
+export const generalverificationApiService = new GeneralverificationApiService();
\ No newline at end of file
diff --git a/apps/st/src/entities/statistics/model/general-verification-interface.ts b/apps/st/src/entities/statistics/model/general-verification-interface.ts
index c22504827..cebd65e7b 100644
--- a/apps/st/src/entities/statistics/model/general-verification-interface.ts
+++ b/apps/st/src/entities/statistics/model/general-verification-interface.ts
@@ -1,12 +1,89 @@
export interface GeneralverificationList {
- vrfSn: number;
- tabNm: string;
- vrfNm: string;
- vrfClsCd: string;
- statsCd: string;
+ vrfcSn: string;
+ vrfcNm: string;
+ vrfcClsfCd: string;
+ vrfcClsfNm: string;
+ trgtStatsCd: string;
+ trgtStatsNm: string;
+ dcptDigtCnt: number;
+ dcptPrcsMthCd: string;
+ dcptPrcsMthNm: string;
useYn: string;
- rgtrId?: string;
regDt?: string;
- mdfrId?: string;
mdfcnDt?: string;
+ rgtrId?: string;
+ mdfrId?: string;
+}
+
+export interface GeneralverificationRequest {
+ trgtStatsCd?: string;
+ vrfcClsfCd?: string;
+ useYn?: string;
+}
+
+export interface GeneralverificationResponse {
+ status: string;
+ message?: string;
+ data: GeneralverificationList[];
+}
+
+export interface GeneralverificationInfoResponse {
+ status: string;
+ message?: string;
+ data: GeneralverificationList;
+}
+
+export interface CheckVerificationNameRequest {
+ trgtStatsCd: string;
+ vrfcNm: string;
+ vrfcClsfCd: string;
+}
+
+export interface CheckVerificationNameResponse {
+ status: string;
+ message?: string;
+ data: boolean;
+}
+
+export interface UpdateVrfcMasterRequest {
+ vrfcSn?: string | null;
+ vrfcNm?: string | null;
+ vrfcClsfCd?: string | null;
+ dcptDigtCnt?: number | null;
+ dcptPrcsMthCd?: string | null;
+ useYn?: string | null;
+ mdfrId?: string | null;
+}
+
+export interface UpdateVrfcMasterResponse {
+ status: string;
+ message?: string;
+}
+
+export interface RegistVrfcMasterRequest {
+ vrfcNm?: string | null;
+ vrfcClsfCd?: string | null;
+ dcptDigtCnt?: number | null;
+ dcptPrcsMthCd?: string | null;
+ rgtrId?: string | null;
+ trgtStatsCd?: string | null;
+}
+
+export interface RegistVrfcMasterResponse {
+ status: string;
+ message?: string;
+}
+
+export interface DeleteVrfcMasterResponse {
+ status: string;
+ message?: string;
+}
+
+export interface GeneralverificationService {
+ getGeneralverificationList(param: GeneralverificationRequest): Promise;
+ getGeneralverificationInfo(param: string): Promise;
+ checkVerificationName(param: CheckVerificationNameRequest): Promise;
+ UpdateVrfcMaster(param: UpdateVrfcMasterRequest): Promise;
+ RegistVrfcMaster(param: RegistVrfcMasterRequest): Promise;
+ DeleteVrfcMaster(param: string): Promise;
}
\ No newline at end of file
diff --git a/apps/st/src/entities/statistics/model/general-verification-master-submit-schema.ts b/apps/st/src/entities/statistics/model/general-verification-master-submit-schema.ts
new file mode 100644
index 000000000..9118504da
--- /dev/null
+++ b/apps/st/src/entities/statistics/model/general-verification-master-submit-schema.ts
@@ -0,0 +1,28 @@
+import { z } from "zod";
+
+export const generalVerificationMasterSubmitSchema = z
+ .object({
+ vrfcSn: z.string().nullable().optional(),
+ vrfcNm: z.string().min(1, "검증명을 입력하세요"),
+ vrfcClsfCd: z.string().min(1, "검증분류를 선택하세요"),
+ trgtStatsCd: z.string().min(1, "통계항목을 선택하세요"),
+ trgtStatsNm: z.string().nullable().optional(),
+ dcptDigtCnt: z.coerce.number(),
+ dcptPrcsMthCd: z.string().nullable().optional(),
+ useYn: z.string().nullable().optional(),
+ rgtrId: z.string().nullable().optional(),
+ regDt: z.string().nullable().optional(),
+ mdfrId: z.string().nullable().optional(),
+ mdfcnDt: z.string().nullable().optional(),
+ })
+ .refine(
+ (data) => data.dcptDigtCnt === 0 || !!data.dcptPrcsMthCd,
+ {
+ path: ["dcptPrcsMthCd"],
+ message: "소수점 처리방식을 선택하세요",
+ },
+ );
+
+export type GeneralVerificationMasterSubmitFormData = z.infer<
+ typeof generalVerificationMasterSubmitSchema
+>;
\ No newline at end of file
diff --git a/apps/st/src/entities/statistics/model/verification-article-mock.ts b/apps/st/src/entities/statistics/model/verification-article-mock.ts
index 272efeb16..f4986656c 100644
--- a/apps/st/src/entities/statistics/model/verification-article-mock.ts
+++ b/apps/st/src/entities/statistics/model/verification-article-mock.ts
@@ -93,7 +93,7 @@ function formatDateTime(date: Date): string {
/** H-B 탭 목데이터 생성 */
export function createHBArticleMock(count = 10): HBArticleItem[] {
faker.seed(100);
- return Array.from({ length: count }, (_, index) => ({
+ return Array.from({ length: count }, (_, _index) => ({
statsItmNm: faker.helpers.arrayElement(HB_STATS_ITEMS),
vrfcItmNm: faker.helpers.arrayElement(HB_VERIFICATION_ITEMS),
tblNm: faker.helpers.arrayElement(TABLE_NAMES),
@@ -113,7 +113,7 @@ export function createHBArticleMock(count = 10): HBArticleItem[] {
/** I-MR 탭 목데이터 생성 */
export function createIMRArticleMock(count = 10): IMRArticleItem[] {
faker.seed(200);
- return Array.from({ length: count }, (_, index) => ({
+ return Array.from({ length: count }, (_, _index) => ({
statsItmNm: faker.helpers.arrayElement(IMR_STATS_ITEMS),
vrfcNm: faker.helpers.arrayElement(VERIFICATION_NAMES),
tblNm: faker.helpers.arrayElement(TABLE_NAMES),
diff --git a/apps/st/src/shared/config/api.ts b/apps/st/src/shared/config/api.ts
index 8fa5dce05..69cdaaf92 100644
--- a/apps/st/src/shared/config/api.ts
+++ b/apps/st/src/shared/config/api.ts
@@ -71,6 +71,7 @@ export const ENDPOINT = {
INPUT: "/st-input-service/api/v1/input",
REVIEW: "/st-management-service/api/v1/review",
INPUTCOMMON: "/st-input-service/api/v1/common",
+ INPUTVERIFY: "/st-input-service/api/v1/verify",
},
STATVERIFY_SERVICE: {
VRFCTGT_MNG: "/statverify-service/api/v1/vrfc-trgt-mng",