import { GtcClauseTreeView, GtcClauseWithVendorView, GtcVendorClauseView, type GtcClause } from "@/db/schema/gtc" import { createSearchParamsCache, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, } from "nuqs/server" import * as z from "zod" import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers" export const searchParamsCache = createSearchParamsCache({ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( [] ), page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(20), sort: getSortingStateParser().withDefault([ { id: "itemNumber", desc: false }, ]), // advanced filter filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), search: parseAsString.withDefault(""), }) export const searchParamsVendorCache = createSearchParamsCache({ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( [] ), page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(20), sort: getSortingStateParser().withDefault([ { id: "effectiveItemNumber", desc: false }, ]), // advanced filter filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), search: parseAsString.withDefault(""), }) const clauseImageSchema = z.object({ id: z.string(), url: z.string().url(), fileName: z.string(), size: z.number().positive(), }) export const createGtcClauseSchema = z.object({ documentId: z.number(), parentId: z.number().nullable().optional(), itemNumber: z.string().min(1, "채번을 입력해주세요"), category: z.string().optional(), subtitle: z.string().min(1, "소제목을 입력해주세요"), images: z.array(clauseImageSchema).optional(), // ✅ 이미지 배열 추가 content: z.string().optional(), // 그룹핑용 조항은 내용이 없을 수 있음 sortOrder: z.number().default(0), editReason: z.string().optional(), }).superRefine(async (data, ctx) => { // 채번 형식 검증 (숫자, 문자 모두 허용하되 특수문자 제한) const itemNumberRegex = /^[a-zA-Z0-9._-]+$/ if (!itemNumberRegex.test(data.itemNumber)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "채번은 영문, 숫자, 점(.), 하이픈(-), 언더스코어(_)만 사용 가능합니다", path: ["itemNumber"], }) } }) export const updateGtcClauseSchema = z.object({ itemNumber: z.string().min(1, "채번을 입력해주세요").optional(), category: z.string().optional(), subtitle: z.string().min(1, "소제목을 입력해주세요").optional(), content: z.string().optional(), // 내용도 nullable sortOrder: z.number().optional(), images: z.array(clauseImageSchema).optional(), // ✅ 이미지 배열 추가 isActive: z.boolean().optional(), editReason: z.string().optional(), }).superRefine(async (data, ctx) => { // 채번 형식 검증 if (data.itemNumber) { const itemNumberRegex = /^[a-zA-Z0-9._-]+$/ if (!itemNumberRegex.test(data.itemNumber)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "채번은 영문, 숫자, 점(.), 하이픈(-), 언더스코어(_)만 사용 가능합니다", path: ["itemNumber"], }) } } }) export const reorderGtcClausesSchema = z.object({ clauses: z.array(z.object({ id: z.number(), sortOrder: z.number(), parentId: z.number().nullable(), depth: z.number(), fullPath: z.string().optional(), })), editReason: z.string().optional(), }) export const bulkUpdateGtcClausesSchema = z.object({ clauseIds: z.array(z.number()).min(1, "수정할 조항을 선택해주세요"), updates: z.object({ category: z.string().optional(), isActive: z.boolean().optional(), }), editReason: z.string().min(1, "편집 사유를 입력해주세요"), }) export const generateVariableNamesSchema = z.object({ documentId: z.number(), prefix: z.string().default("CLAUSE"), includeVendorCode: z.boolean().default(false), vendorCode: z.string().optional(), }).superRefine(async (data, ctx) => { if (data.includeVendorCode && !data.vendorCode) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "벤더 코드 포함 시 벤더 코드를 입력해주세요", path: ["vendorCode"], }) } }) export type GetGtcClausesSchema = Awaited> export type GetGtcVendorClausesSchema = Awaited> export type CreateGtcClauseSchema = z.infer export type UpdateGtcClauseSchema = z.infer export type ReorderGtcClausesSchema = z.infer export type BulkUpdateGtcClausesSchema = z.infer export type GenerateVariableNamesSchema = z.infer // validations.ts에 추가 export const updateVendorGtcClauseSchema = z.object({ modifiedItemNumber: z.string().optional(), modifiedCategory: z.string().optional(), modifiedSubtitle: z.string().optional(), modifiedContent: z.string().optional(), isNumberModified: z.boolean().default(false), isCategoryModified: z.boolean().default(false), isSubtitleModified: z.boolean().default(false), isContentModified: z.boolean().default(false), reviewStatus: z.enum([ "draft", "pending", "reviewing", "approved", "rejected", "revised" ]).default("draft"), negotiationNote: z.string().optional(), isExcluded: z.boolean().default(false), }) export type UpdateVendorGtcClauseSchema = z.infer // validations.ts export const createVendorGtcClauseSchema = z.object({ vendorDocumentId: z.number({ required_error: "벤더 문서 ID는 필수입니다.", }), baseClauseId: z.number({ required_error: "기본 조항 ID는 필수입니다.", }), documentId: z.number({ required_error: "문서 ID는 필수입니다.", }), parentId: z.number().nullable().optional(), modifiedItemNumber: z.string().optional().nullable(), modifiedCategory: z.string().optional().nullable(), modifiedSubtitle: z.string().optional().nullable(), modifiedContent: z.string().optional().nullable(), sortOrder: z.number().default(0), reviewStatus: z.enum(["draft", "pending", "reviewing", "approved", "rejected", "revised"]).default("draft"), negotiationNote: z.string().optional().nullable(), isExcluded: z.boolean().default(false), isNumberModified: z.boolean().default(false), isCategoryModified: z.boolean().default(false), isSubtitleModified: z.boolean().default(false), isContentModified: z.boolean().default(false), editReason: z.string().optional().nullable(), images: z.array( z.object({ id: z.string(), url: z.string(), fileName: z.string(), size: z.number(), savedName: z.string().optional(), mimeType: z.string().optional(), width: z.number().optional(), height: z.number().optional(), hash: z.string().optional(), }) ).optional().nullable(), }) export type CreateVendorGtcClauseSchema = z.infer