summaryrefslogtreecommitdiff
path: root/lib/tech-vendors/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendors/table')
-rw-r--r--lib/tech-vendors/table/add-vendor-dialog.tsx458
-rw-r--r--lib/tech-vendors/table/excel-template-download.tsx276
-rw-r--r--lib/tech-vendors/table/import-button.tsx604
-rw-r--r--lib/tech-vendors/table/tech-vendors-table-columns.tsx29
-rw-r--r--lib/tech-vendors/table/tech-vendors-table-toolbar-actions.tsx19
-rw-r--r--lib/tech-vendors/table/tech-vendors-table.tsx17
-rw-r--r--lib/tech-vendors/table/update-vendor-sheet.tsx2
-rw-r--r--lib/tech-vendors/table/vendor-all-export.ts10
8 files changed, 954 insertions, 461 deletions
diff --git a/lib/tech-vendors/table/add-vendor-dialog.tsx b/lib/tech-vendors/table/add-vendor-dialog.tsx
new file mode 100644
index 00000000..bc260d51
--- /dev/null
+++ b/lib/tech-vendors/table/add-vendor-dialog.tsx
@@ -0,0 +1,458 @@
+"use client"
+
+import * as React from "react"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useForm } from "react-hook-form"
+import { toast } from "sonner"
+import { z } from "zod"
+
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { Input } from "@/components/ui/input"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
+import { Textarea } from "@/components/ui/textarea"
+import { Plus, Loader2 } from "lucide-react"
+
+import { addTechVendor } from "../service"
+
+// 폼 스키마 정의
+const addVendorSchema = z.object({
+ vendorName: z.string().min(1, "업체명을 입력해주세요"),
+ vendorCode: z.string().optional(),
+ email: z.string().email("올바른 이메일 주소를 입력해주세요"),
+ taxId: z.string().optional(),
+ country: z.string().optional(),
+ countryEng: z.string().optional(),
+ countryFab: z.string().optional(),
+ agentName: z.string().optional(),
+ agentPhone: z.string().optional(),
+ agentEmail: z.string().email("올바른 이메일 주소를 입력해주세요").optional().or(z.literal("")),
+ address: z.string().optional(),
+ phone: z.string().optional(),
+ website: z.string().optional(),
+ techVendorType: z.enum(["조선", "해양TOP", "해양HULL"], {
+ required_error: "벤더 타입을 선택해주세요",
+ }),
+ representativeName: z.string().optional(),
+ representativeEmail: z.string().email("올바른 이메일 주소를 입력해주세요").optional().or(z.literal("")),
+ representativePhone: z.string().optional(),
+ representativeBirth: z.string().optional(),
+})
+
+type AddVendorFormData = z.infer<typeof addVendorSchema>
+
+interface AddVendorDialogProps {
+ onSuccess?: () => void
+}
+
+export function AddVendorDialog({ onSuccess }: AddVendorDialogProps) {
+ const [open, setOpen] = React.useState(false)
+ const [isLoading, setIsLoading] = React.useState(false)
+
+ const form = useForm<AddVendorFormData>({
+ resolver: zodResolver(addVendorSchema),
+ defaultValues: {
+ vendorName: "",
+ vendorCode: "",
+ email: "",
+ taxId: "",
+ country: "",
+ countryEng: "",
+ countryFab: "",
+ agentName: "",
+ agentPhone: "",
+ agentEmail: "",
+ address: "",
+ phone: "",
+ website: "",
+ techVendorType: undefined,
+ representativeName: "",
+ representativeEmail: "",
+ representativePhone: "",
+ representativeBirth: "",
+ },
+ })
+
+ const onSubmit = async (data: AddVendorFormData) => {
+ setIsLoading(true)
+ try {
+ const result = await addTechVendor({
+ ...data,
+ vendorCode: data.vendorCode || null,
+ country: data.country || null,
+ countryEng: data.countryEng || null,
+ countryFab: data.countryFab || null,
+ agentName: data.agentName || null,
+ agentPhone: data.agentPhone || null,
+ agentEmail: data.agentEmail || null,
+ address: data.address || null,
+ phone: data.phone || null,
+ website: data.website || null,
+ representativeName: data.representativeName || null,
+ representativeEmail: data.representativeEmail || null,
+ representativePhone: data.representativePhone || null,
+ representativeBirth: data.representativeBirth || null,
+ taxId: data.taxId || "",
+ })
+
+ if (result.success) {
+ toast.success("벤더가 성공적으로 추가되었습니다.")
+ form.reset()
+ setOpen(false)
+ onSuccess?.()
+ } else {
+ toast.error(result.error || "벤더 추가 중 오류가 발생했습니다.")
+ }
+ } catch (error) {
+ console.error("벤더 추가 오류:", error)
+ toast.error("벤더 추가 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={setOpen}>
+ <DialogTrigger asChild>
+ <Button size="sm" className="gap-2">
+ <Plus className="size-4" />
+ 벤더 추가
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
+ <DialogHeader>
+ <DialogTitle>새 기술영업 벤더 추가</DialogTitle>
+ <DialogDescription>
+ 새로운 기술영업 벤더 정보를 입력하고 사용자 계정을 생성합니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <Form {...form}>
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
+ {/* 기본 정보 */}
+ <div className="space-y-4">
+ <h3 className="text-lg font-medium">기본 정보</h3>
+ <div className="grid grid-cols-2 gap-4">
+ <FormField
+ control={form.control}
+ name="vendorName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>업체명 *</FormLabel>
+ <FormControl>
+ <Input placeholder="업체명을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="vendorCode"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>업체 코드</FormLabel>
+ <FormControl>
+ <Input placeholder="업체 코드를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ <div className="grid grid-cols-2 gap-4">
+ <FormField
+ control={form.control}
+ name="email"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>이메일 *</FormLabel>
+ <FormControl>
+ <Input type="email" placeholder="이메일을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="taxId"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>사업자등록번호</FormLabel>
+ <FormControl>
+ <Input placeholder="사업자등록번호를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ <FormField
+ control={form.control}
+ name="techVendorType"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>벤더 타입 *</FormLabel>
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="벤더 타입을 선택하세요" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="조선">조선</SelectItem>
+ <SelectItem value="해양TOP">해양TOP</SelectItem>
+ <SelectItem value="해양HULL">해양HULL</SelectItem>
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* 연락처 정보 */}
+ <div className="space-y-4">
+ <h3 className="text-lg font-medium">연락처 정보</h3>
+ <div className="grid grid-cols-2 gap-4">
+ <FormField
+ control={form.control}
+ name="phone"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>전화번호</FormLabel>
+ <FormControl>
+ <Input placeholder="전화번호를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="website"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>웹사이트</FormLabel>
+ <FormControl>
+ <Input placeholder="웹사이트 URL을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ <FormField
+ control={form.control}
+ name="address"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>주소</FormLabel>
+ <FormControl>
+ <Textarea placeholder="주소를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* 국가 정보 */}
+ <div className="space-y-4">
+ <h3 className="text-lg font-medium">국가 정보</h3>
+ <div className="grid grid-cols-3 gap-4">
+ <FormField
+ control={form.control}
+ name="country"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>국가</FormLabel>
+ <FormControl>
+ <Input placeholder="국가를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="countryEng"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>국가 (영문)</FormLabel>
+ <FormControl>
+ <Input placeholder="국가 영문명을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="countryFab"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>제조국가</FormLabel>
+ <FormControl>
+ <Input placeholder="제조국가를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+ </div>
+
+ {/* 담당자 정보 */}
+ <div className="space-y-4">
+ <h3 className="text-lg font-medium">담당자 정보</h3>
+ <div className="grid grid-cols-2 gap-4">
+ <FormField
+ control={form.control}
+ name="agentName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>담당자명</FormLabel>
+ <FormControl>
+ <Input placeholder="담당자명을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="agentPhone"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>담당자 전화번호</FormLabel>
+ <FormControl>
+ <Input placeholder="담당자 전화번호를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+ <FormField
+ control={form.control}
+ name="agentEmail"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>담당자 이메일</FormLabel>
+ <FormControl>
+ <Input type="email" placeholder="담당자 이메일을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* 대표자 정보 */}
+ <div className="space-y-4">
+ <h3 className="text-lg font-medium">대표자 정보</h3>
+ <div className="grid grid-cols-2 gap-4">
+ <FormField
+ control={form.control}
+ name="representativeName"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자명</FormLabel>
+ <FormControl>
+ <Input placeholder="대표자명을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="representativePhone"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자 전화번호</FormLabel>
+ <FormControl>
+ <Input placeholder="대표자 전화번호를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+ <div className="grid grid-cols-2 gap-4">
+ <FormField
+ control={form.control}
+ name="representativeEmail"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자 이메일</FormLabel>
+ <FormControl>
+ <Input type="email" placeholder="대표자 이메일을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="representativeBirth"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>대표자 생년월일</FormLabel>
+ <FormControl>
+ <Input placeholder="YYYY-MM-DD" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => setOpen(false)}
+ disabled={isLoading}
+ >
+ 취소
+ </Button>
+ <Button type="submit" disabled={isLoading}>
+ {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ 벤더 추가
+ </Button>
+ </DialogFooter>
+ </form>
+ </Form>
+ </DialogContent>
+ </Dialog>
+ )
+} \ No newline at end of file
diff --git a/lib/tech-vendors/table/excel-template-download.tsx b/lib/tech-vendors/table/excel-template-download.tsx
index 65b880da..db2c5fb5 100644
--- a/lib/tech-vendors/table/excel-template-download.tsx
+++ b/lib/tech-vendors/table/excel-template-download.tsx
@@ -1,128 +1,150 @@
-import * as ExcelJS from 'exceljs';
-import { saveAs } from "file-saver";
-
-// 벤더 타입 enum
-const VENDOR_TYPES = ["조선", "해양TOP", "해양HULL"] as const;
-
-/**
- * 기술영업 벤더 데이터 가져오기를 위한 Excel 템플릿 파일 생성 및 다운로드
- */
-export async function exportTechVendorTemplate() {
- // 워크북 생성
- const workbook = new ExcelJS.Workbook();
- workbook.creator = 'Tech Vendor Management System';
- workbook.created = new Date();
-
- // 워크시트 생성
- const worksheet = workbook.addWorksheet('기술영업 벤더');
-
- // 컬럼 헤더 정의 및 스타일 적용
- worksheet.columns = [
- { header: '업체명', key: 'vendorName', width: 20 },
- { header: '이메일', key: 'email', width: 25 },
- { header: '사업자등록번호', key: 'taxId', width: 15 },
- { header: '벤더타입', key: 'techVendorType', width: 15 },
- { header: '주소', key: 'address', width: 30 },
- { header: '국가', key: 'country', width: 15 },
- { header: '전화번호', key: 'phone', width: 15 },
- { header: '웹사이트', key: 'website', width: 25 },
- { header: '아이템', key: 'items', width: 30 },
- ];
-
- // 헤더 스타일 적용
- const headerRow = worksheet.getRow(1);
- headerRow.font = { bold: true };
- headerRow.fill = {
- type: 'pattern',
- pattern: 'solid',
- fgColor: { argb: 'FFE0E0E0' }
- };
- headerRow.alignment = { vertical: 'middle', horizontal: 'center' };
-
- // 테두리 스타일 적용
- headerRow.eachCell((cell) => {
- cell.border = {
- top: { style: 'thin' },
- left: { style: 'thin' },
- bottom: { style: 'thin' },
- right: { style: 'thin' }
- };
- });
-
- // 샘플 데이터 추가
- const sampleData = [
- {
- vendorName: '샘플 업체 1',
- email: 'sample1@example.com',
- taxId: '123-45-67890',
- techVendorType: '조선',
- address: '서울시 강남구',
- country: '대한민국',
- phone: '02-1234-5678',
- website: 'https://example1.com',
- items: 'ITEM001,ITEM002'
- },
- {
- vendorName: '샘플 업체 2',
- email: 'sample2@example.com',
- taxId: '234-56-78901',
- techVendorType: '해양TOP',
- address: '부산시 해운대구',
- country: '대한민국',
- phone: '051-234-5678',
- website: 'https://example2.com',
- items: 'ITEM003,ITEM004'
- }
- ];
-
- // 데이터 행 추가
- sampleData.forEach(item => {
- worksheet.addRow(item);
- });
-
- // 데이터 행 스타일 적용
- worksheet.eachRow((row, rowNumber) => {
- if (rowNumber > 1) { // 헤더를 제외한 데이터 행
- row.eachCell((cell) => {
- cell.border = {
- top: { style: 'thin' },
- left: { style: 'thin' },
- bottom: { style: 'thin' },
- right: { style: 'thin' }
- };
- });
- }
- });
-
- // 워크시트에 벤더 타입 관련 메모 추가
- const infoRow = worksheet.addRow(['벤더 타입 안내: ' + VENDOR_TYPES.join(', ')]);
- infoRow.font = { bold: true, color: { argb: 'FF0000FF' } };
- worksheet.mergeCells(`A${infoRow.number}:I${infoRow.number}`);
-
- // 워크시트 보호 (선택적)
- worksheet.protect('', {
- selectLockedCells: true,
- selectUnlockedCells: true,
- formatColumns: true,
- formatRows: true,
- insertColumns: false,
- insertRows: true,
- insertHyperlinks: false,
- deleteColumns: false,
- deleteRows: true,
- sort: true,
- autoFilter: true,
- pivotTables: false
- });
-
- try {
- // 워크북을 Blob으로 변환
- const buffer = await workbook.xlsx.writeBuffer();
- const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
- saveAs(blob, 'tech-vendor-template.xlsx');
- return true;
- } catch (error) {
- console.error('Excel 템플릿 생성 오류:', error);
- throw error;
- }
+import * as ExcelJS from 'exceljs';
+import { saveAs } from "file-saver";
+
+/**
+ * 기술영업 벤더 데이터 가져오기를 위한 Excel 템플릿 파일 생성 및 다운로드
+ */
+export async function exportTechVendorTemplate() {
+ // 워크북 생성
+ const workbook = new ExcelJS.Workbook();
+ workbook.creator = 'Tech Vendor Management System';
+ workbook.created = new Date();
+
+ // 워크시트 생성
+ const worksheet = workbook.addWorksheet('기술영업 벤더');
+
+ // 컬럼 헤더 정의 및 스타일 적용
+ worksheet.columns = [
+ { header: '업체명', key: 'vendorName', width: 20 },
+ { header: '업체코드', key: 'vendorCode', width: 15 },
+ { header: '사업자등록번호', key: 'taxId', width: 15 },
+ { header: '국가', key: 'country', width: 15 },
+ { header: '영문국가명', key: 'countryEng', width: 15 },
+ { header: '제조국', key: 'countryFab', width: 15 },
+ { header: '대리점명', key: 'agentName', width: 20 },
+ { header: '대리점연락처', key: 'agentPhone', width: 15 },
+ { header: '대리점이메일', key: 'agentEmail', width: 25 },
+ { header: '주소', key: 'address', width: 30 },
+ { header: '전화번호', key: 'phone', width: 15 },
+ { header: '이메일', key: 'email', width: 25 },
+ { header: '웹사이트', key: 'website', width: 25 },
+ { header: '벤더타입', key: 'techVendorType', width: 15 },
+ { header: '대표자명', key: 'representativeName', width: 20 },
+ { header: '대표자이메일', key: 'representativeEmail', width: 25 },
+ { header: '대표자연락처', key: 'representativePhone', width: 15 },
+ { header: '대표자생년월일', key: 'representativeBirth', width: 15 },
+ { header: '아이템', key: 'items', width: 30 },
+ ];
+
+ // 헤더 스타일 적용
+ const headerRow = worksheet.getRow(1);
+ headerRow.font = { bold: true };
+ headerRow.fill = {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: 'FFE0E0E0' }
+ };
+ headerRow.alignment = { vertical: 'middle', horizontal: 'center' };
+
+ // 테두리 스타일 적용
+ headerRow.eachCell((cell) => {
+ cell.border = {
+ top: { style: 'thin' },
+ left: { style: 'thin' },
+ bottom: { style: 'thin' },
+ right: { style: 'thin' }
+ };
+ });
+
+ // 샘플 데이터 추가
+ const sampleData = [
+ {
+ vendorName: '샘플 업체 1',
+ vendorCode: 'TV001',
+ taxId: '123-45-67890',
+ country: '대한민국',
+ countryEng: 'Korea',
+ countryFab: '대한민국',
+ agentName: '대리점1',
+ agentPhone: '02-1234-5678',
+ agentEmail: 'agent1@example.com',
+ address: '서울시 강남구',
+ phone: '02-1234-5678',
+ email: 'sample1@example.com',
+ website: 'https://example1.com',
+ techVendorType: '조선',
+ representativeName: '홍길동',
+ representativeEmail: 'ceo1@example.com',
+ representativePhone: '010-1234-5678',
+ representativeBirth: '1980-01-01',
+ items: 'ITEM001,ITEM002'
+ },
+ {
+ vendorName: '샘플 업체 2',
+ vendorCode: 'TV002',
+ taxId: '234-56-78901',
+ country: '대한민국',
+ countryEng: 'Korea',
+ countryFab: '대한민국',
+ agentName: '대리점2',
+ agentPhone: '051-234-5678',
+ agentEmail: 'agent2@example.com',
+ address: '부산시 해운대구',
+ phone: '051-234-5678',
+ email: 'sample2@example.com',
+ website: 'https://example2.com',
+ techVendorType: '해양TOP',
+ representativeName: '김철수',
+ representativeEmail: 'ceo2@example.com',
+ representativePhone: '010-2345-6789',
+ representativeBirth: '1985-02-02',
+ items: 'ITEM003,ITEM004'
+ }
+ ];
+
+ // 데이터 행 추가
+ sampleData.forEach(item => {
+ worksheet.addRow(item);
+ });
+
+ // 데이터 행 스타일 적용
+ worksheet.eachRow((row, rowNumber) => {
+ if (rowNumber > 1) { // 헤더를 제외한 데이터 행
+ row.eachCell((cell) => {
+ cell.border = {
+ top: { style: 'thin' },
+ left: { style: 'thin' },
+ bottom: { style: 'thin' },
+ right: { style: 'thin' }
+ };
+ });
+ }
+ });
+
+ // 워크시트 보호 (선택적)
+ worksheet.protect('', {
+ selectLockedCells: true,
+ selectUnlockedCells: true,
+ formatColumns: true,
+ formatRows: true,
+ insertColumns: false,
+ insertRows: true,
+ insertHyperlinks: false,
+ deleteColumns: false,
+ deleteRows: true,
+ sort: true,
+ autoFilter: true,
+ pivotTables: false
+ });
+
+ try {
+ // 워크북을 Blob으로 변환
+ const buffer = await workbook.xlsx.writeBuffer();
+ const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+ saveAs(blob, 'tech-vendor-template.xlsx');
+ return true;
+ } catch (error) {
+ console.error('Excel 템플릿 생성 오류:', error);
+ throw error;
+ }
} \ No newline at end of file
diff --git a/lib/tech-vendors/table/import-button.tsx b/lib/tech-vendors/table/import-button.tsx
index 7346e5fe..ba01e150 100644
--- a/lib/tech-vendors/table/import-button.tsx
+++ b/lib/tech-vendors/table/import-button.tsx
@@ -1,293 +1,313 @@
-"use client"
-
-import * as React from "react"
-import { Upload } from "lucide-react"
-import { toast } from "sonner"
-import * as ExcelJS from 'exceljs'
-
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog"
-import { Progress } from "@/components/ui/progress"
-import { importTechVendorsFromExcel } from "../service"
-import { decryptWithServerAction } from "@/components/drm/drmUtils"
-
-interface ImportTechVendorButtonProps {
- onSuccess?: () => void;
-}
-
-export function ImportTechVendorButton({ onSuccess }: ImportTechVendorButtonProps) {
- const [open, setOpen] = React.useState(false);
- const [file, setFile] = React.useState<File | null>(null);
- const [isUploading, setIsUploading] = React.useState(false);
- const [progress, setProgress] = React.useState(0);
- const [error, setError] = React.useState<string | null>(null);
-
- const fileInputRef = React.useRef<HTMLInputElement>(null);
-
- // 파일 선택 처리
- const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- const selectedFile = e.target.files?.[0];
- if (!selectedFile) return;
-
- if (!selectedFile.name.endsWith('.xlsx') && !selectedFile.name.endsWith('.xls')) {
- setError("Excel 파일(.xlsx 또는 .xls)만 가능합니다.");
- return;
- }
-
- setFile(selectedFile);
- setError(null);
- };
-
- // 데이터 가져오기 처리
- const handleImport = async () => {
- if (!file) {
- setError("가져올 파일을 선택해주세요.");
- return;
- }
-
- try {
- setIsUploading(true);
- setProgress(0);
- setError(null);
-
- // DRM 복호화 처리
- let arrayBuffer: ArrayBuffer;
- try {
- setProgress(10);
- toast.info("파일 복호화 중...");
- arrayBuffer = await decryptWithServerAction(file);
- setProgress(30);
- } catch (decryptError) {
- console.error("파일 복호화 실패, 원본 파일 사용:", decryptError);
- toast.warning("파일 복호화에 실패하여 원본 파일을 사용합니다.");
- arrayBuffer = await file.arrayBuffer();
- }
-
- // ExcelJS 워크북 로드
- const workbook = new ExcelJS.Workbook();
- await workbook.xlsx.load(arrayBuffer);
-
- // 첫 번째 워크시트 가져오기
- const worksheet = workbook.worksheets[0];
- if (!worksheet) {
- throw new Error("Excel 파일에 워크시트가 없습니다.");
- }
-
- // 헤더 행 찾기
- let headerRowIndex = 1;
- let headerRow: ExcelJS.Row | undefined;
- let headerValues: (string | null)[] = [];
-
- worksheet.eachRow((row, rowNumber) => {
- const values = row.values as (string | null)[];
- if (!headerRow && values.some(v => v === "업체명" || v === "vendorName")) {
- headerRowIndex = rowNumber;
- headerRow = row;
- headerValues = [...values];
- }
- });
-
- if (!headerRow) {
- throw new Error("Excel 파일에서 헤더 행을 찾을 수 없습니다.");
- }
-
- // 헤더를 기반으로 인덱스 매핑 생성
- const headerMapping: Record<string, number> = {};
- headerValues.forEach((value, index) => {
- if (typeof value === 'string') {
- headerMapping[value] = index;
- }
- });
-
- // 필수 헤더 확인
- const requiredHeaders = ["업체명", "이메일", "사업자등록번호", "벤더타입"];
- const alternativeHeaders = {
- "업체명": ["vendorName"],
- "이메일": ["email"],
- "사업자등록번호": ["taxId"],
- "벤더타입": ["techVendorType"],
- "주소": ["address"],
- "국가": ["country"],
- "전화번호": ["phone"],
- "웹사이트": ["website"],
- "아이템": ["items"]
- };
-
- // 헤더 매핑 확인 (대체 이름 포함)
- const missingHeaders = requiredHeaders.filter(header => {
- const alternatives = alternativeHeaders[header as keyof typeof alternativeHeaders] || [];
- return !(header in headerMapping) &&
- !alternatives.some(alt => alt in headerMapping);
- });
-
- if (missingHeaders.length > 0) {
- throw new Error(`다음 필수 헤더가 누락되었습니다: ${missingHeaders.join(", ")}`);
- }
-
- // 데이터 행 추출
- const dataRows: Record<string, any>[] = [];
-
- worksheet.eachRow((row, rowNumber) => {
- if (rowNumber > headerRowIndex) {
- const rowData: Record<string, any> = {};
- const values = row.values as (string | null | undefined)[];
-
- // 헤더 매핑에 따라 데이터 추출
- Object.entries(headerMapping).forEach(([header, index]) => {
- rowData[header] = values[index] || "";
- });
-
- // 빈 행이 아닌 경우만 추가
- if (Object.values(rowData).some(value => value && value.toString().trim() !== "")) {
- dataRows.push(rowData);
- }
- }
- });
-
- if (dataRows.length === 0) {
- throw new Error("Excel 파일에 가져올 데이터가 없습니다.");
- }
-
- // 진행 상황 업데이트를 위한 콜백
- const updateProgress = (current: number, total: number) => {
- const percentage = Math.round((current / total) * 100);
- setProgress(percentage);
- };
-
- // 벤더 데이터 처리
- const vendors = dataRows.map(row => ({
- vendorName: row["업체명"] || row["vendorName"] || "",
- email: row["이메일"] || row["email"] || "",
- taxId: row["사업자등록번호"] || row["taxId"] || "",
- techVendorType: row["벤더타입"] || row["techVendorType"] || "",
- address: row["주소"] || row["address"] || null,
- country: row["국가"] || row["country"] || null,
- phone: row["전화번호"] || row["phone"] || null,
- website: row["웹사이트"] || row["website"] || null,
- items: row["아이템"] || row["items"] || ""
- }));
-
- // 벤더 데이터 가져오기 실행
- const result = await importTechVendorsFromExcel(vendors);
-
- if (result.success) {
- toast.success(`${vendors.length}개의 기술영업 벤더가 성공적으로 가져와졌습니다.`);
- } else {
- toast.error(result.error || "벤더 가져오기에 실패했습니다.");
- }
-
- // 상태 초기화 및 다이얼로그 닫기
- setFile(null);
- setOpen(false);
-
- // 성공 콜백 호출
- if (onSuccess) {
- onSuccess();
- }
- } catch (error) {
- console.error("Excel 파일 처리 중 오류 발생:", error);
- setError(error instanceof Error ? error.message : "파일 처리 중 오류가 발생했습니다.");
- } finally {
- setIsUploading(false);
- }
- };
-
- // 다이얼로그 열기/닫기 핸들러
- const handleOpenChange = (newOpen: boolean) => {
- if (!newOpen) {
- // 닫을 때 상태 초기화
- setFile(null);
- setError(null);
- setProgress(0);
- if (fileInputRef.current) {
- fileInputRef.current.value = "";
- }
- }
- setOpen(newOpen);
- };
-
- return (
- <>
- <Button
- variant="outline"
- size="sm"
- className="gap-2"
- onClick={() => setOpen(true)}
- disabled={isUploading}
- >
- <Upload className="size-4" aria-hidden="true" />
- <span className="hidden sm:inline">Import</span>
- </Button>
-
- <Dialog open={open} onOpenChange={handleOpenChange}>
- <DialogContent className="sm:max-w-[500px]">
- <DialogHeader>
- <DialogTitle>기술영업 벤더 가져오기</DialogTitle>
- <DialogDescription>
- 기술영업 벤더를 Excel 파일에서 가져옵니다.
- <br />
- 올바른 형식의 Excel 파일(.xlsx)을 업로드하세요.
- </DialogDescription>
- </DialogHeader>
-
- <div className="space-y-4 py-4">
- <div className="flex items-center gap-4">
- <input
- type="file"
- ref={fileInputRef}
- className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-foreground file:font-medium"
- accept=".xlsx,.xls"
- onChange={handleFileChange}
- disabled={isUploading}
- />
- </div>
-
- {file && (
- <div className="text-sm text-muted-foreground">
- 선택된 파일: <span className="font-medium">{file.name}</span> ({(file.size / 1024).toFixed(1)} KB)
- </div>
- )}
-
- {isUploading && (
- <div className="space-y-2">
- <Progress value={progress} />
- <p className="text-sm text-muted-foreground text-center">
- {progress}% 완료
- </p>
- </div>
- )}
-
- {error && (
- <div className="text-sm font-medium text-destructive">
- {error}
- </div>
- )}
- </div>
-
- <DialogFooter>
- <Button
- variant="outline"
- onClick={() => setOpen(false)}
- disabled={isUploading}
- >
- 취소
- </Button>
- <Button
- onClick={handleImport}
- disabled={!file || isUploading}
- >
- {isUploading ? "처리 중..." : "가져오기"}
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- </>
- );
+"use client"
+
+import * as React from "react"
+import { Upload } from "lucide-react"
+import { toast } from "sonner"
+import * as ExcelJS from 'exceljs'
+
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Progress } from "@/components/ui/progress"
+import { importTechVendorsFromExcel } from "../service"
+import { decryptWithServerAction } from "@/components/drm/drmUtils"
+
+interface ImportTechVendorButtonProps {
+ onSuccess?: () => void;
+}
+
+export function ImportTechVendorButton({ onSuccess }: ImportTechVendorButtonProps) {
+ const [open, setOpen] = React.useState(false);
+ const [file, setFile] = React.useState<File | null>(null);
+ const [isUploading, setIsUploading] = React.useState(false);
+ const [progress, setProgress] = React.useState(0);
+ const [error, setError] = React.useState<string | null>(null);
+
+ const fileInputRef = React.useRef<HTMLInputElement>(null);
+
+ // 파일 선택 처리
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const selectedFile = e.target.files?.[0];
+ if (!selectedFile) return;
+
+ if (!selectedFile.name.endsWith('.xlsx') && !selectedFile.name.endsWith('.xls')) {
+ setError("Excel 파일(.xlsx 또는 .xls)만 가능합니다.");
+ return;
+ }
+
+ setFile(selectedFile);
+ setError(null);
+ };
+
+ // 데이터 가져오기 처리
+ const handleImport = async () => {
+ if (!file) {
+ setError("가져올 파일을 선택해주세요.");
+ return;
+ }
+
+ try {
+ setIsUploading(true);
+ setProgress(0);
+ setError(null);
+
+ // DRM 복호화 처리
+ let arrayBuffer: ArrayBuffer;
+ try {
+ setProgress(10);
+ toast.info("파일 복호화 중...");
+ arrayBuffer = await decryptWithServerAction(file);
+ setProgress(30);
+ } catch (decryptError) {
+ console.error("파일 복호화 실패, 원본 파일 사용:", decryptError);
+ toast.warning("파일 복호화에 실패하여 원본 파일을 사용합니다.");
+ arrayBuffer = await file.arrayBuffer();
+ }
+
+ // ExcelJS 워크북 로드
+ const workbook = new ExcelJS.Workbook();
+ await workbook.xlsx.load(arrayBuffer);
+
+ // 첫 번째 워크시트 가져오기
+ const worksheet = workbook.worksheets[0];
+ if (!worksheet) {
+ throw new Error("Excel 파일에 워크시트가 없습니다.");
+ }
+
+ // 헤더 행 찾기
+ let headerRowIndex = 1;
+ let headerRow: ExcelJS.Row | undefined;
+ let headerValues: (string | null)[] = [];
+
+ worksheet.eachRow((row, rowNumber) => {
+ const values = row.values as (string | null)[];
+ if (!headerRow && values.some(v => v === "업체명" || v === "vendorName")) {
+ headerRowIndex = rowNumber;
+ headerRow = row;
+ headerValues = [...values];
+ }
+ });
+
+ if (!headerRow) {
+ throw new Error("Excel 파일에서 헤더 행을 찾을 수 없습니다.");
+ }
+
+ // 헤더를 기반으로 인덱스 매핑 생성
+ const headerMapping: Record<string, number> = {};
+ headerValues.forEach((value, index) => {
+ if (typeof value === 'string') {
+ headerMapping[value] = index;
+ }
+ });
+
+ // 필수 헤더 확인
+ const requiredHeaders = ["업체명", "이메일", "사업자등록번호", "벤더타입"];
+ const alternativeHeaders = {
+ "업체명": ["vendorName"],
+ "업체코드": ["vendorCode"],
+ "이메일": ["email"],
+ "사업자등록번호": ["taxId"],
+ "국가": ["country"],
+ "영문국가명": ["countryEng"],
+ "제조국": ["countryFab"],
+ "대리점명": ["agentName"],
+ "대리점연락처": ["agentPhone"],
+ "대리점이메일": ["agentEmail"],
+ "주소": ["address"],
+ "전화번호": ["phone"],
+ "웹사이트": ["website"],
+ "벤더타입": ["techVendorType"],
+ "대표자명": ["representativeName"],
+ "대표자이메일": ["representativeEmail"],
+ "대표자연락처": ["representativePhone"],
+ "대표자생년월일": ["representativeBirth"],
+ "아이템": ["items"]
+ };
+
+ // 헤더 매핑 확인 (대체 이름 포함)
+ const missingHeaders = requiredHeaders.filter(header => {
+ const alternatives = alternativeHeaders[header as keyof typeof alternativeHeaders] || [];
+ return !(header in headerMapping) &&
+ !alternatives.some(alt => alt in headerMapping);
+ });
+
+ if (missingHeaders.length > 0) {
+ throw new Error(`다음 필수 헤더가 누락되었습니다: ${missingHeaders.join(", ")}`);
+ }
+
+ // 데이터 행 추출
+ const dataRows: Record<string, any>[] = [];
+
+ worksheet.eachRow((row, rowNumber) => {
+ if (rowNumber > headerRowIndex) {
+ const rowData: Record<string, any> = {};
+ const values = row.values as (string | null | undefined)[];
+
+ // 헤더 매핑에 따라 데이터 추출
+ Object.entries(headerMapping).forEach(([header, index]) => {
+ rowData[header] = values[index] || "";
+ });
+
+ // 빈 행이 아닌 경우만 추가
+ if (Object.values(rowData).some(value => value && value.toString().trim() !== "")) {
+ dataRows.push(rowData);
+ }
+ }
+ });
+
+ if (dataRows.length === 0) {
+ throw new Error("Excel 파일에 가져올 데이터가 없습니다.");
+ }
+
+ // 진행 상황 업데이트를 위한 콜백
+ const updateProgress = (current: number, total: number) => {
+ const percentage = Math.round((current / total) * 100);
+ setProgress(percentage);
+ };
+
+ // 벤더 데이터 처리
+ const vendors = dataRows.map(row => ({
+ vendorName: row["업체명"] || row["vendorName"] || "",
+ vendorCode: row["업체코드"] || row["vendorCode"] || null,
+ email: row["이메일"] || row["email"] || "",
+ taxId: row["사업자등록번호"] || row["taxId"] || "",
+ country: row["국가"] || row["country"] || null,
+ countryEng: row["영문국가명"] || row["countryEng"] || null,
+ countryFab: row["제조국"] || row["countryFab"] || null,
+ agentName: row["대리점명"] || row["agentName"] || null,
+ agentPhone: row["대리점연락처"] || row["agentPhone"] || null,
+ agentEmail: row["대리점이메일"] || row["agentEmail"] || null,
+ address: row["주소"] || row["address"] || null,
+ phone: row["전화번호"] || row["phone"] || null,
+ website: row["웹사이트"] || row["website"] || null,
+ techVendorType: row["벤더타입"] || row["techVendorType"] || "",
+ representativeName: row["대표자명"] || row["representativeName"] || null,
+ representativeEmail: row["대표자이메일"] || row["representativeEmail"] || null,
+ representativePhone: row["대표자연락처"] || row["representativePhone"] || null,
+ representativeBirth: row["대표자생년월일"] || row["representativeBirth"] || null,
+ items: row["아이템"] || row["items"] || ""
+ }));
+
+ // 벤더 데이터 가져오기 실행
+ const result = await importTechVendorsFromExcel(vendors);
+
+ if (result.success) {
+ toast.success(`${vendors.length}개의 기술영업 벤더가 성공적으로 가져와졌습니다.`);
+ } else {
+ toast.error(result.error || "벤더 가져오기에 실패했습니다.");
+ }
+
+ // 상태 초기화 및 다이얼로그 닫기
+ setFile(null);
+ setOpen(false);
+
+ // 성공 콜백 호출
+ if (onSuccess) {
+ onSuccess();
+ }
+ } catch (error) {
+ console.error("Excel 파일 처리 중 오류 발생:", error);
+ setError(error instanceof Error ? error.message : "파일 처리 중 오류가 발생했습니다.");
+ } finally {
+ setIsUploading(false);
+ }
+ };
+
+ // 다이얼로그 열기/닫기 핸들러
+ const handleOpenChange = (newOpen: boolean) => {
+ if (!newOpen) {
+ // 닫을 때 상태 초기화
+ setFile(null);
+ setError(null);
+ setProgress(0);
+ if (fileInputRef.current) {
+ fileInputRef.current.value = "";
+ }
+ }
+ setOpen(newOpen);
+ };
+
+ return (
+ <>
+ <Button
+ variant="outline"
+ size="sm"
+ className="gap-2"
+ onClick={() => setOpen(true)}
+ disabled={isUploading}
+ >
+ <Upload className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">Import</span>
+ </Button>
+
+ <Dialog open={open} onOpenChange={handleOpenChange}>
+ <DialogContent className="sm:max-w-[500px]">
+ <DialogHeader>
+ <DialogTitle>기술영업 벤더 가져오기</DialogTitle>
+ <DialogDescription>
+ 기술영업 벤더를 Excel 파일에서 가져옵니다.
+ <br />
+ 올바른 형식의 Excel 파일(.xlsx)을 업로드하세요.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="space-y-4 py-4">
+ <div className="flex items-center gap-4">
+ <input
+ type="file"
+ ref={fileInputRef}
+ className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-foreground file:font-medium"
+ accept=".xlsx,.xls"
+ onChange={handleFileChange}
+ disabled={isUploading}
+ />
+ </div>
+
+ {file && (
+ <div className="text-sm text-muted-foreground">
+ 선택된 파일: <span className="font-medium">{file.name}</span> ({(file.size / 1024).toFixed(1)} KB)
+ </div>
+ )}
+
+ {isUploading && (
+ <div className="space-y-2">
+ <Progress value={progress} />
+ <p className="text-sm text-muted-foreground text-center">
+ {progress}% 완료
+ </p>
+ </div>
+ )}
+
+ {error && (
+ <div className="text-sm font-medium text-destructive">
+ {error}
+ </div>
+ )}
+ </div>
+
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={() => setOpen(false)}
+ disabled={isUploading}
+ >
+ 취소
+ </Button>
+ <Button
+ onClick={handleImport}
+ disabled={!file || isUploading}
+ >
+ {isUploading ? "처리 중..." : "가져오기"}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ </>
+ );
} \ No newline at end of file
diff --git a/lib/tech-vendors/table/tech-vendors-table-columns.tsx b/lib/tech-vendors/table/tech-vendors-table-columns.tsx
index 438f4000..e586a667 100644
--- a/lib/tech-vendors/table/tech-vendors-table-columns.tsx
+++ b/lib/tech-vendors/table/tech-vendors-table-columns.tsx
@@ -217,45 +217,27 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
// Status badge variant mapping - 더 뚜렷한 색상으로 변경
const getStatusConfig = (status: StatusType): StatusConfig & { iconColor: string } => {
switch (status) {
- case "PENDING_REVIEW":
- return {
- variant: "outline",
- className: "bg-yellow-100 text-yellow-800 border-yellow-300",
- iconColor: "text-yellow-600"
- };
- case "IN_REVIEW":
- return {
- variant: "outline",
- className: "bg-blue-100 text-blue-800 border-blue-300",
- iconColor: "text-blue-600"
- };
- case "REJECTED":
- return {
- variant: "outline",
- className: "bg-red-100 text-red-800 border-red-300",
- iconColor: "text-red-600"
- };
case "ACTIVE":
return {
- variant: "outline",
+ variant: "default",
className: "bg-emerald-100 text-emerald-800 border-emerald-300 font-semibold",
iconColor: "text-emerald-600"
};
case "INACTIVE":
return {
- variant: "outline",
+ variant: "default",
className: "bg-gray-100 text-gray-800 border-gray-300",
iconColor: "text-gray-600"
};
case "BLACKLISTED":
return {
- variant: "outline",
+ variant: "destructive",
className: "bg-slate-800 text-white border-slate-900",
iconColor: "text-white"
};
default:
return {
- variant: "outline",
+ variant: "default",
className: "bg-gray-100 text-gray-800 border-gray-300",
iconColor: "text-gray-600"
};
@@ -265,9 +247,6 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
// 상태 표시 텍스트
const getStatusDisplay = (status: StatusType): string => {
const statusMap: StatusDisplayMap = {
- "PENDING_REVIEW": "가입 신청 중",
- "IN_REVIEW": "심사 중",
- "REJECTED": "심사 거부됨",
"ACTIVE": "활성 상태",
"INACTIVE": "비활성 상태",
"BLACKLISTED": "거래 금지"
diff --git a/lib/tech-vendors/table/tech-vendors-table-toolbar-actions.tsx b/lib/tech-vendors/table/tech-vendors-table-toolbar-actions.tsx
index 82383a3a..06b2cc42 100644
--- a/lib/tech-vendors/table/tech-vendors-table-toolbar-actions.tsx
+++ b/lib/tech-vendors/table/tech-vendors-table-toolbar-actions.tsx
@@ -2,7 +2,7 @@
import * as React from "react"
import { type Table } from "@tanstack/react-table"
-import { Download, FileSpreadsheet, Upload, Check, BuildingIcon, FileText } from "lucide-react"
+import { Download, FileSpreadsheet, FileText } from "lucide-react"
import { toast } from "sonner"
import { exportTableToExcel } from "@/lib/export"
@@ -19,12 +19,14 @@ import { exportVendorsWithRelatedData } from "./vendor-all-export"
import { TechVendor } from "@/db/schema/techVendors"
import { ImportTechVendorButton } from "./import-button"
import { exportTechVendorTemplate } from "./excel-template-download"
+import { AddVendorDialog } from "./add-vendor-dialog"
interface TechVendorsTableToolbarActionsProps {
table: Table<TechVendor>
+ onRefresh?: () => void
}
-export function TechVendorsTableToolbarActions({ table }: TechVendorsTableToolbarActionsProps) {
+export function TechVendorsTableToolbarActions({ table, onRefresh }: TechVendorsTableToolbarActionsProps) {
const [isExporting, setIsExporting] = React.useState(false);
// 선택된 모든 벤더 가져오기
@@ -82,9 +84,22 @@ export function TechVendorsTableToolbarActions({ table }: TechVendorsTableToolba
setIsExporting(false);
}
};
+
+ // 벤더 추가 성공 시 테이블 새로고침을 위한 핸들러
+ const handleVendorAddSuccess = () => {
+ // 테이블 데이터 리프레시
+ if (onRefresh) {
+ onRefresh();
+ } else {
+ window.location.reload(); // 간단한 새로고침 방법
+ }
+ };
return (
<div className="flex items-center gap-2">
+ {/* 벤더 추가 다이얼로그 추가 */}
+ <AddVendorDialog onSuccess={handleVendorAddSuccess} />
+
{/* Import 버튼 추가 */}
<ImportTechVendorButton
onSuccess={() => {
diff --git a/lib/tech-vendors/table/tech-vendors-table.tsx b/lib/tech-vendors/table/tech-vendors-table.tsx
index 55632182..d6e6f99f 100644
--- a/lib/tech-vendors/table/tech-vendors-table.tsx
+++ b/lib/tech-vendors/table/tech-vendors-table.tsx
@@ -47,9 +47,6 @@ export function TechVendorsTable({ promises }: TechVendorsTableProps) {
// 상태 한글 변환 유틸리티 함수
const getStatusDisplay = (status: string): string => {
const statusMap: Record<string, string> = {
- "PENDING_REVIEW": "가입 신청 중",
- "IN_REVIEW": "심사 중",
- "REJECTED": "심사 거부됨",
"ACTIVE": "활성 상태",
"INACTIVE": "비활성 상태",
"BLACKLISTED": "거래 금지"
@@ -111,6 +108,11 @@ export function TechVendorsTable({ promises }: TechVendorsTableProps) {
const handleCompactChange = React.useCallback((compact: boolean) => {
setIsCompact(compact)
}, [])
+
+ // 테이블 새로고침 핸들러
+ const handleRefresh = React.useCallback(() => {
+ router.refresh()
+ }, [router])
return (
@@ -128,7 +130,7 @@ export function TechVendorsTable({ promises }: TechVendorsTableProps) {
compactStorageKey="techVendorsTableCompact"
onCompactChange={handleCompactChange}
>
- <TechVendorsTableToolbarActions table={table} />
+ <TechVendorsTableToolbarActions table={table} onRefresh={handleRefresh} />
</DataTableAdvancedToolbar>
</DataTable>
<UpdateVendorSheet
@@ -136,13 +138,6 @@ export function TechVendorsTable({ promises }: TechVendorsTableProps) {
onOpenChange={() => setRowAction(null)}
vendor={rowAction?.row.original ?? null}
/>
-
- {/* ViewTechVendorLogsDialog 컴포넌트는 아직 구현되지 않았습니다.
- <ViewTechVendorLogsDialog
- open={rowAction?.type === "log"}
- onOpenChange={() => setRowAction(null)}
- vendorId={rowAction?.row.original?.id ?? null}
- /> */}
</>
)
} \ No newline at end of file
diff --git a/lib/tech-vendors/table/update-vendor-sheet.tsx b/lib/tech-vendors/table/update-vendor-sheet.tsx
index c33bbf03..cc6b4003 100644
--- a/lib/tech-vendors/table/update-vendor-sheet.tsx
+++ b/lib/tech-vendors/table/update-vendor-sheet.tsx
@@ -326,7 +326,7 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
<FormLabel>업체승인상태</FormLabel>
<FormControl>
<Select
- value={field.value}
+ value={field.value || ""}
onValueChange={field.onChange}
>
<SelectTrigger className="w-full">
diff --git a/lib/tech-vendors/table/vendor-all-export.ts b/lib/tech-vendors/table/vendor-all-export.ts
index 4278249a..a1ad4fd1 100644
--- a/lib/tech-vendors/table/vendor-all-export.ts
+++ b/lib/tech-vendors/table/vendor-all-export.ts
@@ -82,6 +82,13 @@ function createBasicInfoSheet(
{ header: "주소", key: "address", width: 30 },
{ header: "대표자명", key: "representativeName", width: 15 },
{ header: "생성일", key: "createdAt", width: 15 },
+ { header: "벤더타입", key: "techVendorType", width: 15 },
+ { header: "대리점명", key: "agentName", width: 15 },
+ { header: "대리점연락처", key: "agentPhone", width: 15 },
+ { header: "대리점이메일", key: "agentEmail", width: 25 },
+ { header: "대리점주소", key: "agentAddress", width: 30 },
+ { header: "대리점국가", key: "agentCountry", width: 15 },
+ { header: "대리점영문국가명", key: "agentCountryEng", width: 20 },
];
// 헤더 스타일 설정
@@ -240,9 +247,6 @@ function formatDate(date: Date | string): string {
// 상태 코드를 읽기 쉬운 텍스트로 변환하는 함수
function getStatusText(status: string): string {
const statusMap: Record<string, string> = {
- "PENDING_REVIEW": "검토 대기중",
- "IN_REVIEW": "검토 중",
- "REJECTED": "거부됨",
"ACTIVE": "활성",
"INACTIVE": "비활성",
"BLACKLISTED": "거래 금지"