summaryrefslogtreecommitdiff
path: root/lib/menu-list/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/menu-list/table')
-rw-r--r--lib/menu-list/table/excel-export.ts107
-rw-r--r--lib/menu-list/table/export-button.tsx64
-rw-r--r--lib/menu-list/table/menu-list-table.tsx12
3 files changed, 179 insertions, 4 deletions
diff --git a/lib/menu-list/table/excel-export.ts b/lib/menu-list/table/excel-export.ts
new file mode 100644
index 00000000..feba5310
--- /dev/null
+++ b/lib/menu-list/table/excel-export.ts
@@ -0,0 +1,107 @@
+// app/evcp/menu-list/utils/excel-export.ts
+
+import ExcelJS from 'exceljs';
+
+interface MenuExportData {
+ menuPath: string;
+ menuTitle: string;
+ menuDescription?: string | null;
+ menuGroup?: string | null;
+ sectionTitle: string;
+ domain: string;
+ isActive: boolean;
+ manager1Name?: string | null;
+ manager1Email?: string | null;
+ manager2Name?: string | null;
+ manager2Email?: string | null;
+}
+
+export async function exportMenusToExcel(
+ menus: MenuExportData[],
+ translate: (key: string) => string
+) {
+ // 워크북 생성
+ const workbook = new ExcelJS.Workbook();
+ const worksheet = workbook.addWorksheet('메뉴 목록');
+
+ // 헤더 정의
+ worksheet.columns = [
+ { header: '메뉴 경로', key: 'menuPath', width: 40 },
+ { header: '메뉴명', key: 'menuTitle', width: 30 },
+ { header: '설명', key: 'menuDescription', width: 40 },
+ { header: '그룹', key: 'menuGroup', width: 20 },
+ { header: '섹션', key: 'sectionTitle', width: 20 },
+ { header: '도메인', key: 'domain', width: 15 },
+ { header: '상태', key: 'isActive', width: 10 },
+ { header: '담당자1 이름', key: 'manager1Name', width: 20 },
+ { header: '담당자1 이메일', key: 'manager1Email', width: 30 },
+ { header: '담당자2 이름', key: 'manager2Name', width: 20 },
+ { header: '담당자2 이메일', key: 'manager2Email', width: 30 },
+ ];
+
+ // 헤더 스타일 설정
+ worksheet.getRow(1).font = { bold: true };
+ worksheet.getRow(1).fill = {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: 'FFE0E0E0' }
+ };
+ worksheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' };
+
+ // 데이터 추가 (번역 적용)
+ menus.forEach((menu) => {
+ worksheet.addRow({
+ menuPath: menu.menuPath,
+ menuTitle: translate(menu.menuTitle),
+ menuDescription: menu.menuDescription ? translate(menu.menuDescription) : '',
+ menuGroup: menu.menuGroup ? translate(menu.menuGroup) : '',
+ sectionTitle: translate(menu.sectionTitle),
+ domain: menu.domain.toUpperCase(),
+ isActive: menu.isActive ? '활성' : '비활성',
+ manager1Name: menu.manager1Name || '',
+ manager1Email: menu.manager1Email || '',
+ manager2Name: menu.manager2Name || '',
+ manager2Email: menu.manager2Email || '',
+ });
+ });
+
+ // 모든 셀에 테두리 추가
+ worksheet.eachRow((row, rowNumber) => {
+ row.eachCell((cell) => {
+ cell.border = {
+ top: { style: 'thin' },
+ left: { style: 'thin' },
+ bottom: { style: 'thin' },
+ right: { style: 'thin' }
+ };
+ // 데이터 행은 세로 가운데 정렬
+ if (rowNumber > 1) {
+ cell.alignment = { vertical: 'middle', wrapText: true };
+ }
+ });
+ });
+
+ // 자동 필터 추가
+ worksheet.autoFilter = {
+ from: 'A1',
+ to: `K${menus.length + 1}`
+ };
+
+ // 파일 다운로드
+ const buffer = await workbook.xlsx.writeBuffer();
+ const blob = new Blob([buffer], {
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ });
+
+ // 다운로드 링크 생성
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ const fileName = `메뉴목록_${new Date().toISOString().slice(0, 10)}.xlsx`;
+
+ link.href = url;
+ link.download = fileName;
+ link.click();
+
+ // 메모리 정리
+ URL.revokeObjectURL(url);
+} \ No newline at end of file
diff --git a/lib/menu-list/table/export-button.tsx b/lib/menu-list/table/export-button.tsx
new file mode 100644
index 00000000..320e495f
--- /dev/null
+++ b/lib/menu-list/table/export-button.tsx
@@ -0,0 +1,64 @@
+// app/evcp/menu-list/components/export-button.tsx
+
+"use client";
+
+import { useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Download } from "lucide-react";
+import { toast } from "sonner";
+import { exportMenusToExcel } from "./excel-export";
+
+interface MenuAssignment {
+ id: number;
+ menuPath: string;
+ menuTitle: string;
+ menuDescription?: string | null;
+ menuGroup?: string | null;
+ sectionTitle: string;
+ domain: string;
+ isActive: boolean;
+ manager1Name?: string | null;
+ manager1Email?: string | null;
+ manager2Name?: string | null;
+ manager2Email?: string | null;
+}
+
+interface ExportButtonProps {
+ menus: MenuAssignment[];
+ translate: (key: string) => string;
+}
+
+export function ExportButton({ menus, translate }: ExportButtonProps) {
+ const [isExporting, setIsExporting] = useState(false);
+
+ const handleExport = async () => {
+ if (menus.length === 0) {
+ toast.error("내보낼 데이터가 없습니다.");
+ return;
+ }
+
+ setIsExporting(true);
+
+ try {
+ await exportMenusToExcel(menus, translate);
+ toast.success(`${menus.length}개의 메뉴 데이터를 엑셀로 내보냈습니다.`);
+ } catch (error) {
+ console.error("Export error:", error);
+ toast.error("엑셀 파일 생성 중 오류가 발생했습니다.");
+ } finally {
+ setIsExporting(false);
+ }
+ };
+
+ return (
+ <Button
+ onClick={handleExport}
+ disabled={isExporting || menus.length === 0}
+ variant="outline"
+ size="sm"
+ >
+ <Download className="mr-2 h-4 w-4" />
+ {isExporting ? "내보내는 중..." : `엑셀 다운로드 (${menus.length}개)`}
+ </Button>
+ );
+} \ No newline at end of file
diff --git a/lib/menu-list/table/menu-list-table.tsx b/lib/menu-list/table/menu-list-table.tsx
index dedbc9bf..3998e6b5 100644
--- a/lib/menu-list/table/menu-list-table.tsx
+++ b/lib/menu-list/table/menu-list-table.tsx
@@ -28,6 +28,7 @@ import { Search, Filter, ExternalLink } from "lucide-react";
import { toast } from "sonner";
import { ManagerSelect } from "./manager-select";
import { InitializeButton } from "./initialize-button";
+import { ExportButton } from "./export-button";
import { toggleMenuActive } from "../servcie";
interface MenuAssignment {
@@ -190,13 +191,16 @@ export function MenuListTable({ initialMenus, initialUsers }: MenuListTableProps
</div>
</div>
- {/* 결과 요약 및 초기화 버튼 */}
- <div className="flex items-center justify-between text-sm text-muted-foreground">
- <span>
+ {/* 결과 요약, 엑셀 다운로드 및 초기화 버튼 */}
+ <div className="flex items-center justify-between">
+ <span className="text-sm text-muted-foreground">
총 {filteredMenus.length}개의 메뉴
{searchQuery && ` (${initialMenus.length}개 중 검색 결과)`}
</span>
- <InitializeButton />
+ <div className="flex gap-2">
+ <ExportButton menus={filteredMenus} translate={safeTranslate} />
+ <InitializeButton />
+ </div>
</div>
{/* 테이블 */}