Merge commit '6fb39d638495b9510302ffd819cbd8ecd7a16dab' into iipbs/dm/dev
This commit is contained in:
@@ -83,7 +83,6 @@ export default function DoubleBarChart({chartConfig=undefined, chartData=[], cha
|
||||
.map(key => item[key])
|
||||
).flat());
|
||||
};
|
||||
console.log(chartData);
|
||||
const barMaxValue = getMaxValue(chartData, 'barVal');
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -94,6 +94,7 @@ export default function LineBarchart({
|
||||
|
||||
// 데이터의 최대값 계산
|
||||
const getMaxValue = (data: any[], keyPrefix: string) => {
|
||||
if (!data || data.length === 0) return 0;
|
||||
return Math.max(...data.map(item =>
|
||||
Object.keys(item)
|
||||
.filter(key => key.includes(keyPrefix))
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
"use client"
|
||||
import type { CSSProperties } from "react";
|
||||
import { Bar, CartesianGrid, ComposedChart, Legend, Line, Tooltip, XAxis, YAxis } from "recharts";
|
||||
import {
|
||||
ChartContainer,
|
||||
type ChartConfig
|
||||
} from "./lib/chart";
|
||||
import type { ContentType } from "recharts/types/component/DefaultLegendContent";
|
||||
|
||||
// 툴크 커스텀 css
|
||||
/** 차트 기본 스타일 */
|
||||
const customStyles = {
|
||||
legendContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
gap: "20px",
|
||||
fontSize: "9px",
|
||||
fontWeight : "bold",
|
||||
marginTop: "10px",
|
||||
} as CSSProperties,
|
||||
};
|
||||
|
||||
type LineBarChartProps = {
|
||||
chartConfig: ChartConfig | undefined;
|
||||
chartData: any[] | undefined;
|
||||
chartTitle: string;
|
||||
chartClassName?: string;
|
||||
graphSize?: number;
|
||||
yFieldText?: string | undefined;
|
||||
yFieldText2?: string | undefined;
|
||||
showLegend?: boolean;
|
||||
}
|
||||
|
||||
type LegendEntry = {
|
||||
value: string;
|
||||
color: string;
|
||||
type?: string;
|
||||
payload?: unknown;
|
||||
};
|
||||
|
||||
type LegendProps = {
|
||||
payload?: LegendEntry[];
|
||||
};
|
||||
|
||||
// 커스텀 툴팁
|
||||
const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: Array<{ name: string; value: number; color: string }>; label?: string }) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="text-[12px] font-bold m-3">{label}</p>
|
||||
{payload.map((item, index) => (
|
||||
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
{/* 색상 박스 추가 (범례 색상과 동일) */}
|
||||
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
|
||||
const CustomLegend = (props: LegendProps) => {
|
||||
const { payload } = props;
|
||||
if (!payload) return null;
|
||||
|
||||
return (
|
||||
<div style={customStyles.legendContainer}>
|
||||
{payload.map((entry: LegendEntry, index: number) => (
|
||||
<div key={`legend-${index}`} className="flex items-center gap-2">
|
||||
<div className="w-[14px] h-[14px] ml-10" style={{ backgroundColor: entry.color }}></div>
|
||||
<span className="text-[12px]">{entry.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function LineBarchart2({
|
||||
chartConfig,
|
||||
chartData,
|
||||
chartTitle,
|
||||
chartClassName = 'w-full h-full',
|
||||
graphSize = 40,
|
||||
yFieldText,
|
||||
yFieldText2,
|
||||
showLegend = true
|
||||
}: LineBarChartProps) {
|
||||
if (!chartData || chartData.length === 0) {
|
||||
return <div>데이터가 없습니다.</div>;
|
||||
}
|
||||
|
||||
// 데이터의 최대값 계산
|
||||
const getMaxValue = (data: any[], keyPrefix: string) => {
|
||||
if (!data || data.length === 0) return 0;
|
||||
return Math.max(...data.map(item =>
|
||||
Object.keys(item)
|
||||
.filter(key => key.includes(keyPrefix))
|
||||
.map(key => item[key])
|
||||
).flat());
|
||||
};
|
||||
|
||||
const lineMaxValue = getMaxValue(chartData, 'lineVal');
|
||||
const barMaxValue = getMaxValue(chartData, 'barVal');
|
||||
return (
|
||||
<div className={chartClassName}>
|
||||
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
|
||||
<div style={{ textAlign: "left", fontSize: "13px", fontWeight: "bold", marginTop:"13px", marginBottom: "10px" }}>
|
||||
{chartTitle}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="h-[calc(100%-42px)] flex justify-center">
|
||||
<ChartContainer config={chartConfig ?? {}} className="h-full w-full">
|
||||
<ComposedChart
|
||||
data={chartData}
|
||||
height={graphSize * 7.5}
|
||||
width={graphSize * 12.5}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" fontSize="8px" fontWeight="bold"/>
|
||||
{/* 왼쪽 Y축: lineVal */}
|
||||
{yFieldText && <YAxis
|
||||
yAxisId="left"
|
||||
className="text-[12px] font-bold"
|
||||
tickCount={6}
|
||||
orientation="left"
|
||||
domain={[0, Math.ceil(lineMaxValue * 1)*2]}
|
||||
label={{ value: yFieldText, angle: -90, position: 'insideLeft', offset: -8 }}
|
||||
/>}
|
||||
{/* 오른쪽 Y축: barVal */}
|
||||
{yFieldText2 && <YAxis
|
||||
yAxisId="right"
|
||||
className="text-[12px] font-bold"
|
||||
tickCount={6}
|
||||
orientation="right"
|
||||
domain={[0, Math.ceil(barMaxValue * 1)*2]}
|
||||
label={{ value: yFieldText2, angle: -90, position: 'insideRight' }}
|
||||
reversed={true}
|
||||
/>}
|
||||
<Tooltip content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />} />
|
||||
{showLegend && <Legend content={CustomLegend as ContentType}/>}
|
||||
{/* 바 차트 (오른쪽 Y축 사용) */}
|
||||
{Object.keys(chartData[0]).map((key, index) => {
|
||||
const config = chartConfig?.[key];
|
||||
if(key.includes("barVal")) {
|
||||
return (
|
||||
<Bar
|
||||
key={`bar-${key}-${index}`}
|
||||
yAxisId="right"
|
||||
dataKey={key}
|
||||
fill={config?.color || "black"}
|
||||
name={String(config?.label || "Y축")}
|
||||
maxBarSize={80}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
{/* 라인 차트 (왼쪽 Y축 사용) */}
|
||||
{Object.keys(chartData[0]).map((key, index) => {
|
||||
const config = chartConfig?.[key];
|
||||
if(key.includes("lineVal")) {
|
||||
return (
|
||||
<Line
|
||||
key={`line-${key}-${index}`}
|
||||
yAxisId="left"
|
||||
type="linear"
|
||||
dataKey={key}
|
||||
stroke={config?.color || "black"}
|
||||
strokeWidth={2}
|
||||
dot={{r: 0}}
|
||||
name={String(config?.label || "X축")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</ComposedChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import DoubleBarChart from './DoubleBar';
|
||||
import DoughnutChart from './DoughnutChart';
|
||||
import LineBarchart from './LineBarChart';
|
||||
import LineBarchart2 from './LineBarChart2';
|
||||
import LineChart from './LineChart';
|
||||
|
||||
/**차트 FORM */
|
||||
@@ -14,6 +15,8 @@ const CHART_FORM = {
|
||||
DN :'daughnut',
|
||||
/**LINE,BAR차트 */
|
||||
LINEBAR : 'linebar',
|
||||
/**LINE,BAR차트 */
|
||||
LINEBAR2 : 'linebar2',
|
||||
/**더블바 차트 형태*/
|
||||
DOUBLEBAR : 'doublebar'
|
||||
};
|
||||
@@ -72,6 +75,10 @@ function MainChart(props: MainChartProps) {
|
||||
return (
|
||||
<LineBarchart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle} chartClassName={props.chartClassName} graphSize={props.graphSize} yFieldText={props.yFieldText} yFieldText2={props.yFieldText2} showLegend={props.showLegend}/>
|
||||
);
|
||||
} else if (props.chartForm == CHART_FORM.LINEBAR2){
|
||||
return (
|
||||
<LineBarchart2 chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle} chartClassName={props.chartClassName} graphSize={props.graphSize} yFieldText={props.yFieldText} yFieldText2={props.yFieldText2} showLegend={props.showLegend}/>
|
||||
);
|
||||
} else if (props.chartForm == CHART_FORM.DOUBLEBAR){
|
||||
return (
|
||||
<DoubleBarChart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle} chartClassName={props.chartClassName} graphSize={props.graphSize} yFieldText={props.yFieldText} showLegend={props.showLegend}/>
|
||||
|
||||
@@ -16,6 +16,73 @@ import GisApp from './lib/gis.core';
|
||||
|
||||
export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null });
|
||||
export const useGisContext = () => useContext(GisContext);
|
||||
export const searchUpperPpls = async (fcltMngNo) => {
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || '';
|
||||
|
||||
const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (res.ok) {
|
||||
try {
|
||||
// 응답이 비어있는지 확인
|
||||
const contentLength = res.headers.get('content-length');
|
||||
if (contentLength === '0') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
// 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인
|
||||
const text = await res.text();
|
||||
if (!text || text.trim() === '') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
// JSON 파싱 시도
|
||||
data = JSON.parse(text);
|
||||
} catch (error) {
|
||||
console.error('JSON parsing error:', error);
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
}
|
||||
}
|
||||
|
||||
const select = GisApp.Module.select;
|
||||
|
||||
const gisMap = GisApp.Module.core?.map;
|
||||
|
||||
let foundFeature = null;
|
||||
|
||||
data.forEach(item => {
|
||||
const pplMngNo = item.ppl_mng_no.trim();
|
||||
|
||||
gisMap.getLayers().getArray().filter(e => e.get("lyrTyNm") == 'tb_ms_swage_ppl').forEach(layer => {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatures) {
|
||||
const features = source.getFeatures();
|
||||
for (const f of features) {
|
||||
if (f.values_.fclt_mng_no.trim() === pplMngNo) {
|
||||
foundFeature = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (foundFeature) select.selectMultiFeature(foundFeature, true);
|
||||
});
|
||||
|
||||
if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
let geometry = foundFeature?.getGeometry();
|
||||
if (geometry) {
|
||||
let extent = geometry.getExtent();
|
||||
|
||||
gisMap.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 14,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function Gis({
|
||||
isOpen,
|
||||
@@ -94,7 +161,10 @@ function Gis({
|
||||
featureId = data.bscFctCd;
|
||||
} else if (layerName === '온배수시설') {
|
||||
featureId = data.bdstRuseFcltSn;
|
||||
} else {
|
||||
} else if(layerName == '수요처'){
|
||||
featureId = data.dmdInfoId.replaceAll('-','')
|
||||
}
|
||||
else {
|
||||
console.warn('해당 레이어에 대한 featureId 매핑 없음', layerNm);
|
||||
featureId = data.fid || data.id || data.ftrIdn;
|
||||
}
|
||||
@@ -301,6 +371,7 @@ function Gis({
|
||||
onFeatureSelect={handleFeatureSelect}
|
||||
/>
|
||||
)}
|
||||
{/* <GisLonLat /> */}
|
||||
<GisToolBar />
|
||||
<GisMapToggle />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} />
|
||||
|
||||
@@ -429,6 +429,19 @@ GisOlSelect.prototype = {
|
||||
auto: auto,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 피처 다중 셀렉트
|
||||
*/
|
||||
selectMultiFeature: function (feature, auto) {
|
||||
let me = this;
|
||||
me.selectedFeatures.push(feature);
|
||||
me.selectInteraction.dispatchEvent({
|
||||
type: 'select',
|
||||
selected: me.selectedFeatures,
|
||||
deselected: [],
|
||||
auto: auto,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default GisOlSelect;
|
||||
|
||||
@@ -215,6 +215,8 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue('rgtr', '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e');
|
||||
|
||||
if(updateList?.dmdInfoId){
|
||||
form.reset(updateList)
|
||||
if(updateList?.dmdInfoId.slice(10,14) !== '0000') {
|
||||
@@ -322,7 +324,7 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
|
||||
},
|
||||
{
|
||||
name: "bznmTotNo",
|
||||
label: "종사업자등록번호",
|
||||
label: "",
|
||||
component: (field) => (
|
||||
<FormUi
|
||||
label="종사업자등록번호"
|
||||
@@ -419,7 +421,7 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<div className="mb-4">
|
||||
<label htmlFor={inputId} className="block text-gray-800 font-medium">
|
||||
<label htmlFor={inputId} className="block text-gray-800 font-medium text-xl">
|
||||
{label}
|
||||
{isRequired && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
@@ -432,7 +434,7 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
|
||||
})}
|
||||
|
||||
{error?.message && (
|
||||
<p id={errorId} role="alert" className="text-red-500 text-sm mt-1">
|
||||
<p id={errorId} role="alert" className="text-red-500 text-lg mt-1">
|
||||
{error.message}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -24,6 +24,7 @@ type SearchBaseType = {
|
||||
rightClassName?: string;
|
||||
leftClassName?: string;
|
||||
leftSelectClassName?: string;
|
||||
buttonOrder?: ("query" | "init" | "excel")[];
|
||||
}
|
||||
export type ReturnType = {
|
||||
partValue: string;
|
||||
@@ -55,7 +56,8 @@ function SearchBaseForm ({
|
||||
isAdmin=false,
|
||||
rightClassName,
|
||||
leftClassName,
|
||||
leftSelectClassName
|
||||
leftSelectClassName,
|
||||
buttonOrder
|
||||
}: SearchBaseType) {
|
||||
const [partValue, setPartValue] = useState<string>("A");
|
||||
const [sidoValue, setSidoValue] = useState<string>("0");
|
||||
@@ -67,6 +69,30 @@ function SearchBaseForm ({
|
||||
const [middleRegionValue, setMiddleRegionValue] = useState<string>("0");
|
||||
const [envPartValue, setEnvPartValue] = useState<string>("0");
|
||||
const [resetTrigger, setResetTrigger] = useState(false);
|
||||
const renderButton = (type: "query" | "init" | "excel") => {
|
||||
switch (type) {
|
||||
case "query":
|
||||
return (
|
||||
<Button className={btnClass} onClick={queryVal} key="query">
|
||||
조회
|
||||
</Button>
|
||||
);
|
||||
case "init":
|
||||
return (
|
||||
<Button className={btnClass} onClick={initVal} key="init">
|
||||
초기화
|
||||
</Button>
|
||||
);
|
||||
case "excel":
|
||||
return (
|
||||
<Button className={btnClass} color="green" onClick={excelDownload} key="excel">
|
||||
{excelName ? excelName : "엑셀다운로드"}
|
||||
</Button>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const queryVal = () => {
|
||||
console.log("조회버튼 클릭");
|
||||
queryClick({
|
||||
@@ -145,16 +171,8 @@ function SearchBaseForm ({
|
||||
{leftChildren}
|
||||
</SearchFormLeft>
|
||||
<SearchFormRight className={rightClassName}>
|
||||
<Button className={btnClass} color="green" onClick={excelDownload}>
|
||||
{excelName?excelName:"엑셀다운로드"}
|
||||
</Button>
|
||||
<div>
|
||||
<Button className={btnClass} onClick={initVal}>
|
||||
초기화
|
||||
</Button>
|
||||
<Button className={btnClass} onClick={queryVal}>
|
||||
조회
|
||||
</Button>
|
||||
{(buttonOrder ?? ["excel", "init", "query"]).map(renderButton)}
|
||||
</div>
|
||||
{rightChildren}
|
||||
</SearchFormRight>
|
||||
|
||||
@@ -81,6 +81,8 @@ export function UiTable({
|
||||
const handlePageChange = (page: number) => {
|
||||
setCur(page);
|
||||
onPageChange(page);
|
||||
if (index == 0)
|
||||
setIdx(0)
|
||||
};
|
||||
|
||||
const handleCellClick = (index: number, headId: string) => {
|
||||
@@ -90,6 +92,7 @@ export function UiTable({
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{headName ? (
|
||||
@@ -116,14 +119,13 @@ export function UiTable({
|
||||
head.id.length > 1 &&
|
||||
(boldArray.includes(index) ? (
|
||||
<TableCell
|
||||
className={`${head.align === "right" ? "text-right" : head.align === "center" ? "text-center" : "text-center"} ${
|
||||
cellClick && idx === index
|
||||
className={`${head.align === "right" ? "text-right" : head.align === "center" ? "text-center" : "text-center"} ${cellClick && idx === index
|
||||
? "cursor-pointer !font-[1000]"
|
||||
: cellClick
|
||||
? "cursor-pointer"
|
||||
: ""
|
||||
} sticky z-40 bg-secondary !font-bold`}
|
||||
style={{ top: `${headerHeight + index * 39}px` }}
|
||||
} sticky top-20 z-40 bg-secondary !font-bold`}
|
||||
style={{ top: `${headerHeight + index * 32 - 4}px` }}
|
||||
onClick={() => handleCellClick(index, head.id)}
|
||||
key={`cell-${head.id}-${index}`}
|
||||
>
|
||||
@@ -131,39 +133,38 @@ export function UiTable({
|
||||
? String(el[head.id] ?? "")
|
||||
: String(el[head.id]).includes(".")
|
||||
? String(el[head.id])
|
||||
.split(".")
|
||||
.map((part, index) =>
|
||||
index === 0
|
||||
? part.replace(
|
||||
/\B(?=(\d{3})+(?!\d))/g,
|
||||
",",
|
||||
)
|
||||
: part,
|
||||
)
|
||||
.join(".")
|
||||
.split(".")
|
||||
.map((part, index) =>
|
||||
index === 0
|
||||
? part.replace(
|
||||
/\B(?=(\d{3})+(?!\d))/g,
|
||||
",",
|
||||
)
|
||||
: part,
|
||||
)
|
||||
.join(".")
|
||||
: String(el[head.id] ?? "").replace(
|
||||
/\B(?=(\d{3})+(?!\d))/g,
|
||||
",",
|
||||
)}
|
||||
/\B(?=(\d{3})+(?!\d))/g,
|
||||
",",
|
||||
)}
|
||||
</TableCell>
|
||||
) : (
|
||||
<TableCell
|
||||
className={`${head.align === "right" ? "text-right" : head.align === "center" ? "text-center" : "text-center"} ${
|
||||
cellClick && idx === index
|
||||
className={`${head.align === "right" ? "text-right" : head.align === "center" ? "text-center" : "text-center"} ${cellClick && idx === index
|
||||
? "cursor-pointer !font-[1000]"
|
||||
: cellClick
|
||||
? "cursor-pointer"
|
||||
: ""
|
||||
} mt-[${boldArray.length * 40}px]`}
|
||||
} mt-[${boldArray.length * 40}px]`}
|
||||
onClick={() => handleCellClick(index, head.id)}
|
||||
key={`cell-${head.id}-${index}`}
|
||||
>
|
||||
{
|
||||
//체크박스 유형애서 readOnly가 필요한 경우를 위해서 ChkClass 를 사용해서 readOnly 처리함.
|
||||
typeof el[head.id] === "object" &&
|
||||
el[head.id] !== null &&
|
||||
"chk" in (el[head.id] as object) &&
|
||||
"readonly" in (el[head.id] as object) ? (
|
||||
el[head.id] !== null &&
|
||||
"chk" in (el[head.id] as object) &&
|
||||
"readonly" in (el[head.id] as object) ? (
|
||||
<Checkbox
|
||||
checked={
|
||||
(el[head.id] as ChkClass)?.chk === true
|
||||
@@ -199,9 +200,9 @@ export function UiTable({
|
||||
.map((part, index) =>
|
||||
index === 0
|
||||
? part.replace(
|
||||
/\B(?=(\d{3})+(?!\d))/g,
|
||||
",",
|
||||
)
|
||||
/\B(?=(\d{3})+(?!\d))/g,
|
||||
",",
|
||||
)
|
||||
: part,
|
||||
)
|
||||
.join(".")
|
||||
|
||||
@@ -31,7 +31,7 @@ const TableHeader = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead
|
||||
ref={ref}
|
||||
className={cn("sticky top-0 z-10 bg-secondary [&_tr]:border-b", className)}
|
||||
className={cn("sticky -top-3 z-10 bg-secondary [&_tr]:border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user