Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev
This commit is contained in:
@@ -1,129 +1,4 @@
|
||||
.card {
|
||||
position: relative;
|
||||
margin: 0.6rem;
|
||||
box-sizing: border-box;
|
||||
min-height: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.6rem;
|
||||
font-size: 1.3rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card_width10 {
|
||||
width: calc(10% - 12px);
|
||||
}
|
||||
|
||||
.card_width20 {
|
||||
width: calc(20% - 12px);
|
||||
}
|
||||
|
||||
.card_width25 {
|
||||
width: calc(25% - 12px);
|
||||
}
|
||||
|
||||
.card_width30 {
|
||||
width: calc(30% - 12px);
|
||||
}
|
||||
|
||||
.card_width33 {
|
||||
width: calc(33.333% - 12px);
|
||||
}
|
||||
|
||||
.card_width40 {
|
||||
width: calc(40% - 12px);
|
||||
}
|
||||
|
||||
.card_width45 {
|
||||
width: calc(45% - 12px);
|
||||
}
|
||||
|
||||
.card_width50 {
|
||||
width: calc(50% - 12px);
|
||||
}
|
||||
|
||||
.card_width60 {
|
||||
width: calc(60% - 12px);
|
||||
}
|
||||
|
||||
.card_width66 {
|
||||
width: calc(66.666% - 12px);
|
||||
}
|
||||
|
||||
.card_width70 {
|
||||
width: calc(70% - 12px);
|
||||
}
|
||||
|
||||
.card_width75 {
|
||||
width: calc(75% - 12px);
|
||||
}
|
||||
|
||||
.card_width80 {
|
||||
width: calc(80% - 12px);
|
||||
}
|
||||
|
||||
.card_width90 {
|
||||
width: calc(90% - 12px);
|
||||
}
|
||||
|
||||
.card_width100 {
|
||||
width: calc(100% - 12px);
|
||||
}
|
||||
|
||||
.card_nopadding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.card_nomargin {
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.topline {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.bottomline {
|
||||
border-bottom: 1px solid #e6eef3;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.card-border {
|
||||
border: 1px solid #dbe2e6;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
margin-top: -0.6rem;
|
||||
margin-left: -0.6rem;
|
||||
margin-right: -0.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e1e7eb;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
margin-left: -0.6rem;
|
||||
margin-right: -0.6rem;
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #e1e7eb;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 블랙 테마 */
|
||||
[data-theme="black"] {
|
||||
.card-border {
|
||||
background-color: rgba(255, 255, 255, 0.065);
|
||||
border-color: #38383a;
|
||||
}
|
||||
.card-header {
|
||||
border-bottom-color: #3f4042;
|
||||
}
|
||||
margin: 0.5rem !important;
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
@@ -49,7 +49,9 @@ import type {
|
||||
} from "@/entities/statistics/model/analysis-history-interface";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { CodeApi } from "@/entities/code";
|
||||
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
|
||||
import type { VerificationTargetCodeResponse } from "@/entities/statistics/model/verification-target-management-interface";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
|
||||
type DateRangeValue = {
|
||||
from?: Date;
|
||||
@@ -78,7 +80,7 @@ function getDefaultHistoryDateRange(): DateRangeValue {
|
||||
}
|
||||
|
||||
function getVerificationYearOptions(): string[] {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentYear = dayjs().year();
|
||||
const startYear = 2017;
|
||||
const endYear = currentYear - 1;
|
||||
if (endYear < startYear) return [];
|
||||
@@ -116,6 +118,7 @@ const HbStatisticsVerification = () => {
|
||||
open: false,
|
||||
anlsSn: null,
|
||||
});
|
||||
const [selectedStatsCd, setSelectedStatsCd] = useState<string>("");
|
||||
|
||||
const defaultHistoryDateRange = useMemo(
|
||||
() => getDefaultHistoryDateRange(),
|
||||
@@ -133,14 +136,35 @@ const HbStatisticsVerification = () => {
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const { data: statsItemList } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CODE, "STITEM"],
|
||||
queryFn: async () => {
|
||||
const response = await CodeApi.getCode("STITEM");
|
||||
return response;
|
||||
},
|
||||
const { data: verificationTargetCodes } = useQuery<
|
||||
VerificationTargetCodeResponse[]
|
||||
>({
|
||||
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG, "codes"],
|
||||
queryFn: () =>
|
||||
verificationTargetManagementApi.getVerificationTargetCodes(),
|
||||
});
|
||||
|
||||
const filteredVerificationTargetCodes = useMemo(() => {
|
||||
return verificationTargetCodes?.filter((item) =>
|
||||
selectedStatsCd
|
||||
? item.vrfcTrgtSn.startsWith(selectedStatsCd)
|
||||
: true,
|
||||
);
|
||||
}, [verificationTargetCodes, selectedStatsCd]);
|
||||
|
||||
const allVerificationTargetIds = useMemo(
|
||||
() =>
|
||||
(filteredVerificationTargetCodes ?? []).map(
|
||||
(item) => item.vrfcTrgtSn,
|
||||
),
|
||||
[filteredVerificationTargetCodes],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!allVerificationTargetIds.length) return;
|
||||
setSelectedTargets(allVerificationTargetIds);
|
||||
}, [allVerificationTargetIds]);
|
||||
|
||||
const { data: analysisHistory } = useQuery({
|
||||
queryKey: [
|
||||
ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST,
|
||||
@@ -268,7 +292,9 @@ const HbStatisticsVerification = () => {
|
||||
const payload: AnalysisRequestPayload = {
|
||||
dataset:
|
||||
verifyType === "ALL"
|
||||
? "ALL"
|
||||
? allVerificationTargetIds.length > 0
|
||||
? allVerificationTargetIds.join(",")
|
||||
: undefined
|
||||
: selectedTargets.length > 0
|
||||
? selectedTargets.join(",")
|
||||
: undefined,
|
||||
@@ -329,28 +355,52 @@ const HbStatisticsVerification = () => {
|
||||
<div className="card card-border flex flex-col gap-6">
|
||||
{/* 검증 종류 선택 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4 pl-2">
|
||||
<span className="text-[1.3rem] font-medium text-label">
|
||||
검증 방식
|
||||
</span>
|
||||
<RadioGroup
|
||||
value={verifyType}
|
||||
onValueChange={(value) =>
|
||||
setVerifyType(value as VerifyType)
|
||||
}
|
||||
className="flex items-center gap-4"
|
||||
>
|
||||
<label className="flex cursor-pointer items-center gap-2 text-label">
|
||||
<RadioGroupItem value="ALL" />
|
||||
<span>일괄 검증</span>
|
||||
</label>
|
||||
<label className="flex cursor-pointer items-center gap-2 text-label">
|
||||
<RadioGroupItem value="SELECT" />
|
||||
<span>선택 검증</span>
|
||||
</label>
|
||||
</RadioGroup>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="flex items-center gap-4 pl-2">
|
||||
<Label className="text-[1.3rem] font-medium text-label">
|
||||
검증 년도
|
||||
</Label>
|
||||
<Select
|
||||
value={formState.verificationYear}
|
||||
onValueChange={(value) =>
|
||||
handleFormChange("verificationYear", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[10rem] flex-1">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{yearOptions.map((year) => (
|
||||
<SelectItem key={year} value={year}>
|
||||
{year}년
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 pl-2">
|
||||
<Label className="text-[1.3rem] font-medium text-label">
|
||||
검증 방식
|
||||
</Label>
|
||||
<RadioGroup
|
||||
value={verifyType}
|
||||
onValueChange={(value) =>
|
||||
setVerifyType(value as VerifyType)
|
||||
}
|
||||
className="flex items-center gap-4"
|
||||
>
|
||||
<label className="flex cursor-pointer items-center gap-2 text-label">
|
||||
<RadioGroupItem value="ALL" />
|
||||
<span>일괄 검증</span>
|
||||
</label>
|
||||
<label className="flex cursor-pointer items-center gap-2 text-label">
|
||||
<RadioGroupItem value="SELECT" />
|
||||
<span>선택 검증</span>
|
||||
</label>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center gap-4">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
color="green"
|
||||
className="h-[3.6rem] w-[120px]"
|
||||
@@ -378,38 +428,65 @@ const HbStatisticsVerification = () => {
|
||||
<h4 className="mb-3 rounded bg-gray-100 px-4 py-2 text-[1.4rem] font-medium">
|
||||
검증 대상
|
||||
</h4>
|
||||
|
||||
<div className="space-y-4 p-2">
|
||||
<div className="mb-3 flex items-center gap-4 px-4">
|
||||
<Label className="text-[1.3rem] font-medium text-label">
|
||||
통계항목
|
||||
</Label>
|
||||
<CodeSelect
|
||||
upCd="STITEM"
|
||||
onValueChange={setSelectedStatsCd}
|
||||
className="w-[24rem]"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
color="gray"
|
||||
className="h-[3.2rem] px-4 text-[1.3rem]"
|
||||
onClick={() => setSelectedStatsCd("")}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</div>
|
||||
<div className="max-h-[16rem] space-y-4 overflow-auto rounded-md border p-4">
|
||||
{verifyType === "SELECT" &&
|
||||
statsItemList &&
|
||||
statsItemList.length > 0 && (
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{statsItemList.map((item) => (
|
||||
filteredVerificationTargetCodes &&
|
||||
filteredVerificationTargetCodes.length > 0 ? (
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{filteredVerificationTargetCodes.map(
|
||||
(item) => (
|
||||
<div
|
||||
key={item.lwpoCd}
|
||||
key={item.vrfcTrgtSn}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Checkbox
|
||||
id={item.lwpoCd}
|
||||
id={item.vrfcTrgtSn}
|
||||
checked={selectedTargets.includes(
|
||||
item.lwpoCd,
|
||||
item.vrfcTrgtSn,
|
||||
)}
|
||||
onCheckedChange={() =>
|
||||
handleTargetToggle(
|
||||
item.lwpoCd,
|
||||
item.vrfcTrgtSn,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={item.lwpoCd}
|
||||
htmlFor={
|
||||
item.vrfcTrgtSn
|
||||
}
|
||||
className="cursor-pointer text-label"
|
||||
>
|
||||
{item.lwpoCdNm}
|
||||
{item.vrfcTrgtNm}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<p className="text-[1.3rem] text-muted-foreground">
|
||||
검증 대상이 없습니다.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* //검증 대상 */}
|
||||
@@ -482,35 +559,6 @@ const HbStatisticsVerification = () => {
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Label className="w-32 text-label">
|
||||
검증 년도
|
||||
</Label>
|
||||
<Select
|
||||
value={formState.verificationYear}
|
||||
onValueChange={(value) =>
|
||||
handleFormChange(
|
||||
"verificationYear",
|
||||
value,
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{yearOptions.map((year) => (
|
||||
<SelectItem
|
||||
key={year}
|
||||
value={year}
|
||||
>
|
||||
{year}년
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* //검증 변수 */}
|
||||
@@ -518,22 +566,24 @@ const HbStatisticsVerification = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-10 px-4 pb-8">
|
||||
<h3 className="mb-2 px-2 text-[1.5rem] font-semibold text-title">
|
||||
<div className="mt-10 px-2 pb-8">
|
||||
<h3 className="mb-4 text-[1.6rem] font-semibold text-title">
|
||||
검증 이력
|
||||
</h3>
|
||||
<div className="mb-4 flex items-center gap-4 px-2">
|
||||
<span className="text-[1.3rem] font-medium text-label">
|
||||
검증일시
|
||||
</span>
|
||||
<DateRangePicker
|
||||
value={historyDateRange as any}
|
||||
defaultValue={defaultHistoryDateRange as any}
|
||||
onValueChange={(value) =>
|
||||
setHistoryDateRange((value as any) ?? undefined)
|
||||
}
|
||||
className="max-w-[26rem]"
|
||||
/>
|
||||
<div className="card card-border !m-0 !mb-2 flex justify-between">
|
||||
<div className="flex items-center gap-4 px-2">
|
||||
<span className="text-[1.3rem] font-medium text-label">
|
||||
검증일시
|
||||
</span>
|
||||
<DateRangePicker
|
||||
value={historyDateRange as any}
|
||||
defaultValue={defaultHistoryDateRange as any}
|
||||
onValueChange={(value) =>
|
||||
setHistoryDateRange((value as any) ?? undefined)
|
||||
}
|
||||
className="max-w-[26rem]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -590,57 +640,58 @@ const HbStatisticsVerification = () => {
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead
|
||||
colSpan={4}
|
||||
colSpan={5}
|
||||
className="bg-muted text-left text-[1.3rem] font-semibold"
|
||||
>
|
||||
{item.vrfcTrgtNm}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead className="bg-muted font-semibold">
|
||||
시도
|
||||
</TableHead>
|
||||
<TableHead className="bg-muted font-semibold">
|
||||
시군구
|
||||
</TableHead>
|
||||
<TableHead className="bg-muted font-semibold">
|
||||
행정구역
|
||||
</TableHead>
|
||||
<TableHead className="bg-muted font-semibold">
|
||||
시설명
|
||||
</TableHead>
|
||||
<TableHead className="bg-muted text-center font-semibold">
|
||||
입력값
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{Array.from({
|
||||
length: Math.ceil(
|
||||
item.outlierDataList
|
||||
.length / 4,
|
||||
{item.outlierDataList.map(
|
||||
(data) => (
|
||||
<TableRow key={data.rsltSn}>
|
||||
<TableCell className="text-center">
|
||||
{data.sido ?? "-"}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{data.gugun ?? "-"}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{data.adstDong ??
|
||||
"-"}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{/* 시설명: DTO에 해당 필드 없음 */}
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{data.cryrVl != null
|
||||
? String(
|
||||
data.cryrVl,
|
||||
)
|
||||
: "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
}).map((_, rowIdx) => (
|
||||
<TableRow key={rowIdx}>
|
||||
{item.outlierDataList
|
||||
.slice(
|
||||
rowIdx * 4,
|
||||
rowIdx * 4 + 4,
|
||||
)
|
||||
.map((data) => (
|
||||
<TableCell
|
||||
key={
|
||||
data.rsltSn
|
||||
}
|
||||
className="text-[1.25rem]"
|
||||
>
|
||||
{`${data.sido ?? ""}${data.adstDong ? ` ${data.adstDong}` : ""}`.trim() ||
|
||||
"-"}
|
||||
</TableCell>
|
||||
))}
|
||||
{/* 빈 셀 추가 */}
|
||||
{item.outlierDataList.slice(
|
||||
rowIdx * 4,
|
||||
rowIdx * 4 + 4,
|
||||
).length < 4 &&
|
||||
Array.from({
|
||||
length:
|
||||
4 -
|
||||
item.outlierDataList.slice(
|
||||
rowIdx * 4,
|
||||
rowIdx * 4 +
|
||||
4,
|
||||
).length,
|
||||
}).map((_, idx) => (
|
||||
<TableCell
|
||||
key={`empty-${idx}`}
|
||||
/>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
VerificationTargetCodeResponse,
|
||||
VerificationTargetManagementService,
|
||||
VerificationTargetManagementSearchParams,
|
||||
VerificationTargetManagementListApiResponse,
|
||||
@@ -43,6 +44,18 @@ export class VerificationTargetManagementApiService implements VerificationTarge
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 대상 코드 목록 조회
|
||||
*/
|
||||
public async getVerificationTargetCodes(): Promise<
|
||||
VerificationTargetCodeResponse[]
|
||||
> {
|
||||
const response = await client.get<VerificationTargetCodeResponse[]>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG}/code`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 대상 등록
|
||||
* @param request 등록 요청 데이터
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { PageableList } from "@/shared/model";
|
||||
import type { ApiResponse } from "./statistics-interface";
|
||||
|
||||
// 검증 대상 코드 응답 DTO
|
||||
export interface VerificationTargetCodeResponse {
|
||||
vrfcTrgtSn: string;
|
||||
vrfcTrgtNm: string;
|
||||
}
|
||||
|
||||
// 검증 대상 관리 응답 DTO
|
||||
export interface VerificationTargetManagementResponse {
|
||||
vrfcTrgtSn: number; // 검증 대상 일련번호
|
||||
@@ -90,6 +96,8 @@ export interface VerificationTargetManagementService {
|
||||
vrfcTrgtSn: number,
|
||||
): Promise<VerificationTargetManagementDetailApiResponse>;
|
||||
|
||||
getVerificationTargetCodes(): Promise<VerificationTargetCodeResponse[]>;
|
||||
|
||||
saveVerificationTargetManagement(
|
||||
request: VerificationTargetManagementSaveRequest,
|
||||
): Promise<VerificationTargetManagementSaveApiResponse>;
|
||||
|
||||
Reference in New Issue
Block a user