Compare commits

...
7 changed files with 120 additions and 22 deletions
@@ -109,6 +109,41 @@ export default function LineBarchart({
const lineMaxValue = getMaxValue(chartData, 'lineVal');
const barMaxValue = getMaxValue(chartData, 'barVal');
/**
* 최대값과 구간 수에 따른 범례 만들기
* maxValue : 최대값, segments : 구간 수 (구간수가 5면 눈금은 6)
*/
const makeNiceScale = (maxValue: number, segments = 5) => {
// 최대값이 이상할 경우 0, 0으로 반환
if (!isFinite(maxValue) || maxValue <= 0) {
return { max: 0, ticks: [0] };
}
const exponent = Math.floor(Math.log10(maxValue));
const base = Math.pow(10, exponent);
const fraction = maxValue / base;
let niceFraction: number;
if (fraction <= 1) niceFraction = 1;
else if (fraction <= 2) niceFraction = 2;
else if (fraction <= 3) niceFraction = 3;
else if (fraction <= 4) niceFraction = 4;
else if (fraction <= 5) niceFraction = 5;
else if (fraction <= 6) niceFraction = 6;
else if (fraction <= 7) niceFraction = 7;
else if (fraction <= 8) niceFraction = 8;
else if (fraction <= 9) niceFraction = 9;
else niceFraction = 10;
const niceMax = niceFraction * base; // round up to 1/2/5 * 10^k
const step = niceMax / segments;
const ticks = Array.from({ length: segments + 1 }, (_, i) => Number((i * step).toFixed(10)));
return { max: niceMax, ticks };
};
const leftScale = makeNiceScale(lineMaxValue, 5);
const rightScale = makeNiceScale(barMaxValue, 5);
const getTicks = (data : any []) => {
const monthSet = new Set<string>();
const rtnData : string[] = [];
@@ -135,7 +170,7 @@ export default function LineBarchart({
return (
<text
x={x - padding} // 맨 좌측 바깥
y={y + height / 2} // 수직 중앙
y={y + height / 2 - 12} // 수직 중앙
textAnchor="middle"
transform={`rotate(-90, ${x - padding}, ${y + height / 2})`}
className="text-[12px] font-bold"
@@ -166,26 +201,27 @@ export default function LineBarchart({
</div>
) : null}
<div className="h-[calc(100%-42px)] w-[calc(100%-40px)] ">
<ChartContainer config={chartConfig ?? {}} className="h-full w-full">
<ChartContainer config={chartConfig ?? {}} className="h-full w-[100%] ml-5">
<ComposedChart
data={chartData}
height={graphSize * 7.5}
width={graphSize * 12.5}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
margin={{ top: 5, right: 20, left: 30, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
{setXAxisYearTick?
<XAxis dataKey="label" ticks={monthTicks} tickFormatter={(value) => value.slice(0,7)} fontSize="8px" fontWeight="bold"/> :
<XAxis dataKey="label" fontSize="8px" fontWeight="bold"/>
<XAxis dataKey="label" ticks={monthTicks} tickFormatter={(value) => value.slice(0,7)} className="text-[12px] font-bold" /> :
<XAxis dataKey="label" className="text-[12px] font-bold" />
}
{/* 왼쪽 Y축: lineVal */}
{yFieldText && <YAxis
yAxisId="left"
className="text-[12px] font-bold"
tickCount={6}
className="text-[12px] font-bold"
tickFormatter={(value) =>
typeof value === "number" ? value.toLocaleString() : value
}
domain={[0, leftScale.max]}
ticks={leftScale.ticks}
orientation="left"
label={(props) => <CustomAxisText {...props} fieldText={yFieldText} position="left" />}
/>}
@@ -193,12 +229,13 @@ export default function LineBarchart({
{yFieldText2 && <YAxis
yAxisId="right"
className="text-[12px] font-bold"
tickCount={6}
orientation="right"
tickFormatter={(value) =>
typeof value === "number" ? value.toLocaleString() : value
}
label={(props) => <CustomAxisText {...props} fieldText={yFieldText} position="right" />}
domain={[0, rightScale.max]}
ticks={rightScale.ticks}
label={(props) => <CustomAxisText {...props} fieldText={yFieldText2} position="right" />}
reversed={true}
/>}
<Tooltip content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />} />
@@ -235,15 +235,13 @@ export default function Linechart({
{setXAxisYearTick ?
<XAxis
dataKey="label"
fontSize="8px"
fontWeight="bold"
className="text-[12px] font-bold"
ticks={monthTicks}
tickFormatter={(value) => value.slice(0,7)}
/> :
<XAxis
dataKey="label"
fontSize="8px"
fontWeight="bold"
className="text-[12px] font-bold"
/>
}
@@ -6,6 +6,10 @@ type ExcelDownloadProps = {
fileName: string;
sheetNameArray: string[];
headerRange?: string;
decimalArray0? : number[];
decimalArray1? : number[];
decimalArray2? : number[];
decimalArray3? : number[];
}
export const downloadExcel = ({ data, fileName = new Date().getTime().toString() + '.xlsx', sheetNameArray = ["Sheet1"], headerRange='A1:V3'}: ExcelDownloadProps) => {
@@ -15,6 +19,45 @@ export const downloadExcel = ({ data, fileName = new Date().getTime().toString()
excelStyle(XLSX.write(workbook, { bookType: "xlsx", type: "buffer" }), headerRange, fileName);
};
export const downloadExcelFormat = ({ data, fileName = new Date().getTime().toString() + '.xlsx', sheetNameArray = ["Sheet1"], headerRange='A1:V3', decimalArray0=[], decimalArray1=[], decimalArray2=[], decimalArray3=[]}: ExcelDownloadProps) => {
const worksheet = XLSX.utils.json_to_sheet(Array.isArray(data) ? data : [data]);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, sheetNameArray[0]);
console.log('decimalArray0 : ', decimalArray0);
const headerEnd = XLSX.utils.decode_range(headerRange).e.r; // 0-based index
const startRow = headerEnd + 1;
// 🔹 전체 범위
const range = XLSX.utils.decode_range(worksheet["!ref"] as string);
const chaneValue = (array: number[], decimal: number) => {
for (let row = startRow; row <= range.e.r; row++) {
if(array.length>0){
for(let idx=0; idx<array.length; idx++){
const cellAddr = XLSX.utils.encode_cell({ r: row, c: Number(array[idx]) });
const cell = worksheet[cellAddr];
if (cell && typeof cell.v === "number") {
if(decimal===1){
cell.z = "#,##0.0"; // 소수점 3자리
}else if(decimal===2){
cell.z = "#,##0.00";
}else if(decimal===3){
cell.z = "#,##0.000";
}else{
cell.z = "#,##0"; // 기본 천단위 콤마
}
}
}
}
}
};
chaneValue(decimalArray0, 0);
chaneValue(decimalArray1, 1);
chaneValue(decimalArray2, 2);
chaneValue(decimalArray3, 3);
excelStyle(XLSX.write(workbook, { bookType: "xlsx", type: "buffer" }), headerRange, fileName);
};
export type SheetParameter = {
data: any[][];
headerNum: number;
@@ -331,11 +331,7 @@ function Gis({
)}
{/* SearchView가 존재할 때만 렌더링 */}
{SearchView && (
<SearchView
open={isSearchModalOpen}
onOpenChange={setIsSearchModalOpen}
onFeatureSelect={handleFeatureSelect}
/>
<SearchView />
)}
{/* 시도/시군구 조회 영역 */}
{SearchBar && (
@@ -165,7 +165,10 @@ export const SearchBar = ({
let list: SelectDataType[] = [];
if (type === "tb_il_swage_prcs_fclt") {
const cqlFilter = `ctpv_nm='${sidoNm}' and sgg_nm='${sggNm}' and swtrmt_fclt_nm like '%${fcltyNm}%'`;
// 하수처리시설
let cqlFilter = `swtrmt_fclt_nm like '%${fcltyNm}%'`;
if (sggNm !== "시군구" && sggNm) cqlFilter = `sgg_nm='${sggNm}' and ` + cqlFilter;
if (sidoNm !== "시도" && sidoNm) cqlFilter = `ctpv_nm='${sidoNm}' and ` + cqlFilter;
url += encodeURIComponent(cqlFilter);
const res = await fetch(url);
const json = await res.json();
@@ -8,7 +8,8 @@ interface TableUpperProps {
row? : number;
rowSpan?: number;
colSpan?: number;
visible?:boolean;
visible?:boolean;
widthPercent?: number;
}
const HeadMakeColSpan = ({ upHeadList }: { upHeadList: TableUpperProps[] }) => {
@@ -52,7 +53,7 @@ const HeadMakeColSpan = ({ upHeadList }: { upHeadList: TableUpperProps[] }) => {
colSpan={head.colSpan || 1}
rowSpan={head.rowSpan || 1}
style={{ whiteSpace: 'pre-line' }}
className="bg-gray-50"
className="bg-gray-50"
>
{head.title}
</TableHead>
@@ -32,6 +32,7 @@ type UiTableProps = {
index?: number;
curPage?: number;
headButton?: React.ReactNode | undefined;
widthFixed?: boolean;
};
type UiHeadProps = {
@@ -39,6 +40,9 @@ type UiHeadProps = {
title: string;
align?: string;
visible?: boolean;
row?: number;
widthPercent?: number;
rowSpan?: number;
};
export function UiTable({
@@ -57,6 +61,7 @@ export function UiTable({
index = -1,
curPage = 1,
headButton = undefined,
widthFixed = false
}: UiTableProps) {
const [cur, setCur] = useState<number>(curPage);
const [idx, setIdx] = useState<number>(index);
@@ -104,6 +109,15 @@ export function UiTable({
}
};
const getLeafCols = (list: UiHeadProps[]) => {
const maxRow = list?.reduce((max, head) => Math.max(max, head.row || 0), 0);
return list
.filter(h => h.visible !== false)
.filter(h => ((h.row ?? 0) + (h.rowSpan ?? 1) - 1) >= maxRow)
;
};
const leafCols = getLeafCols(headlist);
return (
<>
@@ -122,7 +136,13 @@ export function UiTable({
<></>
)}
<div className={`card !p-0r !m-[1.2rem] ${className}`}>
<Table variant="secondary">
<Table variant="secondary" className={widthFixed?"table-fixed":""}>
{widthFixed?
<colgroup>
{leafCols.map(col => (
<col key={col.id} style={{ width: col.widthPercent ? `${col.widthPercent}%` : undefined }} />
))}
</colgroup>:""}
<TableHeader ref={headerRef} className="font-bold">
{children ? children : <HeadMakeColSpan upHeadList={headlist} />}
</TableHeader>