summaryrefslogtreecommitdiff
path: root/lib/tech-vendors
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendors')
-rw-r--r--lib/tech-vendors/contacts-table/add-contact-dialog.tsx9
-rw-r--r--lib/tech-vendors/contacts-table/delete-contact-dialog.tsx81
-rw-r--r--lib/tech-vendors/contacts-table/update-contact-sheet.tsx13
-rw-r--r--lib/tech-vendors/possible-items/add-item-dialog.tsx4
-rw-r--r--lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx2
-rw-r--r--lib/tech-vendors/service.ts131
-rw-r--r--lib/tech-vendors/table/add-vendor-dialog.tsx13
-rw-r--r--lib/tech-vendors/table/delete-tech-vendors-dialog.tsx80
-rw-r--r--lib/tech-vendors/table/import-button.tsx19
-rw-r--r--lib/tech-vendors/table/tech-vendors-table-columns.tsx12
-rw-r--r--lib/tech-vendors/table/tech-vendors-table.tsx7
-rw-r--r--lib/tech-vendors/table/update-vendor-sheet.tsx13
-rw-r--r--lib/tech-vendors/utils.ts84
13 files changed, 403 insertions, 65 deletions
diff --git a/lib/tech-vendors/contacts-table/add-contact-dialog.tsx b/lib/tech-vendors/contacts-table/add-contact-dialog.tsx
index 90ba4e04..447c44d7 100644
--- a/lib/tech-vendors/contacts-table/add-contact-dialog.tsx
+++ b/lib/tech-vendors/contacts-table/add-contact-dialog.tsx
@@ -21,6 +21,7 @@ import {
type CreateTechVendorContactSchema,
} from "@/lib/tech-vendors/validations"
import { createTechVendorContact } from "@/lib/tech-vendors/service"
+import { normalizeEmail } from "@/lib/tech-vendors/utils"
interface AddContactDialogProps {
vendorId: number
@@ -46,8 +47,14 @@ export function AddContactDialog({ vendorId }: AddContactDialogProps) {
})
async function onSubmit(data: CreateTechVendorContactSchema) {
+ // 이메일을 소문자로 변환
+ const normalizedData = {
+ ...data,
+ contactEmail: normalizeEmail(data.contactEmail),
+ }
+
// 혹은 여기서 data.vendorId = vendorId; 해줘도 됨
- const result = await createTechVendorContact(data)
+ const result = await createTechVendorContact(normalizedData)
if (result.error) {
alert(`에러: ${result.error}`)
return
diff --git a/lib/tech-vendors/contacts-table/delete-contact-dialog.tsx b/lib/tech-vendors/contacts-table/delete-contact-dialog.tsx
new file mode 100644
index 00000000..6c8b14d7
--- /dev/null
+++ b/lib/tech-vendors/contacts-table/delete-contact-dialog.tsx
@@ -0,0 +1,81 @@
+"use client"
+
+import * as React from "react"
+import { toast } from "sonner"
+import { Trash2 } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog"
+
+import { deleteTechVendorContact } from "../service"
+import type { TechVendorContact } from "@/db/schema/techVendors"
+
+interface DeleteContactDialogProps {
+ contact: TechVendorContact
+ vendorId: number
+ onSuccess?: () => void
+}
+
+export function DeleteContactDialog({ contact, vendorId, onSuccess }: DeleteContactDialogProps) {
+ const [isDeleting, setIsDeleting] = React.useState(false)
+
+ const handleDelete = async () => {
+ setIsDeleting(true)
+ try {
+ const result = await deleteTechVendorContact(contact.id, vendorId)
+
+ if (result.success) {
+ toast.success("담당자가 성공적으로 삭제되었습니다.")
+ onSuccess?.()
+ } else {
+ toast.error(result.error || "담당자 삭제 중 오류가 발생했습니다.")
+ }
+ } catch (error) {
+ console.error("담당자 삭제 오류:", error)
+ toast.error("담당자 삭제 중 오류가 발생했습니다.")
+ } finally {
+ setIsDeleting(false)
+ }
+ }
+
+ return (
+ <AlertDialog>
+ <AlertDialogTrigger asChild>
+ <Button variant="ghost" size="sm" className="text-red-600 hover:text-red-700 hover:bg-red-50">
+ <Trash2 className="size-4 mr-2" />
+ 삭제
+ </Button>
+ </AlertDialogTrigger>
+ <AlertDialogContent>
+ <AlertDialogHeader>
+ <AlertDialogTitle>담당자 삭제</AlertDialogTitle>
+ <AlertDialogDescription>
+ <strong>{contact.contactName}</strong> 담당자를 정말 삭제하시겠습니까?
+ <br />
+ 이 작업은 되돌릴 수 없으며, 관련된 모든 데이터(아이템 매핑 등)가 함께 삭제됩니다.
+ </AlertDialogDescription>
+ </AlertDialogHeader>
+ <AlertDialogFooter>
+ <AlertDialogCancel>취소</AlertDialogCancel>
+ <AlertDialogAction
+ onClick={handleDelete}
+ disabled={isDeleting}
+ className="bg-red-600 hover:bg-red-700"
+ >
+ {isDeleting ? "삭제 중..." : "삭제"}
+ </AlertDialogAction>
+ </AlertDialogFooter>
+ </AlertDialogContent>
+ </AlertDialog>
+ )
+}
diff --git a/lib/tech-vendors/contacts-table/update-contact-sheet.tsx b/lib/tech-vendors/contacts-table/update-contact-sheet.tsx
index 4713790c..877e8b4f 100644
--- a/lib/tech-vendors/contacts-table/update-contact-sheet.tsx
+++ b/lib/tech-vendors/contacts-table/update-contact-sheet.tsx
@@ -28,6 +28,7 @@ import { Loader2 } from "lucide-react"
import type { TechVendorContact } from "@/db/schema/techVendors"
import { updateTechVendorContactSchema, type UpdateTechVendorContactSchema } from "../validations"
import { updateTechVendorContact } from "../service"
+import { normalizeEmail } from "../utils"
interface UpdateContactSheetProps
extends React.ComponentPropsWithoutRef<typeof Sheet> {
@@ -67,13 +68,19 @@ export function UpdateContactSheet({ contact, vendorId, ...props }: UpdateContac
async function onSubmit(data: UpdateTechVendorContactSchema) {
if (!contact) return
-
+
startTransition(async () => {
try {
- const { error } = await updateTechVendorContact({
+ // 이메일을 소문자로 변환
+ const normalizedData = {
+ ...data,
+ contactEmail: normalizeEmail(data.contactEmail) || undefined,
+ }
+
+ const { error } = await updateTechVendorContact({
id: contact.id,
vendorId: vendorId,
- ...data
+ ...normalizedData,
})
if (error) throw new Error(error)
diff --git a/lib/tech-vendors/possible-items/add-item-dialog.tsx b/lib/tech-vendors/possible-items/add-item-dialog.tsx
index 0e6edd19..ceb34276 100644
--- a/lib/tech-vendors/possible-items/add-item-dialog.tsx
+++ b/lib/tech-vendors/possible-items/add-item-dialog.tsx
@@ -166,9 +166,9 @@ export function AddItemDialog({ open, onOpenChange, vendorId }: AddItemDialogPro
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] flex flex-col">
<DialogHeader>
- <DialogTitle>아이템 추가</DialogTitle>
+ <DialogTitle>아이템 연결</DialogTitle>
<DialogDescription>
- 추가할 아이템을 선택하세요. 복수 선택이 가능합니다.
+ 연결할 아이템을 선택하세요. 복수 선택이 가능합니다.
</DialogDescription>
</DialogHeader>
diff --git a/lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx b/lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx
index bed65727..49a673ff 100644
--- a/lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx
+++ b/lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx
@@ -362,7 +362,7 @@ export function PossibleItemsTableToolbarActions({
onClick={onAdd}
>
<Plus className="mr-2 h-4 w-4" />
- 아이템 추가
+ 아이템 연결
</Button>
{selectedRows.length > 0 && (
diff --git a/lib/tech-vendors/service.ts b/lib/tech-vendors/service.ts
index 72f8632d..940e59ce 100644
--- a/lib/tech-vendors/service.ts
+++ b/lib/tech-vendors/service.ts
@@ -40,6 +40,7 @@ import { sql } from "drizzle-orm";
import { decryptWithServerAction } from "@/components/drm/drmUtils";
import { deleteFile, saveDRMFile } from "../file-stroage";
import { techSalesContactPossibleItems } from "@/db/schema";
+import { normalizeEmailFields } from "./utils";
/* -----------------------------------------------------
1) 조회 관련
@@ -563,24 +564,6 @@ export async function updateTechVendorContact(input: UpdateTechVendorContactSche
}
}
-export async function deleteTechVendorContact(contactId: number, vendorId: number) {
- unstable_noStore();
- try {
- const [deletedContact] = await db
- .delete(techVendorContacts)
- .where(eq(techVendorContacts.id, contactId))
- .returning();
-
- // 캐시 무효화
- revalidateTag(`tech-vendor-contacts-${contactId}`);
- revalidateTag(`tech-vendor-contacts-${vendorId}`);
-
- return { data: deletedContact, error: null };
- } catch (err) {
- return { data: null, error: getErrorMessage(err) };
- }
-}
-
/* -----------------------------------------------------
5) 아이템 관리
----------------------------------------------------- */
@@ -4116,4 +4099,116 @@ export async function generateContactPossibleItemsErrorExcel(errors: Array<{
return new Blob([buffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
})
+}
+
+/**
+ * 기술영업 벤더 담당자 삭제 (cascade 방식)
+ * 1. tech_sales_contact_possible_items (해당 담당자의 아이템 매핑)
+ * 2. tech_vendor_contacts (해당 담당자)
+ */
+export async function deleteTechVendorContact(contactId: number, vendorId: number) {
+ unstable_noStore()
+
+ try {
+ const result = await db.transaction(async (tx) => {
+ // 1. 해당 담당자의 아이템 매핑 삭제 (tech_sales_contact_possible_items)
+ await tx
+ .delete(techSalesContactPossibleItems)
+ .where(eq(techSalesContactPossibleItems.contactId, contactId))
+
+ // 2. 담당자 삭제 (tech_vendor_contacts)
+ const deletedContact = await tx
+ .delete(techVendorContacts)
+ .where(eq(techVendorContacts.id, contactId))
+ .returning()
+
+ return deletedContact
+ })
+
+ // 캐시 무효화
+ revalidateTag(`tech-vendor-contacts-${vendorId}`)
+ revalidateTag("tech-vendors")
+
+ return {
+ success: true,
+ data: result,
+ error: null
+ }
+ } catch (err) {
+ console.error("담당자 삭제 오류:", err)
+ return {
+ success: false,
+ data: null,
+ error: getErrorMessage(err)
+ }
+ }
+}
+
+/**
+ * 기술영업 벤더 삭제 (cascade 방식)
+ * 1. tech_sales_contact_possible_items (담당자별 아이템)
+ * 2. tech_vendor_possible_items (벤더 아이템)
+ * 3. tech_vendor_contacts (벤더 담당자)
+ * 4. tech_vendor_attachments (벤더 첨부파일)
+ * 5. tech_vendors (벤더)
+ */
+// 임시 삭제 함수
+export async function deleteTechVendor(vendorId: number) {
+ unstable_noStore()
+
+ try {
+ const result = await db.transaction(async (tx) => {
+ // 1. 해당 벤더의 담당자별 아이템 삭제 (tech_sales_contact_possible_items)
+ await tx
+ .delete(techSalesContactPossibleItems)
+ .where(
+ inArray(
+ techSalesContactPossibleItems.contactId,
+ tx
+ .select({ contactId: techVendorContacts.id })
+ .from(techVendorContacts)
+ .where(eq(techVendorContacts.vendorId, vendorId))
+ )
+ )
+
+ // 2. 해당 벤더의 아이템 삭제 (tech_vendor_possible_items)
+ await tx
+ .delete(techVendorPossibleItems)
+ .where(eq(techVendorPossibleItems.vendorId, vendorId))
+
+ // 3. 해당 벤더의 담당자 삭제 (tech_vendor_contacts)
+ await tx
+ .delete(techVendorContacts)
+ .where(eq(techVendorContacts.vendorId, vendorId))
+
+ // 4. 해당 벤더의 첨부파일 삭제 (tech_vendor_attachments)
+ await tx
+ .delete(techVendorAttachments)
+ .where(eq(techVendorAttachments.vendorId, vendorId))
+
+ // 5. 벤더 삭제 (tech_vendors)
+ const deletedVendor = await tx
+ .delete(techVendors)
+ .where(eq(techVendors.id, vendorId))
+ .returning()
+
+ return deletedVendor
+ })
+
+ // 캐시 무효화
+ revalidateTag("tech-vendors")
+
+ return {
+ success: true,
+ data: result,
+ error: null
+ }
+ } catch (err) {
+ console.error("벤더 삭제 오류:", err)
+ return {
+ success: false,
+ data: null,
+ error: getErrorMessage(err)
+ }
+ }
} \ No newline at end of file
diff --git a/lib/tech-vendors/table/add-vendor-dialog.tsx b/lib/tech-vendors/table/add-vendor-dialog.tsx
index e89f5d6b..f696b30a 100644
--- a/lib/tech-vendors/table/add-vendor-dialog.tsx
+++ b/lib/tech-vendors/table/add-vendor-dialog.tsx
@@ -30,6 +30,7 @@ import { Textarea } from "@/components/ui/textarea"
import { Plus, Loader2 } from "lucide-react"
import { addTechVendor } from "../service"
+import { normalizeEmailFields } from "../utils"
// 폼 스키마 정의
const addVendorSchema = z.object({
@@ -92,6 +93,13 @@ export function AddVendorDialog({ onSuccess }: AddVendorDialogProps) {
const onSubmit = async (data: AddVendorFormData) => {
setIsLoading(true)
try {
+ // 이메일 필드들을 소문자로 변환
+ const normalizedEmails = normalizeEmailFields({
+ email: data.email,
+ agentEmail: data.agentEmail,
+ representativeEmail: data.representativeEmail,
+ })
+
const result = await addTechVendor({
...data,
vendorCode: data.vendorCode || null,
@@ -100,13 +108,14 @@ export function AddVendorDialog({ onSuccess }: AddVendorDialogProps) {
countryFab: data.countryFab || null,
agentName: data.agentName || null,
agentPhone: data.agentPhone || null,
- agentEmail: data.agentEmail || null,
+ agentEmail: normalizedEmails.agentEmail,
address: data.address || null,
phone: data.phone || null,
website: data.website || null,
+ email: normalizedEmails.email,
techVendorType: data.techVendorType.join(','),
representativeName: data.representativeName || null,
- representativeEmail: data.representativeEmail || null,
+ representativeEmail: normalizedEmails.representativeEmail,
representativePhone: data.representativePhone || null,
representativeBirth: data.representativeBirth || null,
taxId: data.taxId || "",
diff --git a/lib/tech-vendors/table/delete-tech-vendors-dialog.tsx b/lib/tech-vendors/table/delete-tech-vendors-dialog.tsx
new file mode 100644
index 00000000..4fd3f32a
--- /dev/null
+++ b/lib/tech-vendors/table/delete-tech-vendors-dialog.tsx
@@ -0,0 +1,80 @@
+"use client"
+
+import * as React from "react"
+import { toast } from "sonner"
+import { Trash2 } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog"
+
+import { deleteTechVendor } from "../service"
+import type { TechVendor } from "@/db/schema/techVendors"
+
+interface DeleteTechVendorDialogProps {
+ vendor: TechVendor
+ onSuccess?: () => void
+}
+// 임시 삭제 버튼
+export function DeleteTechVendorDialog({ vendor, onSuccess }: DeleteTechVendorDialogProps) {
+ const [isDeleting, setIsDeleting] = React.useState(false)
+
+ const handleDelete = async () => {
+ setIsDeleting(true)
+ try {
+ const result = await deleteTechVendor(vendor.id)
+
+ if (result.success) {
+ toast.success("벤더가 성공적으로 삭제되었습니다.")
+ onSuccess?.()
+ } else {
+ toast.error(result.error || "벤더 삭제 중 오류가 발생했습니다.")
+ }
+ } catch (error) {
+ console.error("벤더 삭제 오류:", error)
+ toast.error("벤더 삭제 중 오류가 발생했습니다.")
+ } finally {
+ setIsDeleting(false)
+ }
+ }
+
+ return (
+ <AlertDialog>
+ <AlertDialogTrigger asChild>
+ <Button variant="ghost" size="sm" className="text-red-600 hover:text-red-700 hover:bg-red-50">
+ <Trash2 className="size-4 mr-2" />
+ 삭제
+ </Button>
+ </AlertDialogTrigger>
+ <AlertDialogContent>
+ <AlertDialogHeader>
+ <AlertDialogTitle>벤더 삭제</AlertDialogTitle>
+ <AlertDialogDescription>
+ <strong>{vendor.vendorName}</strong> 벤더를 정말 삭제하시겠습니까?
+ <br />
+ 이 작업은 되돌릴 수 없으며, 관련된 모든 데이터(담당자, 아이템 등)가 함께 삭제됩니다.
+ </AlertDialogDescription>
+ </AlertDialogHeader>
+ <AlertDialogFooter>
+ <AlertDialogCancel>취소</AlertDialogCancel>
+ <AlertDialogAction
+ onClick={handleDelete}
+ disabled={isDeleting}
+ className="bg-red-600 hover:bg-red-700"
+ >
+ {isDeleting ? "삭제 중..." : "삭제"}
+ </AlertDialogAction>
+ </AlertDialogFooter>
+ </AlertDialogContent>
+ </AlertDialog>
+ )
+}
diff --git a/lib/tech-vendors/table/import-button.tsx b/lib/tech-vendors/table/import-button.tsx
index 85b16bc7..e0f95195 100644
--- a/lib/tech-vendors/table/import-button.tsx
+++ b/lib/tech-vendors/table/import-button.tsx
@@ -17,6 +17,7 @@ import {
import { Progress } from "@/components/ui/progress"
import { importTechVendorsFromExcel } from "../service"
import { decryptWithServerAction } from "@/components/drm/drmUtils"
+import { normalizeEmail } from "../utils"
interface ImportTechVendorButtonProps {
onSuccess?: () => void;
@@ -192,13 +193,17 @@ export function ImportTechVendorButton({ onSuccess }: ImportTechVendorButtonProp
// 벤더 데이터 처리
const vendors = dataRows.map(row => {
- const vendorEmail = row["이메일"] || row["email"] || "";
+ // 이메일들을 소문자로 변환
+ const vendorEmail = normalizeEmail(row["이메일"] || row["email"]) || null;
+ const contactEmail = normalizeEmail(row["담당자이메일"] || row["contactEmail"]);
+ const agentEmail = normalizeEmail(row["에이전트이메일"] || row["agentEmail"]);
+ const representativeEmail = normalizeEmail(row["대표자이메일"] || row["representativeEmail"]);
+
const contactName = row["담당자명"] || row["contactName"] || "";
- const contactEmail = row["담당자이메일"] || row["contactEmail"] || "";
-
+
// 담당자 정보 처리: 담당자가 없으면 벤더 이메일을 기본 담당자로 사용
const contacts = [];
-
+
if (contactName && contactEmail) {
// 명시적인 담당자가 있는 경우
contacts.push({
@@ -221,7 +226,7 @@ export function ImportTechVendorButton({ onSuccess }: ImportTechVendorButtonProp
isPrimary: true
});
}
-
+
return {
vendorName: row["업체명"] || row["vendorName"] || "",
vendorCode: row["업체코드"] || row["vendorCode"] || null,
@@ -232,13 +237,13 @@ export function ImportTechVendorButton({ onSuccess }: ImportTechVendorButtonProp
countryFab: row["제조국"] || row["countryFab"] || null,
agentName: row["에이전트명"] || row["agentName"] || null,
agentPhone: row["에이전트연락처"] || row["agentPhone"] || null,
- agentEmail: row["에이전트이메일"] || row["agentEmail"] || null,
+ agentEmail: agentEmail,
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,
+ representativeEmail: representativeEmail,
representativePhone: row["대표자연락처"] || row["representativePhone"] || null,
representativeBirth: row["대표자생년월일"] || row["representativeBirth"] || null,
items: row["아이템"] || row["items"] || "",
diff --git a/lib/tech-vendors/table/tech-vendors-table-columns.tsx b/lib/tech-vendors/table/tech-vendors-table-columns.tsx
index da17a975..c1bf6229 100644
--- a/lib/tech-vendors/table/tech-vendors-table-columns.tsx
+++ b/lib/tech-vendors/table/tech-vendors-table-columns.tsx
@@ -30,6 +30,7 @@ import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-
import { techVendorColumnsConfig } from "@/config/techVendorColumnsConfig"
import { Separator } from "@/components/ui/separator"
import { getVendorStatusIcon } from "../utils"
+import { DeleteTechVendorDialog } from "./delete-tech-vendors-dialog"
// 타입 정의 추가
type StatusType = (typeof techVendors.status.enumValues)[number];
@@ -47,6 +48,7 @@ type NextRouter = ReturnType<typeof useRouter>;
interface GetColumnsProps {
setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<TechVendor> | null>>;
router: NextRouter;
+ onVendorDeleted?: () => void;
}
@@ -55,7 +57,7 @@ interface GetColumnsProps {
/**
* tanstack table 컬럼 정의 (중첩 헤더 버전)
*/
-export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef<TechVendor>[] {
+export function getColumns({ setRowAction, router, onVendorDeleted }: GetColumnsProps): ColumnDef<TechVendor>[] {
// ----------------------------------------------------------------
// 1) select 컬럼 (체크박스)
// ----------------------------------------------------------------
@@ -169,6 +171,14 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
</DropdownMenuSubContent>
</DropdownMenuSub>
+ <Separator />
+ {/* 벤더 임시 삭제 버튼 */}
+ <div className="px-2 py-1">
+ <DeleteTechVendorDialog
+ vendor={row.original}
+ onSuccess={onVendorDeleted}
+ />
+ </div>
</DropdownMenuContent>
</DropdownMenu>
diff --git a/lib/tech-vendors/table/tech-vendors-table.tsx b/lib/tech-vendors/table/tech-vendors-table.tsx
index 553ff109..e432a453 100644
--- a/lib/tech-vendors/table/tech-vendors-table.tsx
+++ b/lib/tech-vendors/table/tech-vendors-table.tsx
@@ -58,8 +58,13 @@ export function TechVendorsTable({
const router = useRouter()
// getColumns() 호출 시, router를 주입
+ // 임시 삭제 버튼 추가
const columns = React.useMemo(
- () => getColumns({ setRowAction, router }),
+ () => getColumns({
+ setRowAction,
+ router,
+ onVendorDeleted: () => router.refresh()
+ }),
[setRowAction, router]
)
diff --git a/lib/tech-vendors/table/update-vendor-sheet.tsx b/lib/tech-vendors/table/update-vendor-sheet.tsx
index 8498df51..3cbb2ba3 100644
--- a/lib/tech-vendors/table/update-vendor-sheet.tsx
+++ b/lib/tech-vendors/table/update-vendor-sheet.tsx
@@ -45,6 +45,7 @@ import { useSession } from "next-auth/react"
import { TechVendor, techVendors } from "@/db/schema/techVendors"
import { updateTechVendorSchema, type UpdateTechVendorSchema } from "../validations"
import { modifyTechVendor } from "../service"
+import { normalizeEmailFields } from "../utils"
interface UpdateVendorSheetProps
extends React.ComponentPropsWithRef<typeof Sheet> {
@@ -186,12 +187,22 @@ export function UpdateVendorSheet({ vendor, ...props }: UpdateVendorSheetProps)
? `상태 변경: ${getStatusConfig(oldStatus).label} → ${getStatusConfig(newStatus).label}`
: "" // Empty string instead of undefined
+ // 이메일 필드들을 소문자로 변환
+ const normalizedEmails = normalizeEmailFields({
+ email: data.email,
+ agentEmail: data.agentEmail,
+ representativeEmail: data.representativeEmail,
+ })
+
// 업체 정보 업데이트 - userId와 상태 변경 코멘트 추가
- const { error } = await modifyTechVendor({
+ const { error } = await modifyTechVendor({
id: String(vendor.id),
userId: Number(session.user.id), // Add user ID from session
comment: statusComment, // Add comment for status changes
...data, // 모든 데이터 전달 - 서비스 함수에서 필요한 필드만 처리
+ email: normalizedEmails.email || undefined,
+ agentEmail: normalizedEmails.agentEmail || undefined,
+ representativeEmail: normalizedEmails.representativeEmail || undefined,
techVendorType: Array.isArray(data.techVendorType) ? data.techVendorType.join(',') : undefined,
})
diff --git a/lib/tech-vendors/utils.ts b/lib/tech-vendors/utils.ts
index ac91cd8d..ea8cdbd3 100644
--- a/lib/tech-vendors/utils.ts
+++ b/lib/tech-vendors/utils.ts
@@ -1,28 +1,56 @@
-import { LucideIcon, CheckCircle2, CircleAlert, Clock, ShieldAlert, Mail, BarChart2 } from "lucide-react";
-import type { TechVendor } from "@/db/schema/techVendors";
-
-type StatusType = TechVendor["status"];
-
-/**
- * 기술벤더 상태에 대한 아이콘을 반환합니다.
- */
-export function getVendorStatusIcon(status: StatusType): LucideIcon {
- switch (status) {
- case "PENDING_INVITE":
- return Clock;
- case "INVITED":
- return Mail;
- case "QUOTE_COMPARISON":
- return BarChart2;
- case "ACTIVE":
- return CheckCircle2;
- case "INACTIVE":
- return CircleAlert;
- case "BLACKLISTED":
- return ShieldAlert;
- default:
- return CircleAlert;
- }
-}
-
-
+import { LucideIcon, CheckCircle2, CircleAlert, Clock, ShieldAlert, Mail, BarChart2 } from "lucide-react";
+import type { TechVendor } from "@/db/schema/techVendors";
+
+type StatusType = TechVendor["status"];
+
+/**
+ * 기술벤더 상태에 대한 아이콘을 반환합니다.
+ */
+export function getVendorStatusIcon(status: StatusType): LucideIcon {
+ switch (status) {
+ case "PENDING_INVITE":
+ return Clock;
+ case "INVITED":
+ return Mail;
+ case "QUOTE_COMPARISON":
+ return BarChart2;
+ case "ACTIVE":
+ return CheckCircle2;
+ case "INACTIVE":
+ return CircleAlert;
+ case "BLACKLISTED":
+ return ShieldAlert;
+ default:
+ return CircleAlert;
+ }
+}
+
+/**
+ * 이메일을 소문자로 변환합니다.
+ * null, undefined, 또는 빈 문자열인 경우 null을 반환합니다.
+ */
+export function normalizeEmail(email: string | null | undefined): string | null {
+ if (!email || typeof email !== 'string' || email.trim() === '') {
+ return null;
+ }
+ return email.toLowerCase().trim();
+}
+
+/**
+ * 여러 이메일 필드들을 소문자로 변환합니다.
+ */
+export function normalizeEmailFields(data: {
+ email?: string | null;
+ agentEmail?: string | null;
+ representativeEmail?: string | null;
+ contactEmail?: string | null;
+}) {
+ return {
+ email: normalizeEmail(data.email),
+ agentEmail: normalizeEmail(data.agentEmail),
+ representativeEmail: normalizeEmail(data.representativeEmail),
+ contactEmail: normalizeEmail(data.contactEmail),
+ };
+}
+
+