diff options
Diffstat (limited to 'lib/tech-vendors/table')
| -rw-r--r-- | lib/tech-vendors/table/add-vendor-dialog.tsx | 458 | ||||
| -rw-r--r-- | lib/tech-vendors/table/excel-template-download.tsx | 276 | ||||
| -rw-r--r-- | lib/tech-vendors/table/import-button.tsx | 604 | ||||
| -rw-r--r-- | lib/tech-vendors/table/tech-vendors-table-columns.tsx | 29 | ||||
| -rw-r--r-- | lib/tech-vendors/table/tech-vendors-table-toolbar-actions.tsx | 19 | ||||
| -rw-r--r-- | lib/tech-vendors/table/tech-vendors-table.tsx | 17 | ||||
| -rw-r--r-- | lib/tech-vendors/table/update-vendor-sheet.tsx | 2 | ||||
| -rw-r--r-- | lib/tech-vendors/table/vendor-all-export.ts | 10 |
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": "거래 금지" |
