[ST] - fix: 입력: 새로고침 시 열려있는 모든 텝의 공통 api가 실행되는 현상 수정, 하수도보급률 저장&제출 request 수정(외국인총인구 제거, 총면적 추가), 검증 툴팁 max-width 추가, 지난 검증 결과 및 제출 결과 데이터 마우스 툴팁 추가
This commit is contained in:
@@ -86,6 +86,40 @@ function mergeGridRowsWithSourceMetadata(
|
||||
});
|
||||
}
|
||||
|
||||
/** row.verificationResult 필드별 오류 배열이 하나라도 있으면 true */
|
||||
function rowHasVerificationError(
|
||||
row: Record<string, string | number>,
|
||||
): boolean {
|
||||
const verification = (
|
||||
row as { verificationResult?: Record<string, unknown> }
|
||||
).verificationResult;
|
||||
if (!verification || typeof verification !== "object") return false;
|
||||
for (const value of Object.values(verification)) {
|
||||
if (Array.isArray(value) && value.length > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function rowsHaveVerificationErrors(
|
||||
rows: Record<string, string | number>[],
|
||||
): boolean {
|
||||
return rows.some(rowHasVerificationError);
|
||||
}
|
||||
|
||||
function sortRowsVerifyErrorsFirst(
|
||||
rows: Record<string, string | number>[],
|
||||
): Record<string, string | number>[] {
|
||||
return rows
|
||||
.map((row, index) => ({ row, index }))
|
||||
.sort((a, b) => {
|
||||
const aErr = rowHasVerificationError(a.row) ? 0 : 1;
|
||||
const bErr = rowHasVerificationError(b.row) ? 0 : 1;
|
||||
if (aErr !== bErr) return aErr - bErr;
|
||||
return a.index - b.index;
|
||||
})
|
||||
.map(({ row }) => row);
|
||||
}
|
||||
|
||||
function applyCellStyleCallbacksToColumns(
|
||||
gridView: unknown,
|
||||
columns: ColumnConfig[],
|
||||
@@ -804,6 +838,12 @@ const DataGrid = ({
|
||||
const emptyCellHoverRafRef = useRef<number | null>(null);
|
||||
const [emptyCellHoverTooltip, setEmptyCellHoverTooltip] =
|
||||
useState<EmptyCellHoverTooltip | null>(null);
|
||||
/** 검증 오류 행 우선보기 (오류 있는 데이터일 때만 UI 노출) */
|
||||
const [preferVerifyErrors, setPreferVerifyErrors] = useState(false);
|
||||
const verifyPriorityBaselineRef = useRef<
|
||||
Record<string, string | number>[] | null
|
||||
>(null);
|
||||
const skipVerifyPriorityResetRef = useRef(false);
|
||||
columnsRef.current = columns;
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
@@ -814,6 +854,90 @@ const DataGrid = ({
|
||||
onCellEditedRef.current = onCellEdited;
|
||||
onDataCellClickRef.current = onDataCellClick;
|
||||
|
||||
const hasVerificationErrors = rowsHaveVerificationErrors(rowData);
|
||||
const showVerifyPriorityToggle = hasVerificationErrors;
|
||||
|
||||
// 조회 등으로 rowData가 교체되면 우선보기 해제 (자체 setRowData는 스킵)
|
||||
useEffect(() => {
|
||||
if (skipVerifyPriorityResetRef.current) {
|
||||
skipVerifyPriorityResetRef.current = false;
|
||||
return;
|
||||
}
|
||||
verifyPriorityBaselineRef.current = null;
|
||||
setPreferVerifyErrors(false);
|
||||
}, [rowData]);
|
||||
|
||||
// 오류가 없어지면 체크 해제 + 원본 순서 복원
|
||||
useEffect(() => {
|
||||
if (hasVerificationErrors || !preferVerifyErrors) return;
|
||||
const baseline = verifyPriorityBaselineRef.current;
|
||||
verifyPriorityBaselineRef.current = null;
|
||||
setPreferVerifyErrors(false);
|
||||
if (baseline) {
|
||||
skipVerifyPriorityResetRef.current = true;
|
||||
setRowData(baseline);
|
||||
}
|
||||
}, [hasVerificationErrors, preferVerifyErrors, setRowData]);
|
||||
|
||||
const collectCurrentRowsWithMetadata = () => {
|
||||
const fieldNames = columns
|
||||
.map((c) => c.fieldName)
|
||||
.filter(Boolean) as string[];
|
||||
const gv = gridRef.current?.gridView as
|
||||
| {
|
||||
getDataSource?: () => {
|
||||
getRowCount(): number;
|
||||
getValue(r: number, f: string): unknown;
|
||||
};
|
||||
commit?: (force?: boolean) => boolean;
|
||||
}
|
||||
| undefined;
|
||||
if (!gv?.getDataSource) {
|
||||
return rowDataRef.current.map((r) => ({ ...r }));
|
||||
}
|
||||
try {
|
||||
gv.commit?.();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const ds = gv.getDataSource();
|
||||
if (!ds) return rowDataRef.current.map((r) => ({ ...r }));
|
||||
const cnt = ds.getRowCount();
|
||||
const rows: Record<string, string | number>[] = [];
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const r: Record<string, string | number> = {};
|
||||
for (const f of fieldNames)
|
||||
r[f] = ds.getValue(i, f) as string | number;
|
||||
rows.push(r);
|
||||
}
|
||||
return mergeGridRowsWithSourceMetadata(
|
||||
rows,
|
||||
rowDataRef.current,
|
||||
fieldNames,
|
||||
);
|
||||
};
|
||||
|
||||
const handlePreferVerifyErrorsChange = (checked: boolean) => {
|
||||
if (checked) {
|
||||
const current = collectCurrentRowsWithMetadata();
|
||||
if (!rowsHaveVerificationErrors(current)) return;
|
||||
verifyPriorityBaselineRef.current = current.map((r) => ({ ...r }));
|
||||
sortDirRef.current = {};
|
||||
originalRowsRef.current = {};
|
||||
skipVerifyPriorityResetRef.current = true;
|
||||
setPreferVerifyErrors(true);
|
||||
setRowData(sortRowsVerifyErrorsFirst(current));
|
||||
return;
|
||||
}
|
||||
const baseline = verifyPriorityBaselineRef.current;
|
||||
verifyPriorityBaselineRef.current = null;
|
||||
skipVerifyPriorityResetRef.current = true;
|
||||
setPreferVerifyErrors(false);
|
||||
if (baseline) {
|
||||
setRowData(baseline);
|
||||
}
|
||||
};
|
||||
|
||||
// rowData 변경 시 편집 중인 셀을 commit한 뒤 수동으로 setRows (RealGridReact에 rows prop을 넘기지 않음)
|
||||
const rowDataMountedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
@@ -2086,60 +2210,79 @@ useEffect(() => {
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<div
|
||||
className={`flex items-center ${statusInfo ? "justify-between" : "justify-end"} gap-[0.6rem] pr-[1.2rem]`}
|
||||
className={`flex items-center ${statusInfo || showVerifyPriorityToggle ? "justify-between" : "justify-end"} gap-[0.6rem] pr-[1.2rem]`}
|
||||
>
|
||||
{statusInfo && (
|
||||
<div className="data-grid-status-bubble flex flex-wrap items-center gap-x-4 gap-y-1">
|
||||
<span>
|
||||
진행상황:{" "}
|
||||
{statusInfo.laeSbmsnSttsCd === null ||
|
||||
statusInfo.laeSbmsnSttsCd === "0" ? (
|
||||
"미작성"
|
||||
) : statusInfo.laeSbmsnSttsCd === "1" ? (
|
||||
<span className="animate-pulse">작성중</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "2" ? (
|
||||
<span className="!text-[#2094fa]">제출</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "3" ? (
|
||||
<span className="!text-[#1fd078]">승인</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "4" ? (
|
||||
<span className="relative inline-flex pr-3 !text-[#ff6c64]">
|
||||
반려
|
||||
<span className="absolute -right-0.5 -top-0.5 inline-flex size-[0.6125rem]">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#ff6c64] opacity-75" />
|
||||
<span className="relative inline-flex size-[0.6125rem] rounded-full bg-[#ff6c64]" />
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-3">
|
||||
{statusInfo && (
|
||||
<div className="data-grid-status-bubble flex flex-wrap items-center gap-x-4 gap-y-1">
|
||||
<span>
|
||||
진행상황:{" "}
|
||||
{statusInfo.laeSbmsnSttsCd === null ||
|
||||
statusInfo.laeSbmsnSttsCd === "0" ? (
|
||||
"미작성"
|
||||
) : statusInfo.laeSbmsnSttsCd === "1" ? (
|
||||
<span className="animate-pulse">작성중</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "2" ? (
|
||||
<span className="!text-[#2094fa]">제출</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "3" ? (
|
||||
<span className="!text-[#1fd078]">승인</span>
|
||||
) : statusInfo.laeSbmsnSttsCd === "4" ? (
|
||||
<span className="relative inline-flex pr-3 !text-[#ff6c64]">
|
||||
반려
|
||||
<span className="absolute -right-0.5 -top-0.5 inline-flex size-[0.6125rem]">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#ff6c64] opacity-75" />
|
||||
<span className="relative inline-flex size-[0.6125rem] rounded-full bg-[#ff6c64]" />
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="animate-pulse">검증중</span>
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
반려사유:{" "}
|
||||
{statusInfo.rjctOpnnCn
|
||||
? statusInfo.rjctOpnnCn
|
||||
: "-"}
|
||||
</span>
|
||||
<span>
|
||||
제출일:{" "}
|
||||
{statusInfo.laeSbmsnYmd
|
||||
? statusInfo.laeSbmsnYmd.length === 8 &&
|
||||
/^\d{8}$/.test(statusInfo.laeSbmsnYmd)
|
||||
? `${statusInfo.laeSbmsnYmd.slice(0, 4)}-${statusInfo.laeSbmsnYmd.slice(4, 6)}-${statusInfo.laeSbmsnYmd.slice(6, 8)}`
|
||||
: statusInfo.laeSbmsnYmd
|
||||
: "미제출"}
|
||||
</span>
|
||||
<span>
|
||||
담당자:{" "}
|
||||
{statusInfo.laePicNm ? statusInfo.laePicNm : "-"}
|
||||
</span>
|
||||
<span>
|
||||
연락처:{" "}
|
||||
{statusInfo.laePicTelNo
|
||||
? statusInfo.laePicTelNo
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
) : (
|
||||
<span className="animate-pulse">검증중</span>
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
반려사유:{" "}
|
||||
{statusInfo.rjctOpnnCn
|
||||
? statusInfo.rjctOpnnCn
|
||||
: "-"}
|
||||
</span>
|
||||
<span>
|
||||
제출일:{" "}
|
||||
{statusInfo.laeSbmsnYmd
|
||||
? statusInfo.laeSbmsnYmd.length === 8 &&
|
||||
/^\d{8}$/.test(statusInfo.laeSbmsnYmd)
|
||||
? `${statusInfo.laeSbmsnYmd.slice(0, 4)}-${statusInfo.laeSbmsnYmd.slice(4, 6)}-${statusInfo.laeSbmsnYmd.slice(6, 8)}`
|
||||
: statusInfo.laeSbmsnYmd
|
||||
: "미제출"}
|
||||
</span>
|
||||
<span>
|
||||
담당자:{" "}
|
||||
{statusInfo.laePicNm ? statusInfo.laePicNm : "-"}
|
||||
</span>
|
||||
<span>
|
||||
연락처:{" "}
|
||||
{statusInfo.laePicTelNo
|
||||
? statusInfo.laePicTelNo
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{showVerifyPriorityToggle && (
|
||||
<label className="inline-flex shrink-0 cursor-pointer items-center gap-1.5 text-label data-grid-status-bubble">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="m-0 shrink-0 cursor-pointer accent-primary"
|
||||
checked={preferVerifyErrors}
|
||||
onChange={(e) =>
|
||||
handlePreferVerifyErrorsChange(
|
||||
e.target.checked,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="whitespace-nowrap leading-none">
|
||||
검증 오류 항목 우선보기
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-[0.6rem]">
|
||||
{isExcelImport && (
|
||||
<Button
|
||||
@@ -2467,11 +2610,10 @@ useEffect(() => {
|
||||
{emptyCellHoverTooltip &&
|
||||
createPortal(
|
||||
<div
|
||||
className="rg-tooltip pointer-events-none !visible fixed z-[9999]"
|
||||
className="rg-tooltip pointer-events-none !visible fixed z-[9999] w-max=[60rem]"
|
||||
style={{
|
||||
left: emptyCellHoverTooltip.x,
|
||||
top: emptyCellHoverTooltip.y,
|
||||
whiteSpace: "pre-wrap",
|
||||
}}
|
||||
role="tooltip"
|
||||
>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { cellContentTooltip } from "@/components/module/verificationResultCellTooltip";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type {
|
||||
CellEditedCallbackParams,
|
||||
@@ -413,6 +414,7 @@ export function VerificationResultDialog({
|
||||
getGridDataRef={getGridDataRef}
|
||||
onCellEdited={onPrcsOpnnExplnEdited}
|
||||
cellStyleCallback={verificationMthdCellStyle}
|
||||
cellTooltipCallback={cellContentTooltip}
|
||||
onItemChecked={(itemIndex, checked) => {
|
||||
setCheckedIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from "@common/components";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { cellContentTooltip } from "@/components/module/verificationResultCellTooltip";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
@@ -500,6 +501,7 @@ export function VerificationResultHistoryDialog({
|
||||
enableSort={false}
|
||||
getGridRef={gridRef}
|
||||
cellStyleCallback={verificationPrcsYnNmCellStyle}
|
||||
cellTooltipCallback={cellContentTooltip}
|
||||
onItemChecked={(itemIndex, checked) => {
|
||||
const entry = entries[itemIndex];
|
||||
if (!entry) return;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { CellTooltipCallbackParams } from "@/components/type/DataGrid.type";
|
||||
|
||||
/** RealGrid 셀 호버 시 title처럼 전체 내용 표시 */
|
||||
export function cellContentTooltip({
|
||||
row,
|
||||
fieldName,
|
||||
}: CellTooltipCallbackParams): string | undefined {
|
||||
const value = row[fieldName];
|
||||
if (value == null || value === "") return undefined;
|
||||
return String(value);
|
||||
}
|
||||
Reference in New Issue
Block a user