[st] feat: 검증 항목 상세 다이얼로그 작업
This commit is contained in:
@@ -1087,10 +1087,10 @@ const DataGrid = ({
|
||||
) {
|
||||
try {
|
||||
// 행 전체 데이터 가져오기
|
||||
const rowData = grid.getValues(clickData.itemIndex);
|
||||
const row = rowData[clickData.itemIndex];
|
||||
|
||||
// 부모 컴포넌트로 데이터 전달
|
||||
onRowClick(rowData as Record<string, string | number>);
|
||||
onRowClick(row ?? {});
|
||||
} catch (error) {
|
||||
console.error("행 데이터 가져오기 오류:", error);
|
||||
}
|
||||
|
||||
@@ -17,10 +17,15 @@ import {
|
||||
} from "@common/components";
|
||||
import { CodeSelect } from "@/entities/code/ui/code-select";
|
||||
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { VerificationTargetManagementSearchParams } from "@/entities/statistics/model/verification-target-management-interface";
|
||||
import type {
|
||||
VerificationTargetManagementSearchParams,
|
||||
VerificationTargetManagementResponse,
|
||||
} from "@/entities/statistics/model/verification-target-management-interface";
|
||||
import { VerificationTargetDialog } from "./VerificationTargetDialog";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
const TAB_CONFIG = {
|
||||
HB: { value: "HB", label: "H-B", columnInfo: hbArticleColumnInfo },
|
||||
@@ -35,6 +40,8 @@ const TAB_CONFIG = {
|
||||
type TabValue = keyof typeof TAB_CONFIG;
|
||||
|
||||
function VerificationArticleManagement() {
|
||||
const queryClient = useQueryClient();
|
||||
const { confirm } = useConfirm();
|
||||
const [activeTab, setActiveTab] = useState<TabValue>("HB");
|
||||
const [statsCd, setStatsCd] = useState("");
|
||||
const [vrfcTrgtNm, setVrfcTrgtNm] = useState("");
|
||||
@@ -44,6 +51,9 @@ function VerificationArticleManagement() {
|
||||
statsCd,
|
||||
vrfcTrgtNm,
|
||||
});
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [selectedData, setSelectedData] =
|
||||
useState<VerificationTargetManagementResponse | null>(null);
|
||||
|
||||
const { data: verificationData } = useQuery({
|
||||
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG, searchParams],
|
||||
@@ -94,8 +104,51 @@ function VerificationArticleManagement() {
|
||||
});
|
||||
}
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: VerificationTargetManagementResponse) =>
|
||||
verificationTargetManagementApi.updateVerificationTargetManagement(
|
||||
data.vrfcTrgtSn,
|
||||
{
|
||||
vrfcMthdCd: data.vrfcMthdCd,
|
||||
statsCd: data.statsCd,
|
||||
schmNm: data.schmNm,
|
||||
tblNm: data.tblNm,
|
||||
colNm: data.colNm,
|
||||
pkColNm: data.pkColNm,
|
||||
ipcrCntVl: data.ipcrCntVl,
|
||||
pctlCrtrVl: data.pctlCrtrVl,
|
||||
scopCntCntVl: data.scopCntCntVl,
|
||||
thrsCntVl: data.thrsCntVl,
|
||||
useYn: data.useYn,
|
||||
vrfcTrgtNm: data.vrfcTrgtNm,
|
||||
},
|
||||
),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG],
|
||||
});
|
||||
setDialogOpen(false);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (vrfcTrgtSn: number) =>
|
||||
verificationTargetManagementApi.deleteVerificationTargetManagement(
|
||||
vrfcTrgtSn,
|
||||
),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.STATVERIFY_SERVICE.VRFCTGT_MNG],
|
||||
});
|
||||
setDialogOpen(false);
|
||||
},
|
||||
});
|
||||
|
||||
function handleRowClick(rowData: Record<string, string | number>) {
|
||||
console.log("클릭한 행 데이터:", rowData);
|
||||
setSelectedData(
|
||||
rowData as unknown as VerificationTargetManagementResponse,
|
||||
);
|
||||
setDialogOpen(true);
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
@@ -103,6 +156,18 @@ function VerificationArticleManagement() {
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
async function handleSave(data: VerificationTargetManagementResponse) {
|
||||
if (await confirm("저장하시겠습니까?")) {
|
||||
updateMutation.mutate(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(vrfcTrgtSn: number) {
|
||||
if (await confirm("정말 삭제하시겠습니까?")) {
|
||||
deleteMutation.mutate(vrfcTrgtSn);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue="HB"
|
||||
@@ -132,7 +197,7 @@ function VerificationArticleManagement() {
|
||||
upCd="STITEM"
|
||||
value={statsCd}
|
||||
onValueChange={setStatsCd}
|
||||
placeholder="통계항목"
|
||||
placeholder="선택"
|
||||
className="w-[16rem]"
|
||||
/>
|
||||
</div>
|
||||
@@ -181,6 +246,14 @@ function VerificationArticleManagement() {
|
||||
/>
|
||||
</TabsContent>
|
||||
))}
|
||||
|
||||
<VerificationTargetDialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
data={selectedData}
|
||||
onSave={handleSave}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Input,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
import { CodeSelect } from "@/entities/code/ui/code-select";
|
||||
import type { VerificationTargetManagementResponse } from "@/entities/statistics/model/verification-target-management-interface";
|
||||
import {
|
||||
verificationTargetManagementSchema,
|
||||
type VerificationTargetManagementFormData,
|
||||
} from "@/entities/statistics/model/verification-target-management-schema";
|
||||
import { useEffect } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
interface VerificationTargetDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
data: VerificationTargetManagementResponse | null;
|
||||
onSave: (data: VerificationTargetManagementResponse) => void;
|
||||
onDelete: (vrfcTrgtSn: number) => void;
|
||||
}
|
||||
|
||||
export function VerificationTargetDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
data,
|
||||
onSave,
|
||||
onDelete,
|
||||
}: VerificationTargetDialogProps) {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm<VerificationTargetManagementFormData>({
|
||||
resolver: zodResolver(verificationTargetManagementSchema),
|
||||
defaultValues: data || undefined,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data && open) reset(data);
|
||||
}, [data, open]);
|
||||
|
||||
function onSubmit(formData: VerificationTargetManagementFormData) {
|
||||
if (!data) return;
|
||||
onSave({
|
||||
...data,
|
||||
...formData,
|
||||
} as VerificationTargetManagementResponse);
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
if (data?.vrfcTrgtSn) {
|
||||
onDelete(data.vrfcTrgtSn);
|
||||
}
|
||||
}
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[800px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>검증 항목 상세</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Table variant="secondary">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead
|
||||
className="w-[150px] bg-muted font-medium"
|
||||
data-required
|
||||
>
|
||||
통계항목
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="statsCd"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="space-y-1">
|
||||
<CodeSelect
|
||||
upCd="STITEM"
|
||||
value={field.value}
|
||||
onValueChange={
|
||||
field.onChange
|
||||
}
|
||||
placeholder="선택"
|
||||
/>
|
||||
{errors.statsCd && (
|
||||
<p className="text-xs text-red-500">
|
||||
{errors.statsCd.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead
|
||||
className="w-[150px] bg-muted font-medium"
|
||||
data-required
|
||||
>
|
||||
검증명
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="vrfcTrgtNm"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
placeholder="검증명"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableHead
|
||||
className="bg-muted font-medium"
|
||||
data-required
|
||||
>
|
||||
스키마명
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="schmNm"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="스키마명"
|
||||
/>
|
||||
{errors.schmNm && (
|
||||
<p className="text-xs text-red-500">
|
||||
{errors.schmNm.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead
|
||||
className="bg-muted font-medium"
|
||||
data-required
|
||||
>
|
||||
테이블명
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="tblNm"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="테이블명"
|
||||
/>
|
||||
{errors.tblNm && (
|
||||
<p className="text-xs text-red-500">
|
||||
{errors.tblNm.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableHead
|
||||
className="bg-muted font-medium"
|
||||
data-required
|
||||
>
|
||||
컬럼명
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="colNm"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="컬럼명"
|
||||
/>
|
||||
{errors.colNm && (
|
||||
<p className="text-xs text-red-500">
|
||||
{errors.colNm.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead
|
||||
className="bg-muted font-medium"
|
||||
data-required
|
||||
>
|
||||
PK 키
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="pkColNm"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="PK 키"
|
||||
/>
|
||||
{errors.pkColNm && (
|
||||
<p className="text-xs text-red-500">
|
||||
{errors.pkColNm.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableHead className="bg-muted font-medium">
|
||||
중요도 계수(μ)
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="ipcrCntVl"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
type="number"
|
||||
step="0.1"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value
|
||||
? Number(
|
||||
e.target
|
||||
.value,
|
||||
)
|
||||
: null,
|
||||
)
|
||||
}
|
||||
placeholder="(0 ~ 1)"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead className="bg-muted font-medium">
|
||||
백분위수 기준값(pct)
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="pctlCrtrVl"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
type="number"
|
||||
step="0.1"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value
|
||||
? Number(
|
||||
e.target
|
||||
.value,
|
||||
)
|
||||
: null,
|
||||
)
|
||||
}
|
||||
placeholder="(0 ~ 0.5)"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableHead className="bg-muted font-medium">
|
||||
스케일 조정 계수(A)
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="scopCntCntVl"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="number"
|
||||
step="0.05"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value
|
||||
? Number(
|
||||
e.target
|
||||
.value,
|
||||
)
|
||||
: null,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead className="bg-muted font-medium">
|
||||
임계 계수(c)
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="thrsCntVl"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="number"
|
||||
step="1"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value
|
||||
? Number(
|
||||
e.target
|
||||
.value,
|
||||
)
|
||||
: null,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<DialogFooter className="mt-6">
|
||||
<Button
|
||||
type="button"
|
||||
color="gray"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="red"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
삭제
|
||||
</Button>
|
||||
<Button type="submit" color="green">
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -12,10 +12,10 @@ export interface VerificationTargetManagementResponse {
|
||||
tblNm: string; // 테이블 명
|
||||
colNm: string; // 컬럼 명
|
||||
pkColNm: string; // pk 컬럼 명
|
||||
ipcrCntVl: number | null; // 중요도 개수 값 (mu)
|
||||
ipcrCntVl: number | null; // 중요도 계수 값 (mu)
|
||||
pctlCrtrVl: number | null; // 백분위 기준 값 (percentile)
|
||||
scopCntCntVl: number | null; // 범위 조정 개수 값 (A)
|
||||
thrsCntVl: number | null; // 임계 개수 값 (C)
|
||||
scopCntCntVl: number | null; // 범위 조정 계수 값 (A)
|
||||
thrsCntVl: number | null; // 임계 계수 값 (C)
|
||||
useYn: string; // 사용 여부
|
||||
vrfcTrgtNm: string; // 검증대상명
|
||||
rgtr: string | null; // 등록자 ID
|
||||
@@ -47,10 +47,10 @@ export interface VerificationTargetManagementSaveRequest {
|
||||
tblNm: string; // 테이블 명 (필수, 최대 150자)
|
||||
colNm: string; // 컬럼 명 (필수, 최대 150자)
|
||||
pkColNm: string; // pk 컬럼 명 (필수, 최대 150자)
|
||||
ipcrCntVl?: number | null; // 중요도 개수 값 (mu)
|
||||
ipcrCntVl?: number | null; // 중요도 계수 값 (mu)
|
||||
pctlCrtrVl?: number | null; // 백분위 기준 값 (percentile)
|
||||
scopCntCntVl?: number | null; // 범위 조정 개수 값 (A)
|
||||
thrsCntVl?: number | null; // 임계 개수 값 (C)
|
||||
scopCntCntVl?: number | null; // 범위 조정 계수 값 (A)
|
||||
thrsCntVl?: number | null; // 임계 계수 값 (C)
|
||||
useYn?: string; // 사용 여부 (Y/N, 최대 1자)
|
||||
vrfcTrgtNm?: string; // 검증대상명 (최대 150자)
|
||||
}
|
||||
@@ -63,10 +63,10 @@ export interface VerificationTargetManagementUpdateRequest {
|
||||
tblNm: string; // 테이블 명 (필수, 최대 150자)
|
||||
colNm: string; // 컬럼 명 (필수, 최대 150자)
|
||||
pkColNm: string; // pk 컬럼 명 (필수, 최대 150자)
|
||||
ipcrCntVl?: number | null; // 중요도 개수 값 (mu)
|
||||
ipcrCntVl?: number | null; // 중요도 계수 값 (mu)
|
||||
pctlCrtrVl?: number | null; // 백분위 기준 값 (percentile)
|
||||
scopCntCntVl?: number | null; // 범위 조정 개수 값 (A)
|
||||
thrsCntVl?: number | null; // 임계 개수 값 (C)
|
||||
scopCntCntVl?: number | null; // 범위 조정 계수 값 (A)
|
||||
thrsCntVl?: number | null; // 임계 계수 값 (C)
|
||||
useYn?: string; // 사용 여부 (Y/N, 최대 1자)
|
||||
vrfcTrgtNm?: string; // 검증대상명 (최대 150자)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const verificationTargetManagementSchema = z.object({
|
||||
vrfcTrgtSn: z.number().optional(),
|
||||
vrfcMthdCd: z.string().optional(),
|
||||
vrfcMthdNm: z.string().optional().nullable(),
|
||||
statsCd: z.string().min(1, "통계항목을 선택해주세요"),
|
||||
statsNm: z.string().optional().nullable(),
|
||||
schmNm: z.string().min(1, "스키마명을 입력해주세요"),
|
||||
tblNm: z.string().min(1, "테이블명을 입력해주세요"),
|
||||
colNm: z.string().min(1, "컬럼명을 입력해주세요"),
|
||||
pkColNm: z.string().min(1, "PK 키를 입력해주세요"),
|
||||
ipcrCntVl: z.number().optional().nullable(),
|
||||
pctlCrtrVl: z.number().optional().nullable(),
|
||||
scopCntCntVl: z.number().optional().nullable(),
|
||||
thrsCntVl: z.number().optional().nullable(),
|
||||
useYn: z.string().optional(),
|
||||
vrfcTrgtNm: z.string().optional().nullable(),
|
||||
rgtr: z.string().optional().nullable(),
|
||||
regDt: z.string().optional().nullable(),
|
||||
mdfr: z.string().optional().nullable(),
|
||||
rgtrNm: z.string().optional().nullable(),
|
||||
mdfrNm: z.string().optional().nullable(),
|
||||
mdfcn: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
export type VerificationTargetManagementFormData = z.infer<
|
||||
typeof verificationTargetManagementSchema
|
||||
>;
|
||||
Reference in New Issue
Block a user