summaryrefslogtreecommitdiff
path: root/lib/welding
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-13 07:08:01 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-13 07:08:01 +0000
commitc72d0897f7b37843109c86f61d97eba05ba3ca0d (patch)
tree887dd877f3f8beafa92b4d9a7b16c84b4a5795d8 /lib/welding
parentff902243a658067fae858a615c0629aa2e0a4837 (diff)
(대표님) 20250613 16시 08분 b-rfq, document 등
Diffstat (limited to 'lib/welding')
-rw-r--r--lib/welding/service.ts41
-rw-r--r--lib/welding/table/exporft-ocr-data.ts90
-rw-r--r--lib/welding/table/ocr-table-columns.tsx1
-rw-r--r--lib/welding/table/ocr-table-toolbar-actions.tsx128
4 files changed, 231 insertions, 29 deletions
diff --git a/lib/welding/service.ts b/lib/welding/service.ts
index b3a69c36..feb6272b 100644
--- a/lib/welding/service.ts
+++ b/lib/welding/service.ts
@@ -7,7 +7,7 @@ import { filterColumns } from "@/lib/filter-columns";
import { tagClasses } from "@/db/schema/vendorData";
import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq } from "drizzle-orm";
import { GetOcrRowSchema, UpdateOcrRowSchema } from "./validation";
-import { ocrRows } from "@/db/schema";
+import { OcrRow, ocrRows, users } from "@/db/schema";
import { countOcrRows, selectOcrRows } from "./repository";
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/app/api/auth/[...nextauth]/route"
@@ -153,4 +153,43 @@ export async function modifyOcrRow(input: ModifyOcrRowInput) {
error: "OCR 행을 업데이트하는 중 오류가 발생했습니다."
};
}
+}
+
+
+export async function getOcrAllRows(): Promise<OcrRow[]> {
+ try {
+ const allRows = await db
+ .select({
+ // ocrRows의 모든 필드
+ id: ocrRows.id,
+ tableId: ocrRows.tableId,
+ sessionId: ocrRows.sessionId,
+ rowIndex: ocrRows.rowIndex,
+ reportNo: ocrRows.reportNo,
+ no: ocrRows.no,
+ identificationNo: ocrRows.identificationNo,
+ tagNo: ocrRows.tagNo,
+ jointNo: ocrRows.jointNo,
+ jointType: ocrRows.jointType,
+ weldingDate: ocrRows.weldingDate,
+ confidence: ocrRows.confidence,
+ sourceTable: ocrRows.sourceTable,
+ sourceRow: ocrRows.sourceRow,
+ userId: ocrRows.userId,
+ createdAt: ocrRows.createdAt,
+
+ // users 테이블의 필드
+ userName: users.name,
+ userEmail: users.email,
+ })
+ .from(ocrRows)
+ .leftJoin(users, eq(ocrRows.userId, users.id))
+ .orderBy(desc(ocrRows.createdAt))
+
+
+ return allRows
+ } catch (error) {
+ console.error("Error fetching all OCR rows:", error)
+ throw new Error("Failed to fetch all OCR data")
+ }
} \ No newline at end of file
diff --git a/lib/welding/table/exporft-ocr-data.ts b/lib/welding/table/exporft-ocr-data.ts
new file mode 100644
index 00000000..76856808
--- /dev/null
+++ b/lib/welding/table/exporft-ocr-data.ts
@@ -0,0 +1,90 @@
+// lib/export-ocr-data.ts
+import ExcelJS from "exceljs"
+import type { OcrRow } from "@/db/schema"
+
+/**
+ * OCR 데이터를 엑셀로 내보내는 단순한 함수
+ */
+export async function exportOcrDataToExcel(
+ data: OcrRow[],
+ filename: string = "OCR Data"
+): Promise<void> {
+ if (!data || data.length === 0) {
+ throw new Error("No data to export")
+ }
+
+ // 워크북 생성
+ const workbook = new ExcelJS.Workbook()
+ const worksheet = workbook.addWorksheet("OCR Data")
+
+ // 컬럼 정의 (OCR 데이터에 맞게 설정)
+ const columns = [
+ { key: "reportNo", header: "Report No", width: 15 },
+ { key: "no", header: "No", width: 10 },
+ { key: "identificationNo", header: "Identification No", width: 20 },
+ { key: "tagNo", header: "Tag No", width: 15 },
+ { key: "jointNo", header: "Joint No", width: 15 },
+ { key: "jointType", header: "Joint Type", width: 15 },
+ { key: "weldingDate", header: "Welding Date", width: 15 },
+ { key: "confidence", header: "confidence", width: 15 },
+ { key: "userName", header: "사용자 이름", width: 15 },
+ { key: "userEmail", header: "사용자 이메일", width: 15 },
+ { key: "createdAt", header: "생성일", width: 20 },
+ // 필요한 다른 컬럼들도 추가 가능
+ ]
+
+ // 워크시트에 컬럼 설정
+ worksheet.columns = columns
+
+ // 헤더 스타일 설정
+ const headerRow = worksheet.getRow(1)
+ headerRow.font = { bold: true }
+ headerRow.alignment = { horizontal: "center" }
+ headerRow.fill = {
+ type: "pattern",
+ pattern: "solid",
+ fgColor: { argb: "FFCCCCCC" },
+ }
+
+ // 데이터 추가
+ data.forEach((row) => {
+ worksheet.addRow({
+ reportNo: row.reportNo || "",
+ no: row.no || "",
+ identificationNo: row.identificationNo || "",
+ tagNo: row.tagNo || "",
+ jointNo: row.jointNo || "",
+ jointType: row.jointType || "",
+ confidence: row.confidence || "",
+ weldingDate: row.weldingDate ? new Date(row.weldingDate).toLocaleDateString() : "",
+ userName: row.userName || "",
+ userEmail: row.userEmail || "",
+
+ createdAt: row.createdAt ? new Date(row.createdAt).toLocaleString() : "",
+ })
+ })
+
+ // 모든 행에 테두리 추가
+ worksheet.eachRow((row, rowNumber) => {
+ row.eachCell((cell) => {
+ cell.border = {
+ top: { style: "thin" },
+ left: { style: "thin" },
+ bottom: { style: "thin" },
+ right: { style: "thin" },
+ }
+ })
+ })
+
+ // 파일 다운로드
+ 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")
+ link.href = url
+ link.download = `${filename}.xlsx`
+ link.click()
+ URL.revokeObjectURL(url)
+} \ No newline at end of file
diff --git a/lib/welding/table/ocr-table-columns.tsx b/lib/welding/table/ocr-table-columns.tsx
index d1aefe06..93ee1004 100644
--- a/lib/welding/table/ocr-table-columns.tsx
+++ b/lib/welding/table/ocr-table-columns.tsx
@@ -265,6 +265,7 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<OcrRow>
)
},
enableSorting: true,
+ size:100
},
// Created At 컬럼
{
diff --git a/lib/welding/table/ocr-table-toolbar-actions.tsx b/lib/welding/table/ocr-table-toolbar-actions.tsx
index 6c6d0637..120ff54f 100644
--- a/lib/welding/table/ocr-table-toolbar-actions.tsx
+++ b/lib/welding/table/ocr-table-toolbar-actions.tsx
@@ -2,9 +2,8 @@
import * as React from "react"
import { type Table } from "@tanstack/react-table"
-import { Download, RefreshCcw, Upload, FileText, Loader2 } from "lucide-react"
+import { Download, RefreshCcw, Upload, FileText, Loader2, ChevronDown } from "lucide-react"
-import { exportTableToExcel } from "@/lib/export"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
@@ -20,6 +19,16 @@ import { Progress } from "@/components/ui/progress"
import { Badge } from "@/components/ui/badge"
import { toast } from "sonner"
import { OcrRow } from "@/db/schema"
+import { exportTableToExcel } from "@/lib/export_all"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { getOcrAllRows } from "../service"
+import { exportOcrDataToExcel } from "./exporft-ocr-data"
interface OcrTableToolbarActionsProps {
table: Table<OcrRow>
@@ -38,6 +47,7 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
const [isUploadDialogOpen, setIsUploadDialogOpen] = React.useState(false)
const [selectedFile, setSelectedFile] = React.useState<File | null>(null)
const fileInputRef = React.useRef<HTMLInputElement>(null)
+ const [isExporting, setIsExporting] = React.useState(false)
// 다이얼로그 닫기 핸들러 - 업로드 중에는 닫기 방지
const handleDialogOpenChange = (open: boolean) => {
@@ -50,11 +60,11 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
})
return // 다이얼로그를 닫지 않음
}
-
+
// 업로드가 진행 중이 아니거나 완료되었으면 초기화 후 닫기
resetUpload()
}
-
+
setIsUploadDialogOpen(open)
}
@@ -75,12 +85,12 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
const allowedTypes = [
'application/pdf',
'image/jpeg',
- 'image/jpg',
+ 'image/jpg',
'image/png',
'image/tiff',
'image/bmp'
]
-
+
if (!allowedTypes.includes(file.type)) {
return "Only PDF and image files (JPG, PNG, TIFF, BMP) are supported"
}
@@ -151,8 +161,8 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
toast.success(
`OCR completed! Extracted ${result.metadata.totalRows} rows from ${result.metadata.totalTables} tables`,
{
- description: result.warnings?.length
- ? `Warnings: ${result.warnings.join(', ')}`
+ description: result.warnings?.length
+ ? `Warnings: ${result.warnings.join(', ')}`
: undefined
}
)
@@ -161,7 +171,7 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
setTimeout(() => {
setIsUploadDialogOpen(false)
resetUpload()
-
+
// 테이블 새로고침
window.location.reload()
}, 2000)
@@ -205,6 +215,50 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
}
}
+ // 현재 페이지 데이터만 내보내기
+ const exportCurrentPage = () => {
+ exportTableToExcel(table, {
+ filename: "OCR Result (Current Page)",
+ excludeColumns: ["select", "actions"],
+ })
+ }
+
+ // 전체 데이터 내보내기
+ const exportAllData = async () => {
+ if (isExporting) return
+
+ setIsExporting(true)
+
+ try {
+ toast.loading("전체 데이터를 가져오는 중...", {
+ description: "잠시만 기다려주세요."
+ })
+
+ // 모든 데이터 가져오기
+ const allData = await getOcrAllRows()
+
+ toast.dismiss()
+
+ if (allData.length === 0) {
+ toast.warning("내보낼 데이터가 없습니다.")
+ return
+ }
+
+ console.log(allData)
+
+ // 새로운 단순한 export 함수 사용
+ await exportOcrDataToExcel(allData, `OCR Result (All Data - ${allData.length} rows)`)
+
+ toast.success(`전체 데이터 ${allData.length}개 행이 성공적으로 내보내졌습니다.`)
+
+ } catch (error) {
+ console.error('Error exporting all data:', error)
+ toast.error('전체 데이터 내보내기 중 오류가 발생했습니다.')
+ } finally {
+ setIsExporting(false)
+ }
+ }
+
return (
<div className="flex items-center gap-2">
{/* OCR 업로드 다이얼로그 */}
@@ -215,7 +269,7 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
<span className="hidden sm:inline">Upload OCR</span>
</Button>
</DialogTrigger>
- <DialogContent
+ <DialogContent
className="sm:max-w-md"
// 업로드 중에는 ESC 키로도 닫기 방지
onEscapeKeyDown={(e) => {
@@ -241,13 +295,13 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
)}
</DialogTitle>
<DialogDescription>
- {isUploading && uploadProgress?.stage !== "complete"
+ {isUploading && uploadProgress?.stage !== "complete"
? "Processing in progress. Please do not close this dialog."
: "Upload a PDF or image file to extract table data using OCR technology."
}
</DialogDescription>
</DialogHeader>
-
+
<div className="space-y-4">
{/* 파일 선택 */}
<div className="space-y-2">
@@ -292,7 +346,7 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
<p className="text-xs text-muted-foreground">
{uploadProgress.message}
</p>
-
+
{/* 진행 중일 때 안내 메시지 */}
{isUploading && uploadProgress.stage !== "complete" && (
<div className="flex items-center gap-2 p-2 bg-blue-50 dark:bg-blue-950/20 rounded-md">
@@ -332,22 +386,40 @@ export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) {
</div>
</DialogContent>
</Dialog>
-
+
{/* Export 버튼 */}
- <Button
- variant="outline"
- size="sm"
- onClick={() =>
- exportTableToExcel(table, {
- filename: "OCR Result",
- excludeColumns: ["select", "actions"],
- })
- }
- className="gap-2"
- >
- <Download className="size-4" aria-hidden="true" />
- <span className="hidden sm:inline">Export</span>
- </Button>
+ {/* Export 드롭다운 메뉴 */}
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ className="gap-2"
+ disabled={isExporting}
+ >
+ {isExporting ? (
+ <Loader2 className="size-4 animate-spin" aria-hidden="true" />
+ ) : (
+ <Download className="size-4" aria-hidden="true" />
+ )}
+ <span className="hidden sm:inline">
+ {isExporting ? "Exporting..." : "Export"}
+ </span>
+ <ChevronDown className="size-3" aria-hidden="true" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end" className="w-[200px]">
+ <DropdownMenuItem onClick={exportAllData} disabled={isExporting}>
+ <Download className="mr-2 size-4" />
+ 전체 데이터 내보내기
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem onClick={exportCurrentPage} disabled={isExporting}>
+ <Download className="mr-2 size-4" />
+ 현재 페이지 내보내기
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
</div>
)
} \ No newline at end of file