[st] feat: 입력 저장/제출 로직 수정, 통계입력관리, 검토 반려/제출

This commit is contained in:
2026-05-13 13:18:24 +09:00
parent 0fd2466f67
commit f94b18a64d
26 changed files with 1234 additions and 241 deletions
+29 -13
View File
@@ -918,9 +918,17 @@ const DataGrid = ({
rowIndicator: { visible: false },
});
// footer가 있는 컬럼이 있으면 footer 높이 설정
const hasFooter = columns.some((col) => col.footer);
if (hasFooter) {
const applyFooterSettings = (
targetGridView: typeof gridView,
) => {
const hasFooter = columns.some((col) => col.footer);
if (!hasFooter) return;
targetGridView.setOptions({
summaryMode: "aggregate",
footer: { visible: true },
});
// footer가 배열이거나 multiRow가 true인 footer가 있으면 멀티푸터 설정 (여러 행)
const hasArrayFooter = columns.some(
(col) => col.footer && Array.isArray(col.footer),
@@ -935,23 +943,24 @@ const DataGrid = ({
);
});
if (hasArrayFooter || hasMultiRowFooter) {
// footer 배열의 길이를 확인하여 행 수 결정
const arrayFooterLengths = columns
.filter((col) => col.footer && Array.isArray(col.footer))
.map((col) => (col.footer as unknown[]).length);
const maxFooterLength =
arrayFooterLengths.length > 0
? Math.max(...arrayFooterLengths)
: 2; // 기본값 2
: 2;
const footerHeights = Array.from(
{ length: maxFooterLength },
() => ({ height: 40 }),
);
gridView.setFooters?.(footerHeights);
targetGridView.setFooters?.(footerHeights);
} else {
gridView.footer.height = 40;
targetGridView.footer.height = 40;
}
}
};
applyFooterSettings(gridView);
// 고정 컬럼 설정
if (fixedColumns !== undefined && fixedColumns > 0) {
@@ -960,10 +969,6 @@ const DataGrid = ({
});
}
gridView.setOptions({
summaryMode: "aggregate",
});
// footerUserSpans가 있는 경우 setColumnLayout으로 설정
const checkFooterUserSpans = (layout: unknown[]): boolean => {
return layout.some((item) => {
@@ -986,7 +991,9 @@ const DataGrid = ({
});
};
if (checkFooterUserSpans(columnLayout)) {
const hasFooterUserSpans = checkFooterUserSpans(columnLayout);
if (hasFooterUserSpans) {
// setColumnLayout은 그리드가 완전히 렌더링된 후 호출해야 함
setTimeout(() => {
if (gridRef.current?.gridView && columnLayout) {
@@ -1049,6 +1056,10 @@ const DataGrid = ({
(currentGridView as GridViewWithSetColumns).setColumns?.(
columnsWithFooter,
);
applyFooterSettings(currentGridView);
if (hasFooterUserSpans) {
currentGridView.setColumnLayout?.(columnLayout);
}
// setColumns 호출 후 footer 배열의 valueCallback을 다시 설정
setTimeout(() => {
@@ -1089,6 +1100,11 @@ const DataGrid = ({
);
}
});
applyFooterSettings(currentGridView);
if (hasFooterUserSpans) {
currentGridView.setColumnLayout?.(columnLayout);
}
currentGridView.refresh?.();
}, 50);
} catch (e) {
console.warn("Failed to set columns with footer array:", e);
@@ -109,6 +109,8 @@ export interface VerificationResultDialogProps {
queryContext?: VerificationResultDialogQueryContext | null;
title?: string;
emptyMessage?: string;
/** 예외 제출 API 성공 후 부모(입력 화면)에서 메인 데이터를 다시 조회할 때 사용 */
onExceptionSubmitted?: () => void | Promise<void>;
}
export function VerificationResultDialog({
@@ -117,6 +119,7 @@ export function VerificationResultDialog({
queryContext,
title = "검증 결과",
emptyMessage = "표시할 검증 결과가 없습니다.",
onExceptionSubmitted,
}: VerificationResultDialogProps) {
/** 부모가 매 렌더마다 새 객체를 넘기면 queryContext 참조만 바뀌어 loadInitial이 재실행되므로 필드 값 기준으로 안정화 */
const stableQueryContext = useMemo((): VerificationResultDialogQueryContext | null => {
@@ -385,6 +388,11 @@ export function VerificationResultDialog({
});
if (response.status === "success") {
alert("예외 제출 처리되었습니다.");
try {
await onExceptionSubmitted?.();
} catch {
alert("예외 제출은 완료되었으나 화면 데이터를 다시 불러오지 못했습니다. 조회로 새로고침 해주세요.");
}
onOpenChange(false);
} else {
alert(response.message ?? "예외 제출 처리 중 오류가 발생했습니다.");
@@ -396,9 +404,10 @@ export function VerificationResultDialog({
alert,
imrCheckedIndices,
imrRows,
loadInitial,
normalCheckedIndices,
normalRows,
onExceptionSubmitted,
onOpenChange,
stableQueryContext,
]);
@@ -24,12 +24,12 @@ import { VerificationResultHistoryPagination } from "@/components/module/Verific
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
type VerificationResultGridRow = Record<string, string | number>;
type VerificationHistoryTabKey = "NV" | "IMR" | "HR";
type VerificationHistoryTabKey = "NV" | "IMR" | "HB";
const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string }[] = [
{ value: "NV", label: "일반검증 내역" },
{ value: "IMR", label: "I-MR 검증내역" },
{ value: "HR", label: "HR 검증내역" },
{ value: "HB", label: "HB 검증내역" },
];
function prcsYnCodeToNumber(raw: string | number | undefined): number | undefined {
@@ -90,32 +90,32 @@ export function VerificationResultHistoryDialog({
const [activeTab, setActiveTab] = useState<VerificationHistoryTabKey>("NV");
const [normalEntries, setNormalEntries] = useState<VerificationResultReportItem[]>([]);
const [imrEntries, setImrEntries] = useState<VerificationResultReportItem[]>([]);
const [hrEntries, setHrEntries] = useState<VerificationResultReportItem[]>([]);
const [hbEntries, setHbEntries] = useState<VerificationResultReportItem[]>([]);
const [normalPageIndex, setNormalPageIndex] = useState(0);
const [imrPageIndex, setImrPageIndex] = useState(0);
const [hrPageIndex, setHrPageIndex] = useState(0);
const [hbPageIndex, setHbPageIndex] = useState(0);
const [normalPageCount, setNormalPageCount] = useState(1);
const [imrPageCount, setImrPageCount] = useState(1);
const [hrPageCount, setHrPageCount] = useState(1);
const [hbPageCount, setHbPageCount] = useState(1);
const [normalLoading, setNormalLoading] = useState(false);
const [imrLoading, setImrLoading] = useState(false);
const [hrLoading, setHrLoading] = useState(false);
const [hbLoading, setHbLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const resetState = useCallback(() => {
setActiveTab("NV");
setNormalEntries([]);
setImrEntries([]);
setHrEntries([]);
setHbEntries([]);
setNormalPageIndex(0);
setImrPageIndex(0);
setHrPageIndex(0);
setHbPageIndex(0);
setNormalPageCount(1);
setImrPageCount(1);
setHrPageCount(1);
setHbPageCount(1);
setNormalLoading(false);
setImrLoading(false);
setHrLoading(false);
setHbLoading(false);
setErrorMessage(null);
}, []);
@@ -147,34 +147,34 @@ export function VerificationResultHistoryDialog({
setErrorMessage(null);
setNormalLoading(true);
setImrLoading(true);
setHrLoading(true);
setHbLoading(true);
setNormalPageIndex(0);
setImrPageIndex(0);
setHrPageIndex(0);
setHbPageIndex(0);
try {
const [nvRes, imrRes, hrRes] = await Promise.all([
const [nvRes, imrRes, hbRes] = await Promise.all([
fetchVerificationResultPage("NV", 0),
fetchVerificationResultPage("IMR", 0),
fetchVerificationResultPage("HR", 0),
fetchVerificationResultPage("HB", 0),
]);
setNormalEntries(nvRes?.data?.verifyResultList ?? []);
setImrEntries(imrRes?.data?.verifyResultList ?? []);
setHrEntries(hrRes?.data?.verifyResultList ?? []);
setHbEntries(hbRes?.data?.verifyResultList ?? []);
setNormalPageCount(nvRes?.data?.pageInfo?.totalPages ?? 1);
setImrPageCount(imrRes?.data?.pageInfo?.totalPages ?? 1);
setHrPageCount(hrRes?.data?.pageInfo?.totalPages ?? 1);
setHbPageCount(hbRes?.data?.pageInfo?.totalPages ?? 1);
} catch {
setNormalEntries([]);
setImrEntries([]);
setHrEntries([]);
setHbEntries([]);
setNormalPageCount(1);
setImrPageCount(1);
setHrPageCount(1);
setHbPageCount(1);
setErrorMessage("검증 결과 내역 조회 중 오류가 발생했습니다.");
} finally {
setNormalLoading(false);
setImrLoading(false);
setHrLoading(false);
setHbLoading(false);
}
}, [fetchVerificationResultPage, queryContext]);
@@ -216,23 +216,23 @@ export function VerificationResultHistoryDialog({
[fetchVerificationResultPage, imrPageIndex, queryContext],
);
const handleHrPageChange = useCallback(
const handleHbPageChange = useCallback(
async (pageIndex: number) => {
if (pageIndex === hrPageIndex || !queryContext) return;
if (pageIndex === hbPageIndex || !queryContext) return;
setErrorMessage(null);
setHrLoading(true);
setHbLoading(true);
try {
const response = await fetchVerificationResultPage("HR", pageIndex);
setHrEntries(response?.data?.verifyResultList ?? []);
setHrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
setHrPageIndex(pageIndex);
const response = await fetchVerificationResultPage("HB", pageIndex);
setHbEntries(response?.data?.verifyResultList ?? []);
setHbPageCount(response?.data?.pageInfo?.totalPages ?? 1);
setHbPageIndex(pageIndex);
} catch {
setErrorMessage("HR 검증내역 조회 중 오류가 발생했습니다.");
setErrorMessage("HB 검증내역 조회 중 오류가 발생했습니다.");
} finally {
setHrLoading(false);
setHbLoading(false);
}
},
[fetchVerificationResultPage, hrPageIndex, queryContext],
[fetchVerificationResultPage, hbPageIndex, queryContext],
);
useEffect(() => {
@@ -260,7 +260,7 @@ export function VerificationResultHistoryDialog({
const normalRows = useMemo(() => mapRows(normalEntries), [normalEntries]);
const imrRows = useMemo(() => mapRows(imrEntries), [imrEntries]);
const hrRows = useMemo(() => mapRows(hrEntries), [hrEntries]);
const hbRows = useMemo(() => mapRows(hbEntries), [hbEntries]);
const noopSetRows = ((_: SetStateAction<Record<string, string | number>[]>) => {}) as Dispatch<
SetStateAction<Record<string, string | number>[]>
@@ -332,8 +332,8 @@ export function VerificationResultHistoryDialog({
<TabsContent value="IMR" className="rounded-md border border-border">
{renderGrid(imrRows, imrPageIndex, imrPageCount, handleImrPageChange, imrLoading)}
</TabsContent>
<TabsContent value="HR" className="rounded-md border border-border">
{renderGrid(hrRows, hrPageIndex, hrPageCount, handleHrPageChange, hrLoading)}
<TabsContent value="HB" className="rounded-md border border-border">
{renderGrid(hbRows, hbPageIndex, hbPageCount, handleHbPageChange, hbLoading)}
</TabsContent>
</Tabs>
</div>