import { createSearchParamsCache, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, } from "nuqs/server" import * as z from "zod" import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers" import { techVendors, TechVendor, TechVendorContact, VENDOR_TYPES } from "@/db/schema/techVendors"; // TechVendorPossibleItem 타입 정의 - 새 스키마에 맞게 수정 export interface TechVendorPossibleItem { id: number; vendorId: number; shipbuildingItemId: number | null; offshoreTopItemId: number | null; offshoreHullItemId: number | null; createdAt: Date; updatedAt: Date; // 조인된 정보 (어떤 타입의 아이템인지에 따라) itemCode?: string; workType?: string | null; shipTypes?: string | null; itemList?: string | null; subItemList?: string | null; techVendorType?: "조선" | "해양TOP" | "해양HULL"; } export const searchParamsCache = createSearchParamsCache({ // 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( [] ), // 페이징 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 정렬 (techVendors 테이블에 맞춰 TechVendor 타입 지정) sort: getSortingStateParser().withDefault([ { id: "createdAt", desc: true }, // createdAt 기준 내림차순 ]), // 고급 필터 filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 검색 키워드 search: parseAsString.withDefault(""), // ----------------------------------------------------------------- // 기술영업 협력업체에 특화된 검색 필드 // ----------------------------------------------------------------- // 상태 (ACTIVE, INACTIVE, BLACKLISTED 등) 중에서 선택 status: parseAsStringEnum(["ACTIVE", "INACTIVE", "BLACKLISTED", "PENDING_REVIEW"]), // 협력업체명 검색 vendorName: parseAsString.withDefault(""), // 국가 검색 country: parseAsString.withDefault(""), // 예) 코드 검색 vendorCode: parseAsString.withDefault(""), // 벤더 타입 필터링 (다중 선택 가능) vendorType: parseAsStringEnum(["ship", "top", "hull"]), // workTypes 필터링 (다중 선택 가능) workTypes: parseAsArrayOf(parseAsStringEnum([ // 조선 workTypes "기장", "전장", "선실", "배관", "철의", "선체", // 해양TOP workTypes "TM", "TS", "TE", "TP", // 해양HULL workTypes "HA", "HE", "HH", "HM", "NC", "HO", "HP" ])).withDefault([]), // 필요하다면 이메일 검색 / 웹사이트 검색 등 추가 가능 email: parseAsString.withDefault(""), website: parseAsString.withDefault(""), }); export const searchParamsContactCache = createSearchParamsCache({ // 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( [] ), // 페이징 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 정렬 sort: getSortingStateParser().withDefault([ { id: "createdAt", desc: true }, // createdAt 기준 내림차순 ]), // 고급 필터 filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 검색 키워드 search: parseAsString.withDefault(""), // 특정 필드 검색 contactName: parseAsString.withDefault(""), contactPosition: parseAsString.withDefault(""), contactTitle: parseAsString.withDefault(""), contactEmail: parseAsString.withDefault(""), contactPhone: parseAsString.withDefault(""), }); export const searchParamsItemCache = createSearchParamsCache({ // 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( [] ), // 페이징 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 정렬 sort: getSortingStateParser().withDefault([ { id: "createdAt", desc: true }, // createdAt 기준 내림차순 ]), // 고급 필터 filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 검색 키워드 search: parseAsString.withDefault(""), // 특정 필드 검색 itemName: parseAsString.withDefault(""), itemCode: parseAsString.withDefault(""), }); export const searchParamsPossibleItemsCache = createSearchParamsCache({ // 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( [] ), // 페이징 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 정렬 sort: getSortingStateParser().withDefault([ { id: "createdAt", desc: true }, ]), // 고급 필터 filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 검색 키워드 search: parseAsString.withDefault(""), // 개별 필터 필드들 itemCode: parseAsString.withDefault(""), workType: parseAsString.withDefault(""), itemList: parseAsString.withDefault(""), shipTypes: parseAsString.withDefault(""), subItemList: parseAsString.withDefault(""), }); // 기술영업 벤더 기본 정보 업데이트 스키마 export const updateTechVendorSchema = z.object({ vendorName: z.string().min(1, "업체명은 필수 입력사항입니다"), vendorCode: z.string().optional(), address: z.string().optional(), country: z.string().optional(), countryEng: z.string().optional(), countryFab: z.string().optional(), phone: z.string().optional(), email: z.string().email("유효한 이메일 주소를 입력해주세요").optional(), website: z.string().optional(), techVendorType: z.union([ z.array(z.enum(VENDOR_TYPES)).min(1, "최소 하나의 벤더 타입을 선택해주세요"), z.string().min(1, "벤더 타입을 선택해주세요") ]).optional(), status: z.enum(techVendors.status.enumValues).optional(), // 에이전트 정보 agentName: z.string().optional(), agentEmail: z.string().email("유효한 이메일 주소를 입력해주세요").optional().or(z.literal("")), agentPhone: z.string().optional(), // 대표자 정보 representativeName: z.string().optional(), representativeEmail: z.string().email("유효한 이메일 주소를 입력해주세요").optional().or(z.literal("")), representativePhone: z.string().optional(), representativeBirth: z.string().optional(), userId: z.number().optional(), comment: z.string().optional(), }); // 연락처 스키마 const contactSchema = z.object({ id: z.number().optional(), contactName: z .string() .min(1, "Contact name is required") .max(255, "Max length 255"), contactPosition: z.string().max(100).optional(), contactEmail: z.string().email("Invalid email").max(255), contactCountry: z.string().max(100).optional(), contactPhone: z.string().max(50).optional(), contactTitle: z.string().max(100).optional(), isPrimary: z.boolean().default(false).optional() }); // 기술영업 벤더 생성 스키마 export const createTechVendorSchema = z .object({ vendorName: z .string() .min(1, "Vendor name is required") .max(255, "Max length 255"), email: z.string().email("Invalid email").max(255), // 나머지 optional vendorCode: z.string().max(100, "Max length 100").optional(), address: z.string().optional(), country: z.string() .min(1, "국가 선택은 필수입니다.") .max(100, "Max length 100"), phone: z.string().max(50, "Max length 50").optional(), website: z.string().max(255).optional(), files: z.any().optional(), status: z.enum(techVendors.status.enumValues).default("ACTIVE"), techVendorType: z.union([ z.array(z.enum(VENDOR_TYPES)).min(1, "최소 하나의 벤더 타입을 선택해주세요"), z.string().min(1, "벤더 타입을 선택해주세요") ]).default(["조선"]), representativeName: z.union([z.string().max(255), z.literal("")]).optional(), representativeBirth: z.union([z.string().max(20), z.literal("")]).optional(), representativeEmail: z.union([z.string().email("Invalid email").max(255), z.literal("")]).optional(), representativePhone: z.union([z.string().max(50), z.literal("")]).optional(), taxId: z.string().min(1, { message: "사업자등록번호를 입력해주세요" }), items: z.string().min(1, { message: "공급품목을 입력해주세요" }), contacts: z .array(contactSchema) .nonempty("At least one contact is required.") }) .superRefine((data, ctx) => { if (data.country === "KR") { // 1) 대표자 정보가 누락되면 각각 에러 발생 if (!data.representativeName) { ctx.addIssue({ code: "custom", path: ["representativeName"], message: "대표자 이름은 한국(KR) 업체일 경우 필수입니다.", }) } if (!data.representativeBirth) { ctx.addIssue({ code: "custom", path: ["representativeBirth"], message: "대표자 생년월일은 한국(KR) 업체일 경우 필수입니다.", }) } if (!data.representativeEmail) { ctx.addIssue({ code: "custom", path: ["representativeEmail"], message: "대표자 이메일은 한국(KR) 업체일 경우 필수입니다.", }) } if (!data.representativePhone) { ctx.addIssue({ code: "custom", path: ["representativePhone"], message: "대표자 전화번호는 한국(KR) 업체일 경우 필수입니다.", }) } } }); // 연락처 생성 스키마 export const createTechVendorContactSchema = z.object({ vendorId: z.number(), contactName: z.string() .min(1, "Contact name is required") .max(255, "Max length 255"), contactPosition: z.string().max(100, "Max length 100"), contactTitle: z.string().max(100, "Max length 100").optional(), contactEmail: z.string().email(), contactPhone: z.string().max(50, "Max length 50").optional(), contactCountry: z.string().max(100, "Max length 100").optional(), isPrimary: z.boolean(), }); // 연락처 업데이트 스키마 export const updateTechVendorContactSchema = z.object({ contactName: z.string() .min(1, "Contact name is required") .max(255, "Max length 255"), contactPosition: z.string().max(100, "Max length 100").optional(), contactTitle: z.string().max(100, "Max length 100").optional(), contactEmail: z.string().email().optional(), contactPhone: z.string().max(50, "Max length 50").optional(), contactCountry: z.string().max(100, "Max length 100").optional(), isPrimary: z.boolean().optional(), }); // Possible Items 생성 스키마 - 새 스키마에 맞게 수정 export const createTechVendorPossibleItemSchema = z.object({ vendorId: z.number(), shipbuildingItemId: z.number().optional(), offshoreTopItemId: z.number().optional(), offshoreHullItemId: z.number().optional(), }); // Possible Items 업데이트 스키마 export const updateTechVendorPossibleItemSchema = createTechVendorPossibleItemSchema.extend({ id: z.number(), }); export const searchParamsRfqHistoryCache = createSearchParamsCache({ // 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( [] ), // 페이징 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 정렬 (RFQ 히스토리에 맞춰) sort: getSortingStateParser<{ id: number; rfqCode: string | null; description: string | null; projectCode: string | null; projectName: string | null; projectType: string | null; // 프로젝트 타입 추가 status: string; totalAmount: string | null; currency: string | null; dueDate: Date | null; createdAt: Date; quotationCode: string | null; submittedAt: Date | null; }>().withDefault([ { id: "createdAt", desc: true }, ]), // 고급 필터 filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 검색 키워드 search: parseAsString.withDefault(""), // RFQ 히스토리 특화 필드 rfqCode: parseAsString.withDefault(""), description: parseAsString.withDefault(""), projectCode: parseAsString.withDefault(""), projectName: parseAsString.withDefault(""), projectType: parseAsStringEnum(["SHIP", "TOP", "HULL"]), // 프로젝트 타입 필터 추가 status: parseAsStringEnum(["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"]), }); // 타입 내보내기 export type GetTechVendorsSchema = Awaited> export type GetTechVendorContactsSchema = Awaited> export type GetTechVendorItemsSchema = Awaited> export type GetTechVendorPossibleItemsSchema = Awaited> export type GetTechVendorRfqHistorySchema = Awaited> export type UpdateTechVendorSchema = z.infer export type CreateTechVendorSchema = z.infer export type CreateTechVendorContactSchema = z.infer export type UpdateTechVendorContactSchema = z.infer export type CreateTechVendorPossibleItemSchema = z.infer export type UpdateTechVendorPossibleItemSchema = z.infer