summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/docu-list-rule/number-type-configs/service.ts1
-rw-r--r--lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx13
-rw-r--r--lib/docu-list-rule/number-types/service.ts20
-rw-r--r--lib/docu-list-rule/number-types/table/number-type-initialize-button.tsx99
-rw-r--r--lib/docu-list-rule/number-types/table/number-types-table-columns.tsx2
-rw-r--r--lib/docu-list-rule/number-types/table/number-types-table-toolbar.tsx9
-rw-r--r--lib/pq/service.ts14
-rw-r--r--lib/vendor-document-list/plant/document-stage-dialogs.tsx520
-rw-r--r--lib/vendor-document-list/plant/document-stage-toolbar.tsx2
-rw-r--r--lib/vendor-document-list/plant/document-stages-columns.tsx4
-rw-r--r--lib/vendor-document-list/plant/document-stages-service.ts3
-rw-r--r--lib/vendor-document-list/plant/shi-buyer-system-api.ts8
12 files changed, 453 insertions, 242 deletions
diff --git a/lib/docu-list-rule/number-type-configs/service.ts b/lib/docu-list-rule/number-type-configs/service.ts
index f4cadc70..c29af464 100644
--- a/lib/docu-list-rule/number-type-configs/service.ts
+++ b/lib/docu-list-rule/number-type-configs/service.ts
@@ -121,6 +121,7 @@ export async function getNumberTypeConfigs(input: GetNumberTypeConfigsSchema) {
codeGroupId: documentNumberTypeConfigs.codeGroupId,
sdq: documentNumberTypeConfigs.sdq,
description: documentNumberTypeConfigs.description,
+ delimiter: documentNumberTypeConfigs.delimiter,
remark: documentNumberTypeConfigs.remark,
isActive: documentNumberTypeConfigs.isActive,
createdAt: documentNumberTypeConfigs.createdAt,
diff --git a/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx b/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx
index 260c50cd..27a9f707 100644
--- a/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx
+++ b/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx
@@ -64,6 +64,19 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<NumberT
minSize: 50
},
{
+ accessorKey: "delimiter",
+ enableResizing: true,
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="delimiter" />
+ ),
+ meta: {
+ excelHeader: "delimiter",
+ type: "text",
+ },
+ cell: ({ row }) => row.getValue("delimiter") ?? "",
+ minSize: 50
+ },
+ {
accessorKey: "codeGroupName",
enableResizing: true,
header: ({ column }) => (
diff --git a/lib/docu-list-rule/number-types/service.ts b/lib/docu-list-rule/number-types/service.ts
index 3fb51abd..c4a0cdac 100644
--- a/lib/docu-list-rule/number-types/service.ts
+++ b/lib/docu-list-rule/number-types/service.ts
@@ -524,4 +524,22 @@ export async function getNumberTypesWithConfigs(input: {
pageCount: 0,
}
}
-} \ No newline at end of file
+}
+
+export async function checkNumberTypesExist(projectId: number, names: string[]): Promise<boolean> {
+ try {
+ const existing = await db
+ .select({ name: documentNumberTypes.name })
+ .from(documentNumberTypes)
+ .where(and(
+ eq(documentNumberTypes.projectId, projectId),
+ inArray(documentNumberTypes.name, names)
+ ))
+
+ // 모든 name이 존재하는지 체크
+ return existing.length === names.length
+ } catch (error) {
+ console.error("Error checking number types:", error)
+ return false
+ }
+} \ No newline at end of file
diff --git a/lib/docu-list-rule/number-types/table/number-type-initialize-button.tsx b/lib/docu-list-rule/number-types/table/number-type-initialize-button.tsx
new file mode 100644
index 00000000..904cdda5
--- /dev/null
+++ b/lib/docu-list-rule/number-types/table/number-type-initialize-button.tsx
@@ -0,0 +1,99 @@
+// number-type-initialize-button.tsx
+"use client"
+
+import * as React from "react"
+import { Button } from "@/components/ui/button"
+import { Plus } from "lucide-react"
+import { toast } from "sonner"
+import { createNumberType } from "@/lib/docu-list-rule/number-types/service"
+import { useParams } from "next/navigation"
+import { checkNumberTypesExist } from "@/lib/docu-list-rule/number-types/service"
+
+interface NumberTypeInitializeButtonProps {
+ onSuccess: () => void
+}
+
+export function NumberTypeInitializeButton({ onSuccess }: NumberTypeInitializeButtonProps) {
+ const params = useParams()
+ const projectId = Number(params?.projectId)
+ const [loading, setLoading] = React.useState(false)
+ const [isInitialized, setIsInitialized] = React.useState(false)
+
+ console.log(projectId,"projectId")
+
+ // 컴포넌트 마운트 시 이미 존재하는지 체크
+ React.useEffect(() => {
+ const checkExisting = async () => {
+ if (!projectId) return
+
+ const exists = await checkNumberTypesExist(projectId, ["SHI", "CPY"])
+ setIsInitialized(exists)
+ }
+
+ checkExisting()
+ }, [projectId])
+
+ const handleInitialize = async () => {
+ if (isInitialized) return
+
+ setLoading(true)
+ try {
+ const numberTypes = [
+ {
+ projectId: projectId,
+ name: "SHI",
+ description: "삼성중공업 도서번호",
+ },
+ {
+ projectId: projectId,
+ name: "CPY",
+ description: "프로젝트 문서번호",
+ }
+ ]
+
+ let allSuccess = true
+ let createdCount = 0
+
+ for (const numberType of numberTypes) {
+ const result = await createNumberType(numberType)
+
+ if (result.success) {
+ createdCount++
+ } else if (result.error?.includes("already exists")) {
+ // 이미 존재하는 경우는 무시
+ continue
+ } else {
+ allSuccess = false
+ toast.error(`${numberType.name} 생성 실패: ${result.error}`)
+ }
+ }
+
+ if (createdCount > 0) {
+ toast.success(`${createdCount}개의 Number Type이 생성되었습니다.`)
+ setIsInitialized(true)
+ onSuccess()
+ } else if (allSuccess) {
+ toast.info("이미 모든 Number Type이 존재합니다.")
+ setIsInitialized(true)
+ }
+
+ } catch (error) {
+ console.error("Number Type 초기화 실패:", error)
+ toast.error("Number Type 초기화에 실패했습니다.")
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ return (
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleInitialize}
+ disabled={loading || isInitialized}
+ >
+ <Plus className="mr-2 h-4 w-4" />
+ {loading ? "초기화 중..." : isInitialized ? "초기화 완료" : "기본 Type 생성"}
+ </Button>
+ )
+} \ No newline at end of file
diff --git a/lib/docu-list-rule/number-types/table/number-types-table-columns.tsx b/lib/docu-list-rule/number-types/table/number-types-table-columns.tsx
index 59403f3d..56708f57 100644
--- a/lib/docu-list-rule/number-types/table/number-types-table-columns.tsx
+++ b/lib/docu-list-rule/number-types/table/number-types-table-columns.tsx
@@ -159,6 +159,6 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<NumberT
return [
selectColumn,
...dataColumns,
- actionsColumn,
+ // actionsColumn,
]
} \ No newline at end of file
diff --git a/lib/docu-list-rule/number-types/table/number-types-table-toolbar.tsx b/lib/docu-list-rule/number-types/table/number-types-table-toolbar.tsx
index 3d2d9312..863088de 100644
--- a/lib/docu-list-rule/number-types/table/number-types-table-toolbar.tsx
+++ b/lib/docu-list-rule/number-types/table/number-types-table-toolbar.tsx
@@ -1,3 +1,4 @@
+// number-types-table-toolbar-actions.tsx
"use client"
import * as React from "react"
@@ -7,7 +8,7 @@ import { Download } from "lucide-react"
import { exportTableToExcel } from "@/lib/export"
import { Button } from "@/components/ui/button"
import { DeleteNumberTypesDialog } from "@/lib/docu-list-rule/number-types/table/delete-number-types-dialog"
-import { NumberTypeAddDialog } from "@/lib/docu-list-rule/number-types/table/number-type-add-dialog"
+import { NumberTypeInitializeButton } from "@/lib/docu-list-rule/number-types/table/number-type-initialize-button"
import { documentNumberTypes } from "@/db/schema/docu-list-rule"
// Number Type with configs 타입 정의
@@ -39,8 +40,8 @@ export function NumberTypesTableToolbarActions({ table, onSuccess }: NumberTypes
/>
) : null}
- <NumberTypeAddDialog onSuccess={onSuccess || (() => {})} />
-
+ {/** 2) 기본 Type 생성 버튼 */}
+ <NumberTypeInitializeButton onSuccess={onSuccess || (() => {})} />
</div>
)
-} \ No newline at end of file
+} \ No newline at end of file
diff --git a/lib/pq/service.ts b/lib/pq/service.ts
index 172542a3..7aa80dfa 100644
--- a/lib/pq/service.ts
+++ b/lib/pq/service.ts
@@ -5,7 +5,7 @@ import { CopyPqListInput, CreatePqListInput, copyPqListSchema, createPqListSchem
import { unstable_cache } from "@/lib/unstable-cache";
import { filterColumns } from "@/lib/filter-columns";
import { getErrorMessage } from "@/lib/handle-error";
-import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq, count,isNull,SQL, sql, lt, isNotNull} from "drizzle-orm";
+import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq, ne, count,isNull,SQL, sql, lt, isNotNull} from "drizzle-orm";
import { z } from "zod"
import { revalidateTag, unstable_noStore, revalidatePath} from "next/cache";
import { format } from "date-fns"
@@ -2865,26 +2865,22 @@ function getInvestigationMethodLabel(method: string): string {
// }
export async function getQMManagers() {
try {
- // QM 역할이 할당된 사용자들을 조회
+ // domain이 'partners'가 아니고, isActive가 true인 사용자만 조회
const qmUsers = await db
.select({
id: users.id,
name: users.name,
email: users.email,
- employeeNumber: users.employeeNumber,
- deptName: users.deptName,
isActive: users.isActive,
})
.from(users)
- .innerJoin(userRoles, eq(users.id, userRoles.userId))
- .innerJoin(roles, eq(userRoles.roleId, roles.id))
.where(
and(
- ilike(roles.name, "%QM%"), // "QM"이 포함된 역할명
- eq(users.isActive, true) // 활성 사용자만
+ eq(users.isActive, true),
+ ne(users.domain, "partners")
)
)
- .orderBy(users.name)
+ .orderBy(users.name);
return {
data: qmUsers,
diff --git a/lib/vendor-document-list/plant/document-stage-dialogs.tsx b/lib/vendor-document-list/plant/document-stage-dialogs.tsx
index f49d7d47..4c1861b9 100644
--- a/lib/vendor-document-list/plant/document-stage-dialogs.tsx
+++ b/lib/vendor-document-list/plant/document-stage-dialogs.tsx
@@ -64,6 +64,7 @@ import { cn, formatDate } from "@/lib/utils"
import ExcelJS from 'exceljs'
import { Progress } from "@/components/ui/progress"
import { Alert, AlertDescription } from "@/components/ui/alert"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
const getStatusVariant = (status: string) => {
switch (status) {
@@ -97,69 +98,116 @@ interface AddDocumentDialogProps {
projectType: "ship" | "plant"
}
+
export function AddDocumentDialog({
open,
onOpenChange,
contractId,
projectType
}: AddDocumentDialogProps) {
- const [isLoading, setIsLoading] = React.useState(false)
+ const [isLoadingInitialData, setIsLoadingInitialData] = React.useState(false)
+ const [isSubmitting, setIsSubmitting] = React.useState(false)
const [documentNumberTypes, setDocumentNumberTypes] = React.useState<any[]>([])
const [documentClasses, setDocumentClasses] = React.useState<any[]>([])
const [selectedTypeConfigs, setSelectedTypeConfigs] = React.useState<any[]>([])
const [comboBoxOptions, setComboBoxOptions] = React.useState<Record<number, any[]>>({})
const [documentClassOptions, setDocumentClassOptions] = React.useState<any[]>([])
- const [isLoadingInitialData, setIsLoadingInitialData] = React.useState(false)
- const [isSubmitting, setIsSubmitting] = React.useState(false)
+
+ // SHI와 CPY 타입 체크
+ const [shiType, setShiType] = React.useState<any>(null)
+ const [cpyType, setCpyType] = React.useState<any>(null)
+ const [activeTab, setActiveTab] = React.useState<"SHI" | "CPY">("SHI")
+ const [dataLoaded, setDataLoaded] = React.useState(false)
+ console.log(dataLoaded,"dataLoaded")
const [formData, setFormData] = React.useState({
documentNumberTypeId: "",
documentClassId: "",
title: "",
- vendorDocNumber: "",
fieldValues: {} as Record<string, string>,
- planDates: {} as Record<number, string> // optionId -> planDate
+ planDates: {} as Record<number, string>
})
// Load initial data
- React.useEffect(() => {
- if (open) {
- resetForm() // 폼 리셋 추가
- loadInitialData()
- }
- }, [open])
+// Dialog가 닫힐 때 상태 초기화를 확실히 하기
+React.useEffect(() => {
+ if (!open) {
+ // Dialog가 닫힐 때만 초기화
+ resetForm()
+ } else if (!dataLoaded) {
+ // Dialog가 열리고 데이터가 로드되지 않았을 때만
+ loadInitialData()
+ }
+}, [open])
+
const loadInitialData = async () => {
- setIsLoadingInitialData(true) // isLoading 대신
+ setIsLoadingInitialData(true)
+ let foundShiType = null;
+ let foundCpyType = null;
+
try {
const [typesResult, classesResult] = await Promise.all([
getDocumentNumberTypes(contractId),
getDocumentClasses(contractId)
])
- if (typesResult.success) {
+ console.log(typesResult,"typesResult")
+
+ if (typesResult.success && typesResult.data) {
setDocumentNumberTypes(typesResult.data)
+
+ // 로컬 변수에 먼저 저장
+ foundShiType = typesResult.data.find((type: any) =>
+ type.name?.toUpperCase().trim() === "SHI"
+ )
+ foundCpyType = typesResult.data.find((type: any) =>
+ type.name?.toUpperCase().trim() === "CPY"
+ )
+
+ setShiType(foundShiType || null)
+ setCpyType(foundCpyType || null)
+
+ // 로컬 변수 사용
+ if (foundShiType) {
+ await handleTabChange("SHI", String(foundShiType.id))
+ } else if (foundCpyType) {
+ setActiveTab("CPY")
+ await handleTabChange("CPY", String(foundCpyType.id))
+ }
}
+
if (classesResult.success) {
setDocumentClasses(classesResult.data)
}
+
+ setDataLoaded(true)
} catch (error) {
+ console.error("Error loading data:", error)
toast.error("Error loading data.")
} finally {
+ // 로컬 변수를 체크
+ if (!foundShiType && !foundCpyType) {
+ console.error("No types found after loading")
+ }
setIsLoadingInitialData(false)
}
}
- // Handle document type change
- const handleDocumentTypeChange = async (documentNumberTypeId: string) => {
- setFormData({
- ...formData,
- documentNumberTypeId,
- fieldValues: {}
- })
-
+ // 탭 변경 처리
+ const handleTabChange = async (tab: "SHI" | "CPY", typeId?: string) => {
+ setActiveTab(tab)
+
+ const documentNumberTypeId = typeId || (tab === "SHI" ? shiType?.id : cpyType?.id)
+
if (documentNumberTypeId) {
+ setFormData(prev => ({
+ ...prev,
+ documentNumberTypeId: String(documentNumberTypeId),
+ fieldValues: {}
+ }))
+
const configsResult = await getDocumentNumberTypeConfigs(Number(documentNumberTypeId))
if (configsResult.success) {
setSelectedTypeConfigs(configsResult.data)
@@ -238,22 +286,21 @@ export function AddDocumentDialog({
selectedTypeConfigs.forEach((config, index) => {
const fieldKey = `field_${config.sdq}`
const value = formData.fieldValues[fieldKey] || "[value]"
- preview += value
- if (index < selectedTypeConfigs.length - 1) {
- preview += "-"
+
+ if (index > 0 && config.delimiter) {
+ preview += config.delimiter
}
+ preview += value
})
return preview
}
// Check if form is valid for submission
const isFormValid = () => {
- // Check basic required fields
if (!formData.documentNumberTypeId || !formData.documentClassId || !formData.title.trim()) {
return false
}
- // Check if all required document number components are filled
const requiredConfigs = selectedTypeConfigs.filter(config => config.required)
for (const config of requiredConfigs) {
const fieldKey = `field_${config.sdq}`
@@ -263,7 +310,6 @@ export function AddDocumentDialog({
}
}
- // Check if document number can be generated
const docNumber = generatePreviewDocNumber()
if (!docNumber || docNumber === "" || docNumber.includes("[value]")) {
return false
@@ -278,24 +324,27 @@ export function AddDocumentDialog({
return
}
- const docNumber = generatePreviewDocNumber()
- if (!docNumber) {
+ const generatedDocNumber = generatePreviewDocNumber()
+ if (!generatedDocNumber) {
toast.error("Cannot generate document number.")
return
}
- setIsSubmitting(true) // isLoading 대신
+ setIsSubmitting(true)
try {
- const result = await createDocument({
+ // CPY 탭에서는 생성된 문서번호를 vendorDocNumber로 저장
+ const submitData = {
contractId,
documentNumberTypeId: Number(formData.documentNumberTypeId),
documentClassId: Number(formData.documentClassId),
title: formData.title,
- docNumber: docNumber, // 미리 생성된 문서번호 전송
+ docNumber: activeTab === "SHI" ? generatedDocNumber : "", // SHI는 docNumber로
+ vendorDocNumber: activeTab === "CPY" ? generatedDocNumber : "", // CPY는 vendorDocNumber로
fieldValues: formData.fieldValues,
planDates: formData.planDates,
- vendorDocNumber: formData.vendorDocNumber,
- })
+ }
+
+ const result = await createDocument(submitData)
if (result.success) {
toast.success("Document added successfully.")
@@ -307,7 +356,7 @@ export function AddDocumentDialog({
} catch (error) {
toast.error("Error adding document.")
} finally {
- setIsSubmitting(false) // isLoading 대신
+ setIsSubmitting(false)
}
}
@@ -316,16 +365,165 @@ export function AddDocumentDialog({
documentNumberTypeId: "",
documentClassId: "",
title: "",
- vendorDocNumber: "",
fieldValues: {},
planDates: {}
})
setSelectedTypeConfigs([])
setComboBoxOptions({})
setDocumentClassOptions([])
+ setActiveTab("SHI")
+ setDataLoaded(false)
}
- const isPlantProject = projectType === "plant"
+ // 공통 폼 컴포넌트
+ const DocumentForm = () => (
+ <div className="grid gap-4">
+ {/* Dynamic Fields */}
+ {selectedTypeConfigs.length > 0 && (
+ <div className="border rounded-lg p-4 bg-blue-50/30 dark:bg-blue-950/30">
+ <Label className="text-sm font-medium text-blue-800 dark:text-blue-200 mb-3 block">
+ Document Number Components
+ </Label>
+ <div className="grid gap-3">
+ {selectedTypeConfigs.map((config) => (
+ <div key={config.id} className="grid gap-2">
+ <Label className="text-sm">
+ {config.codeGroup?.description || config.description}
+ {config.required && <span className="text-red-500 ml-1">*</span>}
+ {config.remark && (
+ <span className="text-xs text-gray-500 dark:text-gray-400 ml-2">({config.remark})</span>
+ )}
+ </Label>
+
+ {config.codeGroup?.controlType === 'combobox' ? (
+ <Select
+ value={formData.fieldValues[`field_${config.sdq}`] || ""}
+ onValueChange={(value) => handleFieldValueChange(`field_${config.sdq}`, value)}
+ >
+ <SelectTrigger>
+ <SelectValue placeholder="Select option" />
+ </SelectTrigger>
+ <SelectContent>
+ {(comboBoxOptions[config.codeGroupId!] || []).map((option) => (
+ <SelectItem key={option.id} value={option.code}>
+ {option.code} - {option.description}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ ) : config.documentClass ? (
+ <div className="p-2 bg-gray-100 dark:bg-gray-800 rounded text-sm">
+ {config.documentClass.code} - {config.documentClass.description}
+ </div>
+ ) : (
+ <Input
+ value={formData.fieldValues[`field_${config.sdq}`] || ""}
+ onChange={(e) => handleFieldValueChange(`field_${config.sdq}`, e.target.value)}
+ placeholder="Enter value"
+ />
+ )}
+ </div>
+ ))}
+ </div>
+
+ {/* Document Number Preview */}
+ <div className="mt-3 p-2 bg-white dark:bg-gray-900 border rounded">
+ <Label className="text-xs text-gray-600 dark:text-gray-400">
+ {activeTab === "SHI" ? "Document Number" : "Vendor Document Number"} Preview:
+ </Label>
+ <div className="font-mono text-sm font-medium text-blue-600 dark:text-blue-400">
+ {generatePreviewDocNumber()}
+ </div>
+ </div>
+ </div>
+ )}
+
+ {/* Document Class Selection */}
+ <div className="grid gap-2">
+ <Label htmlFor="documentClassId">
+ Document Class <span className="text-red-500">*</span>
+ </Label>
+ <Select
+ value={formData.documentClassId}
+ onValueChange={handleDocumentClassChange}
+ >
+ <SelectTrigger>
+ <SelectValue placeholder="Select document class" />
+ </SelectTrigger>
+ <SelectContent>
+ {documentClasses.map((cls) => (
+ <SelectItem key={cls.id} value={String(cls.id)}>
+ {cls.value}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ {formData.documentClassId && (
+ <p className="text-xs text-gray-600 dark:text-gray-400">
+ Options from the selected class will be automatically created as stages.
+ </p>
+ )}
+ </div>
+
+ {/* Document Class Options with Plan Dates */}
+ {documentClassOptions.length > 0 && (
+ <div className="border rounded-lg p-4 bg-green-50/30 dark:bg-green-950/30">
+ <Label className="text-sm font-medium text-green-800 dark:text-green-200 mb-3 block">
+ Document Class Stages with Plan Dates
+ </Label>
+ <div className="grid gap-3">
+ {documentClassOptions.map((option) => (
+ <div key={option.id} className="grid grid-cols-2 gap-3 items-center">
+ <div>
+ <Label className="text-sm font-medium">
+ {option.optionValue}
+ </Label>
+ {option.optionCode && (
+ <p className="text-xs text-gray-500 dark:text-gray-400">Code: {option.optionCode}</p>
+ )}
+ </div>
+ <div className="grid gap-1">
+ <Label className="text-xs text-gray-600 dark:text-gray-400">Plan Date</Label>
+ <Input
+ type="date"
+ value={formData.planDates[option.id] || ""}
+ onChange={(e) => handlePlanDateChange(option.id, e.target.value)}
+ className="text-sm"
+ />
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+
+ {/* Document Title */}
+ <div className="grid gap-2">
+ <Label htmlFor="title">
+ Document Title <span className="text-red-500">*</span>
+ </Label>
+ <Input
+ id="title"
+ value={formData.title}
+ onChange={(e) => setFormData({ ...formData, title: e.target.value })}
+ placeholder="Enter document title"
+ />
+ </div>
+ </div>
+ )
+
+ // 로딩 중이거나 데이터 체크 중일 때 표시
+ if (isLoadingInitialData) {
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="sm:max-w-[700px] h-[80vh] flex flex-col">
+ <div className="flex items-center justify-center py-8 flex-1">
+ <Loader2 className="h-8 w-8 animate-spin" />
+ </div>
+ </DialogContent>
+ </Dialog>
+ )
+ }
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -337,195 +535,77 @@ export function AddDocumentDialog({
</DialogDescription>
</DialogHeader>
- {isLoading ? (
- <div className="flex items-center justify-center py-8 flex-1">
- <Loader2 className="h-8 w-8 animate-spin" />
+ {!shiType && !cpyType ? (
+ <div className="flex-1 flex items-center justify-center">
+ <Alert className="max-w-md">
+ <AlertTriangle className="h-4 w-4" />
+ <AlertDescription>
+ 필수 Document Number Type (SHI, CPY)이 설정되지 않았습니다.
+ 먼저 Number Types 관리에서 설정해주세요.
+ </AlertDescription>
+ </Alert>
</div>
) : (
- <div className="flex-1 overflow-y-auto pr-2">
- <div className="grid gap-4 py-4">
- {/* Document Number Type Selection */}
- <div className="grid gap-2">
- <Label htmlFor="documentNumberTypeId">
- Document Number Type <span className="text-red-500">*</span>
- </Label>
- <Select
- value={formData.documentNumberTypeId}
- onValueChange={handleDocumentTypeChange}
- >
- <SelectTrigger>
- <SelectValue placeholder="Select document number type" />
- </SelectTrigger>
- <SelectContent>
- {documentNumberTypes.map((type) => (
- <SelectItem key={type.id} value={String(type.id)}>
- {type.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
-
- {/* Dynamic Fields */}
- {selectedTypeConfigs.length > 0 && (
- <div className="border rounded-lg p-4 bg-blue-50/30 dark:bg-blue-950/30">
- <Label className="text-sm font-medium text-blue-800 dark:text-blue-200 mb-3 block">
- Document Number Components
- </Label>
- <div className="grid gap-3">
- {selectedTypeConfigs.map((config) => (
- <div key={config.id} className="grid gap-2">
- <Label className="text-sm">
- {config.codeGroup?.description || config.description}
- {config.required && <span className="text-red-500 ml-1">*</span>}
- {config.remark && (
- <span className="text-xs text-gray-500 dark:text-gray-400 ml-2">({config.remark})</span>
- )}
- </Label>
-
- {config.codeGroup?.controlType === 'combobox' ? (
- <Select
- value={formData.fieldValues[`field_${config.sdq}`] || ""}
- onValueChange={(value) => handleFieldValueChange(`field_${config.sdq}`, value)}
- >
- <SelectTrigger>
- <SelectValue placeholder="Select option" />
- </SelectTrigger>
- <SelectContent>
- {(comboBoxOptions[config.codeGroupId!] || []).map((option) => (
- <SelectItem key={option.id} value={option.code}>
- {option.code} - {option.description}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- ) : config.documentClass ? (
- <div className="p-2 bg-gray-100 dark:bg-gray-800 rounded text-sm">
- {config.documentClass.code} - {config.documentClass.description}
- </div>
- ) : (
- <Input
- value={formData.fieldValues[`field_${config.sdq}`] || ""}
- onChange={(e) => handleFieldValueChange(`field_${config.sdq}`, e.target.value)}
- placeholder="Enter value"
- />
- )}
- </div>
- ))}
- </div>
-
- {/* Document Number Preview */}
- <div className="mt-3 p-2 bg-white dark:bg-gray-900 border rounded">
- <Label className="text-xs text-gray-600 dark:text-gray-400">Document Number Preview:</Label>
- <div className="font-mono text-sm font-medium text-blue-600 dark:text-blue-400">
- {generatePreviewDocNumber()}
- </div>
- </div>
- </div>
- )}
-
- {/* Document Class Selection */}
- <div className="grid gap-2">
- <Label htmlFor="documentClassId">
- Document Class <span className="text-red-500">*</span>
- </Label>
- <Select
- value={formData.documentClassId}
- onValueChange={handleDocumentClassChange}
- >
- <SelectTrigger>
- <SelectValue placeholder="Select document class" />
- </SelectTrigger>
- <SelectContent>
- {documentClasses.map((cls) => (
- <SelectItem key={cls.id} value={String(cls.id)}>
- {cls.value}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- {formData.documentClassId && (
- <p className="text-xs text-gray-600 dark:text-gray-400">
- Options from the selected class will be automatically created as stages.
- </p>
- )}
- </div>
-
- {/* Document Class Options with Plan Dates */}
- {documentClassOptions.length > 0 && (
- <div className="border rounded-lg p-4 bg-green-50/30 dark:bg-green-950/30">
- <Label className="text-sm font-medium text-green-800 dark:text-green-200 mb-3 block">
- Document Class Stages with Plan Dates
- </Label>
- <div className="grid gap-3">
- {documentClassOptions.map((option) => (
- <div key={option.id} className="grid grid-cols-2 gap-3 items-center">
- <div>
- <Label className="text-sm font-medium">
- {option.optionValue}
- </Label>
- {option.optionCode && (
- <p className="text-xs text-gray-500 dark:text-gray-400">Code: {option.optionCode}</p>
- )}
- </div>
- <div className="grid gap-1">
- <Label className="text-xs text-gray-600 dark:text-gray-400">Plan Date</Label>
- <Input
- type="date"
- value={formData.planDates[option.id] || ""}
- onChange={(e) => handlePlanDateChange(option.id, e.target.value)}
- className="text-sm"
- />
- </div>
- </div>
- ))}
- </div>
- </div>
- )}
-
- {/* Document Title */}
- <div className="grid gap-2">
- <Label htmlFor="title">
- Document Title <span className="text-red-500">*</span>
- </Label>
- <Input
- id="title"
- value={formData.title}
- onChange={(e) => setFormData({ ...formData, title: e.target.value })}
- placeholder="Enter document title"
- />
+ <>
+ <Tabs value={activeTab} onValueChange={(v) => handleTabChange(v as "SHI" | "CPY")} className="flex-1 flex flex-col">
+ <TabsList className="grid w-full grid-cols-2">
+ <TabsTrigger value="SHI" disabled={!shiType}>
+ SHI (삼성중공업 도서번호)
+ {!shiType && <AlertTriangle className="ml-2 h-3 w-3" />}
+ </TabsTrigger>
+ <TabsTrigger value="CPY" disabled={!cpyType}>
+ CPY (프로젝트 문서번호)
+ {!cpyType && <AlertTriangle className="ml-2 h-3 w-3" />}
+ </TabsTrigger>
+ </TabsList>
+
+ <div className="flex-1 overflow-y-auto pr-2 mt-4">
+ <TabsContent value="SHI" className="mt-0">
+ {shiType ? (
+ <DocumentForm />
+ ) : (
+ <Alert>
+ <AlertTriangle className="h-4 w-4" />
+ <AlertDescription>
+ SHI Document Number Type이 설정되지 않았습니다.
+ </AlertDescription>
+ </Alert>
+ )}
+ </TabsContent>
+
+ <TabsContent value="CPY" className="mt-0">
+ {cpyType ? (
+ <DocumentForm />
+ ) : (
+ <Alert>
+ <AlertTriangle className="h-4 w-4" />
+ <AlertDescription>
+ CPY Document Number Type이 설정되지 않았습니다.
+ </AlertDescription>
+ </Alert>
+ )}
+ </TabsContent>
</div>
+ </Tabs>
- {/* Additional Information */}
- {isPlantProject && (
- <div className="grid gap-2">
- <Label htmlFor="vendorDocNumber">Vendor Document Number</Label>
- <Input
- id="vendorDocNumber"
- value={formData.vendorDocNumber}
- onChange={(e) => setFormData({ ...formData, vendorDocNumber: e.target.value })}
- placeholder="Vendor provided document number"
- />
- </div>
- )}
- </div>
- </div>
+ <DialogFooter className="flex-shrink-0">
+ <Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSubmitting}>
+ Cancel
+ </Button>
+ <Button
+ onClick={handleSubmit}
+ disabled={isSubmitting || !isFormValid() || (!shiType && !cpyType)}
+ >
+ {isSubmitting ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : null}
+ Add Document
+ </Button>
+ </DialogFooter>
+ </>
)}
-
- <DialogFooter className="flex-shrink-0">
- <Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSubmitting}>
- Cancel
- </Button>
- <Button onClick={handleSubmit} disabled={isSubmitting || !isFormValid()}>
- {isSubmitting ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : null}
- Add Document
- </Button>
- </DialogFooter>
</DialogContent>
</Dialog>
)
}
-
// =============================================================================
// Edit Document Dialog (with improved stage plan date editing)
// =============================================================================
@@ -1236,7 +1316,7 @@ export function ExcelImportDialog({
<li>Document Name* (문서명)</li>
<li>Document Class* (문서클래스 - 드롭다운 선택)</li>
{projectType === "plant" && (
- <li>Vendor Doc No. (벤더문서번호)</li>
+ <li>Project Doc No. (벤더문서번호)</li>
)}
</ul>
<p className="mt-2"><strong>Stage Plan Dates 시트 (선택사항):</strong></p>
@@ -1513,7 +1593,7 @@ async function createImportTemplate(projectType: "ship" | "plant", contractId: n
"Document Number*",
"Document Name*",
"Document Class*",
- ...(projectType === "plant" ? ["Vendor Doc No."] : []),
+ ...(projectType === "plant" ? ["Project Doc No."] : []),
"Notes",
];
const documentHeaderRow = documentsSheet.addRow(documentHeaders);
@@ -1596,7 +1676,7 @@ async function createImportTemplate(projectType: "ship" | "plant", contractId: n
[" - Document Number*: 고유한 문서 번호를 입력하세요"],
[" - Document Name*: 문서명을 입력하세요"],
[" - Document Class*: 드롭다운에서 문서 클래스를 선택하세요"],
- [" - Vendor Doc No.: 벤더 문서 번호"],
+ [" - Project Doc No.: 벤더 문서 번호"],
[" - Notes: 참고사항"],
[""],
["2. Stage Plan Dates 시트 (선택사항)"],
diff --git a/lib/vendor-document-list/plant/document-stage-toolbar.tsx b/lib/vendor-document-list/plant/document-stage-toolbar.tsx
index 601a9152..ccb9e15c 100644
--- a/lib/vendor-document-list/plant/document-stage-toolbar.tsx
+++ b/lib/vendor-document-list/plant/document-stage-toolbar.tsx
@@ -18,7 +18,7 @@ import { sendDocumentsToSHI } from "./document-stages-service"
import { useDocumentPolling } from "@/hooks/use-document-polling"
import { cn } from "@/lib/utils"
import { MultiUploadDialog } from "./upload/components/multi-upload-dialog"
-// import { useRouter } from "next/navigation"
+import { useRouter } from "next/navigation"
// 서버 액션 import (필요한 경우)
// import { importDocumentsExcel } from "./document-stages-service"
diff --git a/lib/vendor-document-list/plant/document-stages-columns.tsx b/lib/vendor-document-list/plant/document-stages-columns.tsx
index 2f8fd482..0b85c3f8 100644
--- a/lib/vendor-document-list/plant/document-stages-columns.tsx
+++ b/lib/vendor-document-list/plant/document-stages-columns.tsx
@@ -298,7 +298,7 @@ export function getDocumentStagesColumns({
{
accessorKey: "vendorDocNumber",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="Vendor Doc No." />
+ <DataTableColumnHeaderSimple column={column} title="Project Doc No." />
),
cell: ({ row }) => {
const doc = row.original
@@ -311,7 +311,7 @@ export function getDocumentStagesColumns({
size: 120,
enableResizing: true,
meta: {
- excelHeader: "Vendor Doc No."
+ excelHeader: "Project Doc No."
},
},
)
diff --git a/lib/vendor-document-list/plant/document-stages-service.ts b/lib/vendor-document-list/plant/document-stages-service.ts
index 30a235c3..2c65b4e6 100644
--- a/lib/vendor-document-list/plant/document-stages-service.ts
+++ b/lib/vendor-document-list/plant/document-stages-service.ts
@@ -689,6 +689,8 @@ export async function getDocumentNumberTypes(contractId: number) {
}
}
+ console.log(project,"project")
+
const types = await db
.select()
.from(documentNumberTypes)
@@ -711,6 +713,7 @@ export async function getDocumentNumberTypeConfigs(documentNumberTypeId: number)
id: documentNumberTypeConfigs.id,
sdq: documentNumberTypeConfigs.sdq,
description: documentNumberTypeConfigs.description,
+ delimiter: documentNumberTypeConfigs.delimiter,
remark: documentNumberTypeConfigs.remark,
codeGroupId: documentNumberTypeConfigs.codeGroupId,
// documentClassId: documentNumberTypeConfigs.documentClassId,
diff --git a/lib/vendor-document-list/plant/shi-buyer-system-api.ts b/lib/vendor-document-list/plant/shi-buyer-system-api.ts
index 1f15efa6..582490af 100644
--- a/lib/vendor-document-list/plant/shi-buyer-system-api.ts
+++ b/lib/vendor-document-list/plant/shi-buyer-system-api.ts
@@ -162,10 +162,9 @@ export class ShiBuyerSystemAPI {
vendorName: sql<string>`(SELECT vendor_name FROM vendors WHERE id = ${stageDocuments.vendorId})`,
stages: sql<any[]>`
COALESCE(
- (SELECT json_agg(row_to_json(s.*))
+ (SELECT json_agg(row_to_json(s.*) ORDER BY s.stage_order)
FROM stage_issue_stages s
- WHERE s.document_id = ${stageDocuments.id}
- ORDER BY s.stage_order),
+ WHERE s.document_id = ${stageDocuments.id}),
'[]'::json
)
`
@@ -178,7 +177,8 @@ export class ShiBuyerSystemAPI {
ne(stageDocuments.buyerSystemStatus, "승인(DC)")
)
)
-
+
+
return result
}