summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/ship/import-from-dolce-button.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-document-list/ship/import-from-dolce-button.tsx')
-rw-r--r--lib/vendor-document-list/ship/import-from-dolce-button.tsx126
1 files changed, 70 insertions, 56 deletions
diff --git a/lib/vendor-document-list/ship/import-from-dolce-button.tsx b/lib/vendor-document-list/ship/import-from-dolce-button.tsx
index 76d66960..dfbd0600 100644
--- a/lib/vendor-document-list/ship/import-from-dolce-button.tsx
+++ b/lib/vendor-document-list/ship/import-from-dolce-button.tsx
@@ -24,21 +24,28 @@ import { Separator } from "@/components/ui/separator"
import { SimplifiedDocumentsView } from "@/db/schema"
import { ImportStatus } from "../import-service"
import { useSession } from "next-auth/react"
-import { getProjectIdsByVendor } from "../service"
+import { getProjectIdsByVendor, getProjectsByIds } from "../service"
import { useParams } from "next/navigation"
import { useTranslation } from "@/i18n/client"
-// ๐Ÿ”ฅ API ์‘๋‹ต ์บ์‹œ (์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์— ์„ ์–ธํ•˜์—ฌ ์ธ์Šคํ„ด์Šค ๊ฐ„ ๊ณต์œ )
+// API ์‘๋‹ต ์บ์‹œ (์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์— ์„ ์–ธํ•˜์—ฌ ์ธ์Šคํ„ด์Šค ๊ฐ„ ๊ณต์œ )
const statusCache = new Map<string, { data: ImportStatus; timestamp: number }>()
const CACHE_TTL = 2 * 60 * 1000 // 2๋ถ„ ์บ์‹œ
interface ImportFromDOLCEButtonProps {
allDocuments: SimplifiedDocumentsView[]
- projectIds?: number[] // ๐Ÿ”ฅ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐ๋œ projectIds๋ฅผ props๋กœ ๋ฐ›์Œ
+ projectIds?: number[] // ๋ฏธ๋ฆฌ ๊ณ„์‚ฐ๋œ projectIds๋ฅผ props๋กœ ๋ฐ›์Œ
onImportComplete?: () => void
}
-// ๐Ÿ”ฅ ๋””๋ฐ”์šด์Šค ํ›…
+// ํ”„๋กœ์ ํŠธ ์ •๋ณด ํƒ€์ž…
+interface ProjectInfo {
+ id: number
+ code: string
+ name: string
+}
+
+// ๋””๋ฐ”์šด์Šค ํ›…
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = React.useState(value)
@@ -67,6 +74,7 @@ export function ImportFromDOLCEButton({
const [statusLoading, setStatusLoading] = React.useState(false)
const [vendorProjectIds, setVendorProjectIds] = React.useState<number[]>([])
const [loadingVendorProjects, setLoadingVendorProjects] = React.useState(false)
+ const [projectsMap, setProjectsMap] = React.useState<Map<number, ProjectInfo>>(new Map())
const { data: session } = useSession()
const vendorId = session?.user.companyId
@@ -75,15 +83,15 @@ export function ImportFromDOLCEButton({
const lng = (params?.lng as string) || "ko"
const { t } = useTranslation(lng, "engineering")
- // ๐Ÿ”ฅ allDocuments์—์„œ projectIds ์ถ”์ถœ (props๋กœ ์ „๋‹ฌ๋ฐ›์€ ๊ฒฝ์šฐ ์‚ฌ์šฉ)
+ // allDocuments์—์„œ projectIds ์ถ”์ถœ (props๋กœ ์ „๋‹ฌ๋ฐ›์€ ๊ฒฝ์šฐ ์‚ฌ์šฉ)
const documentsProjectIds = React.useMemo(() => {
if (propProjectIds) return propProjectIds // props๋กœ ๋ฐ›์€ ๊ฒฝ์šฐ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
- const uniqueIds = [...new Set(allDocuments.map(doc => doc.projectId).filter(Boolean))]
+ const uniqueIds = [...new Set(allDocuments.map(doc => doc.projectId).filter((id): id is number => id !== null))]
return uniqueIds.sort()
}, [allDocuments, propProjectIds])
- // ๐Ÿ”ฅ ์ตœ์ข… projectIds (๋ณ€๊ฒฝ ๋นˆ๋„ ์ตœ์†Œํ™”)
+ // ์ตœ์ข… projectIds (๋ณ€๊ฒฝ ๋นˆ๋„ ์ตœ์†Œํ™”)
const projectIds = React.useMemo(() => {
if (documentsProjectIds.length > 0) {
return documentsProjectIds
@@ -91,28 +99,10 @@ export function ImportFromDOLCEButton({
return vendorProjectIds
}, [documentsProjectIds, vendorProjectIds])
- // ๐Ÿ”ฅ projectIds ๋””๋ฐ”์šด์‹ฑ (API ํ˜ธ์ถœ ๊ณผ๋‹ค ๋ฐฉ์ง€)
+ // projectIds ๋””๋ฐ”์šด์‹ฑ (API ํ˜ธ์ถœ ๊ณผ๋‹ค ๋ฐฉ์ง€)
const debouncedProjectIds = useDebounce(projectIds, 300)
- // ๐Ÿ”ฅ ์ฃผ์š” projectId ๋ฉ”๋ชจ์ด์ œ์ด์…˜
- const primaryProjectId = React.useMemo(() => {
- if (projectIds.length === 1) return projectIds[0]
-
- if (allDocuments.length > 0) {
- const counts = allDocuments.reduce((acc, doc) => {
- const id = doc.projectId || 0
- acc[id] = (acc[id] || 0) + 1
- return acc
- }, {} as Record<number, number>)
-
- return Number(Object.entries(counts)
- .sort(([,a], [,b]) => b - a)[0]?.[0] || projectIds[0] || 0)
- }
-
- return projectIds[0] || 0
- }, [projectIds, allDocuments])
-
- // ๐Ÿ”ฅ ์บ์‹œ๋œ API ํ˜ธ์ถœ ํ•จ์ˆ˜
+ // ์บ์‹œ๋œ API ํ˜ธ์ถœ ํ•จ์ˆ˜
const fetchImportStatusCached = React.useCallback(async (projectId: number): Promise<ImportStatus | null> => {
const cacheKey = `import-status-${projectId}`
const cached = statusCache.get(cacheKey)
@@ -148,7 +138,7 @@ export function ImportFromDOLCEButton({
}
}, [])
- // ๐Ÿ”ฅ ๋ชจ๋“  projectId์— ๋Œ€ํ•œ ์ƒํƒœ ์กฐํšŒ (์ตœ์ ํ™”๋œ ๋ฒ„์ „)
+ // ๋ชจ๋“  projectId์— ๋Œ€ํ•œ ์ƒํƒœ ์กฐํšŒ (์ตœ์ ํ™”๋œ ๋ฒ„์ „)
const fetchAllImportStatus = React.useCallback(async () => {
if (debouncedProjectIds.length === 0) return
@@ -156,9 +146,9 @@ export function ImportFromDOLCEButton({
const statusMap = new Map<number, ImportStatus>()
try {
- // ๐Ÿ”ฅ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌํ•˜๋˜ ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜ ์ œํ•œ (3๊ฐœ์”ฉ)
+ // ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌํ•˜๋˜ ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜ ์ œํ•œ (3๊ฐœ์”ฉ)
const batchSize = 3
- const batches = []
+ const batches: number[][] = []
for (let i = 0; i < debouncedProjectIds.length; i += batchSize) {
batches.push(debouncedProjectIds.slice(i, i + batchSize))
@@ -194,7 +184,7 @@ export function ImportFromDOLCEButton({
}
}, [debouncedProjectIds, fetchImportStatusCached, t])
- // ๐Ÿ”ฅ vendorId๋กœ projects ๊ฐ€์ ธ์˜ค๊ธฐ (์ตœ์ ํ™”)
+ // vendorId๋กœ projects ๊ฐ€์ ธ์˜ค๊ธฐ (์ตœ์ ํ™”)
React.useEffect(() => {
let isCancelled = false;
@@ -206,7 +196,7 @@ export function ImportFromDOLCEButton({
.then((projectIds) => {
if (!isCancelled) setVendorProjectIds(projectIds);
})
- .catch((error) => {
+ .catch(() => {
if (!isCancelled) toast.error(t('dolceImport.messages.projectFetchError'));
})
.finally(() => {
@@ -215,15 +205,36 @@ export function ImportFromDOLCEButton({
return () => { isCancelled = true; };
}, [allDocuments, vendorId, t]);
+
+ // projectIds๋กœ ํ”„๋กœ์ ํŠธ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (์„œ๋ฒ„ ์•ก์…˜ ์‚ฌ์šฉ)
+ React.useEffect(() => {
+ if (projectIds.length === 0) return;
+
+ const fetchProjectsInfo = async () => {
+ try {
+ const projectsData = await getProjectsByIds(projectIds);
+
+ const newProjectsMap = new Map<number, ProjectInfo>();
+ projectsData.forEach((project) => {
+ newProjectsMap.set(project.id, project);
+ });
+ setProjectsMap(newProjectsMap);
+ } catch (error) {
+ console.error('ํ”„๋กœ์ ํŠธ ์ •๋ณด ์กฐํšŒ ์‹คํŒจ:', error);
+ }
+ };
+
+ fetchProjectsInfo();
+ }, [projectIds]);
- // ๐Ÿ”ฅ ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ์ƒํƒœ ์กฐํšŒ (๋””๋ฐ”์šด์‹ฑ ์ ์šฉ)
+ // ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ์ƒํƒœ ์กฐํšŒ (๋””๋ฐ”์šด์‹ฑ ์ ์šฉ)
React.useEffect(() => {
if (debouncedProjectIds.length > 0) {
fetchAllImportStatus()
}
}, [debouncedProjectIds, fetchAllImportStatus])
- // ๐Ÿ”ฅ ์ „์ฒด ํ†ต๊ณ„ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ - ๋ฆฌ๋น„์ „๊ณผ ์ฒจ๋ถ€ํŒŒ์ผ ์ถ”๊ฐ€
+ // ์ „์ฒด ํ†ต๊ณ„ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ - ๋ฆฌ๋น„์ „๊ณผ ์ฒจ๋ถ€ํŒŒ์ผ ์ถ”๊ฐ€
const totalStats = React.useMemo(() => {
const statuses = Array.from(importStatusMap.values())
return statuses.reduce((acc, status) => ({
@@ -251,12 +262,7 @@ export function ImportFromDOLCEButton({
})
}, [importStatusMap])
- // ๐Ÿ”ฅ ์ฃผ์š” ์ƒํƒœ ๋ฉ”๋ชจ์ด์ œ์ด์…˜
- const primaryImportStatus = React.useMemo(() => {
- return importStatusMap.get(primaryProjectId)
- }, [importStatusMap, primaryProjectId])
-
- // ๐Ÿ”ฅ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํ–‰ ํ•จ์ˆ˜ ์ตœ์ ํ™”
+ // ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํ–‰ ํ•จ์ˆ˜ ์ตœ์ ํ™”
const handleImport = React.useCallback(async () => {
if (projectIds.length === 0) return
@@ -268,8 +274,14 @@ export function ImportFromDOLCEButton({
setImportProgress(prev => Math.min(prev + 10, 85))
}, 500)
- // ๐Ÿ”ฅ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ๋กœ ์„œ๋ฒ„ ๋ถ€ํ•˜ ๋ฐฉ์ง€
- const results = []
+ // ์ˆœ์ฐจ ์ฒ˜๋ฆฌ๋กœ ์„œ๋ฒ„ ๋ถ€ํ•˜ ๋ฐฉ์ง€
+ const results: Array<{
+ success: boolean
+ newCount?: number
+ updatedCount?: number
+ skippedCount?: number
+ error?: string
+ }> = []
for (const projectId of projectIds) {
try {
const response = await fetch('/api/sync/import', {
@@ -304,14 +316,14 @@ export function ImportFromDOLCEButton({
// ๊ฒฐ๊ณผ ์ง‘๊ณ„
const totalResult = results.reduce((acc, result) => ({
- newCount: acc.newCount + (result.newCount || 0),
- updatedCount: acc.updatedCount + (result.updatedCount || 0),
- skippedCount: acc.skippedCount + (result.skippedCount || 0),
+ newCount: (acc.newCount || 0) + (result.newCount || 0),
+ updatedCount: (acc.updatedCount || 0) + (result.updatedCount || 0),
+ skippedCount: (acc.skippedCount || 0) + (result.skippedCount || 0),
success: acc.success && result.success
}), {
- newCount: 0,
- updatedCount: 0,
- skippedCount: 0,
+ newCount: 0 as number,
+ updatedCount: 0 as number,
+ skippedCount: 0 as number,
success: true
})
@@ -341,7 +353,7 @@ export function ImportFromDOLCEButton({
)
}
- // ๐Ÿ”ฅ ์บ์‹œ ๋ฌดํšจํ™”
+ // ์บ์‹œ ๋ฌดํšจํ™”
statusCache.clear()
fetchAllImportStatus()
onImportComplete?.()
@@ -357,14 +369,14 @@ export function ImportFromDOLCEButton({
}
}, [projectIds, fetchAllImportStatus, onImportComplete, t])
- // ๐Ÿ”ฅ ์ „์ฒด ๋ณ€๊ฒฝ ์‚ฌํ•ญ ๊ณ„์‚ฐ
+ // ์ „์ฒด ๋ณ€๊ฒฝ ์‚ฌํ•ญ ๊ณ„์‚ฐ
const totalChanges = React.useMemo(() => {
return totalStats.newDocuments + totalStats.updatedDocuments +
totalStats.newRevisions + totalStats.updatedRevisions +
totalStats.newAttachments + totalStats.updatedAttachments
}, [totalStats])
- // ๐Ÿ”ฅ ์ƒํƒœ ๋ฑƒ์ง€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ - ๋ฆฌ๋น„์ „๊ณผ ์ฒจ๋ถ€ํŒŒ์ผ ํฌํ•จ
+ // ์ƒํƒœ ๋ฑƒ์ง€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ - ๋ฆฌ๋น„์ „๊ณผ ์ฒจ๋ถ€ํŒŒ์ผ ํฌํ•จ
const statusBadge = React.useMemo(() => {
if (loadingVendorProjects) {
return <Badge variant="secondary">{t('dolceImport.status.loadingProjectInfo')}</Badge>
@@ -399,16 +411,16 @@ export function ImportFromDOLCEButton({
)
}, [loadingVendorProjects, statusLoading, importStatusMap.size, totalStats.importEnabled, totalChanges, projectIds.length, t])
- // ๐Ÿ”ฅ ๊ฐ€์ ธ์˜ค๊ธฐ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ - ๋ฆฌ๋น„์ „๊ณผ ์ฒจ๋ถ€ํŒŒ์ผ๋„ ์ฒดํฌ
+ // ๊ฐ€์ ธ์˜ค๊ธฐ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ - ๋ฆฌ๋น„์ „๊ณผ ์ฒจ๋ถ€ํŒŒ์ผ๋„ ์ฒดํฌ
const canImport = totalStats.importEnabled && totalChanges > 0
- // ๐Ÿ”ฅ ์ƒˆ๋กœ๊ณ ์นจ ํ•ธ๋“ค๋Ÿฌ ์ตœ์ ํ™”
+ // ์ƒˆ๋กœ๊ณ ์นจ ํ•ธ๋“ค๋Ÿฌ ์ตœ์ ํ™”
const handleRefresh = React.useCallback(() => {
statusCache.clear() // ์บ์‹œ ๋ฌดํšจํ™”
fetchAllImportStatus()
}, [fetchAllImportStatus])
- // ๐Ÿ”ฅ ์ž๋™ ๋™๊ธฐํ™” ์‹คํ–‰ (๊ธฐ์กด useEffect๋“ค ๋‹ค์Œ์— ์ถ”๊ฐ€)
+ // ์ž๋™ ๋™๊ธฐํ™” ์‹คํ–‰ (๊ธฐ์กด useEffect๋“ค ๋‹ค์Œ์— ์ถ”๊ฐ€)
React.useEffect(() => {
// ์กฐ๊ฑด: ๊ฐ€์ ธ์˜ค๊ธฐ ๊ฐ€๋Šฅํ•˜๊ณ , ๋™๊ธฐํ™”ํ•  ํ•ญ๋ชฉ์ด ์žˆ๊ณ , ํ˜„์žฌ ์ง„ํ–‰์ค‘์ด ์•„๋‹ ๋•Œ
if (canImport && totalChanges > 0 && !isImporting && !isDialogOpen) {
@@ -496,7 +508,7 @@ export function ImportFromDOLCEButton({
<div className="text-muted-foreground">{t('dolceImport.labels.targetProjects')}</div>
<div className="font-medium">{t('dolceImport.labels.projectCount', { count: projectIds.length })}</div>
<div className="text-xs text-muted-foreground">
- {t('dolceImport.labels.projectIds')}: {projectIds.join(', ')}
+ {t('dolceImport.labels.projectIds')}: {projectIds.map(id => projectsMap.get(id)?.code || id).join(', ')}
</div>
</div>
)}
@@ -584,9 +596,11 @@ export function ImportFromDOLCEButton({
<div className="mt-2 space-y-2 pl-2 border-l-2 border-muted">
{projectIds.map(projectId => {
const status = importStatusMap.get(projectId)
+ const projectInfo = projectsMap.get(projectId)
+ const projectLabel = projectInfo?.code || projectId
return (
<div key={projectId} className="text-xs">
- <div className="font-medium">{t('dolceImport.labels.projectLabel', { projectId })}</div>
+ <div className="font-medium">{t('dolceImport.labels.projectLabel', { projectId: projectLabel })}</div>
{status ? (
<div className="text-muted-foreground space-y-1">
<div>