st: feat: 일반검증 컴포넌트 구현중

This commit is contained in:
2026-03-06 13:04:14 +09:00
parent 727d448dc5
commit 6b4dd6aba7
3 changed files with 235 additions and 51 deletions
@@ -1,9 +1,9 @@
import { useRef, useEffect } from "react"
import { useRef, useEffect, useLayoutEffect } from "react"
import { ValueType } from "realgrid"
import RealGrid, { GridFitStyle } from "realgrid"
import { RealGridReact, RGDataColumn } from "realgrid-react"
import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback } from "../type/DataGrid.type"
import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback, CellStyleCallback } from "../type/DataGrid.type"
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
// RealGrid 타입 확장
@@ -62,15 +62,36 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string
};
}
const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, rowData }: DataGridProps) => {
const CompactDataGrid = ({
columnInfo,
fixedColumns,
showHeaderCheckbox = false,
rowData,
setRowData,
getGridDataRef,
cellStyleCallback,
onRowClick,
}: DataGridProps) => {
const { columns, columnLayout } = columnInfo;
const gridRef = useRef<RealGridReact>(null);
const rowDataRef = useRef<Record<string, string | number>[]>(rowData);
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(cellStyleCallback);
rowDataRef.current = rowData;
cellStyleCallbackRef.current = cellStyleCallback;
useLayoutEffect(() => {
if (!cellStyleCallback || !gridRef.current?.gridView) return;
gridRef.current.gridView.refresh();
}, [cellStyleCallback, rowData]);
useEffect(() => {
if (!gridRef.current?.gridView || !gridRef.current?.dataProvider) return;
const gridView = gridRef.current.gridView;
const dataProvider = gridRef.current.dataProvider;
if ((dataProvider as { setRows?: (r: unknown[]) => void })?.setRows && rowData) {
(dataProvider as { setRows: (r: unknown[]) => void }).setRows(rowData);
}
// setDataComparer로 숫자 정렬 (setFields 미사용 → 데이터 유실·필드 중복 오류 없음)
const dp = dataProvider as RealGridDataProviderComparer;
@@ -231,11 +252,11 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false,
// footer가 단일 객체이고 valueCallback이 있는 경우
columns.forEach(col => {
if (!col.footer || Array.isArray(col.footer)) return;
try {
const column = currentGridView.columnByName(col.name);
if (!column || !column.footer) return;
if (col.footer.valueCallback) {
(column.footer as { valueCallback?: typeof col.footer.valueCallback }).valueCallback = col.footer.valueCallback;
}
@@ -243,9 +264,75 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false,
console.warn(`Error setting footer valueCallback for column ${col.name}:`, e);
}
});
// cellStyleCallback이 있으면 컬럼별 styleCallback 설정
if (cellStyleCallbackRef.current) {
columns.forEach((column) => {
const baseStyleName = column.styleName;
const gridCol = currentGridView.columnByName(column.name) as {
styleCallback?: (g: unknown, dataCell: { itemIndex?: number; index?: { itemIndex?: number }; dataField?: string; fieldName?: string }) => string;
} | null;
if (gridCol) {
gridCol.styleCallback = (_g, dataCell) => {
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;
};
}
});
}
currentGridView.refresh?.();
}, 200);
}, [columns, columnLayout, fixedColumns, rowData, showHeaderCheckbox]);
if (getGridDataRef) {
const fieldNames = columns.map((c) => c.fieldName).filter(Boolean) as string[];
getGridDataRef.current = () => {
const gv = gridRef.current?.gridView as {
getDataSource?: () => { getRowCount(): number; getValue(r: number, f: string): unknown };
} | undefined;
if (!gv?.getDataSource) return [];
const ds = gv.getDataSource();
if (!ds) return [];
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 rows;
};
}
return () => {
if (getGridDataRef) getGridDataRef.current = null;
};
}, [columns, columnLayout, fixedColumns, rowData, showHeaderCheckbox, getGridDataRef]);
// 행 클릭 이벤트
useEffect(() => {
const gridView = gridRef.current?.gridView;
if (!gridView || !onRowClick) return;
gridView.onCellClicked = (grid, clickData) => {
if (clickData.cellType !== "data" || clickData.itemIndex === undefined) return;
try {
const row = rowDataRef.current[clickData.itemIndex];
onRowClick((row ?? {}) as Record<string, string | number>);
} catch (error) {
console.error("행 데이터 가져오기 오류:", error);
}
};
return () => {
gridView.onCellClicked = undefined;
};
}, [onRowClick]);
return (
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem] w-1/2">
@@ -256,10 +343,15 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false,
layout={columnLayout}
rows={rowData}
onInitialized={() => {
const dp = gridRef.current?.dataProvider as { setFields?: (fields: unknown[]) => void } | undefined;
const dp = gridRef.current?.dataProvider as
| { setFields?(f: unknown[]): void; setRows?(r: Record<string, string | number>[]): void }
| undefined;
if (dp?.setFields) {
dp.setFields(buildFieldsFromColumns(columns));
}
if (dp?.setRows && rowData?.length) {
dp.setRows(rowData);
}
}}
>
{columns.map((column) => (
@@ -1,61 +1,60 @@
"use client";
import { useState } from 'react';
import useSWR from 'swr';
// import useSWR from 'swr';
import {
Button,
Input,
// Input,
Label,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue
// Select,
// SelectTrigger,
// SelectContent,
// SelectItem,
// SelectValue
}
from "@common/components";
import DataGrid from '@/components/module/DataGrid';
import { CodeSelect } from '@/entities/code';
import { LogHistoryColumn } from '@/components/util/column/management/LogHistoryColumn';
import { useAlertStore } from "@/shared/lib";
import {
LwpoCodeApi,
} from "@/entities/code";
// import { LwpoCodeApi } from "@/entities/code";
const getStateCdListFetcher = async () => {
return LwpoCodeApi.getLwpoCodeList({
sort: 'regDt,desc',
upCd: 'STITEM',
page: 0,
size: 100,
});
}
// const getStateCdListFetcher = async () => {
// return LwpoCodeApi.getLwpoCodeList({
// sort: 'regDt,desc',
// upCd: 'STITEM',
// page: 0,
// size: 100,
// });
// }
const LogHistory = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [lwpoCd, setLwpoCd] = useState<string>('');
// const { setMessage: alert } = useAlertStore((state) => state);
const [statsCd, setStatsCd] = useState('');
const { data: lwpoCdList } = useSWR(
"lwpo-code-list",
getStateCdListFetcher,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
dedupingInterval: 2000,
shouldRetryOnError: false,
onSuccess: (data) => {
if (!data.content || data.content.length == 0) {
alert(`시도 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`);
}
},
onError: () => {
alert(`시도 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`);
},
}
);
// const { data: lwpoCdList } = useSWR(
// "lwpo-code-list",
// getStateCdListFetcher,
// {
// revalidateOnFocus: false,
// revalidateOnReconnect: false,
// revalidateIfStale: false,
// dedupingInterval: 2000,
// shouldRetryOnError: false,
// onSuccess: (data) => {
// if (!data.content || data.content.length == 0) {
// alert(`시도 목록을 불러오는데 실패했습니다.
// 새로고침 후 다시 시도해주세요.`);
// }
// },
// onError: () => {
// alert(`시도 목록을 불러오는데 실패했습니다.
// 새로고침 후 다시 시도해주세요.`);
// },
// }
// );
return (
<div className="relative">
@@ -64,9 +63,9 @@ const LogHistory = () => {
<div className="flex items-center gap-8 p-2">
<div className="flex items-center gap-2">
<Label className="text-label">
메뉴명
통계항목
</Label>
<Select value={lwpoCd} onValueChange={setLwpoCd}>
{/* <Select value={lwpoCd} onValueChange={setLwpoCd}>
<SelectTrigger className="w-[22rem]">
<SelectValue placeholder="전체"></SelectValue>
</SelectTrigger>
@@ -78,7 +77,14 @@ const LogHistory = () => {
</SelectItem>
))}
</SelectContent>
</Select>
</Select> */}
<CodeSelect
upCd="STITEM"
value={statsCd}
onValueChange={setStatsCd}
placeholder="선택"
className="w-[16rem]"
/>
</div>
<div className="flex items-center gap-2 ml-auto">
<Button color="green" className="h-[3.2rem] w-[96px]" type="button">
@@ -0,0 +1,86 @@
"use client";
import { useState } from 'react';
import { CodeSelect } from '@/entities/code';
import {
Button,
Label,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "@common/components";
const GeneralVerificationManagement = () => {
const [statsCd, setStatsCd] = useState('');
return (
<div className="relative">
<div className="card card-border">
<form>
<div className="flex items-center gap-8 p-2">
<div className="flex items-center gap-2">
<Label className="text-label">
통계항목
</Label>
<CodeSelect
upCd="STITEM"
value={statsCd}
onValueChange={setStatsCd}
placeholder="선택"
className="w-[16rem]"
/>
</div>
<div className="flex items-center gap-2 ml-auto">
<Button color="green" className="h-[3.2rem] w-[96px]" type="button">
초기화
</Button>
<Button className="h-[3.2rem] w-[96px]" type="submit">
조회
</Button>
</div>
</div>
</form>
</div>
<div className='card !mb-0 flex gap-5 !p-0'>
<div className='card card-border w-1/2 !mx-0 flex flex-col'>
<Table variant="default">
<TableRow>
<TableHead>No</TableHead>
<TableHead>검증항목</TableHead>
<TableHead>등록자</TableHead>
<TableHead>등록일</TableHead>
<TableHead>사용여부</TableHead>
</TableRow>
<TableBody className='text-center'>
<TableRow>
<TableCell>
1
</TableCell>
<TableCell>
인구수검증
</TableCell>
<TableCell>
관리자
</TableCell>
<TableCell>
2026-02-09
</TableCell>
<TableCell>
Y
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div className='w-1/2'>
</div>
</div>
</div>
);
};
export default GeneralVerificationManagement;