diff options
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.tsx | 126 |
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> |
