[st] feat: 통계검증리포트 화면 레이아웃 구현

This commit is contained in:
2026-04-16 14:29:44 +09:00
parent b1beff9ef0
commit 4a26dae6b4
6 changed files with 778 additions and 4 deletions
@@ -0,0 +1,361 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { useAlertStore } from "@/shared/lib";
import {
Button,
Label,
LoadingWithAnimation,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import {
statisticsVerificationDetailColumnInfo,
statisticsVerificationSummaryColumnInfo,
} from "../../../entities/statistics/model/statistics-verification-report/statistics-verification-report-columns";
import {
APPROVAL_STATUS_OPTIONS,
DEFAULT_FILTERS,
DEFAULT_SUB_FILTERS,
fetchStatisticsVerificationDetailMock,
fetchStatisticsVerificationSummaryMock,
getSigunguOptions,
getYearOptions,
SIDO_OPTIONS,
STATS_INDICATOR_OPTIONS,
STATUS_OPTIONS,
} from "../../../entities/statistics/model/statistics-verification-report/statistics-verification-report-mock";
import type {
StatisticsVerificationDetailRow,
StatisticsVerificationFilterState,
StatisticsVerificationSubFilterState,
StatisticsVerificationSummaryRow,
} from "../../../entities/statistics/model/statistics-verification-report/statistics-verification-report-types";
import ApproveButton from "@/components/button/ApproveButton";
import ReturnButton from "@/components/button/ReturnButton";
import DataGrid from "@/components/module/DataGrid";
import { toGridRows } from "@/components/util/grid/gridFunction";
function StatisticsVerificationReport() {
const { setMessage: alert } = useAlertStore((state) => state);
const yearOptions = useMemo(() => getYearOptions(), []);
const [filters, setFilters] =
useState<StatisticsVerificationFilterState>(DEFAULT_FILTERS);
const [subFilters, setSubFilters] =
useState<StatisticsVerificationSubFilterState>(DEFAULT_SUB_FILTERS);
const [summaryRows, setSummaryRows] = useState<
StatisticsVerificationSummaryRow[]
>([]);
const [detailRows, setDetailRows] = useState<
StatisticsVerificationDetailRow[]
>([]);
const [summaryGridRows, setSummaryGridRows] = useState<
Record<string, string | number>[]
>([]);
const [detailGridRows, setDetailGridRows] = useState<
Record<string, string | number>[]
>([]);
const [selectedSummaryRow, setSelectedSummaryRow] =
useState<StatisticsVerificationSummaryRow | null>(null);
const [selectedDetailRow, setSelectedDetailRow] =
useState<StatisticsVerificationDetailRow | null>(null);
const [isLoading, setIsLoading] = useState(false);
const sigunguOptions = useMemo(
() => getSigunguOptions(filters.sido),
[filters.sido],
);
useEffect(() => {
void handleSearch(filters, subFilters);
}, []);
async function handleSearch(
nextFilters: StatisticsVerificationFilterState,
nextSubFilters: StatisticsVerificationSubFilterState,
summaryId?: number | null,
) {
try {
setIsLoading(true);
const [summaryData, detailData] = await Promise.all([
fetchStatisticsVerificationSummaryMock(nextFilters),
fetchStatisticsVerificationDetailMock(
nextFilters,
nextSubFilters,
summaryId,
),
]);
setSummaryRows(summaryData);
setDetailRows(detailData);
setSummaryGridRows(toGridRows(summaryData));
setDetailGridRows(toGridRows(detailData));
if (summaryData.length === 0)
alert("조회된 상단 목록 데이터가 없습니다.");
if (detailData.length === 0)
alert("조회된 상세 목록 데이터가 없습니다.");
} catch {
alert("통계검증리포트 조회 중 오류가 발생했습니다.");
} finally {
setIsLoading(false);
}
}
function handleFilterChange<
K extends keyof StatisticsVerificationFilterState,
>(key: K, value: StatisticsVerificationFilterState[K]) {
if (key === "sido") {
setFilters((prev) => ({ ...prev, sido: value, sigungu: "ALL" }));
return;
}
setFilters((prev) => ({ ...prev, [key]: value }));
}
function handleSubFilterChange<
K extends keyof StatisticsVerificationSubFilterState,
>(key: K, value: StatisticsVerificationSubFilterState[K]) {
setSubFilters((prev) => ({ ...prev, [key]: value }));
}
function handleReset() {
setFilters(DEFAULT_FILTERS);
setSubFilters(DEFAULT_SUB_FILTERS);
setSelectedSummaryRow(null);
setSelectedDetailRow(null);
void handleSearch(DEFAULT_FILTERS, DEFAULT_SUB_FILTERS);
}
function handleApprove() {
if (!selectedSummaryRow) {
alert("승인할 상단 목록 행을 먼저 선택해주세요.");
return;
}
alert(
`승인 API TBD: ${selectedSummaryRow.sido} ${selectedSummaryRow.sigungu} (${selectedSummaryRow.reportType})`,
);
}
function handleReject() {
if (!selectedDetailRow) {
alert("반려할 상세 항목 행을 먼저 선택해주세요.");
return;
}
alert(
`반려 API TBD: ${selectedDetailRow.sido} ${selectedDetailRow.sigungu} ${selectedDetailRow.detailItem}`,
);
}
function handleDownload() {
if (detailRows.length === 0) {
alert("다운로드할 상세 목록이 없습니다.");
return;
}
const headers = [
"시도",
"시군구",
"지표",
"세부항목",
"검증날짜",
"오류내역",
"입력값",
"비교값",
"상태",
"예외사유",
"반려의견",
"승인여부",
];
const rows = detailRows.map((row) =>
[
row.sido,
row.sigungu,
row.indicator,
row.detailItem,
row.verifyDate,
row.errorDetail,
String(row.inputValue),
String(row.compareValue),
row.status,
row.exceptionReason,
row.returnOpinion,
row.approvalYn,
].join(","),
);
const csv = [headers.join(","), ...rows].join("\n");
const blob = new Blob([`\uFEFF${csv}`], {
type: "text/csv;charset=utf-8;",
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "statistics-verification-report-detail.csv";
link.click();
URL.revokeObjectURL(url);
}
function handleSummaryRowClick(row: Record<string, string | number>) {
const summaryId = Number(row.summaryId);
const selected = summaryRows.find(
(item) => item.summaryId === summaryId,
);
setSelectedSummaryRow(selected ?? null);
if (!summaryId) return;
void handleSearch(filters, subFilters, summaryId);
}
function handleDetailRowClick(row: Record<string, string | number>) {
const detailId = Number(row.detailId);
const selected = detailRows.find((item) => item.detailId === detailId);
setSelectedDetailRow(selected ?? null);
}
return (
<div className="relative">
<div className="card !py-0">
<div className="grid grid-cols-1 gap-[0.8rem] p-[1.2rem] md:grid-cols-[repeat(4,minmax(0,1fr))_auto]">
<FilterSelect
label="시도"
value={filters.sido}
onChange={(value) => handleFilterChange("sido", value)}
options={SIDO_OPTIONS}
/>
<FilterSelect
label="시군구"
value={filters.sigungu}
onChange={(value) =>
handleFilterChange("sigungu", value)
}
options={sigunguOptions}
/>
<FilterSelect
label="년도"
value={filters.year}
onChange={(value) => handleFilterChange("year", value)}
options={yearOptions}
/>
<FilterSelect
label="통계지표"
value={filters.statsIndicator}
onChange={(value) =>
handleFilterChange("statsIndicator", value)
}
options={STATS_INDICATOR_OPTIONS}
/>
<div className="flex items-end justify-end gap-[0.6rem]">
<Button color="green" onClick={handleReset}>
초기화
</Button>
<Button
onClick={() =>
void handleSearch(filters, subFilters)
}
>
조회
</Button>
</div>
</div>
</div>
<div className="card !py-0">
<div className="m-[0.6rem] flex items-center justify-end">
<ApproveButton approveFunc={handleApprove} />
</div>
</div>
<DataGrid
columnInfo={statisticsVerificationSummaryColumnInfo}
fixedColumns={3}
isExcelImport={false}
onRowClick={handleSummaryRowClick}
rowData={summaryGridRows}
rowStartNum={2}
setRowData={setSummaryGridRows}
showHeaderCheckbox={true}
/>
<div className="card !py-0">
<div className="m-[0.6rem] flex flex-wrap items-end justify-between gap-[0.8rem]">
<div className="grid w-full grid-cols-1 gap-[0.8rem] md:w-auto md:grid-cols-2">
<FilterSelect
label="상태"
value={subFilters.status}
onChange={(value) =>
handleSubFilterChange("status", value)
}
options={STATUS_OPTIONS}
/>
<FilterSelect
label="승인상태"
value={subFilters.approvalStatus}
onChange={(value) =>
handleSubFilterChange("approvalStatus", value)
}
options={APPROVAL_STATUS_OPTIONS}
/>
</div>
<div className="flex items-end justify-end gap-[0.6rem]">
<Button color="gray" onClick={handleDownload}>
다운로드
</Button>
<ReturnButton returnFunc={handleReject} />
</div>
</div>
</div>
<DataGrid
columnInfo={statisticsVerificationDetailColumnInfo}
fixedColumns={4}
isExcelImport={false}
onRowClick={handleDetailRowClick}
rowData={detailGridRows}
rowStartNum={2}
setRowData={setDetailGridRows}
showHeaderCheckbox={true}
/>
{isLoading && (
<div className="[data-theme='black']:bg-transparent absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
</div>
);
}
interface FilterSelectProps {
label: string;
onChange: (value: string) => void;
options: Array<{ label: string; value: string }>;
value: string;
}
function FilterSelect({ label, onChange, options, value }: FilterSelectProps) {
return (
<div className="flex items-center gap-[0.8rem]">
<Label className="min-w-[4rem] text-[1.3rem]">{label}</Label>
<Select value={value} onValueChange={onChange}>
<SelectTrigger className="h-[3.2rem] min-w-[14rem]">
<SelectValue placeholder="전체" />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
);
}
export { StatisticsVerificationReport };
export default StatisticsVerificationReport;
@@ -6,6 +6,7 @@ export interface StatsInputStatusListRequestParams {
sewerBzmnCd?: string;
statsYr?: string;
mode?: string;
statsCd?: string;
}
export interface StatsInputStatusList {
@@ -25,10 +26,14 @@ export interface StatsInputStatusList {
rjctOpnnCn?: string;
}
export type StatsInputStatusListApiResponse = ApiResponse<StatsInputStatusList[]> & {
export type StatsInputStatusListApiResponse = ApiResponse<
StatsInputStatusList[]
> & {
message: string;
}
};
export interface StatisticsInputStatusService {
getStatisticsInputStatusList(param: StatsInputStatusListRequestParams): Promise<StatsInputStatusListApiResponse>;
getStatisticsInputStatusList(
param: StatsInputStatusListRequestParams,
): Promise<StatsInputStatusListApiResponse>;
}
@@ -0,0 +1,71 @@
import type { ColumnInfo } from "@/components/type/DataGrid.type";
const READ_ONLY_STYLE = "rg-cell-readonly";
export const statisticsVerificationSummaryColumnInfo: ColumnInfo = {
columns: [
{ name: "summaryId", fieldName: "summaryId", header: { text: "summaryId" }, visible: false },
{ name: "sido", fieldName: "sido", header: { text: "시도" }, styleName: READ_ONLY_STYLE },
{ name: "sigungu", fieldName: "sigungu", header: { text: "시군구" }, styleName: READ_ONLY_STYLE },
{ name: "reportType", fieldName: "reportType", header: { text: "구분" }, styleName: READ_ONLY_STYLE },
{ name: "progressStatus", fieldName: "progressStatus", header: { text: "진행상태" }, width: 120, styleName: READ_ONLY_STYLE },
{ name: "writerName", fieldName: "writerName", header: { text: "작성자" }, styleName: READ_ONLY_STYLE },
{ name: "writerContact", fieldName: "writerContact", header: { text: "작성자연락처" }, width: 130, styleName: READ_ONLY_STYLE },
{ name: "writerDept", fieldName: "writerDept", header: { text: "부서" }, styleName: READ_ONLY_STYLE },
{ name: "writerPosition", fieldName: "writerPosition", header: { text: "직급" }, styleName: READ_ONLY_STYLE },
{ name: "approverName", fieldName: "approverName", header: { text: "승인자" }, styleName: READ_ONLY_STYLE },
{ name: "approverDept", fieldName: "approverDept", header: { text: "부서" }, styleName: READ_ONLY_STYLE },
{ name: "approverPosition", fieldName: "approverPosition", header: { text: "직급" }, styleName: READ_ONLY_STYLE },
{ name: "returnReason", fieldName: "returnReason", header: { text: "반려사유" }, width: 220, styleName: READ_ONLY_STYLE },
],
columnLayout: [
"summaryId",
"sido",
"sigungu",
"reportType",
"progressStatus",
"writerName",
"writerContact",
"writerDept",
"writerPosition",
"approverName",
"approverDept",
"approverPosition",
"returnReason",
],
};
export const statisticsVerificationDetailColumnInfo: ColumnInfo = {
columns: [
{ name: "detailId", fieldName: "detailId", header: { text: "detailId" }, visible: false },
{ name: "summaryId", fieldName: "summaryId", header: { text: "summaryId" }, visible: false },
{ name: "sido", fieldName: "sido", header: { text: "시도" }, styleName: READ_ONLY_STYLE },
{ name: "sigungu", fieldName: "sigungu", header: { text: "시군구" }, styleName: READ_ONLY_STYLE },
{ name: "indicator", fieldName: "indicator", header: { text: "지표" }, width: 140, styleName: READ_ONLY_STYLE },
{ name: "detailItem", fieldName: "detailItem", header: { text: "세부항목" }, width: 180, styleName: READ_ONLY_STYLE },
{ name: "verifyDate", fieldName: "verifyDate", header: { text: "검증날짜" }, width: 110, styleName: READ_ONLY_STYLE },
{ name: "errorDetail", fieldName: "errorDetail", header: { text: "오류내역" }, width: 200, styleName: READ_ONLY_STYLE },
{ name: "inputValue", fieldName: "inputValue", header: { text: "입력값" }, width: 90, styleName: READ_ONLY_STYLE },
{ name: "compareValue", fieldName: "compareValue", header: { text: "비교값" }, width: 90, styleName: READ_ONLY_STYLE },
{ name: "status", fieldName: "status", header: { text: "상태" }, width: 90, styleName: READ_ONLY_STYLE },
{ name: "exceptionReason", fieldName: "exceptionReason", header: { text: "예외사유" }, width: 170, styleName: READ_ONLY_STYLE },
{ name: "returnOpinion", fieldName: "returnOpinion", header: { text: "반려의견" }, width: 170, styleName: READ_ONLY_STYLE },
{ name: "approvalYn", fieldName: "approvalYn", header: { text: "승인여부" }, width: 90, styleName: READ_ONLY_STYLE },
],
columnLayout: [
"detailId",
"summaryId",
"sido",
"sigungu",
"indicator",
"detailItem",
"verifyDate",
"errorDetail",
"inputValue",
"compareValue",
"status",
"exceptionReason",
"returnOpinion",
"approvalYn",
],
};
@@ -0,0 +1,288 @@
import { fakerKO as faker } from "@faker-js/faker";
import type {
StatisticsVerificationDetailRow,
StatisticsVerificationFilterState,
StatisticsVerificationOption,
StatisticsVerificationSubFilterState,
StatisticsVerificationSummaryRow,
} from "./statistics-verification-report-types";
const NETWORK_DELAY_MS = 300;
export const SIDO_OPTIONS: StatisticsVerificationOption[] = [
{ label: "전체", value: "ALL" },
{ label: "서울특별시", value: "SEOUL" },
{ label: "부산광역시", value: "BUSAN" },
{ label: "대구광역시", value: "DAEGU" },
{ label: "경기도", value: "GYEONGGI" },
];
const SIGUNGU_OPTIONS_BY_SIDO: Record<string, StatisticsVerificationOption[]> =
{
ALL: [{ label: "전체", value: "ALL" }],
SEOUL: [
{ label: "전체", value: "ALL" },
{ label: "중구", value: "SEOUL_JUNGGU" },
{ label: "강남구", value: "SEOUL_GANGNAM" },
],
BUSAN: [
{ label: "전체", value: "ALL" },
{ label: "해운대구", value: "BUSAN_HAEUNDAE" },
{ label: "수영구", value: "BUSAN_SUYEONG" },
],
DAEGU: [
{ label: "전체", value: "ALL" },
{ label: "중구", value: "DAEGU_JUNGGU" },
{ label: "달서구", value: "DAEGU_DALSEO" },
],
GYEONGGI: [
{ label: "전체", value: "ALL" },
{ label: "수원시", value: "GYEONGGI_SUWON" },
{ label: "성남시", value: "GYEONGGI_SEONGNAM" },
],
};
export const STATS_INDICATOR_OPTIONS: StatisticsVerificationOption[] = [
{ label: "통계항목", value: "ALL" },
{ label: "하수도 보급률", value: "SEWERAGE_SUPPLY" },
{ label: "관로 보급 현황", value: "PIPE_STATUS" },
{ label: "분류식 비율", value: "SEPARATED_RATIO" },
];
export const STATUS_OPTIONS: StatisticsVerificationOption[] = [
{ label: "수정완료", value: "DONE" },
{ label: "검토중", value: "REVIEW" },
{ label: "오류", value: "ERROR" },
];
export const APPROVAL_STATUS_OPTIONS: StatisticsVerificationOption[] = [
{ label: "전체", value: "ALL" },
{ label: "승인완료", value: "APPROVED" },
{ label: "반려", value: "REJECTED" },
{ label: "대기", value: "PENDING" },
];
export const DEFAULT_FILTERS: StatisticsVerificationFilterState = {
sido: "ALL",
sigungu: "ALL",
year: "ALL",
statsIndicator: "ALL",
};
export const DEFAULT_SUB_FILTERS: StatisticsVerificationSubFilterState = {
status: "DONE",
approvalStatus: "ALL",
};
export function getYearOptions(): StatisticsVerificationOption[] {
const thisYear = new Date().getFullYear();
return [
{ label: "전체", value: "ALL" },
...Array.from({ length: 5 }, (_, index) => {
const year = String(thisYear - index - 1);
return { label: year, value: year };
}),
];
}
export function getSigunguOptions(
sido: string,
): StatisticsVerificationOption[] {
return SIGUNGU_OPTIONS_BY_SIDO[sido] ?? [];
}
export async function fetchStatisticsVerificationSummaryMock(
filters: StatisticsVerificationFilterState,
): Promise<StatisticsVerificationSummaryRow[]> {
await wait();
faker.seed(20260416);
const rows = Array.from({ length: 12 }, (_, index) => {
const sido = pickSidoName(filters.sido);
const sigungu = pickSigunguName(filters.sido, filters.sigungu);
const progressStatus = faker.helpers.arrayElement([
"승인대기",
"승인완료",
"반려",
]);
return {
summaryId: index + 1,
sido,
sigungu,
reportType: faker.helpers.arrayElement([
"정기보고",
"수정보고",
"보완보고",
]),
progressStatus,
writerName: faker.person.lastName() + faker.person.firstName(),
writerContact: faker.phone.number({ style: "national" }),
writerDept: faker.helpers.arrayElement([
"통계팀",
"하수행정과",
"정책기획과",
]),
writerPosition: faker.helpers.arrayElement([
"주무관",
"팀장",
"과장",
]),
approverName:
progressStatus === "승인대기"
? "-"
: faker.person.lastName() + faker.person.firstName(),
approverDept:
progressStatus === "승인대기"
? "-"
: faker.helpers.arrayElement(["검증운영팀", "하수행정과"]),
approverPosition:
progressStatus === "승인대기"
? "-"
: faker.helpers.arrayElement(["팀장", "과장"]),
returnReason:
progressStatus === "반려"
? faker.helpers.arrayElement([
"오류내역 확인 필요",
"예외사유 보강 필요",
"입력값 검증 재요청",
])
: "-",
};
});
return rows.filter((row) => {
if (filters.sido !== "ALL" && row.sido !== pickSidoName(filters.sido))
return false;
if (filters.sigungu !== "ALL") {
const currentSigungu = pickSigunguName(
filters.sido,
filters.sigungu,
);
if (row.sigungu !== currentSigungu) return false;
}
return true;
});
}
export async function fetchStatisticsVerificationDetailMock(
filters: StatisticsVerificationFilterState,
subFilters: StatisticsVerificationSubFilterState,
selectedSummaryId?: number | null,
): Promise<StatisticsVerificationDetailRow[]> {
await wait();
faker.seed(20260417);
const rows = Array.from({ length: 24 }, (_, index) => {
const approvalYn = faker.helpers.arrayElement(["승인", "미승인"]);
const status = faker.helpers.arrayElement([
"수정완료",
"검토중",
"오류",
]);
const summaryId = faker.number.int({ min: 1, max: 12 });
return {
detailId: index + 1,
summaryId,
sido: pickSidoName(filters.sido),
sigungu: pickSigunguName(filters.sido, filters.sigungu),
indicator: pickIndicator(filters.statsIndicator),
detailItem: faker.helpers.arrayElement([
"처리구역면적",
"처리인구",
"분류식관거연장",
"하수관로보급률",
]),
verifyDate: faker.date
.recent({ days: 20 })
.toISOString()
.slice(0, 10),
errorDetail: faker.helpers.arrayElement([
"전년 대비 편차 초과",
"입력값 누락",
"상호검증 불일치",
]),
inputValue: faker.number.int({ min: 100, max: 5000 }),
compareValue: faker.number.int({ min: 100, max: 5000 }),
status,
exceptionReason:
status === "오류"
? faker.lorem.sentence({ min: 1, max: 2 })
: "-",
returnOpinion:
approvalYn === "미승인"
? faker.helpers.arrayElement([
"산식 재검토 필요",
"근거자료 보강 요청",
"담당자 확인 필요",
])
: "-",
approvalYn,
};
});
return rows.filter((row) => {
if (selectedSummaryId && row.summaryId !== selectedSummaryId)
return false;
if (subFilters.status === "DONE" && row.status !== "수정완료")
return false;
if (subFilters.status === "REVIEW" && row.status !== "검토중")
return false;
if (subFilters.status === "ERROR" && row.status !== "오류")
return false;
if (
subFilters.approvalStatus === "APPROVED" &&
row.approvalYn !== "승인"
)
return false;
if (
subFilters.approvalStatus === "REJECTED" &&
row.approvalYn !== "미승인"
)
return false;
return true;
});
}
function wait() {
return new Promise((resolve) => setTimeout(resolve, NETWORK_DELAY_MS));
}
function pickSidoName(value: string): string {
if (value === "ALL")
return faker.helpers.arrayElement([
"서울특별시",
"부산광역시",
"대구광역시",
"경기도",
]);
const matched = SIDO_OPTIONS.find((option) => option.value === value);
return matched?.label ?? "서울특별시";
}
function pickSigunguName(sido: string, sigungu: string): string {
if (sigungu !== "ALL") {
const options = getSigunguOptions(sido);
const matched = options.find((option) => option.value === sigungu);
if (matched) return matched.label;
}
const options = getSigunguOptions(sido).filter(
(option) => option.value !== "ALL",
);
if (options.length === 0) return "전체";
return faker.helpers.arrayElement(options).label;
}
function pickIndicator(indicator: string): string {
if (indicator === "ALL")
return faker.helpers.arrayElement(
STATS_INDICATOR_OPTIONS.filter((item) => item.value !== "ALL"),
).label;
const matched = STATS_INDICATOR_OPTIONS.find(
(option) => option.value === indicator,
);
return matched?.label ?? "통계항목";
}
@@ -0,0 +1,49 @@
export interface StatisticsVerificationFilterState {
sido: string;
sigungu: string;
year: string;
statsIndicator: string;
}
export interface StatisticsVerificationSubFilterState {
status: string;
approvalStatus: string;
}
export interface StatisticsVerificationOption {
label: string;
value: string;
}
export interface StatisticsVerificationSummaryRow {
summaryId: number;
sido: string;
sigungu: string;
reportType: string;
progressStatus: string;
writerName: string;
writerContact: string;
writerDept: string;
writerPosition: string;
approverName: string;
approverDept: string;
approverPosition: string;
returnReason: string;
}
export interface StatisticsVerificationDetailRow {
detailId: number;
summaryId: number;
sido: string;
sigungu: string;
indicator: string;
detailItem: string;
verifyDate: string;
errorDetail: string;
inputValue: number;
compareValue: number;
status: string;
exceptionReason: string;
returnOpinion: string;
approvalYn: string;
}
+1 -1
View File
@@ -13,4 +13,4 @@ export const REFRESH_TOKEN =
export const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
export const ST_SERVICE_URL =
process.env.NEXT_PUBLIC_ST_SERVICE_URL || "http://localhost:8080";
export const COOKIE_DOMAIN = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;
export const COOKIE_DOMAIN = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;