[st] feat: 차트 포인터 클릭 시 그리드 포커싱되는 기능 추가
This commit is contained in:
@@ -1,24 +1,25 @@
|
||||
import { useRef, useEffect, useLayoutEffect } from "react";
|
||||
import { ValueType } from "realgrid";
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
|
||||
import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid";
|
||||
import { RealGridReact, RGDataColumn } from "realgrid-react";
|
||||
import type {
|
||||
DataGridProps,
|
||||
RealGridColumnInfo,
|
||||
GridViewWithSetColumns,
|
||||
FooterItemWithCallback,
|
||||
ColumnLayoutItem,
|
||||
CellStyleCallback,
|
||||
CellTooltipCallback,
|
||||
} from "../type/DataGrid.type";
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks";
|
||||
import JSZip from "jszip";
|
||||
import { ValueType } from "realgrid";
|
||||
import RealGrid, { type DisplayOptions, GridFitStyle } from "realgrid";
|
||||
import { RealGridReact, RGDataColumn } from "realgrid-react";
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import type {
|
||||
CellStyleCallback,
|
||||
CellTooltipCallback,
|
||||
ColumnLayoutItem,
|
||||
DataGridProps,
|
||||
FooterItemWithCallback,
|
||||
GridViewWithSetColumns,
|
||||
RealGridColumnInfo,
|
||||
} from "../type/DataGrid.type";
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
// RealGrid 타입 확장
|
||||
@@ -321,19 +322,43 @@ const DataGrid = ({
|
||||
cellTooltipCallback,
|
||||
onRowClick,
|
||||
statusInfo,
|
||||
highlightedRowId,
|
||||
idFieldName = "id",
|
||||
}: DataGridProps) => {
|
||||
const { columns, columnLayout } = columnInfo;
|
||||
const gridRef = useRef<RealGridReact>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const sortDirRef = useRef<Record<string, "asc" | "desc" | "none">>({});
|
||||
const originalRowsRef = useRef<Record<string, Record<string, string | number>[]>>({});
|
||||
const originalRowsRef = useRef<
|
||||
Record<string, Record<string, string | number>[]>
|
||||
>({});
|
||||
const rowDataRef = useRef<Record<string, string | number>[]>(rowData);
|
||||
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(cellStyleCallback);
|
||||
const cellTooltipCallbackRef = useRef<CellTooltipCallback | undefined>(cellTooltipCallback);
|
||||
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(
|
||||
cellStyleCallback,
|
||||
);
|
||||
const cellTooltipCallbackRef = useRef<CellTooltipCallback | undefined>(
|
||||
cellTooltipCallback,
|
||||
);
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
cellTooltipCallbackRef.current = cellTooltipCallback;
|
||||
|
||||
// highlightedRowId가 변경될 때 해당 행으로 스크롤 및 선택
|
||||
useEffect(() => {
|
||||
if (!gridRef.current?.gridView || highlightedRowId == null) return;
|
||||
|
||||
const gridView = gridRef.current.gridView;
|
||||
const index = rowData.findIndex(
|
||||
(row) => String(row[idFieldName]) === String(highlightedRowId),
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
// 해당 행으로 포커스 이동 및 스크롤
|
||||
gridView.setCurrent({ itemIndex: index });
|
||||
gridView.setFocus();
|
||||
}
|
||||
}, [highlightedRowId, rowData, idFieldName]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!cellStyleCallback || !gridRef.current?.gridView) return;
|
||||
gridRef.current.gridView.refresh();
|
||||
@@ -532,10 +557,10 @@ const DataGrid = ({
|
||||
) {
|
||||
const cell =
|
||||
worksheet[
|
||||
XLSX.utils.encode_cell({
|
||||
r: rowIndex,
|
||||
c: col,
|
||||
})
|
||||
XLSX.utils.encode_cell({
|
||||
r: rowIndex,
|
||||
c: col,
|
||||
})
|
||||
];
|
||||
if (
|
||||
cell &&
|
||||
@@ -611,7 +636,7 @@ const DataGrid = ({
|
||||
const col = range.s.c + colIndex;
|
||||
const cell =
|
||||
worksheet[
|
||||
XLSX.utils.encode_cell({ r: row, c: col })
|
||||
XLSX.utils.encode_cell({ r: row, c: col })
|
||||
];
|
||||
const headerName =
|
||||
headers[colIndex] || `Column${colIndex + 1}`;
|
||||
@@ -909,7 +934,7 @@ const DataGrid = ({
|
||||
) {
|
||||
(
|
||||
column.footer[
|
||||
index
|
||||
index
|
||||
] as FooterItemWithCallback
|
||||
).valueCallback =
|
||||
footerItem.valueCallback;
|
||||
@@ -962,11 +987,11 @@ const DataGrid = ({
|
||||
getGridDataRef.current = () => {
|
||||
const gv = gridRef.current?.gridView as
|
||||
| {
|
||||
getDataSource?: () => {
|
||||
getRowCount(): number;
|
||||
getValue(r: number, f: string): unknown;
|
||||
};
|
||||
}
|
||||
getDataSource?: () => {
|
||||
getRowCount(): number;
|
||||
getValue(r: number, f: string): unknown;
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
if (!gv?.getDataSource) return [];
|
||||
const ds = gv.getDataSource();
|
||||
@@ -1001,23 +1026,57 @@ const DataGrid = ({
|
||||
if (!gridView || (!onRowClick && !cellTooltipCallback)) return;
|
||||
|
||||
gridView.onCellClicked = (grid, clickData) => {
|
||||
if (clickData.cellType !== "data" || clickData.itemIndex === undefined) return;
|
||||
if (
|
||||
clickData.cellType !== "data" ||
|
||||
clickData.itemIndex === undefined
|
||||
)
|
||||
return;
|
||||
try {
|
||||
const row = rowDataRef.current[clickData.itemIndex];
|
||||
const cellIndex = (grid as { getCurrent?: () => { itemIndex: number; column?: string; fieldName?: string } }).getCurrent?.();
|
||||
const cellIndex = (
|
||||
grid as {
|
||||
getCurrent?: () => {
|
||||
itemIndex: number;
|
||||
column?: string;
|
||||
fieldName?: string;
|
||||
};
|
||||
}
|
||||
).getCurrent?.();
|
||||
// cellTooltipCallback 있으면 해당 셀 메시지로 showMessage (공식 문서: TooltipOptions + CellIndex)
|
||||
if (cellTooltipCallbackRef.current && row && cellIndex) {
|
||||
const fieldName = cellIndex.fieldName ?? cellIndex.column ?? "";
|
||||
const msg = cellTooltipCallbackRef.current({ row: row as Record<string, string | number>, fieldName });
|
||||
const fieldName =
|
||||
cellIndex.fieldName ?? cellIndex.column ?? "";
|
||||
const msg = cellTooltipCallbackRef.current({
|
||||
row: row as Record<string, string | number>,
|
||||
fieldName,
|
||||
});
|
||||
if (msg) {
|
||||
const colName = columns.find((c) => c.fieldName === fieldName || c.name === cellIndex.column)?.name ?? cellIndex.column ?? fieldName;
|
||||
(gridView as { showMessage?: (opts: { message: string; hideDuration?: number }, idx: { itemIndex: number; column: string }) => void }).showMessage?.(
|
||||
const colName =
|
||||
columns.find(
|
||||
(c) =>
|
||||
c.fieldName === fieldName ||
|
||||
c.name === cellIndex.column,
|
||||
)?.name ??
|
||||
cellIndex.column ??
|
||||
fieldName;
|
||||
(
|
||||
gridView as {
|
||||
showMessage?: (
|
||||
opts: {
|
||||
message: string;
|
||||
hideDuration?: number;
|
||||
},
|
||||
idx: { itemIndex: number; column: string },
|
||||
) => void;
|
||||
}
|
||||
).showMessage?.(
|
||||
{ message: msg, hideDuration: 5000 },
|
||||
{ itemIndex: cellIndex.itemIndex, column: colName }
|
||||
{ itemIndex: cellIndex.itemIndex, column: colName },
|
||||
);
|
||||
}
|
||||
}
|
||||
if (onRowClick) onRowClick((row ?? {}) as Record<string, string | number>);
|
||||
if (onRowClick)
|
||||
onRowClick((row ?? {}) as Record<string, string | number>);
|
||||
} catch (error) {
|
||||
console.error("행 데이터 가져오기 오류:", error);
|
||||
}
|
||||
@@ -1038,26 +1097,42 @@ const DataGrid = ({
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<div className="flex items-center justify-end gap-[0.6rem] px-[1.2rem]">
|
||||
{statusInfo &&
|
||||
{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' ? '작성중' : statusInfo.laeSbmsnSttsCd === '2' ? '제출'
|
||||
:
|
||||
statusInfo.laeSbmsnSttsCd === '3' ? '승인' : '반려'
|
||||
}
|
||||
<span>
|
||||
진행상황:{" "}
|
||||
{statusInfo.laeSbmsnSttsCd === null ||
|
||||
statusInfo.laeSbmsnSttsCd === "0"
|
||||
? "미작성"
|
||||
: statusInfo.laeSbmsnSttsCd === "1"
|
||||
? "작성중"
|
||||
: statusInfo.laeSbmsnSttsCd === "2"
|
||||
? "제출"
|
||||
: statusInfo.laeSbmsnSttsCd === "3"
|
||||
? "승인"
|
||||
: "반려"}
|
||||
</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>
|
||||
<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>
|
||||
}
|
||||
)}
|
||||
{isExcelImport && (
|
||||
<Button
|
||||
className="h-[3.2rem] w-[96px] bg-gradient-green px-6 hover:bg-gradient-green-hover"
|
||||
@@ -1087,12 +1162,36 @@ const DataGrid = ({
|
||||
rows={rowData}
|
||||
onInitialized={(grid) => {
|
||||
if (!grid) return;
|
||||
(grid as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef);
|
||||
const dataProvider = gridRef.current?.dataProvider as unknown as
|
||||
| { setFields?(f: unknown[]): void; setRows?(r: Record<string, string | number>[]): void; getJsonRow?(r: number): Record<string, unknown> }
|
||||
(
|
||||
grid as {
|
||||
onSorting?: (
|
||||
g: unknown,
|
||||
f: unknown[],
|
||||
d: unknown[],
|
||||
) => boolean;
|
||||
}
|
||||
).onSorting = createOnSortingHandler(
|
||||
columns,
|
||||
setRowData,
|
||||
sortDirRef,
|
||||
originalRowsRef,
|
||||
);
|
||||
const dataProvider = gridRef.current
|
||||
?.dataProvider as unknown as
|
||||
| {
|
||||
setFields?(f: unknown[]): void;
|
||||
setRows?(
|
||||
r: Record<string, string | number>[],
|
||||
): void;
|
||||
getJsonRow?(
|
||||
r: number,
|
||||
): Record<string, unknown>;
|
||||
}
|
||||
| undefined;
|
||||
if (dataProvider?.setFields) {
|
||||
dataProvider.setFields(buildFieldsFromColumns(columns));
|
||||
dataProvider.setFields(
|
||||
buildFieldsFromColumns(columns),
|
||||
);
|
||||
}
|
||||
if (dataProvider?.setRows && rowData?.length) {
|
||||
dataProvider.setRows(rowData);
|
||||
@@ -1104,33 +1203,101 @@ const DataGrid = ({
|
||||
if (!gv?.columnByName) return;
|
||||
// 공식 문서: 데이터 영역 툴팁 = renderer.showTooltip + onShowTooltip (displayOptions는 Ellipsis용이라 사용 안 함)
|
||||
if (cellTooltipCallbackRef.current) {
|
||||
(gv as { onShowTooltip?: (grid: unknown, index: { itemIndex: number; column: string }, value: string) => string | undefined }).onShowTooltip = (_grid, index, value) => {
|
||||
const row = rowDataRef.current[index?.itemIndex];
|
||||
(
|
||||
gv as {
|
||||
onShowTooltip?: (
|
||||
grid: unknown,
|
||||
index: {
|
||||
itemIndex: number;
|
||||
column: string;
|
||||
},
|
||||
value: string,
|
||||
) => string | undefined;
|
||||
}
|
||||
).onShowTooltip = (_grid, index, value) => {
|
||||
const row =
|
||||
rowDataRef.current[index?.itemIndex];
|
||||
if (row == null) return value;
|
||||
const column = index.column;
|
||||
const col = columns.find((c) => c.name === column);
|
||||
const col = columns.find(
|
||||
(c) => c.name === column,
|
||||
);
|
||||
const fieldName = col?.fieldName ?? column;
|
||||
const custom = cellTooltipCallbackRef.current?.({ row: row as Record<string, string | number>, fieldName });
|
||||
const custom =
|
||||
cellTooltipCallbackRef.current?.({
|
||||
row: row as Record<
|
||||
string,
|
||||
string | number
|
||||
>,
|
||||
fieldName,
|
||||
});
|
||||
// verificationResult 등 메시지가 있을 때만 툴팁 표시 (없으면 undefined 반환 → 툴팁 안 띄움)
|
||||
return custom && custom.length > 0 ? custom : undefined;
|
||||
return custom && custom.length > 0
|
||||
? custom
|
||||
: undefined;
|
||||
};
|
||||
}
|
||||
columns.forEach((column) => {
|
||||
const baseStyleName = column.styleName;
|
||||
const gridCol = gv.columnByName(column.name) as { styleCallback?: (g: unknown, dataCell: { itemIndex?: number; index?: { itemIndex?: number }; dataField?: string; fieldName?: string }) => string; renderer?: { showTooltip?: boolean } } | null;
|
||||
const gridCol = gv.columnByName(
|
||||
column.name,
|
||||
) as {
|
||||
styleCallback?: (
|
||||
g: unknown,
|
||||
dataCell: {
|
||||
itemIndex?: number;
|
||||
index?: { itemIndex?: number };
|
||||
dataField?: string;
|
||||
fieldName?: string;
|
||||
},
|
||||
) => string;
|
||||
renderer?: { showTooltip?: boolean };
|
||||
} | null;
|
||||
if (gridCol) {
|
||||
if (cellTooltipCallbackRef.current) {
|
||||
(gv as { setColumnProperty?: (col: string, key: string, value: unknown) => void }).setColumnProperty?.(column.name, 'renderer', { showTooltip: true });
|
||||
(
|
||||
gv as {
|
||||
setColumnProperty?: (
|
||||
col: string,
|
||||
key: string,
|
||||
value: unknown,
|
||||
) => void;
|
||||
}
|
||||
).setColumnProperty?.(
|
||||
column.name,
|
||||
"renderer",
|
||||
{ showTooltip: true },
|
||||
);
|
||||
}
|
||||
gridCol.styleCallback = (_g, dataCell) => {
|
||||
const itemIndex = dataCell?.index?.itemIndex ?? dataCell?.itemIndex ?? -1;
|
||||
const fieldName = dataCell?.dataField ?? dataCell?.fieldName ?? column.fieldName;
|
||||
const itemIndex =
|
||||
dataCell?.index?.itemIndex ??
|
||||
dataCell?.itemIndex ??
|
||||
-1;
|
||||
const fieldName =
|
||||
dataCell?.dataField ??
|
||||
dataCell?.fieldName ??
|
||||
column.fieldName;
|
||||
const data = rowDataRef.current;
|
||||
if (itemIndex < 0 || itemIndex >= data.length) return baseStyleName ?? '';
|
||||
const row = data[itemIndex] as Record<string, string | number>;
|
||||
const extra = cellStyleCallbackRef.current?.({ rowIndex: itemIndex, fieldName, row });
|
||||
if (!extra) return baseStyleName ?? '';
|
||||
return baseStyleName ? `${extra} ${baseStyleName}` : extra;
|
||||
if (
|
||||
itemIndex < 0 ||
|
||||
itemIndex >= data.length
|
||||
)
|
||||
return baseStyleName ?? "";
|
||||
const row = data[itemIndex] as Record<
|
||||
string,
|
||||
string | number
|
||||
>;
|
||||
const extra =
|
||||
cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
});
|
||||
if (!extra) return baseStyleName ?? "";
|
||||
return baseStyleName
|
||||
? `${extra} ${baseStyleName}`
|
||||
: extra;
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1146,20 +1313,47 @@ const DataGrid = ({
|
||||
key={column.name}
|
||||
name={column.name}
|
||||
fieldName={column.fieldName}
|
||||
valueType={column.valueType ? column.valueType : ValueType.TEXT}
|
||||
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })}
|
||||
valueType={
|
||||
column.valueType
|
||||
? column.valueType
|
||||
: ValueType.TEXT
|
||||
}
|
||||
{...(column.valueType === ValueType.NUMBER && {
|
||||
numberFormat: column.dataFormat
|
||||
? column.dataFormat
|
||||
: "#,##0.000",
|
||||
})}
|
||||
header={column.header}
|
||||
editable={column.editable}
|
||||
{...(column.width != null && { width: column.width })}
|
||||
{...(baseStyleName != null && { styleName: baseStyleName })}
|
||||
{...(column.visible === false && { visible: false })}
|
||||
{...(cellTooltipCallback && { renderer: { showTooltip: true } })}
|
||||
footer={column.footer && !Array.isArray(column.footer) ? {
|
||||
text: column.footer.text,
|
||||
expression: column.footer.expression,
|
||||
numberFormat: column.footer.numberFormat,
|
||||
} : undefined}
|
||||
mergeRule={column.mergeCells ? { criteria: 'value' } : undefined}
|
||||
{...(column.width != null && {
|
||||
width: column.width,
|
||||
})}
|
||||
{...(baseStyleName != null && {
|
||||
styleName: baseStyleName,
|
||||
})}
|
||||
{...(column.visible === false && {
|
||||
visible: false,
|
||||
})}
|
||||
{...(cellTooltipCallback && {
|
||||
renderer: { showTooltip: true },
|
||||
})}
|
||||
footer={
|
||||
column.footer &&
|
||||
!Array.isArray(column.footer)
|
||||
? {
|
||||
text: column.footer.text,
|
||||
expression:
|
||||
column.footer.expression,
|
||||
numberFormat:
|
||||
column.footer.numberFormat,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
mergeRule={
|
||||
column.mergeCells
|
||||
? { criteria: "value" }
|
||||
: undefined
|
||||
}
|
||||
sortable={!column.sortdisable}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
|
||||
null,
|
||||
);
|
||||
const [selectedSido, setSelectedSido] = useState<string | null>(null);
|
||||
const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
|
||||
const [selectedTargetInfo, setSelectedTargetInfo] = useState<{
|
||||
statsNm: string;
|
||||
vrfcTrgtNm: string;
|
||||
@@ -32,6 +33,7 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
|
||||
statsNm: row.statsNm,
|
||||
vrfcTrgtNm: row.vrfcTrgtNm,
|
||||
});
|
||||
setSelectedRowId(null);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -76,6 +78,7 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
|
||||
}
|
||||
selectedSido={selectedSido}
|
||||
onSidoSelect={setSelectedSido}
|
||||
selectedRowId={selectedRowId}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-[0.5] flex-col gap-4 p-4 pt-[4.8rem]">
|
||||
@@ -88,6 +91,7 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
|
||||
anlsSn={anlsSn}
|
||||
vrfcTrgtSn={selectedVrfcTrgtSn}
|
||||
selectedSido={selectedSido}
|
||||
onPointClick={setSelectedRowId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,7 @@ interface RegionalGridProps {
|
||||
vrfcTrgtNm: string;
|
||||
selectedSido: string | null;
|
||||
onSidoSelect: (sido: string | null) => void;
|
||||
selectedRowId?: number | null;
|
||||
}
|
||||
|
||||
interface AppliedFilter {
|
||||
@@ -43,6 +44,7 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
|
||||
vrfcTrgtNm,
|
||||
selectedSido,
|
||||
onSidoSelect,
|
||||
selectedRowId,
|
||||
}) => {
|
||||
const [selectedGugun, setSelectedGugun] = useState<string>("");
|
||||
const [selectedStatus, setSelectedStatus] = useState<string>("");
|
||||
@@ -369,6 +371,8 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
|
||||
setRowData={setGridRowData}
|
||||
isExcelExport={false}
|
||||
isExcelImport={false}
|
||||
highlightedRowId={selectedRowId}
|
||||
idFieldName="rsltSn"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -41,6 +41,7 @@ interface ScatterChartProps {
|
||||
anlsSn: number;
|
||||
vrfcTrgtSn: string | null;
|
||||
selectedSido: string | null;
|
||||
onPointClick?: (rsltSn: number | null) => void;
|
||||
}
|
||||
|
||||
interface LinePoint {
|
||||
@@ -56,6 +57,7 @@ const ScatterChart: React.FC<ScatterChartProps> = ({
|
||||
anlsSn,
|
||||
vrfcTrgtSn,
|
||||
selectedSido,
|
||||
onPointClick,
|
||||
}) => {
|
||||
const { data: regionCodes } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CODE, "LAESECD"],
|
||||
@@ -157,6 +159,27 @@ const ScatterChart: React.FC<ScatterChartProps> = ({
|
||||
const options: ChartOptions<"scatter"> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
onClick: (event, elements) => {
|
||||
if (elements.length > 0) {
|
||||
const element = elements[0];
|
||||
if (!element) return;
|
||||
const datasetIndex = element.datasetIndex;
|
||||
const index = element.index;
|
||||
const dataset = data.datasets[datasetIndex];
|
||||
|
||||
if (!dataset) return;
|
||||
|
||||
// 상한선/하한선 클릭 시 무시
|
||||
if (dataset.showLine) return;
|
||||
|
||||
const pt = dataset.data[index] as DataPoint;
|
||||
if (pt?.item?.rsltSn) {
|
||||
onPointClick?.(pt.item.rsltSn);
|
||||
}
|
||||
} else {
|
||||
onPointClick?.(null);
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
zoom: {
|
||||
pan: {
|
||||
|
||||
@@ -244,6 +244,10 @@ export type DataGridProps = {
|
||||
};
|
||||
onRowClick?: (rowData: Record<string, string | number>) => void; // 행 클릭 이벤트
|
||||
statusInfo?: SewerageSupplyRateStatusInfo | null;
|
||||
/** 강조 및 스크롤할 행의 ID */
|
||||
highlightedRowId?: string | number | null;
|
||||
/** ID로 사용할 필드명 (기본값: 'id') */
|
||||
idFieldName?: string;
|
||||
};
|
||||
|
||||
export type TabDataGridProps = {
|
||||
|
||||
Reference in New Issue
Block a user