Merge commit '381d95dd753b4af51433dc3d2815399d3223b4df' into iipbs/wr/hasuReg
iipbs_wr / build (push) Waiting to run

This commit is contained in:
leedj312
2025-06-26 10:50:43 +09:00
6 changed files with 95 additions and 8 deletions
@@ -53,7 +53,8 @@ export const excelArrayStyle = async (data: ArrayBuffer, range: SheetParameter[]
if (item.data && item.data.length > 0) {
const numCols = item.data[0]?.length || 0;
const numRows = item.data.length;
const dataRange = workbook.activeSheet(index)?.sheet(index)?.range(`A1:${String.fromCharCode(65 + numCols - 1)}${numRows}`);
const lastColLetter = getExcelColumnLetter(numCols);
const dataRange = workbook.activeSheet(index)?.sheet(index)?.range(`A1:${lastColLetter}${numRows}`);
dataRange?.style((styleQ) => {
styleQ.verticalAlignment("center");
});
@@ -62,4 +63,60 @@ export const excelArrayStyle = async (data: ArrayBuffer, range: SheetParameter[]
}
makeExcelFile(workbook, fileNm)
}
}
export const excelStyleWithKoreanHeaders = async (
data: ArrayBuffer,
range: string = "A1:V3",
fileNm: string,
headerMapping: { [key: string]: string },
sampleData: any
) => {
const workbook = await XlsxPopulate.Workbook.fromData(data);
const sheet = workbook.activeSheet();
if (!sheet) {
throw new Error('워크시트를 찾을 수 없습니다.');
}
// 1. 영문 헤더를 한글로 교체
const englishHeaders = Object.keys(sampleData);
englishHeaders.forEach((englishHeader, index) => {
const koreanHeader = headerMapping[englishHeader] || englishHeader;
const cellAddress = String.fromCharCode(65 + index) + '1'; // A1, B1, C1, ...
sheet.cell(cellAddress).value(koreanHeader);
});
// 2. 헤더 스타일 적용
const headerCells = sheet.range(range);
headerCells?.style((styleQ) => {
makeCellStyle(styleQ);
});
// 3. 모든 데이터 셀에 세로 가운데 정렬 적용
const numCols = englishHeaders.length;
const numRows = sheet.usedRange()?.endCell()?.rowNumber() || 1;
if (numRows > 1) {
const dataRange = sheet.range(`A1:${String.fromCharCode(65 + numCols - 1)}${numRows}`);
dataRange?.style((styleQ) => {
styleQ.verticalAlignment("center");
styleQ.borderEach({ style: "thin", color: { rgb: "CCCCCC" } });
});
}
// 4. 파일 다운로드
await makeExcelFile(workbook, fileNm);
};
const getExcelColumnLetter = (colIndex: number): string => {
let letter = '';
while (colIndex > 0) {
const mod = (colIndex - 1) % 26;
letter = String.fromCharCode(65 + mod) + letter;
colIndex = Math.floor((colIndex - 1) / 26);
}
return letter;
};
@@ -1,5 +1,5 @@
import * as XLSX from 'xlsx';
import { excelArrayStyle, excelStyle } from './excelStyle';
import { excelArrayStyle, excelStyle, excelStyleWithKoreanHeaders } from './excelStyle';
type ExcelDownloadProps = {
data: JSON | JSON[];
@@ -164,4 +164,38 @@ export const filePickedBlobArray = (oFile: ArrayBuffer, fileNm: string, sheetArr
});
await excelArrayStyle(XLSX.write(wb, { bookType: "xlsx", type: "buffer" }), sheetArray, fileNm)
});
}
}
type ExcelDownloadWithHeaderProps = {
data: JSON | JSON[];
fileName: string;
sheetNameArray: string[];
headerRange?: string;
headerMapping: { [key: string]: string };
}
export const downloadExcelWithKoreanHeaders = async ({
data,
fileName = new Date().getTime().toString() + '.xlsx',
sheetNameArray = ["Sheet1"],
headerRange = 'A1:V3',
headerMapping
}: ExcelDownloadWithHeaderProps) => {
const dataArray = Array.isArray(data) ? data : [data];
if (dataArray.length === 0) {
throw new Error('데이터가 없습니다.');
}
// 1. 원본 데이터로 워크시트 생성
const worksheet = XLSX.utils.json_to_sheet(dataArray);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, sheetNameArray[0]);
// 2. 워크북을 ArrayBuffer로 변환
const buffer = XLSX.write(workbook, { bookType: "xlsx", type: "buffer" });
// 3. 헤더만 한글로 교체하는 커스텀 스타일 함수 호출
await excelStyleWithKoreanHeaders(buffer, headerRange, fileName, headerMapping, dataArray[0]);
};
@@ -308,7 +308,6 @@ export const FileUploaderItem = forwardRef<
{children}
</div>
<button
type="button"
className={cn(
"absolute",
"!w-1/5 z-50",
@@ -223,7 +223,6 @@ function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = fa
{isSearch && (
<div className="absolute right-8 top-[305px] z-20">
<button
type="button"
onClick={() => setIsSearchModalOpen(true)}
className="overflow-hidden w-[40px] h-[40px] bg-white
border border-[#9aa7b8]
@@ -60,7 +60,6 @@ export default function GisContent({ edit = false, onFeatureSelect, isSearch, se
{isSearch && (
<div className="absolute right-8 top-[254px] z-20">
<button
type="button"
onClick={() => setIsSearchModalOpen(true)}
className="overflow-hidden w-[40px] h-[40px] bg-white
border border-[#9aa7b8]
@@ -20,7 +20,6 @@
// <TabsTrigger value="layers" className="flex-1 h-10">레이어</TabsTrigger>
// {/* {isSearch && (
// <button
// type="button"
// onClick={() => setIsSearchModalOpen(true)}
// className="h-10 w-10 flex items-center justify-center px-0"
// >