diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-11 09:02:00 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-11 09:02:00 +0000 |
| commit | cbb4c7fe0b94459162ad5e998bc05cd293e0ff96 (patch) | |
| tree | 0a26712f7685e4f6511e637b9a81269d90a47c8f /lib/tags | |
| parent | eb654f88214095f71be142b989e620fd28db3f69 (diff) | |
(대표님) 입찰, EDP 변경사항 대응, spreadJS 오류 수정, 벤더실사 수정
Diffstat (limited to 'lib/tags')
| -rw-r--r-- | lib/tags/service.ts | 66 | ||||
| -rw-r--r-- | lib/tags/table/add-tag-dialog.tsx | 46 |
2 files changed, 72 insertions, 40 deletions
diff --git a/lib/tags/service.ts b/lib/tags/service.ts index a1dff137..bec342e1 100644 --- a/lib/tags/service.ts +++ b/lib/tags/service.ts @@ -14,7 +14,7 @@ import { getFormMappingsByTagType } from './form-mapping-service'; import { contractItems, contracts } from "@/db/schema/contract"; import { getCodeListsByID } from "../sedp/sync-object-class"; import { projects } from "@/db/schema"; - +import { randomBytes } from 'crypto'; // 폼 결과를 위한 인터페이스 정의 interface CreatedOrExistingForm { @@ -24,6 +24,14 @@ interface CreatedOrExistingForm { isNewlyCreated: boolean; } +/** + * 16진수 24자리 고유 식별자 생성 + * @returns 24자리 16진수 문자열 (예: "a1b2c3d4e5f6789012345678") + */ +function generateTagIdx(): string { + return randomBytes(12).toString('hex'); // 12바이트 = 24자리 16진수 +} + export async function getTags(input: GetTagsSchema, packagesId: number) { // return unstable_cache( @@ -90,6 +98,7 @@ export async function getTags(input: GetTagsSchema, packagesId: number) { // )(); } + export async function createTag( formData: CreateTagSchema, selectedPackageId: number | null @@ -259,10 +268,15 @@ export async function createTag( } } - // 5) 새 Tag 생성 (같은 트랜잭션 `tx` 사용) + // 🆕 16진수 24자리 태그 고유 식별자 생성 + const generatedTagIdx = generateTagIdx(); + console.log(`[CREATE TAG] Generated tagIdx: ${generatedTagIdx}`); + + // 5) 새 Tag 생성 (tagIdx 추가) const [newTag] = await insertTag(tx, { contractItemId: selectedPackageId, formId: primaryFormId, + tagIdx: generatedTagIdx, // 🆕 생성된 16진수 24자리 추가 tagNo: validated.data.tagNo, class: validated.data.class, tagType: validated.data.tagType, @@ -271,7 +285,7 @@ export async function createTag( console.log(`tags-${selectedPackageId}`, "create", newTag) - // 6) 생성된 각 form에 대해 formEntries에 데이터 추가 + // 6) 생성된 각 form에 대해 formEntries에 데이터 추가 (TAG_IDX 포함) for (const form of createdOrExistingForms) { try { // 기존 formEntry 가져오기 @@ -282,16 +296,18 @@ export async function createTag( ) }); - // 새로운 태그 데이터 객체 생성 + // 새로운 태그 데이터 객체 생성 (TAG_IDX 포함) const newTagData = { + TAG_IDX: generatedTagIdx, // 🆕 같은 16진수 24자리 값 사용 TAG_NO: validated.data.tagNo, TAG_DESC: validated.data.description ?? null, status: "New" // 수동으로 생성된 태그임을 표시 }; if (existingEntry && existingEntry.id) { - // 기존 formEntry가 있는 경우 + // 기존 formEntry가 있는 경우 - TAG_IDX 타입 추가 let existingData: Array<{ + TAG_IDX?: string; // 🆕 TAG_IDX 필드 추가 TAG_NO: string; TAG_DESC?: string; status?: string; @@ -302,13 +318,14 @@ export async function createTag( existingData = existingEntry.data; } - // TAG_NO가 이미 존재하는지 확인 + // TAG_IDX 또는 TAG_NO가 이미 존재하는지 확인 (우선순위: TAG_IDX) const existingTagIndex = existingData.findIndex( - item => item.TAG_NO === validated.data.tagNo + item => item.TAG_IDX === generatedTagIdx || + (item.TAG_NO === validated.data.tagNo && !item.TAG_IDX) ); if (existingTagIndex === -1) { - // TAG_NO가 없으면 새로 추가 + // 태그가 없으면 새로 추가 const updatedData = [...existingData, newTagData]; await tx @@ -319,12 +336,12 @@ export async function createTag( }) .where(eq(formEntries.id, existingEntry.id)); - console.log(`[CREATE TAG] Added tag ${validated.data.tagNo} to existing formEntry for form ${form.formCode}`); + console.log(`[CREATE TAG] Added tag ${validated.data.tagNo} with tagIdx ${generatedTagIdx} to existing formEntry for form ${form.formCode}`); } else { console.log(`[CREATE TAG] Tag ${validated.data.tagNo} already exists in formEntry for form ${form.formCode}`); } } else { - // formEntry가 없는 경우 새로 생성 + // formEntry가 없는 경우 새로 생성 (TAG_IDX 포함) await tx.insert(formEntries).values({ formCode: form.formCode, contractItemId: selectedPackageId, @@ -333,7 +350,7 @@ export async function createTag( updatedAt: new Date(), }); - console.log(`[CREATE TAG] Created new formEntry with tag ${validated.data.tagNo} for form ${form.formCode}`); + console.log(`[CREATE TAG] Created new formEntry with tag ${validated.data.tagNo} and tagIdx ${generatedTagIdx} for form ${form.formCode}`); } } catch (formEntryError) { console.error(`[CREATE TAG] Error updating formEntry for form ${form.formCode}:`, formEntryError); @@ -351,13 +368,14 @@ export async function createTag( revalidateTag(`form-data-${form.formCode}-${selectedPackageId}`) }) - // 8) 성공 시 반환 + // 8) 성공 시 반환 (tagIdx 추가) return { success: true, data: { forms: createdOrExistingForms, primaryFormId, - tagNo: validated.data.tagNo + tagNo: validated.data.tagNo, + tagIdx: generatedTagIdx, // 🆕 생성된 tagIdx도 반환 }, } }) @@ -369,7 +387,6 @@ export async function createTag( } } - export async function createTagInForm( formData: CreateTagSchema, selectedPackageId: number | null, @@ -499,10 +516,15 @@ export async function createTagInForm( } if (form?.id) { - // 5) 새 Tag 생성 (같은 트랜잭션 `tx` 사용) + // 🆕 16진수 24자리 태그 고유 식별자 생성 + const generatedTagIdx = generateTagIdx(); + console.log(`[CREATE TAG IN FORM] Generated tagIdx: ${generatedTagIdx}`); + + // 5) 새 Tag 생성 (tagIdx 추가) const [newTag] = await insertTag(tx, { contractItemId: selectedPackageId, formId: form.id, + tagIdx: generatedTagIdx, // 🆕 생성된 16진수 24자리 추가 tagNo: validated.data.tagNo, class: validated.data.class, tagType: validated.data.tagType, @@ -518,8 +540,9 @@ export async function createTagInForm( }); if (entry && entry.id) { - // 7) 기존 데이터 가져오기 (배열인지 확인) + // 7) 기존 데이터 가져오기 (배열인지 확인) - TAG_IDX 타입 추가 let existingData: Array<{ + TAG_IDX?: string; // 🆕 TAG_IDX 필드 추가 TAG_NO: string; TAG_DESC?: string; status?: string; @@ -532,8 +555,9 @@ export async function createTagInForm( console.log(`[CREATE TAG IN FORM] Existing data count: ${existingData.length}`); - // 8) 새로운 태그를 기존 데이터에 추가 (status 필드 포함) + // 8) 새로운 태그를 기존 데이터에 추가 (TAG_IDX 포함) const newTagData = { + TAG_IDX: generatedTagIdx, // 🆕 같은 16진수 24자리 값 사용 TAG_NO: validated.data.tagNo, TAG_DESC: validated.data.description ?? null, status: "New" // 수동으로 생성된 태그임을 표시 @@ -542,7 +566,7 @@ export async function createTagInForm( const updatedData = [...existingData, newTagData]; console.log(`[CREATE TAG IN FORM] Updated data count: ${updatedData.length}`); - console.log(`[CREATE TAG IN FORM] Added tag: ${validated.data.tagNo} with status: 수동 생성`); + console.log(`[CREATE TAG IN FORM] Added tag: ${validated.data.tagNo} with tagIdx: ${generatedTagIdx}, status: 수동 생성`); // 9) formEntries 업데이트 await tx @@ -553,10 +577,11 @@ export async function createTagInForm( }) .where(eq(formEntries.id, entry.id)); } else { - // 10) formEntry가 없는 경우 새로 생성 (status 필드 포함) + // 10) formEntry가 없는 경우 새로 생성 (TAG_IDX 포함) console.log(`[CREATE TAG IN FORM] No existing formEntry found, creating new one`); const newEntryData = [{ + TAG_IDX: generatedTagIdx, // 🆕 같은 16진수 24자리 값 사용 TAG_NO: validated.data.tagNo, TAG_DESC: validated.data.description ?? null, status: "New" // 수동으로 생성된 태그임을 표시 @@ -571,7 +596,7 @@ export async function createTagInForm( }); } - console.log(`[CREATE TAG IN FORM] Successfully created tag: ${validated.data.tagNo}`) + console.log(`[CREATE TAG IN FORM] Successfully created tag: ${validated.data.tagNo} with tagIdx: ${generatedTagIdx}`) } else { return { error: "Failed to create or find form" }; } @@ -588,6 +613,7 @@ export async function createTagInForm( data: { formId: form.id, tagNo: validated.data.tagNo, + tagIdx: generatedTagIdx, // 🆕 생성된 tagIdx도 반환 formCreated: !form // form이 새로 생성되었는지 여부 } } diff --git a/lib/tags/table/add-tag-dialog.tsx b/lib/tags/table/add-tag-dialog.tsx index 10167c62..e5207cd8 100644 --- a/lib/tags/table/add-tag-dialog.tsx +++ b/lib/tags/table/add-tag-dialog.tsx @@ -61,6 +61,7 @@ import { type ClassOption, TagTypeOption, } from "@/lib/tags/service" +import { ScrollArea } from "@/components/ui/scroll-area" // Updated to support multiple rows and subclass interface MultiTagFormValues { @@ -96,7 +97,7 @@ interface UpdatedClassOption extends ClassOption { } interface AddTagDialogProps { - selectedPackageId: number + selectedPackageId: number } export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { @@ -171,7 +172,7 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { setIsLoadingSubFields(true) try { // 수정된 getSubfieldsByTagType 함수 호출 (subclassRemark 파라미터 추가) - const { subFields: apiSubFields } = await getSubfieldsByTagType(tagTypeCode, selectedPackageId, subclassRemark ,subclass ) + const { subFields: apiSubFields } = await getSubfieldsByTagType(tagTypeCode, selectedPackageId, subclassRemark, subclass) const formattedSubFields: SubFieldDef[] = apiSubFields.map(field => ({ name: field.name, label: field.label, @@ -213,7 +214,7 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { form.setValue("subclass", "") // 서브클래스 초기화 setSelectedClassOption(classOption) setSelectedSubclass("") - + if (classOption.tagTypeCode) { setSelectedTagTypeCode(classOption.tagTypeCode) // If you have tagTypeList, you can find the label @@ -223,13 +224,13 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { } else if (classOption.tagTypeDescription) { form.setValue("tagType", classOption.tagTypeDescription) } - + // 서브클래스가 있으면 서브필드 로딩을 하지 않고 대기 if (classOption.subclasses && classOption.subclasses.length > 0) { setSubFields([]) // 서브클래스 선택을 기다림 } else { // 서브클래스가 없으면 바로 서브필드 로딩 - await loadFilteredSubFieldsByTagTypeCode(classOption.tagTypeCode, "","") + await loadFilteredSubFieldsByTagTypeCode(classOption.tagTypeCode, "", "") } } } @@ -239,13 +240,13 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { // --------------- async function handleSelectSubclass(subclassCode: string) { if (!selectedClassOption || !selectedTagTypeCode) return - + setSelectedSubclass(subclassCode) form.setValue("subclass", subclassCode) - + // 선택된 서브클래스의 리마크 값 가져오기 const subclassRemarkValue = selectedClassOption.subclassRemark[subclassCode] || "" - + // 리마크 값으로 필터링된 서브필드 로드 await loadFilteredSubFieldsByTagTypeCode(selectedTagTypeCode, subclassRemarkValue, subclassCode) } @@ -257,28 +258,28 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { if (subFields.length === 0) { return; } - + const subscription = form.watch((value) => { if (!value.rows || subFields.length === 0) { return; } - + const rows = [...value.rows]; rows.forEach((row, rowIndex) => { if (!row) return; - + let combined = ""; subFields.forEach((sf, idx) => { const fieldValue = row[sf.name] || ""; - + // delimiter를 앞에 붙이기 (첫 번째 필드가 아니고, 현재 필드에 값이 있고, delimiter가 있는 경우) if (idx > 0 && fieldValue && sf.delimiter) { combined += sf.delimiter; } - + combined += fieldValue; }); - + const currentTagNo = form.getValues(`rows.${rowIndex}.tagNo`); if (currentTagNo !== combined) { form.setValue(`rows.${rowIndex}.tagNo`, combined, { @@ -289,7 +290,7 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { } }); }); - + return () => subscription.unsubscribe(); }, [subFields, form]); @@ -340,7 +341,7 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { try { const res = await createTag(tagData, selectedPackageId); if ("error" in res) { - console.log(res.error ) + console.log(res.error) failedTags.push({ tag: row.tagNo, error: res.error }); } else { successfulTags.push(row.tagNo); @@ -468,7 +469,12 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { value={classSearchTerm} onValueChange={setClassSearchTerm} /> - <CommandList key={`${commandId}-list`} className="max-h-[300px]"> + + <CommandList key={`${commandId}-list`} className="max-h-[300px]" onWheel={(e) => { + e.stopPropagation(); // 이벤트 전파 차단 + const target = e.currentTarget; + target.scrollTop += e.deltaY; // 직접 스크롤 처리 + }}> <CommandEmpty key={`${commandId}-empty`}>검색 결과가 없습니다.</CommandEmpty> <CommandGroup key={`${commandId}-group`}> {classOptions.map((opt, optIndex) => { @@ -519,7 +525,7 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { // --------------- function renderSubclassField(field: any) { const hasSubclasses = selectedClassOption?.subclasses && selectedClassOption.subclasses.length > 0 - + if (!hasSubclasses) { return null } @@ -542,7 +548,7 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { <SelectContent> {selectedClassOption?.subclasses.map((subclass) => ( <SelectItem key={subclass.id} value={subclass.id}> - {subclass.desc} + {subclass.desc} </SelectItem> ))} </SelectContent> @@ -613,7 +619,7 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) { const message = selectedClassOption?.subclasses && selectedClassOption.subclasses.length > 0 ? "서브클래스를 선택해주세요." : "이 태그 유형에 대한 필드가 없습니다." - + return ( <div className="py-4 text-center text-muted-foreground"> {message} |
